001: /*
002: * ====================================================================
003: * Copyright (c) 2004-2008 TMate Software Ltd. All rights reserved.
004: *
005: * This software is licensed as described in the file COPYING, which
006: * you should have received as part of this distribution. The terms
007: * are also available at http://svnkit.com/license.html
008: * If newer versions of this license are posted there, you may use a
009: * newer version instead, at your option.
010: * ====================================================================
011: */
012: package org.tmatesoft.svn.core.internal.io.dav.http;
013:
014: import java.io.ByteArrayInputStream;
015: import java.io.IOException;
016: import java.io.InputStream;
017: import java.io.OutputStream;
018: import java.io.UnsupportedEncodingException;
019: import java.net.HttpURLConnection;
020:
021: import org.tmatesoft.svn.core.SVNErrorCode;
022: import org.tmatesoft.svn.core.SVNErrorMessage;
023: import org.tmatesoft.svn.core.SVNURL;
024: import org.tmatesoft.svn.util.Version;
025: import org.xml.sax.helpers.DefaultHandler;
026:
027: /**
028: * @version 1.1.1
029: * @author TMate Software Ltd.
030: */
031: class HTTPRequest {
032:
033: public static final char[] CRLF = { '\r', '\n' };
034:
035: private boolean myIsSecured;
036: private boolean myIsProxied;
037: private HTTPConnection myConnection;
038:
039: private String myAuthentication;
040: private String myProxyAuthentication;
041:
042: private HTTPHeader myResponseHeader;
043: private HTTPStatus myStatus;
044:
045: private SVNErrorMessage myErrorMessage;
046: private DefaultHandler myResponseHandler;
047: private OutputStream myResponseStream;
048:
049: private byte[] myRequestBody;
050: private InputStream myRequestStream;
051: private boolean myIsProxyAuthForced;
052: private boolean myIsKeepAlive;
053: private String myCharset;
054:
055: public HTTPRequest(String charset) {
056: myCharset = charset;
057: }
058:
059: public void reset() {
060: if (myRequestStream != null) {
061: try {
062: myRequestStream.reset();
063: } catch (IOException e) {
064: }
065: }
066: myAuthentication = null;
067: myProxyAuthentication = null;
068: myResponseHeader = null;
069: myStatus = null;
070: myErrorMessage = null;
071: }
072:
073: public void setProxied(boolean proxied) {
074: myIsProxied = proxied;
075: }
076:
077: public void setSecured(boolean secured) {
078: myIsSecured = secured;
079: }
080:
081: public void setConnection(HTTPConnection connection) {
082: myConnection = connection;
083: }
084:
085: public void initCredentials(HTTPAuthentication authentication,
086: String method, String path) {
087: if (authentication != null) {
088: authentication.setChallengeParameter("method", method);
089: authentication.setChallengeParameter("uri",
090: composeRequestURI(method, path));
091: }
092: }
093:
094: public void setAuthentication(String auth) {
095: myAuthentication = auth;
096: }
097:
098: public void setProxyAuthentication(String auth) {
099: myProxyAuthentication = auth;
100: }
101:
102: public void setForceProxyAuth(boolean force) {
103: myIsProxyAuthForced = force;
104: }
105:
106: public void setResponseHandler(DefaultHandler handler) {
107: myResponseHandler = handler;
108: }
109:
110: public void setResponseStream(OutputStream os) {
111: myResponseStream = os;
112: }
113:
114: public void setRequestBody(byte[] body) {
115: myRequestBody = body;
116: }
117:
118: public void setRequestBody(StringBuffer sb) {
119: try {
120: myRequestBody = sb.toString().getBytes("UTF-8");
121: } catch (UnsupportedEncodingException e) {
122: myRequestBody = sb.toString().getBytes();
123: }
124: }
125:
126: public void setRequestBody(InputStream is) {
127: myRequestStream = is;
128: }
129:
130: /**
131: * heart of http engine.
132: *
133: * features:
134: * // all this should be moved outside this method:
135: * - authentication callback to process 401 and 403 codes, failure results in returning error message.
136: * - another callback to process 301 and 302 codes, failure results in returning error message.
137: * - code that process ssl exceptions and re-prompts for client certificate when allowed.
138: * // auth error, ssl exception and "moved" errors should be processed by the caller.
139: *
140: * - code to send request body.
141: * - code to parse svn error response in case return code is not ok1 and ok2.
142: * - standard http error should be returned otherwise.
143: *
144: * - body may be resetable inputStream + length - IMeasurable.
145: * // this may throw IOException that will be converted to: timeout error, can't connect error, or ssl will re-prompt.
146: */
147: public void dispatch(String request, String path,
148: HTTPHeader header, int ok1, int ok2, SVNErrorMessage context)
149: throws IOException {
150: long length = 0;
151: if (myRequestBody != null) {
152: length = myRequestBody.length;
153: } else if (myRequestStream instanceof ByteArrayInputStream) {
154: length = ((ByteArrayInputStream) myRequestStream)
155: .available();
156: } else if (header != null
157: && header.hasHeader(HTTPHeader.CONTENT_LENGTH_HEADER)) {
158: length = Long
159: .parseLong(header
160: .getFirstHeaderValue(HTTPHeader.CONTENT_LENGTH_HEADER));
161: }
162: StringBuffer headerText = composeHTTPHeader(request, path,
163: header, length, myIsKeepAlive);
164: myConnection
165: .sendData(headerText.toString().getBytes(myCharset));
166: if (myRequestBody != null && length > 0) {
167: myConnection.sendData(myRequestBody);
168: } else if (myRequestStream != null && length > 0) {
169: myConnection.sendData(myRequestStream, length);
170: }
171: // if method is "CONNECT", then just return normal status
172: // only if there is nothing to read.
173: myConnection.readHeader(this );
174: context = context == null ? SVNErrorMessage.create(
175: SVNErrorCode.RA_DAV_REQUEST_FAILED, "{0} of ''{1}''",
176: new Object[] { request, path }) : context;
177:
178: // check status.
179: if (myStatus.getCode() == HttpURLConnection.HTTP_MOVED_PERM
180: || myStatus.getCode() == HttpURLConnection.HTTP_MOVED_TEMP
181: || myStatus.getCode() == HttpURLConnection.HTTP_FORBIDDEN
182: || myStatus.getCode() == HttpURLConnection.HTTP_UNAUTHORIZED
183: || myStatus.getCode() == HttpURLConnection.HTTP_PROXY_AUTH) {
184: // these errors are always processed by the caller, to allow retry.
185: myErrorMessage = createDefaultErrorMessage(myConnection
186: .getHost(), myStatus, context.getMessageTemplate(),
187: context.getRelatedObjects());
188: myConnection.skipData(this );
189: return;
190: }
191:
192: boolean notExpected = false;
193: int expectedCode = "PROPFIND".equals(request) ? 207 : 200;
194: if (ok1 >= 0) {
195: if (ok1 == 0) {
196: ok1 = "PROPFIND".equals(request) ? 207 : 200;
197: }
198: if (ok2 <= 0) {
199: ok2 = ok1;
200: }
201: notExpected = !(myStatus.getCode() == ok1 || myStatus
202: .getCode() == ok2);
203: } else if ("CONNECT".equalsIgnoreCase(request)
204: && myStatus.getCode() != HttpURLConnection.HTTP_OK) {
205: notExpected = true;
206: }
207: if (notExpected) {
208: // unexpected response code.
209: myErrorMessage = readError(request, path, context);
210: } else if (myStatus.getCode() == HttpURLConnection.HTTP_NO_CONTENT) {
211: myConnection.skipData(this );
212: } else if (myStatus.getCode() >= 300
213: || myStatus.getCode() != expectedCode) {
214: SVNErrorMessage error = readError(request, path, context);
215: myStatus.setError(error);
216: } else if (myResponseStream != null) {
217: myErrorMessage = myConnection.readData(this ,
218: myResponseStream);
219: } else if (myResponseHandler != null) {
220: myErrorMessage = myConnection.readData(this , request, path,
221: myResponseHandler);
222: } else {
223: if (!"CONNECT".equalsIgnoreCase(request)) {
224: myConnection.skipData(this );
225: }
226: }
227: }
228:
229: private SVNErrorMessage readError(String request, String path,
230: SVNErrorMessage context) {
231: String contextMessage = context.getMessageTemplate();
232: Object[] contextObjects = context.getRelatedObjects();
233: if (myStatus.getCode() == HttpURLConnection.HTTP_NOT_FOUND) {
234: contextMessage = "''{0}'' path not found";
235: contextObjects = new Object[] { path };
236: }
237: SVNErrorMessage error = createDefaultErrorMessage(myConnection
238: .getHost(), myStatus, contextMessage, contextObjects);
239: SVNErrorMessage davError = myConnection.readError(this ,
240: request, path);
241: if (davError != null) {
242: if (error != null) {
243: davError.setChildErrorMessage(error);
244: }
245: return davError;
246: }
247: return error;
248: }
249:
250: public HTTPHeader getResponseHeader() {
251: return myResponseHeader;
252: }
253:
254: public HTTPStatus getStatus() {
255: return myStatus;
256: }
257:
258: public void setStatus(HTTPStatus status) {
259: myStatus = status;
260: }
261:
262: public void setResponseHeader(HTTPHeader header) {
263: myResponseHeader = header;
264: }
265:
266: public SVNErrorMessage getErrorMessage() {
267: return myErrorMessage;
268: }
269:
270: private StringBuffer composeHTTPHeader(String request, String path,
271: HTTPHeader header, long length, boolean keepAlive) {
272: StringBuffer sb = new StringBuffer();
273: sb.append(request);
274: sb.append(' ');
275: sb.append(composeRequestURI(request, path));
276: sb.append(' ');
277: sb.append("HTTP/1.1");
278: sb.append(HTTPRequest.CRLF);
279: sb.append("Host: ");
280: sb.append(myConnection.getHost().getHost());
281: // only append if URL has port indeed.
282: int defaultPort = "https".equals(myConnection.getHost()
283: .getProtocol()) ? 443 : 80;
284: if (myConnection.getHost().getPort() != defaultPort) {
285: sb.append(":");
286: sb.append(myConnection.getHost().getPort());
287: }
288: sb.append(HTTPRequest.CRLF);
289: sb.append("User-Agent: ");
290: sb.append(Version.getUserAgent());
291: sb.append(HTTPRequest.CRLF);
292: if (keepAlive) {
293: sb.append("Keep-Alive:");
294: sb.append(HTTPRequest.CRLF);
295: sb.append("Connection: TE, Keep-Alive");
296: sb.append(HTTPRequest.CRLF);
297: }
298: sb.append("TE: trailers");
299: sb.append(HTTPRequest.CRLF);
300: if (myAuthentication != null) {
301: sb.append("Authorization: ");
302: sb.append(myAuthentication);
303: sb.append(HTTPRequest.CRLF);
304: }
305: if ((myIsProxyAuthForced || (myIsProxied && !myIsSecured))
306: && myProxyAuthentication != null) {
307: sb.append("Proxy-Authorization: ");
308: sb.append(myProxyAuthentication);
309: sb.append(HTTPRequest.CRLF);
310: }
311: if (header == null
312: || !header.hasHeader(HTTPHeader.CONTENT_LENGTH_HEADER)) {
313: sb.append("Content-Length: ");
314: sb.append(length);
315: sb.append(HTTPRequest.CRLF);
316: }
317: sb.append("Accept-Encoding: gzip");
318: sb.append(HTTPRequest.CRLF);
319: if (header == null
320: || !header.hasHeader(HTTPHeader.CONTENT_TYPE_HEADER)) {
321: sb.append("Content-Type: text/xml; charset=\"utf-8\"");
322: sb.append(HTTPRequest.CRLF);
323: }
324: if (header != null) {
325: sb.append(header.toString());
326: }
327: sb.append(HTTPRequest.CRLF);
328: return sb;
329: }
330:
331: private String composeRequestURI(String request, String path) {
332: StringBuffer sb = new StringBuffer();
333: if (myIsProxied && !myIsSecured) {
334: // prepend path with host name.
335: sb.append("http://");
336: sb.append(myConnection.getHost().getHost());
337: sb.append(":");
338: sb.append(myConnection.getHost().getPort());
339: }
340: if (path == null) {
341: path = "/";
342: }
343: if (!"CONNECT".equals(request)
344: && (path.length() == 0 || path.charAt(0) != '/')) {
345: path = "/" + path;
346: }
347: HTTPParser.getCanonicalPath(path, sb);
348: return sb.toString();
349:
350: }
351:
352: public static SVNErrorMessage createDefaultErrorMessage(
353: SVNURL host, HTTPStatus status, String context,
354: Object[] contextObjects) {
355: SVNErrorCode errorCode = SVNErrorCode.RA_DAV_REQUEST_FAILED;
356: String message = status != null ? status.getCode() + " "
357: + status.getReason() : "";
358: if (status != null
359: && status.getCode() == HttpURLConnection.HTTP_FORBIDDEN
360: || status.getCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
361: errorCode = SVNErrorCode.RA_NOT_AUTHORIZED;
362: message = status.getCode() + " " + status.getReason();
363: } else if (status != null
364: && status.getCode() == HttpURLConnection.HTTP_NOT_FOUND) {
365: errorCode = SVNErrorCode.RA_DAV_PATH_NOT_FOUND;
366: }
367: // extend context object to include host:port (empty location).
368: Object[] messageObjects = contextObjects == null ? new Object[1]
369: : new Object[contextObjects.length + 1];
370: int index = messageObjects.length - 1;
371: messageObjects[messageObjects.length - 1] = host;
372: if (messageObjects.length > 1) {
373: System.arraycopy(contextObjects, 0, messageObjects, 0,
374: contextObjects.length);
375: }
376: return SVNErrorMessage.create(errorCode, context + ": "
377: + message + " ({" + index + "})", messageObjects);
378: }
379:
380: public void setKeepAlive(boolean isKeepAlive) {
381: myIsKeepAlive = isKeepAlive;
382: }
383:
384: }
|