Remove old autovect-branch by moving to "dead" directory.
[official-gcc.git] / old-autovect-branch / libjava / classpath / gnu / java / net / protocol / http / Request.java
blobb9441b3f7369e3144c86e43595b46aff5c72f276
1 /* Request.java --
2 Copyright (C) 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package gnu.java.net.protocol.http;
41 import gnu.java.net.BASE64;
42 import gnu.java.net.LineInputStream;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.OutputStream;
47 import java.net.ProtocolException;
48 import java.security.MessageDigest;
49 import java.security.NoSuchAlgorithmException;
50 import java.text.DateFormat;
51 import java.text.ParseException;
52 import java.util.Calendar;
53 import java.util.Date;
54 import java.util.HashMap;
55 import java.util.Iterator;
56 import java.util.Map;
57 import java.util.Properties;
58 import java.util.zip.GZIPInputStream;
59 import java.util.zip.InflaterInputStream;
61 /**
62 * A single HTTP request.
64 * @author Chris Burdess (dog@gnu.org)
66 public class Request
69 /**
70 * The connection context in which this request is invoked.
72 protected final HTTPConnection connection;
74 /**
75 * The HTTP method to invoke.
77 protected final String method;
79 /**
80 * The path identifying the resource.
81 * This string must conform to the abs_path definition given in RFC2396,
82 * with an optional "?query" part, and must be URI-escaped by the caller.
84 protected final String path;
86 /**
87 * The headers in this request.
89 protected final Headers requestHeaders;
91 /**
92 * The request body provider.
94 protected RequestBodyWriter requestBodyWriter;
96 /**
97 * Request body negotiation threshold for 100-continue expectations.
99 protected int requestBodyNegotiationThreshold;
102 * Map of response header handlers.
104 protected Map responseHeaderHandlers;
107 * The authenticator.
109 protected Authenticator authenticator;
112 * Whether this request has been dispatched yet.
114 private boolean dispatched;
117 * Constructor for a new request.
118 * @param connection the connection context
119 * @param method the HTTP method
120 * @param path the resource path including query part
122 protected Request(HTTPConnection connection, String method,
123 String path)
125 this.connection = connection;
126 this.method = method;
127 this.path = path;
128 requestHeaders = new Headers();
129 responseHeaderHandlers = new HashMap();
130 requestBodyNegotiationThreshold = 4096;
134 * Returns the connection associated with this request.
135 * @see #connection
137 public HTTPConnection getConnection()
139 return connection;
143 * Returns the HTTP method to invoke.
144 * @see #method
146 public String getMethod()
148 return method;
152 * Returns the resource path.
153 * @see #path
155 public String getPath()
157 return path;
161 * Returns the full request-URI represented by this request, as specified
162 * by HTTP/1.1.
164 public String getRequestURI()
166 return connection.getURI() + path;
170 * Returns the headers in this request.
172 public Headers getHeaders()
174 return requestHeaders;
178 * Returns the value of the specified header in this request.
179 * @param name the header name
181 public String getHeader(String name)
183 return requestHeaders.getValue(name);
187 * Returns the value of the specified header in this request as an integer.
188 * @param name the header name
190 public int getIntHeader(String name)
192 return requestHeaders.getIntValue(name);
196 * Returns the value of the specified header in this request as a date.
197 * @param name the header name
199 public Date getDateHeader(String name)
201 return requestHeaders.getDateValue(name);
205 * Sets the specified header in this request.
206 * @param name the header name
207 * @param value the header value
209 public void setHeader(String name, String value)
211 requestHeaders.put(name, value);
215 * Convenience method to set the entire request body.
216 * @param requestBody the request body content
218 public void setRequestBody(byte[] requestBody)
220 setRequestBodyWriter(new ByteArrayRequestBodyWriter(requestBody));
224 * Sets the request body provider.
225 * @param requestBodyWriter the handler used to obtain the request body
227 public void setRequestBodyWriter(RequestBodyWriter requestBodyWriter)
229 this.requestBodyWriter = requestBodyWriter;
233 * Sets a callback handler to be invoked for the specified header name.
234 * @param name the header name
235 * @param handler the handler to receive the value for the header
237 public void setResponseHeaderHandler(String name,
238 ResponseHeaderHandler handler)
240 responseHeaderHandlers.put(name, handler);
244 * Sets an authenticator that can be used to handle authentication
245 * automatically.
246 * @param authenticator the authenticator
248 public void setAuthenticator(Authenticator authenticator)
250 this.authenticator = authenticator;
254 * Sets the request body negotiation threshold.
255 * If this is set, it determines the maximum size that the request body
256 * may be before body negotiation occurs(via the
257 * <code>100-continue</code> expectation). This ensures that a large
258 * request body is not sent when the server wouldn't have accepted it
259 * anyway.
260 * @param threshold the body negotiation threshold, or &lt;=0 to disable
261 * request body negotation entirely
263 public void setRequestBodyNegotiationThreshold(int threshold)
265 requestBodyNegotiationThreshold = threshold;
269 * Dispatches this request.
270 * A request can only be dispatched once; calling this method a second
271 * time results in a protocol exception.
272 * @exception IOException if an I/O error occurred
273 * @return an HTTP response object representing the result of the operation
275 public Response dispatch()
276 throws IOException
278 if (dispatched)
280 throw new ProtocolException("request already dispatched");
282 final String CRLF = "\r\n";
283 final String HEADER_SEP = ": ";
284 final String US_ASCII = "US-ASCII";
285 final String version = connection.getVersion();
286 Response response;
287 int contentLength = -1;
288 boolean retry = false;
289 int attempts = 0;
290 boolean expectingContinue = false;
291 if (requestBodyWriter != null)
293 contentLength = requestBodyWriter.getContentLength();
294 if (contentLength > requestBodyNegotiationThreshold)
296 expectingContinue = true;
297 setHeader("Expect", "100-continue");
299 else
301 setHeader("Content-Length", Integer.toString(contentLength));
307 // Loop while authentication fails or continue
310 retry = false;
312 // Get socket output and input streams
313 OutputStream out = connection.getOutputStream();
315 // Request line
316 String requestUri = path;
317 if (connection.isUsingProxy() &&
318 !"*".equals(requestUri) &&
319 !"CONNECT".equals(method))
321 requestUri = getRequestURI();
323 String line = method + ' ' + requestUri + ' ' + version + CRLF;
324 out.write(line.getBytes(US_ASCII));
325 // Request headers
326 for (Iterator i = requestHeaders.keySet().iterator();
327 i.hasNext(); )
329 String name =(String) i.next();
330 String value =(String) requestHeaders.get(name);
331 line = name + HEADER_SEP + value + CRLF;
332 out.write(line.getBytes(US_ASCII));
334 out.write(CRLF.getBytes(US_ASCII));
335 // Request body
336 if (requestBodyWriter != null && !expectingContinue)
338 byte[] buffer = new byte[4096];
339 int len;
340 int count = 0;
342 requestBodyWriter.reset();
345 len = requestBodyWriter.write(buffer);
346 if (len > 0)
348 out.write(buffer, 0, len);
350 count += len;
352 while (len > -1 && count < contentLength);
354 out.flush();
355 // Get response
356 while(true)
358 response = readResponse(connection.getInputStream());
359 int sc = response.getCode();
360 if (sc == 401 && authenticator != null)
362 if (authenticate(response, attempts++))
364 retry = true;
367 else if (sc == 100)
369 if (expectingContinue)
371 requestHeaders.remove("Expect");
372 setHeader("Content-Length",
373 Integer.toString(contentLength));
374 expectingContinue = false;
375 retry = true;
377 else
379 // A conforming server can send an unsoliceted
380 // Continue response but *should* not (RFC 2616
381 // sec 8.2.3). Ignore the bogus Continue
382 // response and get the real response that
383 // should follow
384 continue;
387 break;
390 while (retry);
392 catch (IOException e)
394 connection.close();
395 throw e;
397 return response;
400 Response readResponse(InputStream in)
401 throws IOException
403 String line;
404 int len;
406 // Read response status line
407 LineInputStream lis = new LineInputStream(in);
409 line = lis.readLine();
410 if (line == null)
412 throw new ProtocolException("Peer closed connection");
414 if (!line.startsWith("HTTP/"))
416 throw new ProtocolException(line);
418 len = line.length();
419 int start = 5, end = 6;
420 while (line.charAt(end) != '.')
422 end++;
424 int majorVersion = Integer.parseInt(line.substring(start, end));
425 start = end + 1;
426 end = start + 1;
427 while (line.charAt(end) != ' ')
429 end++;
431 int minorVersion = Integer.parseInt(line.substring(start, end));
432 start = end + 1;
433 end = start + 3;
434 int code = Integer.parseInt(line.substring(start, end));
435 String message = line.substring(end + 1, len - 1);
436 // Read response headers
437 Headers responseHeaders = new Headers();
438 responseHeaders.parse(lis);
439 notifyHeaderHandlers(responseHeaders);
440 InputStream body = null;
442 switch (code)
444 case 100:
445 case 204:
446 case 205:
447 case 304:
448 break;
449 default:
450 body = createResponseBodyStream(responseHeaders, majorVersion,
451 minorVersion, in);
454 // Construct response
455 Response ret = new Response(majorVersion, minorVersion, code,
456 message, responseHeaders, body);
457 return ret;
460 void notifyHeaderHandlers(Headers headers)
462 for (Iterator i = headers.entrySet().iterator(); i.hasNext(); )
464 Map.Entry entry = (Map.Entry) i.next();
465 String name =(String) entry.getKey();
466 // Handle Set-Cookie
467 if ("Set-Cookie".equalsIgnoreCase(name))
469 String value = (String) entry.getValue();
470 handleSetCookie(value);
472 ResponseHeaderHandler handler =
473 (ResponseHeaderHandler) responseHeaderHandlers.get(name);
474 if (handler != null)
476 String value = (String) entry.getValue();
477 handler.setValue(value);
482 private InputStream createResponseBodyStream(Headers responseHeaders,
483 int majorVersion,
484 int minorVersion,
485 InputStream in)
486 throws IOException
488 long contentLength = -1;
489 Headers trailer = null;
491 // Persistent connections are the default in HTTP/1.1
492 boolean doClose = "close".equalsIgnoreCase(getHeader("Connection")) ||
493 "close".equalsIgnoreCase(responseHeaders.getValue("Connection")) ||
494 (connection.majorVersion == 1 && connection.minorVersion == 0) ||
495 (majorVersion == 1 && minorVersion == 0);
497 String transferCoding = responseHeaders.getValue("Transfer-Encoding");
498 if ("chunked".equalsIgnoreCase(transferCoding))
500 in = new LimitedLengthInputStream(in, -1, false, connection, doClose);
502 in = new ChunkedInputStream(in, responseHeaders);
504 else
506 contentLength = responseHeaders.getLongValue("Content-Length");
508 if (contentLength < 0)
509 doClose = true; // No Content-Length, must close.
511 in = new LimitedLengthInputStream(in, contentLength,
512 contentLength >= 0,
513 connection, doClose);
515 String contentCoding = responseHeaders.getValue("Content-Encoding");
516 if (contentCoding != null && !"identity".equals(contentCoding))
518 if ("gzip".equals(contentCoding))
520 in = new GZIPInputStream(in);
522 else if ("deflate".equals(contentCoding))
524 in = new InflaterInputStream(in);
526 else
528 throw new ProtocolException("Unsupported Content-Encoding: " +
529 contentCoding);
532 return in;
535 boolean authenticate(Response response, int attempts)
536 throws IOException
538 String challenge = response.getHeader("WWW-Authenticate");
539 if (challenge == null)
541 challenge = response.getHeader("Proxy-Authenticate");
543 int si = challenge.indexOf(' ');
544 String scheme = (si == -1) ? challenge : challenge.substring(0, si);
545 if ("Basic".equalsIgnoreCase(scheme))
547 Properties params = parseAuthParams(challenge.substring(si + 1));
548 String realm = params.getProperty("realm");
549 Credentials creds = authenticator.getCredentials(realm, attempts);
550 String userPass = creds.getUsername() + ':' + creds.getPassword();
551 byte[] b_userPass = userPass.getBytes("US-ASCII");
552 byte[] b_encoded = BASE64.encode(b_userPass);
553 String authorization =
554 scheme + " " + new String(b_encoded, "US-ASCII");
555 setHeader("Authorization", authorization);
556 return true;
558 else if ("Digest".equalsIgnoreCase(scheme))
560 Properties params = parseAuthParams(challenge.substring(si + 1));
561 String realm = params.getProperty("realm");
562 String nonce = params.getProperty("nonce");
563 String qop = params.getProperty("qop");
564 String algorithm = params.getProperty("algorithm");
565 String digestUri = getRequestURI();
566 Credentials creds = authenticator.getCredentials(realm, attempts);
567 String username = creds.getUsername();
568 String password = creds.getPassword();
569 connection.incrementNonce(nonce);
572 MessageDigest md5 = MessageDigest.getInstance("MD5");
573 final byte[] COLON = { 0x3a };
575 // Calculate H(A1)
576 md5.reset();
577 md5.update(username.getBytes("US-ASCII"));
578 md5.update(COLON);
579 md5.update(realm.getBytes("US-ASCII"));
580 md5.update(COLON);
581 md5.update(password.getBytes("US-ASCII"));
582 byte[] ha1 = md5.digest();
583 if ("md5-sess".equals(algorithm))
585 byte[] cnonce = generateNonce();
586 md5.reset();
587 md5.update(ha1);
588 md5.update(COLON);
589 md5.update(nonce.getBytes("US-ASCII"));
590 md5.update(COLON);
591 md5.update(cnonce);
592 ha1 = md5.digest();
594 String ha1Hex = toHexString(ha1);
596 // Calculate H(A2)
597 md5.reset();
598 md5.update(method.getBytes("US-ASCII"));
599 md5.update(COLON);
600 md5.update(digestUri.getBytes("US-ASCII"));
601 if ("auth-int".equals(qop))
603 byte[] hEntity = null; // TODO hash of entity body
604 md5.update(COLON);
605 md5.update(hEntity);
607 byte[] ha2 = md5.digest();
608 String ha2Hex = toHexString(ha2);
610 // Calculate response
611 md5.reset();
612 md5.update(ha1Hex.getBytes("US-ASCII"));
613 md5.update(COLON);
614 md5.update(nonce.getBytes("US-ASCII"));
615 if ("auth".equals(qop) || "auth-int".equals(qop))
617 String nc = getNonceCount(nonce);
618 byte[] cnonce = generateNonce();
619 md5.update(COLON);
620 md5.update(nc.getBytes("US-ASCII"));
621 md5.update(COLON);
622 md5.update(cnonce);
623 md5.update(COLON);
624 md5.update(qop.getBytes("US-ASCII"));
626 md5.update(COLON);
627 md5.update(ha2Hex.getBytes("US-ASCII"));
628 String digestResponse = toHexString(md5.digest());
630 String authorization = scheme +
631 " username=\"" + username + "\"" +
632 " realm=\"" + realm + "\"" +
633 " nonce=\"" + nonce + "\"" +
634 " uri=\"" + digestUri + "\"" +
635 " response=\"" + digestResponse + "\"";
636 setHeader("Authorization", authorization);
637 return true;
639 catch (NoSuchAlgorithmException e)
641 return false;
644 // Scheme not recognised
645 return false;
648 Properties parseAuthParams(String text)
650 int len = text.length();
651 String key = null;
652 StringBuilder buf = new StringBuilder();
653 Properties ret = new Properties();
654 boolean inQuote = false;
655 for (int i = 0; i < len; i++)
657 char c = text.charAt(i);
658 if (c == '"')
660 inQuote = !inQuote;
662 else if (c == '=' && key == null)
664 key = buf.toString().trim();
665 buf.setLength(0);
667 else if (c == ' ' && !inQuote)
669 String value = unquote(buf.toString().trim());
670 ret.put(key, value);
671 key = null;
672 buf.setLength(0);
674 else if (c != ',' || (i <(len - 1) && text.charAt(i + 1) != ' '))
676 buf.append(c);
679 if (key != null)
681 String value = unquote(buf.toString().trim());
682 ret.put(key, value);
684 return ret;
687 String unquote(String text)
689 int len = text.length();
690 if (len > 0 && text.charAt(0) == '"' && text.charAt(len - 1) == '"')
692 return text.substring(1, len - 1);
694 return text;
698 * Returns the number of times the specified nonce value has been seen.
699 * This always returns an 8-byte 0-padded hexadecimal string.
701 String getNonceCount(String nonce)
703 int nc = connection.getNonceCount(nonce);
704 String hex = Integer.toHexString(nc);
705 StringBuilder buf = new StringBuilder();
706 for (int i = 8 - hex.length(); i > 0; i--)
708 buf.append('0');
710 buf.append(hex);
711 return buf.toString();
715 * Client nonce value.
717 byte[] nonce;
720 * Generates a new client nonce value.
722 byte[] generateNonce()
723 throws IOException, NoSuchAlgorithmException
725 if (nonce == null)
727 long time = System.currentTimeMillis();
728 MessageDigest md5 = MessageDigest.getInstance("MD5");
729 md5.update(Long.toString(time).getBytes("US-ASCII"));
730 nonce = md5.digest();
732 return nonce;
735 String toHexString(byte[] bytes)
737 char[] ret = new char[bytes.length * 2];
738 for (int i = 0, j = 0; i < bytes.length; i++)
740 int c =(int) bytes[i];
741 if (c < 0)
743 c += 0x100;
745 ret[j++] = Character.forDigit(c / 0x10, 0x10);
746 ret[j++] = Character.forDigit(c % 0x10, 0x10);
748 return new String(ret);
752 * Parse the specified cookie list and notify the cookie manager.
754 void handleSetCookie(String text)
756 CookieManager cookieManager = connection.getCookieManager();
757 if (cookieManager == null)
759 return;
761 String name = null;
762 String value = null;
763 String comment = null;
764 String domain = connection.getHostName();
765 String path = this.path;
766 int lsi = path.lastIndexOf('/');
767 if (lsi != -1)
769 path = path.substring(0, lsi);
771 boolean secure = false;
772 Date expires = null;
774 int len = text.length();
775 String attr = null;
776 StringBuilder buf = new StringBuilder();
777 boolean inQuote = false;
778 for (int i = 0; i <= len; i++)
780 char c =(i == len) ? '\u0000' : text.charAt(i);
781 if (c == '"')
783 inQuote = !inQuote;
785 else if (!inQuote)
787 if (c == '=' && attr == null)
789 attr = buf.toString().trim();
790 buf.setLength(0);
792 else if (c == ';' || i == len || c == ',')
794 String val = unquote(buf.toString().trim());
795 if (name == null)
797 name = attr;
798 value = val;
800 else if ("Comment".equalsIgnoreCase(attr))
802 comment = val;
804 else if ("Domain".equalsIgnoreCase(attr))
806 domain = val;
808 else if ("Path".equalsIgnoreCase(attr))
810 path = val;
812 else if ("Secure".equalsIgnoreCase(val))
814 secure = true;
816 else if ("Max-Age".equalsIgnoreCase(attr))
818 int delta = Integer.parseInt(val);
819 Calendar cal = Calendar.getInstance();
820 cal.setTimeInMillis(System.currentTimeMillis());
821 cal.add(Calendar.SECOND, delta);
822 expires = cal.getTime();
824 else if ("Expires".equalsIgnoreCase(attr))
826 DateFormat dateFormat = new HTTPDateFormat();
829 expires = dateFormat.parse(val);
831 catch (ParseException e)
833 // if this isn't a valid date, it may be that
834 // the value was returned unquoted; in that case, we
835 // want to continue buffering the value
836 buf.append(c);
837 continue;
840 attr = null;
841 buf.setLength(0);
842 // case EOL
843 if (i == len || c == ',')
845 Cookie cookie = new Cookie(name, value, comment, domain,
846 path, secure, expires);
847 cookieManager.setCookie(cookie);
849 if (c == ',')
851 // Reset cookie fields
852 name = null;
853 value = null;
854 comment = null;
855 domain = connection.getHostName();
856 path = this.path;
857 if (lsi != -1)
859 path = path.substring(0, lsi);
861 secure = false;
862 expires = null;
865 else
867 buf.append(c);
870 else
872 buf.append(c);