Source for gnu.java.net.protocol.http.Request

   1: /* Request.java --
   2:    Copyright (C) 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   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.
  10: 
  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.
  15: 
  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.
  20: 
  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.
  25: 
  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. */
  37: 
  38: 
  39: package gnu.java.net.protocol.http;
  40: 
  41: import gnu.java.lang.CPStringBuilder;
  42: import gnu.java.net.LineInputStream;
  43: import gnu.java.util.Base64;
  44: 
  45: import java.io.IOException;
  46: import java.io.InputStream;
  47: import java.io.OutputStream;
  48: import java.net.ProtocolException;
  49: import java.security.MessageDigest;
  50: import java.security.NoSuchAlgorithmException;
  51: import java.text.DateFormat;
  52: import java.text.ParseException;
  53: import java.util.Calendar;
  54: import java.util.Date;
  55: import java.util.HashMap;
  56: import java.util.Map;
  57: import java.util.Properties;
  58: import java.util.zip.GZIPInputStream;
  59: import java.util.zip.InflaterInputStream;
  60: 
  61: /**
  62:  * A single HTTP request.
  63:  *
  64:  * @author Chris Burdess (dog@gnu.org)
  65:  */
  66: public class Request
  67: {
  68: 
  69:   /**
  70:    * The connection context in which this request is invoked.
  71:    */
  72:   protected final HTTPConnection connection;
  73: 
  74:   /**
  75:    * The HTTP method to invoke.
  76:    */
  77:   protected final String method;
  78: 
  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.
  83:    */
  84:   protected final String path;
  85: 
  86:   /**
  87:    * The headers in this request.
  88:    */
  89:   protected final Headers requestHeaders;
  90: 
  91:   /**
  92:    * The request body provider.
  93:    */
  94:   protected RequestBodyWriter requestBodyWriter;
  95: 
  96:   /**
  97:    * Map of response header handlers.
  98:    */
  99:   protected Map<String, ResponseHeaderHandler> responseHeaderHandlers;
 100: 
 101:   /**
 102:    * The authenticator.
 103:    */
 104:   protected Authenticator authenticator;
 105: 
 106:   /**
 107:    * Whether this request has been dispatched yet.
 108:    */
 109:   private boolean dispatched;
 110: 
 111:   /**
 112:    * Constructor for a new request.
 113:    * @param connection the connection context
 114:    * @param method the HTTP method
 115:    * @param path the resource path including query part
 116:    */
 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:   /**
 128:    * Returns the connection associated with this request.
 129:    * @see #connection
 130:    */
 131:   public HTTPConnection getConnection()
 132:   {
 133:     return connection;
 134:   }
 135: 
 136:   /**
 137:    * Returns the HTTP method to invoke.
 138:    * @see #method
 139:    */
 140:   public String getMethod()
 141:   {
 142:     return method;
 143:   }
 144: 
 145:   /**
 146:    * Returns the resource path.
 147:    * @see #path
 148:    */
 149:   public String getPath()
 150:   {
 151:     return path;
 152:   }
 153: 
 154:   /**
 155:    * Returns the full request-URI represented by this request, as specified
 156:    * by HTTP/1.1.
 157:    */
 158:   public String getRequestURI()
 159:   {
 160:     return connection.getURI() + path;
 161:   }
 162: 
 163:   /**
 164:    * Returns the headers in this request.
 165:    */
 166:   public Headers getHeaders()
 167:   {
 168:     return requestHeaders;
 169:   }
 170: 
 171:   /**
 172:    * Returns the value of the specified header in this request.
 173:    * @param name the header name
 174:    */
 175:   public String getHeader(String name)
 176:   {
 177:     return requestHeaders.getValue(name);
 178:   }
 179: 
 180:   /**
 181:    * Returns the value of the specified header in this request as an integer.
 182:    * @param name the header name
 183:    */
 184:   public int getIntHeader(String name)
 185:   {
 186:     return requestHeaders.getIntValue(name);
 187:   }
 188: 
 189:   /**
 190:    * Returns the value of the specified header in this request as a date.
 191:    * @param name the header name
 192:    */
 193:   public Date getDateHeader(String name)
 194:   {
 195:     return requestHeaders.getDateValue(name);
 196:   }
 197: 
 198:   /**
 199:    * Sets the specified header in this request.
 200:    * @param name the header name
 201:    * @param value the header value
 202:    */
 203:   public void setHeader(String name, String value)
 204:   {
 205:     requestHeaders.put(name, value);
 206:   }
 207: 
 208:   /**
 209:    * Convenience method to set the entire request body.
 210:    * @param requestBody the request body content
 211:    */
 212:   public void setRequestBody(byte[] requestBody)
 213:   {
 214:     setRequestBodyWriter(new ByteArrayRequestBodyWriter(requestBody));
 215:   }
 216: 
 217:   /**
 218:    * Sets the request body provider.
 219:    * @param requestBodyWriter the handler used to obtain the request body
 220:    */
 221:   public void setRequestBodyWriter(RequestBodyWriter requestBodyWriter)
 222:   {
 223:     this.requestBodyWriter = requestBodyWriter;
 224:   }
 225: 
 226:   /**
 227:    * Sets a callback handler to be invoked for the specified header name.
 228:    * @param name the header name
 229:    * @param handler the handler to receive the value for the header
 230:    */
 231:   public void setResponseHeaderHandler(String name,
 232:                                        ResponseHeaderHandler handler)
 233:   {
 234:     responseHeaderHandlers.put(name, handler);
 235:   }
 236: 
 237:   /**
 238:    * Sets an authenticator that can be used to handle authentication
 239:    * automatically.
 240:    * @param authenticator the authenticator
 241:    */
 242:   public void setAuthenticator(Authenticator authenticator)
 243:   {
 244:     this.authenticator = authenticator;
 245:   }
 246: 
 247:   /**
 248:    * Dispatches this request.
 249:    * A request can only be dispatched once; calling this method a second
 250:    * time results in a protocol exception.
 251:    * @exception IOException if an I/O error occurred
 252:    * @return an HTTP response object representing the result of the operation
 253:    */
 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:         // Loop while authentication fails or continue
 287:         do
 288:           {
 289:             retry = false;
 290: 
 291:             // Get socket output and input streams
 292:             OutputStream out = connection.getOutputStream();
 293: 
 294:             // Request line
 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:             // Request headers
 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:             // Request body
 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:             // Get response
 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:                       // A conforming server can send an unsoliceted
 356:                       // Continue response but *should* not (RFC 2616
 357:                       // sec 8.2.3).  Ignore the bogus Continue
 358:                       // response and get the real response that
 359:                       // should follow
 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:     // Read response status line
 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:     // Read response headers
 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:     // Construct response
 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:         // Handle Set-Cookie
 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:     // Persistent connections are the default in HTTP/1.1
 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:         // Special case no body.
 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;  // No Content-Length, must close.
 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:         // Remove the Content-Encoding header because the content is
 509:         // no longer compressed.
 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:             // Calculate H(A1)
 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:             // Calculate H(A2)
 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; // TODO hash of entity body
 584:                 md5.update(COLON);
 585:                 md5.update(hEntity);
 586:               }
 587:             byte[] ha2 = md5.digest();
 588:             String ha2Hex = toHexString(ha2);
 589: 
 590:             // Calculate response
 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:     // Scheme not recognised
 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:   /**
 678:    * Returns the number of times the specified nonce value has been seen.
 679:    * This always returns an 8-byte 0-padded hexadecimal string.
 680:    */
 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:   /**
 695:    * Client nonce value.
 696:    */
 697:   byte[] nonce;
 698: 
 699:   /**
 700:    * Generates a new client nonce value.
 701:    */
 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:   /**
 732:    * Parse the specified cookie list and notify the cookie manager.
 733:    */
 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:                         // if this isn't a valid date, it may be that
 814:                         // the value was returned unquoted; in that case, we
 815:                         // want to continue buffering the value
 816:                         buf.append(c);
 817:                         continue;
 818:                       }
 819:                   }
 820:                 attr = null;
 821:                 buf.setLength(0);
 822:                 // case EOL
 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:                     // Reset cookie fields
 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: }