1:
37:
38:
39: package ;
40:
41: import ;
42: import ;
43: import ;
44:
45: import ;
46: import ;
47: import ;
48: import ;
49: import ;
50: import ;
51: import ;
52: import ;
53: import ;
54: import ;
55: import ;
56: import ;
57: import ;
58: import ;
59: import ;
60:
61:
66: public class Request
67: {
68:
69:
72: protected final HTTPConnection connection;
73:
74:
77: protected final String method;
78:
79:
84: protected final String path;
85:
86:
89: protected final Headers requestHeaders;
90:
91:
94: protected RequestBodyWriter requestBodyWriter;
95:
96:
99: protected Map<String, ResponseHeaderHandler> responseHeaderHandlers;
100:
101:
104: protected Authenticator authenticator;
105:
106:
109: private boolean dispatched;
110:
111:
117: protected Request(HTTPConnection connection, String method,
118: String path)
119: {
120: this.connection = connection;
121: this.method = method;
122: this.path = path;
123: requestHeaders = new Headers();
124: responseHeaderHandlers = new HashMap<String, ResponseHeaderHandler>();
125: }
126:
127:
131: public HTTPConnection getConnection()
132: {
133: return connection;
134: }
135:
136:
140: public String getMethod()
141: {
142: return method;
143: }
144:
145:
149: public String getPath()
150: {
151: return path;
152: }
153:
154:
158: public String getRequestURI()
159: {
160: return connection.getURI() + path;
161: }
162:
163:
166: public Headers getHeaders()
167: {
168: return requestHeaders;
169: }
170:
171:
175: public String getHeader(String name)
176: {
177: return requestHeaders.getValue(name);
178: }
179:
180:
184: public int getIntHeader(String name)
185: {
186: return requestHeaders.getIntValue(name);
187: }
188:
189:
193: public Date getDateHeader(String name)
194: {
195: return requestHeaders.getDateValue(name);
196: }
197:
198:
203: public void setHeader(String name, String value)
204: {
205: requestHeaders.put(name, value);
206: }
207:
208:
212: public void setRequestBody(byte[] requestBody)
213: {
214: setRequestBodyWriter(new ByteArrayRequestBodyWriter(requestBody));
215: }
216:
217:
221: public void setRequestBodyWriter(RequestBodyWriter requestBodyWriter)
222: {
223: this.requestBodyWriter = requestBodyWriter;
224: }
225:
226:
231: public void setResponseHeaderHandler(String name,
232: ResponseHeaderHandler handler)
233: {
234: responseHeaderHandlers.put(name, handler);
235: }
236:
237:
242: public void setAuthenticator(Authenticator authenticator)
243: {
244: this.authenticator = authenticator;
245: }
246:
247:
254: public Response dispatch()
255: throws IOException
256: {
257: if (dispatched)
258: {
259: throw new ProtocolException("request already dispatched");
260: }
261: final String CRLF = "\r\n";
262: final String HEADER_SEP = ": ";
263: final String US_ASCII = "US-ASCII";
264: final String version = connection.getVersion();
265: Response response;
266: int contentLength = -1;
267: boolean retry = false;
268: int attempts = 0;
269: boolean expectingContinue = false;
270: if (requestBodyWriter != null)
271: {
272: contentLength = requestBodyWriter.getContentLength();
273: String expect = getHeader("Expect");
274: if (expect != null && expect.equals("100-continue"))
275: {
276: expectingContinue = true;
277: }
278: else
279: {
280: setHeader("Content-Length", Integer.toString(contentLength));
281: }
282: }
283:
284: try
285: {
286:
287: do
288: {
289: retry = false;
290:
291:
292: OutputStream out = connection.getOutputStream();
293:
294:
295: String requestUri = path;
296: if (connection.isUsingProxy() &&
297: !"*".equals(requestUri) &&
298: !"CONNECT".equals(method))
299: {
300: requestUri = getRequestURI();
301: }
302: String line = method + ' ' + requestUri + ' ' + version + CRLF;
303: out.write(line.getBytes(US_ASCII));
304:
305: for (Headers.HeaderElement elt : requestHeaders)
306: {
307: line = elt.name + HEADER_SEP + elt.value + CRLF;
308: out.write(line.getBytes(US_ASCII));
309: }
310: out.write(CRLF.getBytes(US_ASCII));
311:
312: if (requestBodyWriter != null && !expectingContinue)
313: {
314: byte[] buffer = new byte[4096];
315: int len;
316: int count = 0;
317:
318: requestBodyWriter.reset();
319: do
320: {
321: len = requestBodyWriter.write(buffer);
322: if (len > 0)
323: {
324: out.write(buffer, 0, len);
325: }
326: count += len;
327: }
328: while (len > -1 && count < contentLength);
329: }
330: out.flush();
331:
332: while(true)
333: {
334: response = readResponse(connection.getInputStream());
335: int sc = response.getCode();
336: if (sc == 401 && authenticator != null)
337: {
338: if (authenticate(response, attempts++))
339: {
340: retry = true;
341: }
342: }
343: else if (sc == 100)
344: {
345: if (expectingContinue)
346: {
347: requestHeaders.remove("Expect");
348: setHeader("Content-Length",
349: Integer.toString(contentLength));
350: expectingContinue = false;
351: retry = true;
352: }
353: else
354: {
355:
356:
357:
358:
359:
360: continue;
361: }
362: }
363: break;
364: }
365: }
366: while (retry);
367: }
368: catch (IOException e)
369: {
370: connection.close();
371: throw e;
372: }
373: return response;
374: }
375:
376: Response readResponse(InputStream in)
377: throws IOException
378: {
379: String line;
380: int len;
381:
382:
383: LineInputStream lis = new LineInputStream(in);
384:
385: line = lis.readLine();
386: if (line == null)
387: {
388: throw new ProtocolException("Peer closed connection");
389: }
390: if (!line.startsWith("HTTP/"))
391: {
392: throw new ProtocolException(line);
393: }
394: len = line.length();
395: int start = 5, end = 6;
396: while (line.charAt(end) != '.')
397: {
398: end++;
399: }
400: int majorVersion = Integer.parseInt(line.substring(start, end));
401: start = end + 1;
402: end = start + 1;
403: while (line.charAt(end) != ' ')
404: {
405: end++;
406: }
407: int minorVersion = Integer.parseInt(line.substring(start, end));
408: start = end + 1;
409: end = start + 3;
410: int code = Integer.parseInt(line.substring(start, end));
411: String message = line.substring(end + 1, len - 1);
412:
413: Headers responseHeaders = new Headers();
414: responseHeaders.parse(lis);
415: notifyHeaderHandlers(responseHeaders);
416: InputStream body = null;
417:
418: switch (code)
419: {
420: case 100:
421: break;
422: case 204:
423: case 205:
424: case 304:
425: body = createResponseBodyStream(responseHeaders, majorVersion,
426: minorVersion, in, false);
427: break;
428: default:
429: body = createResponseBodyStream(responseHeaders, majorVersion,
430: minorVersion, in, true);
431: }
432:
433:
434: Response ret = new Response(majorVersion, minorVersion, code,
435: message, responseHeaders, body);
436: return ret;
437: }
438:
439: void notifyHeaderHandlers(Headers headers)
440: {
441: for (Headers.HeaderElement entry : headers)
442: {
443:
444: if ("Set-Cookie".equalsIgnoreCase(entry.name))
445: handleSetCookie(entry.value);
446:
447: ResponseHeaderHandler handler =
448: (ResponseHeaderHandler) responseHeaderHandlers.get(entry.name);
449: if (handler != null)
450: handler.setValue(entry.value);
451: }
452: }
453:
454: private InputStream createResponseBodyStream(Headers responseHeaders,
455: int majorVersion,
456: int minorVersion,
457: InputStream in,
458: boolean mayHaveBody)
459: throws IOException
460: {
461: long contentLength = -1;
462:
463:
464: boolean doClose = "close".equalsIgnoreCase(getHeader("Connection")) ||
465: "close".equalsIgnoreCase(responseHeaders.getValue("Connection")) ||
466: (connection.majorVersion == 1 && connection.minorVersion == 0) ||
467: (majorVersion == 1 && minorVersion == 0);
468:
469: String transferCoding = responseHeaders.getValue("Transfer-Encoding");
470: if ("HEAD".equals(method) || !mayHaveBody)
471: {
472:
473: in = new LimitedLengthInputStream(in, 0, true, connection, doClose);
474: }
475: else if ("chunked".equalsIgnoreCase(transferCoding))
476: {
477: in = new LimitedLengthInputStream(in, -1, false, connection, doClose);
478:
479: in = new ChunkedInputStream(in, responseHeaders);
480: }
481: else
482: {
483: contentLength = responseHeaders.getLongValue("Content-Length");
484:
485: if (contentLength < 0)
486: doClose = true;
487:
488: in = new LimitedLengthInputStream(in, contentLength,
489: contentLength >= 0,
490: connection, doClose);
491: }
492: String contentCoding = responseHeaders.getValue("Content-Encoding");
493: if (contentCoding != null && !"identity".equals(contentCoding))
494: {
495: if ("gzip".equals(contentCoding))
496: {
497: in = new GZIPInputStream(in);
498: }
499: else if ("deflate".equals(contentCoding))
500: {
501: in = new InflaterInputStream(in);
502: }
503: else
504: {
505: throw new ProtocolException("Unsupported Content-Encoding: " +
506: contentCoding);
507: }
508:
509:
510: responseHeaders.remove("Content-Encoding");
511: }
512: return in;
513: }
514:
515: boolean authenticate(Response response, int attempts)
516: throws IOException
517: {
518: String challenge = response.getHeader("WWW-Authenticate");
519: if (challenge == null)
520: {
521: challenge = response.getHeader("Proxy-Authenticate");
522: }
523: int si = challenge.indexOf(' ');
524: String scheme = (si == -1) ? challenge : challenge.substring(0, si);
525: if ("Basic".equalsIgnoreCase(scheme))
526: {
527: Properties params = parseAuthParams(challenge.substring(si + 1));
528: String realm = params.getProperty("realm");
529: Credentials creds = authenticator.getCredentials(realm, attempts);
530: String userPass = creds.getUsername() + ':' + creds.getPassword();
531: byte[] b_userPass = userPass.getBytes("US-ASCII");
532: byte[] b_encoded = Base64.encode(b_userPass).getBytes("US-ASCII");
533: String authorization =
534: scheme + " " + new String(b_encoded, "US-ASCII");
535: setHeader("Authorization", authorization);
536: return true;
537: }
538: else if ("Digest".equalsIgnoreCase(scheme))
539: {
540: Properties params = parseAuthParams(challenge.substring(si + 1));
541: String realm = params.getProperty("realm");
542: String nonce = params.getProperty("nonce");
543: String qop = params.getProperty("qop");
544: String algorithm = params.getProperty("algorithm");
545: String digestUri = getRequestURI();
546: Credentials creds = authenticator.getCredentials(realm, attempts);
547: String username = creds.getUsername();
548: String password = creds.getPassword();
549: connection.incrementNonce(nonce);
550: try
551: {
552: MessageDigest md5 = MessageDigest.getInstance("MD5");
553: final byte[] COLON = { 0x3a };
554:
555:
556: md5.reset();
557: md5.update(username.getBytes("US-ASCII"));
558: md5.update(COLON);
559: md5.update(realm.getBytes("US-ASCII"));
560: md5.update(COLON);
561: md5.update(password.getBytes("US-ASCII"));
562: byte[] ha1 = md5.digest();
563: if ("md5-sess".equals(algorithm))
564: {
565: byte[] cnonce = generateNonce();
566: md5.reset();
567: md5.update(ha1);
568: md5.update(COLON);
569: md5.update(nonce.getBytes("US-ASCII"));
570: md5.update(COLON);
571: md5.update(cnonce);
572: ha1 = md5.digest();
573: }
574: String ha1Hex = toHexString(ha1);
575:
576:
577: md5.reset();
578: md5.update(method.getBytes("US-ASCII"));
579: md5.update(COLON);
580: md5.update(digestUri.getBytes("US-ASCII"));
581: if ("auth-int".equals(qop))
582: {
583: byte[] hEntity = null;
584: md5.update(COLON);
585: md5.update(hEntity);
586: }
587: byte[] ha2 = md5.digest();
588: String ha2Hex = toHexString(ha2);
589:
590:
591: md5.reset();
592: md5.update(ha1Hex.getBytes("US-ASCII"));
593: md5.update(COLON);
594: md5.update(nonce.getBytes("US-ASCII"));
595: if ("auth".equals(qop) || "auth-int".equals(qop))
596: {
597: String nc = getNonceCount(nonce);
598: byte[] cnonce = generateNonce();
599: md5.update(COLON);
600: md5.update(nc.getBytes("US-ASCII"));
601: md5.update(COLON);
602: md5.update(cnonce);
603: md5.update(COLON);
604: md5.update(qop.getBytes("US-ASCII"));
605: }
606: md5.update(COLON);
607: md5.update(ha2Hex.getBytes("US-ASCII"));
608: String digestResponse = toHexString(md5.digest());
609:
610: String authorization = scheme +
611: " username=\"" + username + "\"" +
612: " realm=\"" + realm + "\"" +
613: " nonce=\"" + nonce + "\"" +
614: " uri=\"" + digestUri + "\"" +
615: " response=\"" + digestResponse + "\"";
616: setHeader("Authorization", authorization);
617: return true;
618: }
619: catch (NoSuchAlgorithmException e)
620: {
621: return false;
622: }
623: }
624:
625: return false;
626: }
627:
628: Properties parseAuthParams(String text)
629: {
630: int len = text.length();
631: String key = null;
632: CPStringBuilder buf = new CPStringBuilder();
633: Properties ret = new Properties();
634: boolean inQuote = false;
635: for (int i = 0; i < len; i++)
636: {
637: char c = text.charAt(i);
638: if (c == '"')
639: {
640: inQuote = !inQuote;
641: }
642: else if (c == '=' && key == null)
643: {
644: key = buf.toString().trim();
645: buf.setLength(0);
646: }
647: else if (c == ' ' && !inQuote)
648: {
649: String value = unquote(buf.toString().trim());
650: ret.put(key, value);
651: key = null;
652: buf.setLength(0);
653: }
654: else if (c != ',' || (i <(len - 1) && text.charAt(i + 1) != ' '))
655: {
656: buf.append(c);
657: }
658: }
659: if (key != null)
660: {
661: String value = unquote(buf.toString().trim());
662: ret.put(key, value);
663: }
664: return ret;
665: }
666:
667: String unquote(String text)
668: {
669: int len = text.length();
670: if (len > 0 && text.charAt(0) == '"' && text.charAt(len - 1) == '"')
671: {
672: return text.substring(1, len - 1);
673: }
674: return text;
675: }
676:
677:
681: String getNonceCount(String nonce)
682: {
683: int nc = connection.getNonceCount(nonce);
684: String hex = Integer.toHexString(nc);
685: CPStringBuilder buf = new CPStringBuilder();
686: for (int i = 8 - hex.length(); i > 0; i--)
687: {
688: buf.append('0');
689: }
690: buf.append(hex);
691: return buf.toString();
692: }
693:
694:
697: byte[] nonce;
698:
699:
702: byte[] generateNonce()
703: throws IOException, NoSuchAlgorithmException
704: {
705: if (nonce == null)
706: {
707: long time = System.currentTimeMillis();
708: MessageDigest md5 = MessageDigest.getInstance("MD5");
709: md5.update(Long.toString(time).getBytes("US-ASCII"));
710: nonce = md5.digest();
711: }
712: return nonce;
713: }
714:
715: String toHexString(byte[] bytes)
716: {
717: char[] ret = new char[bytes.length * 2];
718: for (int i = 0, j = 0; i < bytes.length; i++)
719: {
720: int c =(int) bytes[i];
721: if (c < 0)
722: {
723: c += 0x100;
724: }
725: ret[j++] = Character.forDigit(c / 0x10, 0x10);
726: ret[j++] = Character.forDigit(c % 0x10, 0x10);
727: }
728: return new String(ret);
729: }
730:
731:
734: void handleSetCookie(String text)
735: {
736: CookieManager cookieManager = connection.getCookieManager();
737: if (cookieManager == null)
738: {
739: return;
740: }
741: String name = null;
742: String value = null;
743: String comment = null;
744: String domain = connection.getHostName();
745: String path = this.path;
746: int lsi = path.lastIndexOf('/');
747: if (lsi != -1)
748: {
749: path = path.substring(0, lsi);
750: }
751: boolean secure = false;
752: Date expires = null;
753:
754: int len = text.length();
755: String attr = null;
756: CPStringBuilder buf = new CPStringBuilder();
757: boolean inQuote = false;
758: for (int i = 0; i <= len; i++)
759: {
760: char c =(i == len) ? '\u0000' : text.charAt(i);
761: if (c == '"')
762: {
763: inQuote = !inQuote;
764: }
765: else if (!inQuote)
766: {
767: if (c == '=' && attr == null)
768: {
769: attr = buf.toString().trim();
770: buf.setLength(0);
771: }
772: else if (c == ';' || i == len || c == ',')
773: {
774: String val = unquote(buf.toString().trim());
775: if (name == null)
776: {
777: name = attr;
778: value = val;
779: }
780: else if ("Comment".equalsIgnoreCase(attr))
781: {
782: comment = val;
783: }
784: else if ("Domain".equalsIgnoreCase(attr))
785: {
786: domain = val;
787: }
788: else if ("Path".equalsIgnoreCase(attr))
789: {
790: path = val;
791: }
792: else if ("Secure".equalsIgnoreCase(val))
793: {
794: secure = true;
795: }
796: else if ("Max-Age".equalsIgnoreCase(attr))
797: {
798: int delta = Integer.parseInt(val);
799: Calendar cal = Calendar.getInstance();
800: cal.setTimeInMillis(System.currentTimeMillis());
801: cal.add(Calendar.SECOND, delta);
802: expires = cal.getTime();
803: }
804: else if ("Expires".equalsIgnoreCase(attr))
805: {
806: DateFormat dateFormat = new HTTPDateFormat();
807: try
808: {
809: expires = dateFormat.parse(val);
810: }
811: catch (ParseException e)
812: {
813:
814:
815:
816: buf.append(c);
817: continue;
818: }
819: }
820: attr = null;
821: buf.setLength(0);
822:
823: if (i == len || c == ',')
824: {
825: Cookie cookie = new Cookie(name, value, comment, domain,
826: path, secure, expires);
827: cookieManager.setCookie(cookie);
828: }
829: if (c == ',')
830: {
831:
832: name = null;
833: value = null;
834: comment = null;
835: domain = connection.getHostName();
836: path = this.path;
837: if (lsi != -1)
838: {
839: path = path.substring(0, lsi);
840: }
841: secure = false;
842: expires = null;
843: }
844: }
845: else
846: {
847: buf.append(c);
848: }
849: }
850: else
851: {
852: buf.append(c);
853: }
854: }
855: }
856:
857: }