001: /*
002: * Copyright (c) 2000, Jacob Smullyan.
003: *
004: * This is part of SkunkDAV, a WebDAV client. See http://skunkdav.sourceforge.net/
005: * for the latest version.
006: *
007: * SkunkDAV is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License as published
009: * by the Free Software Foundation; either version 2, or (at your option)
010: * any later version.
011: *
012: * SkunkDAV is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with SkunkDAV; see the file COPYING. If not, write to the Free
019: * Software Foundation, 59 Temple Place - Suite 330, Boston, MA
020: * 02111-1307, USA.
021: */
022:
023: package org.skunk.dav.client;
024:
025: import HTTPClient.AuthorizationHandler;
026: import HTTPClient.AuthorizationInfo;
027: import HTTPClient.AuthSchemeNotImplException;
028: import HTTPClient.Codecs;
029: import HTTPClient.CookieModule;
030: import HTTPClient.HTTPConnection;
031: import HTTPClient.HTTPResponse;
032: import HTTPClient.ModuleException;
033: import HTTPClient.NVPair;
034: import HTTPClient.ProtocolNotSuppException;
035: import HTTPClient.Response;
036: import HTTPClient.RoRequest;
037: import HTTPClient.RoResponse;
038: import java.net.PasswordAuthentication;
039: import java.io.IOException;
040: import java.util.Enumeration;
041: import java.util.Iterator;
042: import java.util.Map;
043: import java.util.Set;
044: import org.skunk.minixml.MalformedXMLException;
045: import org.skunk.trace.Debug;
046:
047: /**
048: * An http connection that can execute <code>DAVMethod</code> objects.
049: *
050: * @author Jacob Smullyan
051: */
052: public class DAVConnection {
053: /*
054: rather than subclassing HTTPConnection directly,
055: I'm using it as a delegate to free myself from
056: its peculiarities
057: */
058: private HTTPConnection connectionDelegate;
059: private String host;
060: private int port;
061: private String username = "anonymous";
062: private String authorization;
063: private boolean closed = true;
064: private String protocol;
065: private int socketTimeout = 5000;
066:
067: public static final String SOCKET_TIMEOUT_PROPERTY = "socketTimeout";
068: public static final int DEFAULT_SOCKET_TIMEOUT = 5000;
069:
070: // static
071: // {
072: // //this permits all cookies -- let the floodgates open!
073: // CookieModule.setCookiePolicyHandler(null);
074: // }
075:
076: /**
077: * creates an DAVConnection over the http protocol
078: * @param host the hostname
079: * @param port the port
080: */
081: public DAVConnection(String host, int port) {
082: this (host, port, false);
083: }
084:
085: /**
086: * creates a DAVConnection
087: * @param host the host to which to connect
088: * @param port the port on which to connect
089: * @param https whether to use https or http (https if true, http otherwise)
090: */
091: public DAVConnection(String host, int port, boolean https) {
092: this .host = host;
093: this .port = port;
094: this .protocol = (https) ? DAVConstants.HTTPS
095: : DAVConstants.HTTP;
096: openConnection();
097: }
098:
099: /**
100: * returns the protocol, either "http" or "https"
101: * @return the protocol
102: */
103: public String getProtocol() {
104: return this .protocol;
105: }
106:
107: /**
108: * set a DAVAuthenticator for this connection.
109: *
110: * the DAVAuthenticator is only being used for its requestPA() method (which provides
111: * access to the output of Authenticator's protected requestPasswordAuthentication() method)
112: *
113: * @param authenticator a DAVAuthenticator
114: */
115: public void setAuthenticator(DAVAuthenticator authenticator) {
116: AuthorizationInfo.setAuthHandler(new HTTPAuthHandler(
117: authenticator, this ));
118: }
119:
120: /**
121: * sets an encoded authorization string
122: * @param authorization the authorization string
123: */
124: public void setAuthorization(String authorization) {
125: this .authorization = authorization;
126: }
127:
128: /**
129: * indicates whether the connection is closed or not
130: * @return whether the connection is closed
131: */
132: public boolean isClosed() {
133: return this .closed;
134: }
135:
136: public int getSocketTimeout() {
137: return socketTimeout;
138: }
139:
140: public void setSocketTimeout(int socketTimeout) {
141: this .socketTimeout = socketTimeout;
142: connectionDelegate.setTimeout(socketTimeout);
143: }
144:
145: /**
146: * opens the connection
147: */
148: public void openConnection() {
149: if (!closed)
150: closeConnection();
151: if (protocol.equals(DAVConstants.HTTP))
152: connectionDelegate = new HTTPConnection(host, port);
153: else {
154: try {
155: connectionDelegate = new HTTPConnection(protocol, host,
156: port);
157: } catch (ProtocolNotSuppException pencil) {
158: Debug.trace(this , Debug.DP2, pencil);
159: return;
160: }
161: }
162: NVPair[] def_hdrs = { new NVPair("Connection", "close"),
163: new NVPair("User-Agent", "Skunkzilla/1.0") };
164: connectionDelegate.setDefaultHeaders(def_hdrs);
165: connectionDelegate.setTimeout(this .socketTimeout);
166:
167: this .closed = false;
168: }
169:
170: /**
171: * closes the connection
172: */
173: public void closeConnection() {
174: connectionDelegate.stop();
175: connectionDelegate = null;
176: this .closed = true;
177: }
178:
179: /**
180: * executes a DAVMethod
181: *
182: * @param method the DAVMethod instance
183: */
184: public synchronized void execute(DAVMethod method)
185: throws IOException, DAVException {
186: if (authorization != null) {
187: Debug.trace(this , Debug.DP4,
188: "adding authorization header: " + authorization);
189: method.getRequestHeaders().put("Authorization",
190: authorization);
191: }
192: method.setProtocol(protocol);
193: method.setHost(host);
194: method.setPort(port);
195: method.processRequestHeaders();
196: method.processRequestBody();
197: Debug.trace(this , Debug.DP4, "request headers: "
198: + method.getRequestHeaders());
199: if (Debug.isDebug(this , Debug.DP4)) {
200: byte[] reqbod = method.getRequestBody();
201: if (reqbod != null)
202: Debug.trace(this , Debug.DP4, "request body: {0} ",
203: new String(method.getRequestBody()));
204: else
205: Debug.trace(this , Debug.DP4, "request body is null");
206: }
207: try {
208: HTTPResponse res = connectionDelegate
209: .ExtensionMethod(method.getRequestMethodName()
210: .toString(), method.getRequestURL(), method
211: .getRequestBody(),
212: convertToNameValuePairs(method
213: .getRequestHeaders()));
214: processResponse(method, res);
215: } catch (ModuleException modex) {
216: Debug.trace(this , Debug.DP3, modex);
217: throw new DAVException(modex.getMessage());
218: } catch (IOException oyVeh) {
219: Debug.trace(this , Debug.DP3, oyVeh);
220: throw new DAVException(oyVeh.getMessage());
221: }
222: }
223:
224: protected synchronized void processResponse(DAVMethod method,
225: HTTPResponse res) throws IOException, ModuleException,
226: DAVException {
227: method.setResponseHeaders(extractHeaders(res));
228: Debug.trace(this , Debug.DP4, "headers of response: "
229: + method.getResponseHeaders());
230: method.setResponseBody(res.getData());
231: method.setStatus(res.getStatusCode());
232: method.processResponseHeaders();
233: try {
234: method.processResponseBody();
235: } catch (MalformedXMLException malex) {
236: Debug.trace(this , Debug.DP3, malex);
237: throw new DAVException(malex.getMessage());
238: }
239: Debug.trace(this , Debug.DP4, "status of response: "
240: + method.getStatus());
241: byte[] body = method.getResponseBody();
242: Debug.trace(this , Debug.DP4, "body of response: "
243: + ((body != null) ? new String(body) : "null"));
244: }
245:
246: private Map extractHeaders(HTTPResponse res) throws IOException,
247: DAVException {
248: try {
249: Map m = new HeaderMap();
250: Enumeration headerList = res.listHeaders();
251: while (headerList.hasMoreElements()) {
252: String headerKey = headerList.nextElement().toString();
253: m.put(headerKey, res.getHeader(headerKey));
254: }
255: return m;
256: } catch (ModuleException modex) {
257: Debug.trace(this , Debug.DP3, modex);
258: throw new DAVException(modex.getMessage());
259: }
260: }
261:
262: /**
263: * convert Maps to arrays of HTTPClient package's NVPair
264: */
265: static NVPair[] convertToNameValuePairs(Map m) {
266: if ((m == null) || (m.keySet() == null))
267: return null;
268: Set keys = m.keySet();
269:
270: NVPair[] pairs = new NVPair[keys.size()];
271: int i = 0;
272: for (Iterator it = keys.iterator(); it.hasNext(); i++) {
273: Object key = it.next();
274: Object val = m.get(key);
275: pairs[i] = new NVPair(key.toString(), val.toString());
276: }
277: return pairs;
278: }
279:
280: /**
281: * returns the host
282: * @return the host
283: */
284: public String getHost() {
285: return this .host;
286: }
287:
288: /**
289: * returns the port
290: * @return the port
291: */
292: public int getPort() {
293: return this .port;
294: }
295:
296: /**
297: * returns the username
298: * @return the username
299: */
300: public String getUsername() {
301: return this .username;
302: }
303:
304: /**
305: * sets the username
306: * @param username the username
307: */
308: public void setUsername(String username) {
309: this .username = username;
310: }
311: }
312:
313: /**
314: * for use with the HTTPClient connection delegate. This is largely cribbed
315: * from DAVExplorer, which in turn cribbed it from HTTPClient's more fully
316: * functional (but ugly) DefaultAuthHandler. Should the HTTPClient delegate
317: * be removed, a standard java.net.Authenticator can replace this.
318: *
319: * @author Jacob Smullyan
320: */
321: class HTTPAuthHandler implements AuthorizationHandler {
322: private DAVAuthenticator authenticator;
323: private DAVConnection connection;
324:
325: /**
326: * creates an HTTPAuthHandler
327: * @param authenticator the authenticator
328: * @param connection the DAVConnection
329: */
330: HTTPAuthHandler(DAVAuthenticator authenticator,
331: DAVConnection connection) {
332: this .authenticator = authenticator;
333: this .connection = connection;
334: }
335:
336: /**
337: * populates the connection with the necessary authorization information
338: */
339: public AuthorizationInfo getAuthorization(AuthorizationInfo info,
340: RoRequest req, RoResponse resp)
341: throws AuthSchemeNotImplException {
342: if (!info.getScheme().equalsIgnoreCase("Basic"))
343: throw new AuthSchemeNotImplException(info.getScheme());
344: authenticator.reset();
345: PasswordAuthentication pa = authenticator.getPA();
346: if (pa == null)
347: return null;
348: String username = pa.getUserName();
349: String password = new String(pa.getPassword());
350: Debug.trace(this , Debug.DP6, "username is " + username
351: + ", password is " + password);
352: if (username == null || username.equals(""))
353: return null;
354: //pass along the username to the connection class, so it can be used elsewhere
355: // (e.g., for lockowner)
356: connection.setUsername(username);
357: AuthorizationInfo ai = new AuthorizationInfo(info.getHost(),
358: info.getPort(), info.getScheme(), info.getRealm(),
359: Codecs.base64Encode(username + ":" + password));
360: connection.setAuthorization(ai.toString());
361: return ai;
362: }
363:
364: /**
365: * enforces that the authorization scheme is Basic
366: */
367: public AuthorizationInfo fixupAuthInfo(AuthorizationInfo info,
368: RoRequest req, AuthorizationInfo challenge, RoResponse resp)
369: throws AuthSchemeNotImplException {
370: if (info.getScheme().equalsIgnoreCase("Basic"))
371: return challenge;
372: else
373: throw new AuthSchemeNotImplException(info.getScheme());
374:
375: }
376:
377: public void handleAuthHeaders(Response resp, RoRequest req,
378: AuthorizationInfo prev, AuthorizationInfo prxy)
379: throws IOException {
380: //evidently okay to do nothing here
381: }
382:
383: public void handleAuthTrailers(Response resp, RoRequest req,
384: AuthorizationInfo prev, AuthorizationInfo prxy)
385: throws IOException {
386: //evidently okay to do nothing here, too
387: }
388: }
389:
390: /* $Log: DAVConnection.java,v $
391: /* Revision 1.23 2001/05/30 03:49:35 smulloni
392: /* Some bug fixes and improvements to authentication handling. Users are now
393: /* prompted for a password the first time they connect to a given server
394: /* in a session; if the password is rejected, they are asked again.
395: /* Users also now have the option of whether a given password will be remembered
396: /* or not.
397: /*
398: /* Revision 1.22 2001/01/23 20:50:09 smulloni
399: /* added configurable socket timeout
400: /*
401: /* Revision 1.21 2001/01/03 22:51:53 smulloni
402: /* added documentation
403: /*
404: /* Revision 1.20 2001/01/03 20:11:30 smulloni
405: /* the DAVFileChooser now replaces JFileChooser for remote file access.
406: /* DAVMethod now has a protocol property.
407: /*
408: /* Revision 1.19 2000/12/19 22:06:15 smulloni
409: /* adding documentation.
410: /*
411: /* Revision 1.18 2000/12/18 21:04:56 smulloni
412: /* changes to support SSL.
413: /*
414: /* Revision 1.17 2000/12/03 23:53:25 smulloni
415: /* added license and copyright preamble to java files.
416: /*
417: /* Revision 1.16 2000/11/15 19:45:35 smullyan
418: /* beginning of revamp of application to include multiple buffers.
419: /*
420: /* Revision 1.15 2000/11/15 05:20:04 smullyan
421: /* rolled back, for the time being, sending the Connection: close header and
422: /* nullifying the cookie policy handler. mydocsonline has stopped working,
423: /* although probably not for this reason; I need to eliminate potentially
424: /* relevant changes to find the problem. (Perhaps they have stopped accepted
425: /* allprop?)
426: /*
427: /* Revision 1.14 2000/11/14 23:18:31 smullyan
428: /* fix for responsedescription property in multistatus
429: /*
430: /* Revision 1.13 2000/11/09 23:34:49 smullyan
431: /* log added to every Java file, with the help of python. Lock stealing
432: /* implemented, and treatment of locks made more robust.
433: /* */
|