aboutsummaryrefslogtreecommitdiffstats
path: root/gcc-4.4.3/libjava/classpath/gnu/java/net/protocol/http/Request.java
diff options
context:
space:
mode:
Diffstat (limited to 'gcc-4.4.3/libjava/classpath/gnu/java/net/protocol/http/Request.java')
-rw-r--r--gcc-4.4.3/libjava/classpath/gnu/java/net/protocol/http/Request.java858
1 files changed, 858 insertions, 0 deletions
diff --git a/gcc-4.4.3/libjava/classpath/gnu/java/net/protocol/http/Request.java b/gcc-4.4.3/libjava/classpath/gnu/java/net/protocol/http/Request.java
new file mode 100644
index 000000000..88e2fd077
--- /dev/null
+++ b/gcc-4.4.3/libjava/classpath/gnu/java/net/protocol/http/Request.java
@@ -0,0 +1,858 @@
+/* Request.java --
+ Copyright (C) 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.net.protocol.http;
+
+import gnu.java.lang.CPStringBuilder;
+import gnu.java.net.LineInputStream;
+import gnu.java.util.Base64;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ProtocolException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.InflaterInputStream;
+
+/**
+ * A single HTTP request.
+ *
+ * @author Chris Burdess (dog@gnu.org)
+ */
+public class Request
+{
+
+ /**
+ * The connection context in which this request is invoked.
+ */
+ protected final HTTPConnection connection;
+
+ /**
+ * The HTTP method to invoke.
+ */
+ protected final String method;
+
+ /**
+ * The path identifying the resource.
+ * This string must conform to the abs_path definition given in RFC2396,
+ * with an optional "?query" part, and must be URI-escaped by the caller.
+ */
+ protected final String path;
+
+ /**
+ * The headers in this request.
+ */
+ protected final Headers requestHeaders;
+
+ /**
+ * The request body provider.
+ */
+ protected RequestBodyWriter requestBodyWriter;
+
+ /**
+ * Map of response header handlers.
+ */
+ protected Map<String, ResponseHeaderHandler> responseHeaderHandlers;
+
+ /**
+ * The authenticator.
+ */
+ protected Authenticator authenticator;
+
+ /**
+ * Whether this request has been dispatched yet.
+ */
+ private boolean dispatched;
+
+ /**
+ * Constructor for a new request.
+ * @param connection the connection context
+ * @param method the HTTP method
+ * @param path the resource path including query part
+ */
+ protected Request(HTTPConnection connection, String method,
+ String path)
+ {
+ this.connection = connection;
+ this.method = method;
+ this.path = path;
+ requestHeaders = new Headers();
+ responseHeaderHandlers = new HashMap<String, ResponseHeaderHandler>();
+ }
+
+ /**
+ * Returns the connection associated with this request.
+ * @see #connection
+ */
+ public HTTPConnection getConnection()
+ {
+ return connection;
+ }
+
+ /**
+ * Returns the HTTP method to invoke.
+ * @see #method
+ */
+ public String getMethod()
+ {
+ return method;
+ }
+
+ /**
+ * Returns the resource path.
+ * @see #path
+ */
+ public String getPath()
+ {
+ return path;
+ }
+
+ /**
+ * Returns the full request-URI represented by this request, as specified
+ * by HTTP/1.1.
+ */
+ public String getRequestURI()
+ {
+ return connection.getURI() + path;
+ }
+
+ /**
+ * Returns the headers in this request.
+ */
+ public Headers getHeaders()
+ {
+ return requestHeaders;
+ }
+
+ /**
+ * Returns the value of the specified header in this request.
+ * @param name the header name
+ */
+ public String getHeader(String name)
+ {
+ return requestHeaders.getValue(name);
+ }
+
+ /**
+ * Returns the value of the specified header in this request as an integer.
+ * @param name the header name
+ */
+ public int getIntHeader(String name)
+ {
+ return requestHeaders.getIntValue(name);
+ }
+
+ /**
+ * Returns the value of the specified header in this request as a date.
+ * @param name the header name
+ */
+ public Date getDateHeader(String name)
+ {
+ return requestHeaders.getDateValue(name);
+ }
+
+ /**
+ * Sets the specified header in this request.
+ * @param name the header name
+ * @param value the header value
+ */
+ public void setHeader(String name, String value)
+ {
+ requestHeaders.put(name, value);
+ }
+
+ /**
+ * Convenience method to set the entire request body.
+ * @param requestBody the request body content
+ */
+ public void setRequestBody(byte[] requestBody)
+ {
+ setRequestBodyWriter(new ByteArrayRequestBodyWriter(requestBody));
+ }
+
+ /**
+ * Sets the request body provider.
+ * @param requestBodyWriter the handler used to obtain the request body
+ */
+ public void setRequestBodyWriter(RequestBodyWriter requestBodyWriter)
+ {
+ this.requestBodyWriter = requestBodyWriter;
+ }
+
+ /**
+ * Sets a callback handler to be invoked for the specified header name.
+ * @param name the header name
+ * @param handler the handler to receive the value for the header
+ */
+ public void setResponseHeaderHandler(String name,
+ ResponseHeaderHandler handler)
+ {
+ responseHeaderHandlers.put(name, handler);
+ }
+
+ /**
+ * Sets an authenticator that can be used to handle authentication
+ * automatically.
+ * @param authenticator the authenticator
+ */
+ public void setAuthenticator(Authenticator authenticator)
+ {
+ this.authenticator = authenticator;
+ }
+
+ /**
+ * Dispatches this request.
+ * A request can only be dispatched once; calling this method a second
+ * time results in a protocol exception.
+ * @exception IOException if an I/O error occurred
+ * @return an HTTP response object representing the result of the operation
+ */
+ public Response dispatch()
+ throws IOException
+ {
+ if (dispatched)
+ {
+ throw new ProtocolException("request already dispatched");
+ }
+ final String CRLF = "\r\n";
+ final String HEADER_SEP = ": ";
+ final String US_ASCII = "US-ASCII";
+ final String version = connection.getVersion();
+ Response response;
+ int contentLength = -1;
+ boolean retry = false;
+ int attempts = 0;
+ boolean expectingContinue = false;
+ if (requestBodyWriter != null)
+ {
+ contentLength = requestBodyWriter.getContentLength();
+ String expect = getHeader("Expect");
+ if (expect != null && expect.equals("100-continue"))
+ {
+ expectingContinue = true;
+ }
+ else
+ {
+ setHeader("Content-Length", Integer.toString(contentLength));
+ }
+ }
+
+ try
+ {
+ // Loop while authentication fails or continue
+ do
+ {
+ retry = false;
+
+ // Get socket output and input streams
+ OutputStream out = connection.getOutputStream();
+
+ // Request line
+ String requestUri = path;
+ if (connection.isUsingProxy() &&
+ !"*".equals(requestUri) &&
+ !"CONNECT".equals(method))
+ {
+ requestUri = getRequestURI();
+ }
+ String line = method + ' ' + requestUri + ' ' + version + CRLF;
+ out.write(line.getBytes(US_ASCII));
+ // Request headers
+ for (Headers.HeaderElement elt : requestHeaders)
+ {
+ line = elt.name + HEADER_SEP + elt.value + CRLF;
+ out.write(line.getBytes(US_ASCII));
+ }
+ out.write(CRLF.getBytes(US_ASCII));
+ // Request body
+ if (requestBodyWriter != null && !expectingContinue)
+ {
+ byte[] buffer = new byte[4096];
+ int len;
+ int count = 0;
+
+ requestBodyWriter.reset();
+ do
+ {
+ len = requestBodyWriter.write(buffer);
+ if (len > 0)
+ {
+ out.write(buffer, 0, len);
+ }
+ count += len;
+ }
+ while (len > -1 && count < contentLength);
+ }
+ out.flush();
+ // Get response
+ while(true)
+ {
+ response = readResponse(connection.getInputStream());
+ int sc = response.getCode();
+ if (sc == 401 && authenticator != null)
+ {
+ if (authenticate(response, attempts++))
+ {
+ retry = true;
+ }
+ }
+ else if (sc == 100)
+ {
+ if (expectingContinue)
+ {
+ requestHeaders.remove("Expect");
+ setHeader("Content-Length",
+ Integer.toString(contentLength));
+ expectingContinue = false;
+ retry = true;
+ }
+ else
+ {
+ // A conforming server can send an unsoliceted
+ // Continue response but *should* not (RFC 2616
+ // sec 8.2.3). Ignore the bogus Continue
+ // response and get the real response that
+ // should follow
+ continue;
+ }
+ }
+ break;
+ }
+ }
+ while (retry);
+ }
+ catch (IOException e)
+ {
+ connection.close();
+ throw e;
+ }
+ return response;
+ }
+
+ Response readResponse(InputStream in)
+ throws IOException
+ {
+ String line;
+ int len;
+
+ // Read response status line
+ LineInputStream lis = new LineInputStream(in);
+
+ line = lis.readLine();
+ if (line == null)
+ {
+ throw new ProtocolException("Peer closed connection");
+ }
+ if (!line.startsWith("HTTP/"))
+ {
+ throw new ProtocolException(line);
+ }
+ len = line.length();
+ int start = 5, end = 6;
+ while (line.charAt(end) != '.')
+ {
+ end++;
+ }
+ int majorVersion = Integer.parseInt(line.substring(start, end));
+ start = end + 1;
+ end = start + 1;
+ while (line.charAt(end) != ' ')
+ {
+ end++;
+ }
+ int minorVersion = Integer.parseInt(line.substring(start, end));
+ start = end + 1;
+ end = start + 3;
+ int code = Integer.parseInt(line.substring(start, end));
+ String message = line.substring(end + 1, len - 1);
+ // Read response headers
+ Headers responseHeaders = new Headers();
+ responseHeaders.parse(lis);
+ notifyHeaderHandlers(responseHeaders);
+ InputStream body = null;
+
+ switch (code)
+ {
+ case 100:
+ break;
+ case 204:
+ case 205:
+ case 304:
+ body = createResponseBodyStream(responseHeaders, majorVersion,
+ minorVersion, in, false);
+ break;
+ default:
+ body = createResponseBodyStream(responseHeaders, majorVersion,
+ minorVersion, in, true);
+ }
+
+ // Construct response
+ Response ret = new Response(majorVersion, minorVersion, code,
+ message, responseHeaders, body);
+ return ret;
+ }
+
+ void notifyHeaderHandlers(Headers headers)
+ {
+ for (Headers.HeaderElement entry : headers)
+ {
+ // Handle Set-Cookie
+ if ("Set-Cookie".equalsIgnoreCase(entry.name))
+ handleSetCookie(entry.value);
+
+ ResponseHeaderHandler handler =
+ (ResponseHeaderHandler) responseHeaderHandlers.get(entry.name);
+ if (handler != null)
+ handler.setValue(entry.value);
+ }
+ }
+
+ private InputStream createResponseBodyStream(Headers responseHeaders,
+ int majorVersion,
+ int minorVersion,
+ InputStream in,
+ boolean mayHaveBody)
+ throws IOException
+ {
+ long contentLength = -1;
+
+ // Persistent connections are the default in HTTP/1.1
+ boolean doClose = "close".equalsIgnoreCase(getHeader("Connection")) ||
+ "close".equalsIgnoreCase(responseHeaders.getValue("Connection")) ||
+ (connection.majorVersion == 1 && connection.minorVersion == 0) ||
+ (majorVersion == 1 && minorVersion == 0);
+
+ String transferCoding = responseHeaders.getValue("Transfer-Encoding");
+ if ("HEAD".equals(method) || !mayHaveBody)
+ {
+ // Special case no body.
+ in = new LimitedLengthInputStream(in, 0, true, connection, doClose);
+ }
+ else if ("chunked".equalsIgnoreCase(transferCoding))
+ {
+ in = new LimitedLengthInputStream(in, -1, false, connection, doClose);
+
+ in = new ChunkedInputStream(in, responseHeaders);
+ }
+ else
+ {
+ contentLength = responseHeaders.getLongValue("Content-Length");
+
+ if (contentLength < 0)
+ doClose = true; // No Content-Length, must close.
+
+ in = new LimitedLengthInputStream(in, contentLength,
+ contentLength >= 0,
+ connection, doClose);
+ }
+ String contentCoding = responseHeaders.getValue("Content-Encoding");
+ if (contentCoding != null && !"identity".equals(contentCoding))
+ {
+ if ("gzip".equals(contentCoding))
+ {
+ in = new GZIPInputStream(in);
+ }
+ else if ("deflate".equals(contentCoding))
+ {
+ in = new InflaterInputStream(in);
+ }
+ else
+ {
+ throw new ProtocolException("Unsupported Content-Encoding: " +
+ contentCoding);
+ }
+ // Remove the Content-Encoding header because the content is
+ // no longer compressed.
+ responseHeaders.remove("Content-Encoding");
+ }
+ return in;
+ }
+
+ boolean authenticate(Response response, int attempts)
+ throws IOException
+ {
+ String challenge = response.getHeader("WWW-Authenticate");
+ if (challenge == null)
+ {
+ challenge = response.getHeader("Proxy-Authenticate");
+ }
+ int si = challenge.indexOf(' ');
+ String scheme = (si == -1) ? challenge : challenge.substring(0, si);
+ if ("Basic".equalsIgnoreCase(scheme))
+ {
+ Properties params = parseAuthParams(challenge.substring(si + 1));
+ String realm = params.getProperty("realm");
+ Credentials creds = authenticator.getCredentials(realm, attempts);
+ String userPass = creds.getUsername() + ':' + creds.getPassword();
+ byte[] b_userPass = userPass.getBytes("US-ASCII");
+ byte[] b_encoded = Base64.encode(b_userPass).getBytes("US-ASCII");
+ String authorization =
+ scheme + " " + new String(b_encoded, "US-ASCII");
+ setHeader("Authorization", authorization);
+ return true;
+ }
+ else if ("Digest".equalsIgnoreCase(scheme))
+ {
+ Properties params = parseAuthParams(challenge.substring(si + 1));
+ String realm = params.getProperty("realm");
+ String nonce = params.getProperty("nonce");
+ String qop = params.getProperty("qop");
+ String algorithm = params.getProperty("algorithm");
+ String digestUri = getRequestURI();
+ Credentials creds = authenticator.getCredentials(realm, attempts);
+ String username = creds.getUsername();
+ String password = creds.getPassword();
+ connection.incrementNonce(nonce);
+ try
+ {
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ final byte[] COLON = { 0x3a };
+
+ // Calculate H(A1)
+ md5.reset();
+ md5.update(username.getBytes("US-ASCII"));
+ md5.update(COLON);
+ md5.update(realm.getBytes("US-ASCII"));
+ md5.update(COLON);
+ md5.update(password.getBytes("US-ASCII"));
+ byte[] ha1 = md5.digest();
+ if ("md5-sess".equals(algorithm))
+ {
+ byte[] cnonce = generateNonce();
+ md5.reset();
+ md5.update(ha1);
+ md5.update(COLON);
+ md5.update(nonce.getBytes("US-ASCII"));
+ md5.update(COLON);
+ md5.update(cnonce);
+ ha1 = md5.digest();
+ }
+ String ha1Hex = toHexString(ha1);
+
+ // Calculate H(A2)
+ md5.reset();
+ md5.update(method.getBytes("US-ASCII"));
+ md5.update(COLON);
+ md5.update(digestUri.getBytes("US-ASCII"));
+ if ("auth-int".equals(qop))
+ {
+ byte[] hEntity = null; // TODO hash of entity body
+ md5.update(COLON);
+ md5.update(hEntity);
+ }
+ byte[] ha2 = md5.digest();
+ String ha2Hex = toHexString(ha2);
+
+ // Calculate response
+ md5.reset();
+ md5.update(ha1Hex.getBytes("US-ASCII"));
+ md5.update(COLON);
+ md5.update(nonce.getBytes("US-ASCII"));
+ if ("auth".equals(qop) || "auth-int".equals(qop))
+ {
+ String nc = getNonceCount(nonce);
+ byte[] cnonce = generateNonce();
+ md5.update(COLON);
+ md5.update(nc.getBytes("US-ASCII"));
+ md5.update(COLON);
+ md5.update(cnonce);
+ md5.update(COLON);
+ md5.update(qop.getBytes("US-ASCII"));
+ }
+ md5.update(COLON);
+ md5.update(ha2Hex.getBytes("US-ASCII"));
+ String digestResponse = toHexString(md5.digest());
+
+ String authorization = scheme +
+ " username=\"" + username + "\"" +
+ " realm=\"" + realm + "\"" +
+ " nonce=\"" + nonce + "\"" +
+ " uri=\"" + digestUri + "\"" +
+ " response=\"" + digestResponse + "\"";
+ setHeader("Authorization", authorization);
+ return true;
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ return false;
+ }
+ }
+ // Scheme not recognised
+ return false;
+ }
+
+ Properties parseAuthParams(String text)
+ {
+ int len = text.length();
+ String key = null;
+ CPStringBuilder buf = new CPStringBuilder();
+ Properties ret = new Properties();
+ boolean inQuote = false;
+ for (int i = 0; i < len; i++)
+ {
+ char c = text.charAt(i);
+ if (c == '"')
+ {
+ inQuote = !inQuote;
+ }
+ else if (c == '=' && key == null)
+ {
+ key = buf.toString().trim();
+ buf.setLength(0);
+ }
+ else if (c == ' ' && !inQuote)
+ {
+ String value = unquote(buf.toString().trim());
+ ret.put(key, value);
+ key = null;
+ buf.setLength(0);
+ }
+ else if (c != ',' || (i <(len - 1) && text.charAt(i + 1) != ' '))
+ {
+ buf.append(c);
+ }
+ }
+ if (key != null)
+ {
+ String value = unquote(buf.toString().trim());
+ ret.put(key, value);
+ }
+ return ret;
+ }
+
+ String unquote(String text)
+ {
+ int len = text.length();
+ if (len > 0 && text.charAt(0) == '"' && text.charAt(len - 1) == '"')
+ {
+ return text.substring(1, len - 1);
+ }
+ return text;
+ }
+
+ /**
+ * Returns the number of times the specified nonce value has been seen.
+ * This always returns an 8-byte 0-padded hexadecimal string.
+ */
+ String getNonceCount(String nonce)
+ {
+ int nc = connection.getNonceCount(nonce);
+ String hex = Integer.toHexString(nc);
+ CPStringBuilder buf = new CPStringBuilder();
+ for (int i = 8 - hex.length(); i > 0; i--)
+ {
+ buf.append('0');
+ }
+ buf.append(hex);
+ return buf.toString();
+ }
+
+ /**
+ * Client nonce value.
+ */
+ byte[] nonce;
+
+ /**
+ * Generates a new client nonce value.
+ */
+ byte[] generateNonce()
+ throws IOException, NoSuchAlgorithmException
+ {
+ if (nonce == null)
+ {
+ long time = System.currentTimeMillis();
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ md5.update(Long.toString(time).getBytes("US-ASCII"));
+ nonce = md5.digest();
+ }
+ return nonce;
+ }
+
+ String toHexString(byte[] bytes)
+ {
+ char[] ret = new char[bytes.length * 2];
+ for (int i = 0, j = 0; i < bytes.length; i++)
+ {
+ int c =(int) bytes[i];
+ if (c < 0)
+ {
+ c += 0x100;
+ }
+ ret[j++] = Character.forDigit(c / 0x10, 0x10);
+ ret[j++] = Character.forDigit(c % 0x10, 0x10);
+ }
+ return new String(ret);
+ }
+
+ /**
+ * Parse the specified cookie list and notify the cookie manager.
+ */
+ void handleSetCookie(String text)
+ {
+ CookieManager cookieManager = connection.getCookieManager();
+ if (cookieManager == null)
+ {
+ return;
+ }
+ String name = null;
+ String value = null;
+ String comment = null;
+ String domain = connection.getHostName();
+ String path = this.path;
+ int lsi = path.lastIndexOf('/');
+ if (lsi != -1)
+ {
+ path = path.substring(0, lsi);
+ }
+ boolean secure = false;
+ Date expires = null;
+
+ int len = text.length();
+ String attr = null;
+ CPStringBuilder buf = new CPStringBuilder();
+ boolean inQuote = false;
+ for (int i = 0; i <= len; i++)
+ {
+ char c =(i == len) ? '\u0000' : text.charAt(i);
+ if (c == '"')
+ {
+ inQuote = !inQuote;
+ }
+ else if (!inQuote)
+ {
+ if (c == '=' && attr == null)
+ {
+ attr = buf.toString().trim();
+ buf.setLength(0);
+ }
+ else if (c == ';' || i == len || c == ',')
+ {
+ String val = unquote(buf.toString().trim());
+ if (name == null)
+ {
+ name = attr;
+ value = val;
+ }
+ else if ("Comment".equalsIgnoreCase(attr))
+ {
+ comment = val;
+ }
+ else if ("Domain".equalsIgnoreCase(attr))
+ {
+ domain = val;
+ }
+ else if ("Path".equalsIgnoreCase(attr))
+ {
+ path = val;
+ }
+ else if ("Secure".equalsIgnoreCase(val))
+ {
+ secure = true;
+ }
+ else if ("Max-Age".equalsIgnoreCase(attr))
+ {
+ int delta = Integer.parseInt(val);
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(System.currentTimeMillis());
+ cal.add(Calendar.SECOND, delta);
+ expires = cal.getTime();
+ }
+ else if ("Expires".equalsIgnoreCase(attr))
+ {
+ DateFormat dateFormat = new HTTPDateFormat();
+ try
+ {
+ expires = dateFormat.parse(val);
+ }
+ catch (ParseException e)
+ {
+ // if this isn't a valid date, it may be that
+ // the value was returned unquoted; in that case, we
+ // want to continue buffering the value
+ buf.append(c);
+ continue;
+ }
+ }
+ attr = null;
+ buf.setLength(0);
+ // case EOL
+ if (i == len || c == ',')
+ {
+ Cookie cookie = new Cookie(name, value, comment, domain,
+ path, secure, expires);
+ cookieManager.setCookie(cookie);
+ }
+ if (c == ',')
+ {
+ // Reset cookie fields
+ name = null;
+ value = null;
+ comment = null;
+ domain = connection.getHostName();
+ path = this.path;
+ if (lsi != -1)
+ {
+ path = path.substring(0, lsi);
+ }
+ secure = false;
+ expires = null;
+ }
+ }
+ else
+ {
+ buf.append(c);
+ }
+ }
+ else
+ {
+ buf.append(c);
+ }
+ }
+ }
+
+}
+