Source for gnu.inet.http.Request

   1: /*
   2:  * Request.java
   3:  * Copyright (C) 2004 The Free Software Foundation
   4:  * 
   5:  * This file is part of GNU inetlib, a library.
   6:  * 
   7:  * GNU inetlib is free software; you can redistribute it and/or modify
   8:  * it under the terms of the GNU General Public License as published by
   9:  * the Free Software Foundation; either version 2 of the License, or
  10:  * (at your option) any later version.
  11:  * 
  12:  * GNU inetlib is distributed in the hope that it will be useful,
  13:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15:  * GNU General Public License for more details.
  16:  * 
  17:  * You should have received a copy of the GNU General Public License
  18:  * along with this library; if not, write to the Free Software
  19:  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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:  * obliged to do so.  If you do not wish to do so, delete this
  36:  * exception statement from your version.
  37:  */
  38: 
  39: package gnu.inet.http;
  40: 
  41: import java.io.InputStream;
  42: import java.io.IOException;
  43: import java.io.OutputStream;
  44: import java.net.ProtocolException;
  45: import java.net.Socket;
  46: import java.security.MessageDigest;
  47: import java.security.NoSuchAlgorithmException;
  48: import java.text.DateFormat;
  49: import java.text.ParseException;
  50: import java.util.Calendar;
  51: import java.util.Date;
  52: import java.util.HashMap;
  53: import java.util.Iterator;
  54: import java.util.Map;
  55: import java.util.Properties;
  56: import java.util.zip.GZIPInputStream;
  57: import java.util.zip.InflaterInputStream;
  58: 
  59: import gnu.inet.http.event.RequestEvent;
  60: import gnu.inet.util.BASE64;
  61: import gnu.inet.util.LineInputStream;
  62: 
  63: /**
  64:  * A single HTTP request.
  65:  *
  66:  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
  67:  */
  68: public class Request
  69: {
  70: 
  71:   /**
  72:    * The connection context in which this request is invoked.
  73:    */
  74:   protected final HTTPConnection connection;
  75: 
  76:   /**
  77:    * The HTTP method to invoke.
  78:    */
  79:   protected final String method;
  80: 
  81:   /**
  82:    * The path identifying the resource.
  83:    * This string must conform to the abs_path definition given in RFC2396,
  84:    * with an optional "?query" part, and must be URI-escaped by the caller.
  85:    */
  86:   protected final String path;
  87: 
  88:   /**
  89:    * The headers in this request.
  90:    */
  91:   protected final Headers requestHeaders;
  92: 
  93:   /**
  94:    * The request body provider.
  95:    */
  96:   protected RequestBodyWriter requestBodyWriter;
  97: 
  98:   /**
  99:    * Request body negotiation threshold for 100-continue expectations.
 100:    */
 101:   protected int requestBodyNegotiationThreshold;
 102: 
 103:   /**
 104:    * The response body reader.
 105:    */
 106:   protected ResponseBodyReader responseBodyReader;
 107: 
 108:   /**
 109:    * Map of response header handlers.
 110:    */
 111:   protected Map responseHeaderHandlers;
 112: 
 113:   /**
 114:    * The authenticator.
 115:    */
 116:   protected Authenticator authenticator;
 117: 
 118:   /**
 119:    * Whether this request has been dispatched yet.
 120:    */
 121:   private boolean dispatched;
 122: 
 123:   /**
 124:    * Constructor for a new request.
 125:    * @param connection the connection context
 126:    * @param method the HTTP method
 127:    * @param path the resource path including query part
 128:    */
 129:   protected Request(HTTPConnection connection, String method,
 130:                     String path)
 131:   {
 132:     this.connection = connection;
 133:     this.method = method;
 134:     this.path = path;
 135:     requestHeaders = new Headers();
 136:     responseHeaderHandlers = new HashMap();
 137:     requestBodyNegotiationThreshold = 4096;
 138:   }
 139: 
 140:   /**
 141:    * Returns the connection associated with this request.
 142:    * @see #connection
 143:    */
 144:   public HTTPConnection getConnection()
 145:   {
 146:     return connection;
 147:   }
 148: 
 149:   /**
 150:    * Returns the HTTP method to invoke.
 151:    * @see #method
 152:    */
 153:   public String getMethod()
 154:   {
 155:     return method;
 156:   }
 157: 
 158:   /**
 159:    * Returns the resource path.
 160:    * @see #path
 161:    */
 162:   public String getPath()
 163:   {
 164:     return path;
 165:   }
 166: 
 167:   /**
 168:    * Returns the full request-URI represented by this request, as specified
 169:    * by HTTP/1.1.
 170:    */
 171:   public String getRequestURI()
 172:   {
 173:     return connection.getURI() + path;
 174:   }
 175: 
 176:   /**
 177:    * Returns the headers in this request.
 178:    */
 179:   public Headers getHeaders()
 180:   {
 181:     return requestHeaders;
 182:   }
 183: 
 184:   /**
 185:    * Returns the value of the specified header in this request.
 186:    * @param name the header name
 187:    */
 188:   public String getHeader(String name)
 189:   {
 190:     return requestHeaders.getValue(name);
 191:   }
 192: 
 193:   /**
 194:    * Returns the value of the specified header in this request as an integer.
 195:    * @param name the header name
 196:    */
 197:   public int getIntHeader(String name)
 198:   {
 199:     return requestHeaders.getIntValue(name);
 200:   }
 201: 
 202:   /**
 203:    * Returns the value of the specified header in this request as a date.
 204:    * @param name the header name
 205:    */
 206:   public Date getDateHeader(String name)
 207:   {
 208:     return requestHeaders.getDateValue(name);
 209:   }
 210: 
 211:   /**
 212:    * Sets the specified header in this request.
 213:    * @param name the header name
 214:    * @param value the header value
 215:    */
 216:   public void setHeader(String name, String value)
 217:   {
 218:     requestHeaders.put(name, value);
 219:   }
 220: 
 221:   /**
 222:    * Convenience method to set the entire request body.
 223:    * @param requestBody the request body content
 224:    */
 225:   public void setRequestBody(byte[] requestBody)
 226:   {
 227:     setRequestBodyWriter(new ByteArrayRequestBodyWriter(requestBody));
 228:   }
 229: 
 230:   /**
 231:    * Sets the request body provider.
 232:    * @param requestBodyWriter the handler used to obtain the request body
 233:    */
 234:   public void setRequestBodyWriter(RequestBodyWriter requestBodyWriter)
 235:   {
 236:     this.requestBodyWriter = requestBodyWriter;
 237:   }
 238: 
 239:   /**
 240:    * Sets the response body reader.
 241:    * @param responseBodyReader the handler to receive notifications of
 242:    * response body content
 243:    */
 244:   public void setResponseBodyReader(ResponseBodyReader responseBodyReader)
 245:   {
 246:     this.responseBodyReader = responseBodyReader;
 247:   }
 248: 
 249:   /**
 250:    * Sets a callback handler to be invoked for the specified header name.
 251:    * @param name the header name
 252:    * @param handler the handler to receive the value for the header
 253:    */
 254:   public void setResponseHeaderHandler(String name,
 255:                                        ResponseHeaderHandler handler)
 256:   {
 257:     responseHeaderHandlers.put(name, handler);
 258:   }
 259: 
 260:   /**
 261:    * Sets an authenticator that can be used to handle authentication
 262:    * automatically.
 263:    * @param authenticator the authenticator
 264:    */
 265:   public void setAuthenticator(Authenticator authenticator)
 266:   {
 267:     this.authenticator = authenticator;
 268:   }
 269: 
 270:   /**
 271:    * Sets the request body negotiation threshold.
 272:    * If this is set, it determines the maximum size that the request body
 273:    * may be before body negotiation occurs(via the
 274:    * <code>100-continue</code> expectation). This ensures that a large
 275:    * request body is not sent when the server wouldn't have accepted it
 276:    * anyway.
 277:    * @param threshold the body negotiation threshold, or &lt;=0 to disable
 278:    * request body negotation entirely
 279:    */
 280:   public void setRequestBodyNegotiationThreshold(int threshold)
 281:   {
 282:     requestBodyNegotiationThreshold = threshold;
 283:   }
 284: 
 285:   /**
 286:    * Dispatches this request.
 287:    * A request can only be dispatched once; calling this method a second
 288:    * time results in a protocol exception.
 289:    * @exception IOException if an I/O error occurred
 290:    * @return an HTTP response object representing the result of the operation
 291:    */
 292:   public Response dispatch()
 293:     throws IOException
 294:   {
 295:     if (dispatched)
 296:       {
 297:         throw new ProtocolException("request already dispatched");
 298:       }
 299:     final String CRLF = "\r\n";
 300:     final String HEADER_SEP = ": ";
 301:     final String US_ASCII = "US-ASCII";
 302:     final String version = connection.getVersion();
 303:     Response response;
 304:     int contentLength = -1;
 305:     boolean retry = false;
 306:     int attempts = 0;
 307:     boolean expectingContinue = false;
 308:     if (requestBodyWriter != null)
 309:       {
 310:         contentLength = requestBodyWriter.getContentLength();
 311:         if (contentLength > requestBodyNegotiationThreshold)
 312:           {
 313:             expectingContinue = true;
 314:             setHeader("Expect", "100-continue");
 315:           }
 316:         else
 317:           {
 318:             setHeader("Content-Length", Integer.toString(contentLength));
 319:           }
 320:       }
 321:     
 322:     try
 323:       {
 324:         // Loop while authentication fails or continue
 325:         do
 326:           {
 327:             retry = false;
 328:             // Send request
 329:             connection.fireRequestEvent(RequestEvent.REQUEST_SENDING, this);
 330:             
 331:             // Get socket output and input streams
 332:             OutputStream out = connection.getOutputStream();
 333:             LineInputStream in =
 334:               new LineInputStream(connection.getInputStream());
 335:             // Request line
 336:             String requestUri = path;
 337:             if (connection.isUsingProxy() &&
 338:                 !"*".equals(requestUri) &&
 339:                 !"CONNECT".equals(method))
 340:               {
 341:                 requestUri = getRequestURI();
 342:               }
 343:             String line = method + ' ' + requestUri + ' ' + version + CRLF;
 344:             out.write(line.getBytes(US_ASCII));
 345:             // Request headers
 346:             for (Iterator i = requestHeaders.keySet().iterator();
 347:                  i.hasNext(); )
 348:               {
 349:                 String name =(String) i.next();
 350:                 String value =(String) requestHeaders.get(name);
 351:                 line = name + HEADER_SEP + value + CRLF;
 352:                 out.write(line.getBytes(US_ASCII));
 353:               }
 354:             out.write(CRLF.getBytes(US_ASCII));
 355:             // Request body
 356:             if (requestBodyWriter != null && !expectingContinue)
 357:               {
 358:                 byte[] buffer = new byte[4096];
 359:                 int len;
 360:                 int count = 0;
 361:                 
 362:                 requestBodyWriter.reset();
 363:                 do
 364:                   {
 365:                     len = requestBodyWriter.write(buffer);
 366:                     if (len > 0)
 367:                       {
 368:                         out.write(buffer, 0, len);
 369:                       }
 370:                     count += len;
 371:                   }
 372:                 while (len > -1 && count < contentLength);
 373:                 out.write(CRLF.getBytes(US_ASCII));
 374:               }
 375:             out.flush();
 376:             // Sent event
 377:             connection.fireRequestEvent(RequestEvent.REQUEST_SENT, this);
 378:             // Get response
 379:             response = readResponse(in);
 380:             int sc = response.getCode();
 381:             if (sc == 401 && authenticator != null)
 382:               {
 383:                 if (authenticate(response, attempts++))
 384:                   {
 385:                     retry = true;
 386:                   }
 387:               }
 388:             else if (sc == 100 && expectingContinue)
 389:               {
 390:                 requestHeaders.remove("Expect");
 391:                 setHeader("Content-Length", Integer.toString(contentLength));
 392:                 expectingContinue = false;
 393:                 retry = true;
 394:               }
 395:           }
 396:         while (retry);
 397:       }
 398:     catch (IOException e)
 399:       {
 400:         connection.close();
 401:         throw e;
 402:       }
 403:     return response;
 404:   }
 405:     
 406:   Response readResponse(LineInputStream in)
 407:     throws IOException
 408:   {
 409:     String line;
 410:     int len;
 411:     
 412:     // Read response status line
 413:     line = in.readLine();
 414:     if (line == null)
 415:       {
 416:         throw new ProtocolException("Peer closed connection");
 417:       }
 418:     if (!line.startsWith("HTTP/"))
 419:       {
 420:         throw new ProtocolException(line);
 421:       }
 422:     len = line.length();
 423:     int start = 5, end = 6;
 424:     while (line.charAt(end) != '.')
 425:       {
 426:         end++;
 427:       }
 428:     int majorVersion = Integer.parseInt(line.substring(start, end));
 429:     start = end + 1;
 430:     end = start + 1;
 431:     while (line.charAt(end) != ' ')
 432:       {
 433:         end++;
 434:       }
 435:     int minorVersion = Integer.parseInt(line.substring(start, end));
 436:     start = end + 1;
 437:     end = start + 3;
 438:     int code = Integer.parseInt(line.substring(start, end));
 439:     String message = line.substring(end + 1, len - 1);
 440:     // Read response headers
 441:     Headers responseHeaders = new Headers();
 442:     responseHeaders.parse(in);
 443:     notifyHeaderHandlers(responseHeaders);
 444:     // Construct response
 445:     int codeClass = code / 100;
 446:     Response ret = new Response(majorVersion, minorVersion, code,
 447:                                 codeClass, message, responseHeaders);
 448:     switch (code)
 449:       {
 450:       case 204:
 451:       case 205:
 452:         break;
 453:       default:
 454:         // Does response body reader want body?
 455:         boolean notify = (responseBodyReader != null);
 456:         if (notify)
 457:           {
 458:             if (!responseBodyReader.accept(this, ret))
 459:               {
 460:                 notify = false;
 461:               }
 462:           }
 463:         readResponseBody(ret, in, notify);
 464:       }
 465:     return ret;
 466:   }
 467: 
 468:   void notifyHeaderHandlers(Headers headers)
 469:   {
 470:     for (Iterator i = headers.entrySet().iterator(); i.hasNext(); )
 471:       {
 472:         Map.Entry entry = (Map.Entry) i.next();
 473:         String name =(String) entry.getKey();
 474:         // Handle Set-Cookie
 475:         if ("Set-Cookie".equalsIgnoreCase(name))
 476:           {
 477:             String value = (String) entry.getValue();
 478:             handleSetCookie(value);
 479:           }
 480:         ResponseHeaderHandler handler =
 481:           (ResponseHeaderHandler) responseHeaderHandlers.get(name);
 482:         if (handler != null)
 483:           {
 484:             String value = (String) entry.getValue();
 485:             handler.setValue(value);
 486:           }
 487:       }
 488:   }
 489: 
 490:   void readResponseBody(Response response, InputStream in,
 491:                         boolean notify)
 492:     throws IOException
 493:   {
 494:     byte[] buffer = new byte[4096];
 495:     int contentLength = -1;
 496:     Headers trailer = null;
 497:     
 498:     String transferCoding = response.getHeader("Transfer-Encoding");
 499:     if ("chunked".equalsIgnoreCase(transferCoding))
 500:       {
 501:         trailer = new Headers();
 502:         in = new ChunkedInputStream(in, trailer);
 503:       } 
 504:     else
 505:       {
 506:         contentLength = response.getIntHeader("Content-Length");
 507:       }
 508:     String contentCoding = response.getHeader("Content-Encoding");
 509:     if (contentCoding != null && !"identity".equals(contentCoding))
 510:       {
 511:         if ("gzip".equals(contentCoding))
 512:           {
 513:             in = new GZIPInputStream(in);
 514:           }
 515:         else if ("deflate".equals(contentCoding))
 516:           {
 517:             in = new InflaterInputStream(in);
 518:           }
 519:         else
 520:           {
 521:             throw new ProtocolException("Unsupported Content-Encoding: " +
 522:                                         contentCoding);
 523:           }
 524:       }
 525:     
 526:     // Persistent connections are the default in HTTP/1.1
 527:     boolean doClose = "close".equalsIgnoreCase(getHeader("Connection")) ||
 528:       "close".equalsIgnoreCase(response.getHeader("Connection")) ||
 529:       (connection.majorVersion == 1 && connection.minorVersion == 0) ||
 530:       (response.majorVersion == 1 && response.minorVersion == 0);
 531:     
 532:     int count = contentLength;
 533:     int len = (count > -1) ? count : buffer.length;
 534:     len = (len > buffer.length) ? buffer.length : len;
 535:     while (len > -1)
 536:       {
 537:         len = in.read(buffer, 0, len);
 538:         if (len < 0)
 539:           {
 540:             // EOF
 541:             connection.closeConnection();
 542:             break;
 543:           }
 544:         if (notify)
 545:           {
 546:             responseBodyReader.read(buffer, 0, len);
 547:           }
 548:         if (count > -1)
 549:           {
 550:             count -= len;
 551:             if (count < 1)
 552:               {
 553:                 if (doClose)
 554:                   {
 555:                     connection.closeConnection();
 556:                   }
 557:                 break;
 558:               }
 559:           }
 560:       }
 561:     if (notify)
 562:       {
 563:         responseBodyReader.close();
 564:       }
 565:     if (trailer != null)
 566:       {
 567:         response.getHeaders().putAll(trailer);
 568:         notifyHeaderHandlers(trailer);
 569:       }
 570:   }
 571: 
 572:   boolean authenticate(Response response, int attempts)
 573:     throws IOException
 574:   {
 575:     String challenge = response.getHeader("WWW-Authenticate");
 576:     if (challenge == null)
 577:       {
 578:         challenge = response.getHeader("Proxy-Authenticate");
 579:       }
 580:     int si = challenge.indexOf(' ');
 581:     String scheme = (si == -1) ? challenge : challenge.substring(0, si);
 582:     if ("Basic".equalsIgnoreCase(scheme))
 583:       {
 584:         Properties params = parseAuthParams(challenge.substring(si + 1));
 585:         String realm = params.getProperty("realm");
 586:         Credentials creds = authenticator.getCredentials(realm, attempts);
 587:         String userPass = creds.getUsername() + ':' + creds.getPassword();
 588:         byte[] b_userPass = userPass.getBytes("US-ASCII");
 589:         byte[] b_encoded = BASE64.encode(b_userPass);
 590:         String authorization =
 591:           scheme + " " + new String(b_encoded, "US-ASCII");
 592:         setHeader("Authorization", authorization);
 593:         return true;
 594:       }
 595:     else if ("Digest".equalsIgnoreCase(scheme))
 596:       {
 597:         Properties params = parseAuthParams(challenge.substring(si + 1));
 598:         String realm = params.getProperty("realm");
 599:         String nonce = params.getProperty("nonce");
 600:         String qop = params.getProperty("qop");
 601:         String algorithm = params.getProperty("algorithm");
 602:         String digestUri = getRequestURI();
 603:         Credentials creds = authenticator.getCredentials(realm, attempts);
 604:         String username = creds.getUsername();
 605:         String password = creds.getPassword();
 606:         connection.incrementNonce(nonce);
 607:         try
 608:           {
 609:             MessageDigest md5 = MessageDigest.getInstance("MD5");
 610:             final byte[] COLON = { 0x3a };
 611:             
 612:             // Calculate H(A1)
 613:             md5.reset();
 614:             md5.update(username.getBytes("US-ASCII"));
 615:             md5.update(COLON);
 616:             md5.update(realm.getBytes("US-ASCII"));
 617:             md5.update(COLON);
 618:             md5.update(password.getBytes("US-ASCII"));
 619:             byte[] ha1 = md5.digest();
 620:             if ("md5-sess".equals(algorithm))
 621:               {
 622:                 byte[] cnonce = generateNonce();
 623:                 md5.reset();
 624:                 md5.update(ha1);
 625:                 md5.update(COLON);
 626:                 md5.update(nonce.getBytes("US-ASCII"));
 627:                 md5.update(COLON);
 628:                 md5.update(cnonce);
 629:                 ha1 = md5.digest();
 630:               }
 631:             String ha1Hex = toHexString(ha1);
 632:             
 633:             // Calculate H(A2)
 634:             md5.reset();
 635:             md5.update(method.getBytes("US-ASCII"));
 636:             md5.update(COLON);
 637:             md5.update(digestUri.getBytes("US-ASCII"));
 638:             if ("auth-int".equals(qop))
 639:               {
 640:                 byte[] hEntity = null; // TODO hash of entity body
 641:                 md5.update(COLON);
 642:                 md5.update(hEntity);
 643:               }
 644:             byte[] ha2 = md5.digest();
 645:             String ha2Hex = toHexString(ha2);
 646:             
 647:             // Calculate response
 648:             md5.reset();
 649:             md5.update(ha1Hex.getBytes("US-ASCII"));
 650:             md5.update(COLON);
 651:             md5.update(nonce.getBytes("US-ASCII"));
 652:             if ("auth".equals(qop) || "auth-int".equals(qop))
 653:               {
 654:                 String nc = getNonceCount(nonce);
 655:                 byte[] cnonce = generateNonce();
 656:                 md5.update(COLON);
 657:                 md5.update(nc.getBytes("US-ASCII"));
 658:                 md5.update(COLON);
 659:                 md5.update(cnonce);
 660:                 md5.update(COLON);
 661:                 md5.update(qop.getBytes("US-ASCII"));
 662:               }
 663:             md5.update(COLON);
 664:             md5.update(ha2Hex.getBytes("US-ASCII"));
 665:             String digestResponse = toHexString(md5.digest());
 666:             
 667:             String authorization = scheme + 
 668:               " username=\"" + username + "\"" +
 669:               " realm=\"" + realm + "\"" +
 670:               " nonce=\"" + nonce + "\"" +
 671:               " uri=\"" + digestUri + "\"" +
 672:               " response=\"" + digestResponse + "\"";
 673:             setHeader("Authorization", authorization);
 674:             return true;
 675:           }
 676:         catch (NoSuchAlgorithmException e)
 677:           {
 678:             return false;
 679:           }
 680:       }
 681:     // Scheme not recognised
 682:     return false;
 683:   }
 684: 
 685:   Properties parseAuthParams(String text)
 686:   {
 687:     int len = text.length();
 688:     String key = null;
 689:     StringBuffer buf = new StringBuffer();
 690:     Properties ret = new Properties();
 691:     boolean inQuote = false;
 692:     for (int i = 0; i < len; i++)
 693:       {
 694:         char c = text.charAt(i);
 695:         if (c == '"')
 696:           {
 697:             inQuote = !inQuote;
 698:           }
 699:         else if (c == '=' && key == null)
 700:           {
 701:             key = buf.toString().trim();
 702:             buf.setLength(0);
 703:           }
 704:         else if (c == ' ' && !inQuote)
 705:           {
 706:             String value = unquote(buf.toString().trim());
 707:             ret.put(key, value);
 708:             key = null;
 709:             buf.setLength(0);
 710:           }
 711:         else if (c != ',' || (i <(len - 1) && text.charAt(i + 1) != ' '))
 712:           {   
 713:             buf.append(c);
 714:           }
 715:       }
 716:     if (key != null)
 717:       {
 718:         String value = unquote(buf.toString().trim());
 719:         ret.put(key, value);
 720:       }
 721:     return ret;
 722:   }
 723: 
 724:   String unquote(String text)
 725:   {
 726:     int len = text.length();
 727:     if (len > 0 && text.charAt(0) == '"' && text.charAt(len - 1) == '"')
 728:       {
 729:         return text.substring(1, len - 1);
 730:       }
 731:     return text;
 732:   }
 733: 
 734:   /**
 735:    * Returns the number of times the specified nonce value has been seen.
 736:    * This always returns an 8-byte 0-padded hexadecimal string.
 737:    */
 738:   String getNonceCount(String nonce)
 739:   {
 740:     int nc = connection.getNonceCount(nonce);
 741:     String hex = Integer.toHexString(nc);
 742:     StringBuffer buf = new StringBuffer();
 743:     for (int i = 8 - hex.length(); i > 0; i--)
 744:       {
 745:         buf.append('0');
 746:       }
 747:     buf.append(hex);
 748:     return buf.toString();
 749:   }
 750: 
 751:   /**
 752:    * Client nonce value.
 753:    */
 754:   byte[] nonce;
 755: 
 756:   /**
 757:    * Generates a new client nonce value.
 758:    */
 759:   byte[] generateNonce()
 760:     throws IOException, NoSuchAlgorithmException
 761:   {
 762:     if (nonce == null)
 763:       {
 764:         long time = System.currentTimeMillis();
 765:         MessageDigest md5 = MessageDigest.getInstance("MD5");
 766:         md5.update(Long.toString(time).getBytes("US-ASCII"));
 767:         nonce = md5.digest();
 768:       }
 769:     return nonce;
 770:   }
 771: 
 772:   String toHexString(byte[] bytes)
 773:   {
 774:     char[] ret = new char[bytes.length * 2];
 775:     for (int i = 0, j = 0; i < bytes.length; i++)
 776:       {
 777:         int c =(int) bytes[i];
 778:         if (c < 0)
 779:           {
 780:             c += 0x100;
 781:           }
 782:         ret[j++] = Character.forDigit(c / 0x10, 0x10);
 783:         ret[j++] = Character.forDigit(c % 0x10, 0x10);
 784:       }
 785:     return new String(ret);
 786:   }
 787: 
 788:   /**
 789:    * Parse the specified cookie list and notify the cookie manager.
 790:    */
 791:   void handleSetCookie(String text)
 792:   {
 793:     CookieManager cookieManager = connection.getCookieManager();
 794:     if (cookieManager == null)
 795:       {
 796:         return;
 797:       }
 798:     String name = null;
 799:     String value = null;
 800:     String comment = null;
 801:     String domain = connection.getHostName();
 802:     String path = this.path;
 803:     int lsi = path.lastIndexOf('/');
 804:     if (lsi != -1)
 805:       {
 806:         path = path.substring(0, lsi);
 807:       }
 808:     boolean secure = false;
 809:     Date expires = null;
 810: 
 811:     int len = text.length();
 812:     String attr = null;
 813:     StringBuffer buf = new StringBuffer();
 814:     boolean inQuote = false;
 815:     for (int i = 0; i <= len; i++)
 816:       {
 817:         char c =(i == len) ? '\u0000' : text.charAt(i);
 818:         if (c == '"')
 819:           {
 820:             inQuote = !inQuote;
 821:           }
 822:         else if (!inQuote)
 823:           {
 824:             if (c == '=' && attr == null)
 825:               {
 826:                 attr = buf.toString().trim();
 827:                 buf.setLength(0);
 828:               }
 829:             else if (c == ';' || i == len || c == ',')
 830:               {
 831:                 String val = unquote(buf.toString().trim());
 832:                 if (name == null)
 833:                   {
 834:                     name = attr;
 835:                     value = val;
 836:                   }
 837:                 else if ("Comment".equalsIgnoreCase(attr))
 838:                   {
 839:                     comment = val;
 840:                   }
 841:                 else if ("Domain".equalsIgnoreCase(attr))
 842:                   {
 843:                     domain = val;
 844:                   }
 845:                 else if ("Path".equalsIgnoreCase(attr))
 846:                   {
 847:                     path = val;
 848:                   }
 849:                 else if ("Secure".equalsIgnoreCase(val))
 850:                   {
 851:                     secure = true;
 852:                   }
 853:                 else if ("Max-Age".equalsIgnoreCase(attr))
 854:                   {
 855:                     int delta = Integer.parseInt(val);
 856:                     Calendar cal = Calendar.getInstance();
 857:                     cal.setTimeInMillis(System.currentTimeMillis());
 858:                     cal.add(Calendar.SECOND, delta);
 859:                     expires = cal.getTime();
 860:                   }
 861:                 else if ("Expires".equalsIgnoreCase(attr))
 862:                   {
 863:                     DateFormat dateFormat = new HTTPDateFormat();
 864:                     try
 865:                       {
 866:                         expires = dateFormat.parse(val);
 867:                       }
 868:                     catch (ParseException e)
 869:                       {
 870:                         // if this isn't a valid date, it may be that
 871:                         // the value was returned unquoted; in that case, we
 872:                         // want to continue buffering the value
 873:                         buf.append(c);
 874:                         continue;
 875:                       }
 876:                   }
 877:                 attr = null;
 878:                 buf.setLength(0);
 879:                 // case EOL
 880:                 if (i == len || c == ',')
 881:                   {
 882:                     Cookie cookie = new Cookie(name, value, comment, domain,
 883:                                                path, secure, expires);
 884:                     cookieManager.setCookie(cookie);
 885:                   }
 886:                 if (c == ',')
 887:                   {
 888:                     // Reset cookie fields
 889:                     name = null;
 890:                     value = null;
 891:                     comment = null;
 892:                     domain = connection.getHostName();
 893:                     path = this.path;
 894:                     if (lsi != -1)
 895:                       {
 896:                         path = path.substring(0, lsi);
 897:                       }
 898:                     secure = false;
 899:                     expires = null;
 900:                   }
 901:               }
 902:             else
 903:               {
 904:                 buf.append(c);
 905:               }
 906:           }
 907:         else
 908:           {
 909:             buf.append(c);
 910:           }
 911:       }
 912:   }
 913: 
 914: }