Java Source Code: sun.net.www.protocol.http.HttpURLConnection


   1: /*
   2:  * @(#)HttpURLConnection.java    1.86 06/10/10
   3:  *
   4:  * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.  
   5:  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER  
   6:  *   
   7:  * This program is free software; you can redistribute it and/or  
   8:  * modify it under the terms of the GNU General Public License version  
   9:  * 2 only, as published by the Free Software Foundation.   
  10:  *   
  11:  * This program 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 version 2 for more details (a copy is  
  15:  * included at /legal/license.txt).   
  16:  *   
  17:  * You should have received a copy of the GNU General Public License  
  18:  * version 2 along with this work; if not, write to the Free Software  
  19:  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  
  20:  * 02110-1301 USA   
  21:  *   
  22:  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa  
  23:  * Clara, CA 95054 or visit www.sun.com if you need additional  
  24:  * information or have any questions. 
  25:  *
  26:  */
  27: 
  28: package sun.net.www.protocol.http;
  29: 
  30: import java.net.URL;
  31: import java.net.URLConnection;
  32: import java.net.ProtocolException;
  33: import java.net.PasswordAuthentication;
  34: import java.net.Authenticator;
  35: import java.net.InetAddress;
  36: import java.net.UnknownHostException;
  37: import java.net.SocketTimeoutException;
  38: import java.io.*;
  39: import java.util.Date;
  40: import java.util.Map;
  41: import java.util.Locale;
  42: import java.util.StringTokenizer;
  43: import sun.net.*;
  44: import sun.net.www.*;
  45: import sun.net.www.http.HttpClient;
  46: import sun.net.www.http.PosterOutputStream;
  47: import sun.net.www.http.ChunkedInputStream;
  48: import java.text.SimpleDateFormat;
  49: import java.util.TimeZone;
  50: import java.net.MalformedURLException;
  51: 
  52: /**
  53:  * A class to represent an HTTP connection to a remote object.
  54:  */
  55: 
  56: 
  57:	  public class HttpURLConnection extends HttpURLConnection {
  58:    
  59:    static final String version;
  60:    public static final String userAgent;
  61:
  62:    /* max # of allowed re-directs */
  63:    static final int defaultmaxRedirects = 20;
  64:    static final int maxRedirects;
  65:
  66:    /* Not all servers support the (Proxy)-Authentication-Info headers.
  67:     * By default, we don't require them to be sent
  68:     */
  69:    static final boolean validateProxy;
  70:    static final boolean validateServer;
  71:
  72:	      static {
  73:    maxRedirects = ((Integer)java.security.AccessController.doPrivileged(
  74:        new sun.security.action.GetIntegerAction("http.maxRedirects", 
  75:        defaultmaxRedirects))).intValue();
  76:    version = (String) java.security.AccessController.doPrivileged(
  77:                    new sun.security.action.GetPropertyAction("java.version"));
  78:    String agent = (String) java.security.AccessController.doPrivileged(
  79:            new sun.security.action.GetPropertyAction("http.agent"));
  80:	      if (agent == null) {
  81:        agent = "Java/"+version;
  82:    } else {
  83:        agent = agent + " Java/"+version;
  84:    }
  85:    userAgent = agent;
  86:    validateProxy = ((Boolean)java.security.AccessController.doPrivileged(
  87:        new sun.security.action.GetBooleanAction(
  88:            "http.auth.digest.validateProxy"))).booleanValue();
  89:    validateServer = ((Boolean)java.security.AccessController.doPrivileged(
  90:        new sun.security.action.GetBooleanAction(
  91:            "http.auth.digest.validateServer"))).booleanValue();
  92:    }
  93:
  94:    static final String httpVersion = "HTTP/1.1";
  95:    static final String acceptString =
  96:        "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2";
  97:
  98:    // the following http request headers should NOT have their values
  99:    // returned for security reasons.
 100:	      private static final String[] EXCLUDE_HEADERS = {
 101:        "Proxy-Authorization", 
 102:        "Authorization"
 103:    };
 104:    protected HttpClient http;
 105:    protected Handler handler;
 106:
 107:
 108:    /* output stream to server */
 109:    protected PrintStream ps = null;
 110:
 111:    /* We only have a single static authenticator for now.
 112:     * For backwards compatibility with JDK 1.1.  Should be
 113:     * eliminated for JDK 2.0.
 114:     */
 115:    private static HttpAuthenticator defaultAuth;
 116:    
 117:    /* all the headers we send 
 118:     * NOTE: do *NOT* dump out the content of 'requests' in the 
 119:     * output or stacktrace since it may contain security-sensitive 
 120:     * headers such as those defined in EXCLUDE_HEADERS.
 121:     */
 122:    private MessageHeader requests;
 123:
 124:    /* The following two fields are only used with Digest Authentication */
 125:    String domain;     /* The list of authentication domains */
 126:    DigestAuthentication.Parameters digestparams;
 127:
 128:    /* Current credentials in use */
 129:    AuthenticationInfo  currentProxyCredentials = null;
 130:    AuthenticationInfo  currentServerCredentials = null;
 131:    boolean        needToCheck = true;
 132:    private boolean doingNTLM2ndStage = false; /* doing the 2nd stage of an NTLM server authentication */
 133:    private boolean doingNTLMp2ndStage = false; /* doing the 2nd stage of an NTLM proxy authentication */
 134:    Object authObj; 
 135:
 136:    /* Progress entry */
 137:    protected ProgressEntry pe;
 138:
 139:    /* all the response headers we get back */
 140:    private MessageHeader responses;
 141:    /* the stream _from_ the server */
 142:    private InputStream inputStream = null;
 143:    /* post stream _to_ the server, if any */
 144:    private PosterOutputStream poster = null;
 145:
 146:    /* Indicates if the std. request headers have been set in requests. */
 147:    private boolean setRequests=false;
 148:
 149:    /* Indicates whether a request has already failed or not */
 150:    private boolean failedOnce=false;
 151:
 152:    /* Remembered Exception, we will throw it again if somebody
 153:       calls getInputStream after disconnect */
 154:    private Exception rememberedException = null;
 155:
 156:    /* If we decide we want to reuse a client, we put it here */
 157:    private HttpClient reuseClient = null;
 158:
 159:    /*
 160:     * privileged request password authentication 
 161:     *
 162:     */
 163:    private static PasswordAuthentication 
 164:    privilegedRequestPasswordAuthentication(
 165:                        final String host,
 166:                        final InetAddress addr,
 167:                        final int port,
 168:                        final String protocol,
 169:                        final String prompt,
 170:	                          final String scheme) {
 171:    return (PasswordAuthentication)
 172:        java.security.AccessController.doPrivileged( 
 173:	          new java.security.PrivilegedAction() {
 174:	          public Object run() {
 175:            return Authenticator.requestPasswordAuthentication(
 176:                       host, addr, port, protocol, prompt, scheme);
 177:        }
 178:        });
 179:    }
 180:
 181:    /* 
 182:     * checks the validity of http message header and throws 
 183:     * IllegalArgumentException if invalid.
 184:     */
 185:	      private void checkMessageHeader(String key, String value) {
 186:    char LF = '\n';
 187:    int index = key.indexOf(LF);
 188:	      if (index != -1) {
 189:        throw new IllegalArgumentException(
 190:        "Illegal character(s) in message header field: " + key);
 191:    }
 192:	      else {
 193:	          if (value == null) {
 194:                return;
 195:            }
 196:
 197:        index = value.indexOf(LF);
 198:	          while (index != -1) {
 199:        index++;
 200:	          if (index < value.length()) {
 201:            char c = value.charAt(index);
 202:	              if ((c==' ') || (c=='\t')) {
 203:            // ok, check the next occurrence
 204:                index = value.indexOf(LF, index);
 205:            continue;
 206:            }
 207:        }
 208:        throw new IllegalArgumentException(
 209:            "Illegal character(s) in message header value: " + value);
 210:        }
 211:    }
 212:    }
 213:
 214:    /* adds the standard key/val pairs to reqests if necessary & write to
 215:     * given PrintStream
 216:     */
 217:	      private void writeRequests() throws IOException {
 218:
 219:    /* print all message headers in the MessageHeader 
 220:     * onto the wire - all the ones we've set and any
 221:     * others that have been set
 222:     */
 223:	      if (!setRequests) {
 224:
 225:        /* We're very particular about the order in which we
 226:         * set the request headers here.  The order should not
 227:         * matter, but some careless CGI programs have been
 228:         * written to expect a very particular order of the
 229:         * standard headers.  To name names, the order in which
 230:         * Navigator3.0 sends them.  In particular, we make *sure*
 231:         * to send Content-type: <> and Content-length:<> second
 232:         * to last and last, respectively, in the case of a POST
 233:         * request.
 234:         */
 235:        if (!failedOnce)
 236:        requests.prepend(method + " " + http.getURLFile()+" "  + 
 237:                 httpVersion, null);
 238:	          if (!getUseCaches()) {
 239:        requests.setIfNotSet ("Cache-Control", "no-cache");
 240:        requests.setIfNotSet ("Pragma", "no-cache");
 241:        }
 242:        requests.setIfNotSet("User-Agent", userAgent);
 243:        int port = url.getPort();
 244:        String host = url.getHost();
 245:	          if (port != -1 && port != 80) {
 246:        host += ":" + String.valueOf(port);
 247:        }
 248:        requests.setIfNotSet("Host", host);
 249:        requests.setIfNotSet("Accept", acceptString);
 250:
 251:        /*
 252:         * For HTTP/1.1 the default behavior is to keep connections alive.
 253:         * However, we may be talking to a 1.0 server so we should set
 254:         * keep-alive just in case, except if we have encountered an error
 255:         * or if keep alive is disabled via a system property
 256:         */
 257:         
 258:        // Try keep-alive only on first attempt
 259:	          if (!failedOnce && http.getHttpKeepAliveSet()) {
 260:	          if (http.usingProxy) {
 261:            requests.setIfNotSet("Proxy-Connection", "keep-alive");
 262:        } else {
 263:            requests.setIfNotSet("Connection", "keep-alive");
 264:        }
 265:        } 
 266:        // send any pre-emptive authentication
 267:	          if (http.usingProxy) {
 268:        setPreemptiveProxyAuthentication(requests);
 269:        }
 270:            // Set modified since if necessary
 271:            long modTime = getIfModifiedSince();
 272:	              if (modTime != 0 ) {
 273:                Date date = new Date(modTime);
 274:        //use the preferred date format according to RFC 2068(HTTP1.1),
 275:        // RFC 822 and RFC 1123
 276:        SimpleDateFormat fo =
 277:          new SimpleDateFormat ("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
 278:        fo.setTimeZone(TimeZone.getTimeZone("GMT"));
 279:                requests.setIfNotSet("If-Modified-Since", fo.format(date));
 280:            }
 281:        // check for preemptive authorization
 282:        AuthenticationInfo sauth = AuthenticationInfo.getServerAuth(url);
 283:	          if (sauth != null && sauth.supportsPreemptiveAuthorization() ) {
 284:        // Sets "Authorization"
 285:        requests.setIfNotSet(sauth.getHeaderName(), sauth.getHeaderValue(url,method));
 286:        currentServerCredentials = sauth;
 287:        }
 288:
 289:	          if (poster != null) {
 290:        /* add Content-Length & POST/PUT data */
 291:	          synchronized (poster) {
 292:            /* close it, so no more data can be added */
 293:            poster.close();
 294:	              if (!method.equals("PUT")) {
 295:            String type = "application/x-www-form-urlencoded";
 296:            requests.setIfNotSet("Content-Type", type);
 297:            }
 298:            requests.set("Content-Length", 
 299:                 String.valueOf(poster.size()));
 300:        }
 301:        }
 302:        setRequests=true;
 303:    }
 304:    http.writeRequests(requests, poster);
 305:	      if (ps.checkError()) {
 306:        String proxyHost = http.getProxyHostUsed();
 307:        int proxyPort = http.getProxyPortUsed();
 308:        disconnectInternal();
 309:	          if (failedOnce) {
 310:        throw new IOException("Error writing to server");
 311:        } else { // try once more
 312:        failedOnce=true;
 313:	          if (proxyHost != null) {
 314:            setProxiedClient(url, proxyHost, proxyPort);
 315:        } else {
 316:            setNewClient (url);
 317:        }
 318:        ps = (PrintStream) http.getOutputStream();
 319:        connected=true;
 320:        responses = new MessageHeader();
 321:        setRequests=false;
 322:        writeRequests();
 323:        }
 324:    }
 325:    }
 326:
 327:
 328:    /**
 329:     * Create a new HttpClient object, bypassing the cache of
 330:     * HTTP client objects/connections.
 331:     *
 332:     * @param url    the URL being accessed
 333:     */
 334:    protected void setNewClient (URL url)
 335:	      throws IOException {
 336:    setNewClient(url, false);
 337:    }
 338:
 339:    /**
 340:     * Obtain a HttpsClient object. Use the cached copy if specified. 
 341:     *
 342:     * @param url       the URL being accessed
 343:     * @param useCache  whether the cached connection should be used
 344:     *        if present
 345:     */
 346:    protected void setNewClient (URL url, boolean useCache)
 347:	      throws IOException {
 348:    http = HttpClient.New(url, useCache);
 349:    }
 350:
 351:
 352:    /**
 353:     * Create a new HttpClient object, set up so that it uses
 354:     * per-instance proxying to the given HTTP proxy.  This
 355:     * bypasses the cache of HTTP client objects/connections.
 356:     *
 357:     * @param url    the URL being accessed
 358:     * @param proxyHost    the proxy host to use
 359:     * @param proxyPort    the proxy port to use
 360:     */
 361:    protected void setProxiedClient (URL url, String proxyHost, int proxyPort)
 362:	      throws IOException {
 363:    setProxiedClient(url, proxyHost, proxyPort, false); 
 364:    }
 365:
 366:    /**
 367:     * Obtain a HttpClient object, set up so that it uses per-instance
 368:     * proxying to the given HTTP proxy. Use the cached copy of HTTP
 369:     * client objects/connections if specified.
 370:     *
 371:     * @param url       the URL being accessed
 372:     * @param proxyHost the proxy host to use
 373:     * @param proxyPort the proxy port to use
 374:     * @param useCache  whether the cached connection should be used
 375:     *        if present
 376:     */
 377:    protected void setProxiedClient (URL url,
 378:                       String proxyHost, int proxyPort,
 379:                       boolean useCache)
 380:	      throws IOException {
 381:    proxiedConnect(url, proxyHost, proxyPort, useCache);
 382:    }
 383:
 384:    protected void proxiedConnect(URL url,
 385:                       String proxyHost, int proxyPort,
 386:                       boolean useCache)
 387:	      throws IOException {
 388:    SecurityManager security = System.getSecurityManager(); 
 389:	      if (security != null) { 
 390:        security.checkConnect(proxyHost, proxyPort); 
 391:    }
 392:    http = HttpClient.New (url, proxyHost, proxyPort, useCache);
 393:    }
 394:
 395:    protected HttpURLConnection(URL u, Handler handler)
 396:	      throws IOException {
 397:    super(u);
 398:    requests = new MessageHeader();
 399:    responses = new MessageHeader();
 400:    this.handler = handler;
 401:    }
 402:    
 403:    /** this constructor is used by other protocol handlers such as ftp
 404:        that want to use http to fetch urls on their behalf. */
 405:    public HttpURLConnection(URL u, String host, int port)
 406:	      throws IOException {
 407:    this(u, new Handler(host, port));
 408:    }
 409:
 410:    /** 
 411:     * @deprecated.  Use java.net.Authenticator.setDefault() instead.
 412:     */
 413:	      public static void setDefaultAuthenticator(HttpAuthenticator a) {
 414:    defaultAuth = a;
 415:    }
 416:
 417:    /**
 418:     * opens a stream allowing redirects only to the same host.
 419:     */
 420:    public static InputStream openConnectionCheckRedirects(URLConnection c)
 421:    throws IOException
 422:	      {
 423:        boolean redir;
 424:        int redirects = 0;
 425:        InputStream in = null;
 426:
 427:	          do {
 428:	              if (c instanceof HttpURLConnection) {
 429:                ((HttpURLConnection) c).setInstanceFollowRedirects(false);
 430:            }
 431: 
 432:            // We want to open the input stream before
 433:            // getting headers, because getHeaderField()
 434:            // et al swallow IOExceptions.
 435:            in = c.getInputStream();
 436:            redir = false;
 437: 
 438:	              if (c instanceof HttpURLConnection) {
 439:                HttpURLConnection http = (HttpURLConnection) c;
 440:                int stat = http.getResponseCode();
 441:                if (stat >= 300 && stat <= 307 && stat != 306 &&
 442:	                          stat != HttpURLConnection.HTTP_NOT_MODIFIED) {
 443:                    URL base = http.getURL();
 444:                    String loc = http.getHeaderField("Location");
 445:                    URL target = null;
 446:	                      if (loc != null) {
 447:                        target = new URL(base, loc);
 448:                    }
 449:                    http.disconnect();
 450:                    if (target == null
 451:                        || !base.getProtocol().equals(target.getProtocol())
 452:                        || base.getPort() != target.getPort()
 453:                        || !hostsEqual(base, target)
 454:                        || redirects >= 5)
 455:	                      {
 456:                        throw new SecurityException("illegal URL redirect");
 457:            }
 458:                    redir = true;
 459:                    c = target.openConnection();
 460:                    redirects++;
 461:                }
 462:            }
 463:        } while (redir);
 464:        return in;
 465:    }
 466:
 467:
 468:    //
 469:    // Same as java.net.URL.hostsEqual
 470:    //
 471:	      private static boolean hostsEqual(URL u1, URL u2) {
 472:    final String h1 = u1.getHost();
 473:    final String h2 = u2.getHost();
 474:
 475:	      if (h1 == null) {
 476:        return h2 == null;
 477:    } else if (h2 == null) {
 478:        return false;
 479:    } else if (h1.equalsIgnoreCase(h2)) {
 480:        return true;
 481:    }
 482:        // Have to resolve addresses before comparing, otherwise
 483:        // names like tachyon and tachyon.eng would compare different
 484:    final boolean result[] = {false};
 485:
 486:    java.security.AccessController.doPrivileged(
 487:	          new java.security.PrivilegedAction() {
 488:	          public Object run() {
 489:	          try {
 490:            InetAddress a1 = InetAddress.getByName(h1);
 491:            InetAddress a2 = InetAddress.getByName(h2);
 492:            result[0] = a1.equals(a2);
 493:        } catch(UnknownHostException e) {
 494:        } catch(SecurityException e) {
 495:        }
 496:        return null;
 497:        }
 498:    });
 499:
 500:        return result[0];
 501:    }
 502:
 503:    // overridden in HTTPS subclass
 504:
 505:	      public void connect() throws IOException {
 506:    plainConnect();
 507:    }
 508:
 509:	      private boolean checkReuseConnection () {
 510:	      if (connected) {
 511:        return true;
 512:    }
 513:	      if (reuseClient != null) {
 514:        http = reuseClient;
 515:        http.reuse = false;
 516:        reuseClient = null;
 517:        connected = true;
 518:        return true;
 519:    }
 520:    return false;
 521:    }
 522:
 523:	      protected void plainConnect()  throws IOException {
 524:	      if (connected) {
 525:        return;
 526:    }
 527:	      try {
 528:	          if ("http".equals(url.getProtocol()) && !failedOnce) {
 529:        http = HttpClient.New(url);
 530:        } else {
 531:        // make sure to construct new connection if first
 532:        // attempt failed
 533:        http = new HttpClient(url, handler.proxy, handler.proxyPort);
 534:        }
 535:        ps = (PrintStream)http.getOutputStream();
 536:    } catch (IOException e) {
 537:        throw e;
 538:    }
 539:    // constructor to HTTP client calls openserver
 540:    connected = true;
 541:    }
 542:
 543:    /*
 544:     * Allowable input/output sequences:
 545:     * [interpreted as POST/PUT]
 546:     * - get output, [write output,] get input, [read input]
 547:     * - get output, [write output]
 548:     * [interpreted as GET]
 549:     * - get input, [read input]
 550:     * Disallowed:
 551:     * - get input, [read input,] get output, [write output]
 552:     */
 553:
 554:	      public synchronized OutputStream getOutputStream() throws IOException {
 555:
 556:	      try {
 557:	          if (!doOutput) {
 558:        throw new ProtocolException("cannot write to a URLConnection"
 559:                   + " if doOutput=false - call setDoOutput(true)");
 560:        }
 561:        
 562:	          if (method.equals("GET")) {
 563:        method = "POST"; // Backward compatibility
 564:        }
 565:        if (!"POST".equals(method) && !"PUT".equals(method) && 
 566:	          "http".equals(url.getProtocol())) {
 567:        throw new ProtocolException("HTTP method " + method + 
 568:                        " doesn't support output");
 569:        }
 570:
 571:        // if there's already an input stream open, throw an exception
 572:	          if (inputStream != null) {
 573:        throw new ProtocolException("Cannot write output after reading input.");
 574:        }
 575:
 576:        if (!checkReuseConnection())
 577:            connect();
 578:
 579:        /* This exists to fix the HttpsURLConnection subclass.
 580:         * Hotjava needs to run on JDK1.1.  Do proper fix in subclass
 581:         * for 1.2 and remove this.
 582:         */
 583:        ps = (PrintStream)http.getOutputStream();
 584:
 585:        if (poster == null)
 586:        poster = new PosterOutputStream();
 587:        return poster;
 588:    } catch (RuntimeException e) {
 589:        disconnectInternal();
 590:        throw e;
 591:    } catch (IOException e) {
 592:        disconnectInternal();
 593:        throw e;
 594:    }
 595:    }
 596:
 597:	      public synchronized InputStream getInputStream() throws IOException {
 598:
 599:	      if (!doInput) {
 600:        throw new ProtocolException("Cannot read from URLConnection"
 601:           + " if doInput=false (call setDoInput(true))");
 602:    }
 603:
 604:	      if (rememberedException != null) {
 605:        if (rememberedException instanceof RuntimeException)
 606:        throw new RuntimeException(rememberedException);
 607:	          else {
 608:        IOException exception;
 609:	          try {
 610:            exception = new IOException();
 611:            exception.initCause(rememberedException);
 612:        } catch (Exception t) {
 613:            exception = (IOException) rememberedException;
 614:        }
 615:        throw exception;
 616:        }
 617:    }
 618:
 619:	      if (inputStream != null) {
 620:        return inputStream;
 621:    }
 622:
 623:    int redirects = 0;
 624:    int respCode = 0;
 625:    AuthenticationInfo serverAuthentication = null;
 626:    AuthenticationInfo proxyAuthentication = null;
 627:    AuthenticationHeader srvHdr = null; 
 628:	      try {
 629:	          do {
 630:
 631:        pe = new ProgressEntry(url.getFile(), null);
 632:        ProgressData.pdata.register(pe);
 633:        if (!checkReuseConnection())
 634:            connect();
 635:
 636:        /* This exists to fix the HttpsURLConnection subclass.
 637:         * Hotjava needs to run on JDK1.1.  Do proper fix once a
 638:         * proper solution for SSL can be found.
 639:         */
 640:        ps = (PrintStream)http.getOutputStream();
 641:
 642:        writeRequests();
 643:        http.parseHTTP(responses, pe);
 644:        inputStream = new HttpInputStream (http.getInputStream());
 645:
 646:        respCode = getResponseCode();
 647:	          if (respCode == HTTP_PROXY_AUTH) {
 648:            AuthenticationHeader authhdr = new AuthenticationHeader (
 649:            "Proxy-Authenticate", responses
 650:            );
 651:	              if (!doingNTLMp2ndStage) {
 652:                 proxyAuthentication =
 653:                    resetProxyAuthentication(proxyAuthentication, authhdr);
 654:	                  if (proxyAuthentication != null) {
 655:                redirects++;
 656:                disconnectInternal();
 657:                continue;
 658:                }
 659:            } else {
 660:            /* in this case, only one header field will be present */
 661:                String raw = responses.findValue ("Proxy-Authenticate");
 662:            reset ();
 663:            if (!proxyAuthentication.setHeaders(this, 
 664:	                              authhdr.headerParser(), raw)) {
 665:                disconnectInternal();
 666:                throw new IOException ("Authentication failure");
 667:            }
 668:            if (serverAuthentication != null && srvHdr != null &&
 669:                !serverAuthentication.setHeaders(this, 
 670:	                              srvHdr.headerParser(), raw)) {
 671:                disconnectInternal ();
 672:                throw new IOException ("Authentication failure");
 673:            }
 674:            authObj = null; 
 675:            doingNTLMp2ndStage = false;
 676:            continue;
 677:            }
 678:        }
 679:
 680:        // cache proxy authentication info
 681:	          if (proxyAuthentication != null) {
 682:            // cache auth info on success, domain header not relevant.
 683:            proxyAuthentication.addToCache();
 684:        }
 685:
 686:	          if (respCode == HTTP_UNAUTHORIZED) {
 687:                srvHdr = new AuthenticationHeader (
 688:                    "WWW-Authenticate", responses
 689:                );
 690:            String raw = srvHdr.raw();
 691:	              if (!doingNTLM2ndStage) {
 692:	                  if (serverAuthentication != null) { 
 693:	                  if (serverAuthentication.isAuthorizationStale (raw)) {
 694:                    /* we can retry with the current credentials */
 695:                    disconnectInternal();
 696:                    redirects++;
 697:                    requests.set(serverAuthentication.getHeaderName(), 
 698:                            serverAuthentication.getHeaderValue(url, method));
 699:                        currentServerCredentials = serverAuthentication;
 700:                    continue;
 701:                } else {
 702:                    serverAuthentication.removeFromCache();
 703:                }
 704:                }
 705:                serverAuthentication = getServerAuthentication(srvHdr);
 706:                currentServerCredentials = serverAuthentication;
 707:    
 708:	                  if (serverAuthentication != null) {
 709:                    disconnectInternal();
 710:                    redirects++; // don't let things loop ad nauseum
 711:                    continue;
 712:                }
 713:            } else {
 714:            reset ();
 715:            /* header not used for ntlm */
 716:	              if (!serverAuthentication.setHeaders(this, null, raw)) {
 717:                disconnectInternal();
 718:                throw new IOException ("Authentication failure");
 719:            }
 720:            doingNTLM2ndStage = false;
 721:            authObj = null; 
 722:            continue;
 723:            }
 724:        }
 725:        // cache server authentication info
 726:	          if (serverAuthentication != null) {
 727:            // cache auth info on success
 728:            if (!(serverAuthentication instanceof DigestAuthentication) ||
 729:	              (domain == null)) {
 730:	              if (serverAuthentication instanceof BasicAuthentication) {
 731:                // check if the path is shorter than the existing entry
 732:                String npath = AuthenticationInfo.reducePath (url.getPath());
 733:                String opath = serverAuthentication.path;
 734:	                  if (!opath.startsWith (npath) || npath.length() >= opath.length()) {
 735:                /* npath is longer, there must be a common root */
 736:                npath = BasicAuthentication.getRootPath (opath, npath);
 737:                }
 738:                // remove the entry and create a new one 
 739:                BasicAuthentication a = 
 740:                    (BasicAuthentication) serverAuthentication.clone();
 741:                serverAuthentication.removeFromCache();
 742:                a.path = npath;
 743:                serverAuthentication = a;
 744:            }
 745:            serverAuthentication.addToCache();
 746:            } else {
 747:            // what we cache is based on the domain list in the request
 748:            DigestAuthentication srv = (DigestAuthentication)
 749:                serverAuthentication;
 750:            StringTokenizer tok = new StringTokenizer (domain," ");
 751:            String realm = srv.realm;
 752:            PasswordAuthentication pw = srv.pw;
 753:            digestparams = srv.params;
 754:	              while (tok.hasMoreTokens()) {
 755:                String path = tok.nextToken();
 756:	                  try {
 757:                /* path could be an abs_path or a complete URI */
 758:                URL u = new URL (url, path);
 759:                DigestAuthentication d = new DigestAuthentication (
 760:                           false, u, realm, "Digest", pw, digestparams);
 761:                d.addToCache ();
 762:                } catch (Exception e) {}
 763:            }
 764:            }
 765:        }
 766:        
 767:	          if (respCode == HTTP_OK) {
 768:            checkResponseCredentials (false);
 769:        } else {
 770:            needToCheck = false;
 771:        }
 772:
 773:	          if (followRedirect()) {
 774:            /* if we should follow a redirect, then the followRedirects()
 775:             * method will disconnect() and re-connect us to the new
 776:             * location
 777:             */
 778:            redirects++;
 779:            continue;
 780:        }
 781:
 782:        int cl = -1;
 783:	          try {
 784:            cl = Integer.parseInt(responses.findValue("content-length"));
 785:        } catch (Exception exc) { };
 786:
 787:        if (method.equals("HEAD") || method.equals("TRACE") || cl == 0 ||
 788:            respCode == HTTP_NOT_MODIFIED ||
 789:	              respCode == HTTP_NO_CONTENT) {
 790:
 791:	              if (pe != null) {
 792:            ProgressData.pdata.unregister(pe);
 793:            }
 794:            http.finished();
 795:            http = null;
 796:            inputStream = new EmptyInputStream();
 797:	              if ( respCode < 400) {
 798:            connected = false;
 799:            return inputStream;
 800:            }
 801:        }
 802:        
 803:	          if (respCode >= 400) {
 804:	              if (respCode == 404 || respCode == 410) {
 805:            throw new FileNotFoundException(url.toString());
 806:            } else {
 807:            throw new java.io.IOException("Server returned HTTP" +
 808:                  " response code: " + respCode + " for URL: " +
 809:                  url.toString());
 810:            }
 811:        }
 812:
 813:        return inputStream;
 814:        } while (redirects < maxRedirects);
 815:
 816:        throw new ProtocolException("Server redirected too many " +
 817:                    " times ("+ redirects + ")");
 818:    } catch (RuntimeException e) {
 819:        disconnectInternal();
 820:        rememberedException = e;
 821:        throw e;
 822:    } catch (IOException e) {
 823:        rememberedException = e;
 824:        throw e;
 825:    } finally {
 826:	          if (respCode == HTTP_PROXY_AUTH && proxyAuthentication != null) {
 827:        proxyAuthentication.endAuthRequest();
 828:        } 
 829:	          else if (respCode == HTTP_UNAUTHORIZED && serverAuthentication != null) {
 830:        serverAuthentication.endAuthRequest();
 831:        }
 832:    }
 833:    }
 834:
 835:	      public InputStream getErrorStream() {
 836:	      if (connected && responseCode >= 400) {
 837:        // Client Error 4xx and Server Error 5xx
 838:	          if (inputStream != null) {
 839:        return inputStream;
 840:        }
 841:    }
 842:    return null;
 843:    }
 844:    
 845:    /**
 846:     * set or reset proxy authentication info in request headers
 847:     * after receiving a 407 error. In the case of NTLM however,
 848:     * receiving a 407 is normal and we just skip the stale check
 849:     * because ntlm does not support this feature.
 850:     */
 851:    private AuthenticationInfo
 852:	      resetProxyAuthentication(AuthenticationInfo proxyAuthentication, AuthenticationHeader auth) {
 853:	      if (proxyAuthentication != null ) { 
 854:        String raw = auth.raw();
 855:	          if (proxyAuthentication.isAuthorizationStale (raw)) {
 856:        /* we can retry with the current credentials */
 857:        requests.set (proxyAuthentication.getHeaderName(), 
 858:                  proxyAuthentication.getHeaderValue(
 859:                             url, method));
 860:        currentProxyCredentials = proxyAuthentication;
 861:        return  proxyAuthentication;
 862:        } else {
 863:        proxyAuthentication.removeFromCache();
 864:        }
 865:    }
 866:    proxyAuthentication = getHttpProxyAuthentication(auth);
 867:    currentProxyCredentials = proxyAuthentication;
 868:    return  proxyAuthentication;
 869:    }
 870:
 871:    /**
 872:     * establish a tunnel through proxy server
 873:     */
 874:	      protected synchronized void doTunneling() throws IOException {
 875:    int retryTunnel = 0;
 876:    String statusLine = "";
 877:    int respCode = 0;
 878:    AuthenticationInfo proxyAuthentication = null;
 879:    String proxyHost = null;
 880:    int proxyPort = -1;
 881:	      try {
 882:	          do {
 883:	              if (!checkReuseConnection()) {
 884:            proxiedConnect(url, proxyHost, proxyPort, false);
 885:            }
 886:            // send the "CONNECT" request to establish a tunnel
 887:            // through proxy server
 888:            sendCONNECTRequest();
 889:            responses.reset();
 890:            http.parseHTTP(responses, new ProgressEntry(url.getFile(), null));
 891:            statusLine = responses.getValue(0);
 892:            StringTokenizer st = new StringTokenizer(statusLine);
 893:            st.nextToken();
 894:            respCode = Integer.parseInt(st.nextToken().trim());
 895:	              if (respCode == HTTP_PROXY_AUTH) {
 896:                AuthenticationHeader authhdr = new AuthenticationHeader (
 897:                "Proxy-Authenticate", responses
 898:                );
 899:	                  if (!doingNTLMp2ndStage) {
 900:                proxyAuthentication =
 901:                    resetProxyAuthentication(proxyAuthentication, authhdr);
 902:	                  if (proxyAuthentication != null) {
 903:                proxyHost = http.getProxyHostUsed();
 904:                proxyPort = http.getProxyPortUsed();
 905:                    disconnectInternal();
 906:                    retryTunnel++;
 907:                    continue;
 908:                }
 909:            } else {
 910:                String raw = responses.findValue ("Proxy-Authenticate");
 911:                reset ();
 912:                if (!proxyAuthentication.setHeaders(this, 
 913:	                          authhdr.headerParser(), raw)) {
 914:                proxyHost = http.getProxyHostUsed();
 915:                proxyPort = http.getProxyPortUsed();
 916:                    disconnectInternal();
 917:                    throw new IOException ("Authentication failure");
 918:                }
 919:            authObj = null;
 920:                doingNTLMp2ndStage = false;
 921:                continue;
 922:            }
 923:            }
 924:            // cache proxy authentication info
 925:	              if (proxyAuthentication != null) {
 926:            // cache auth info on success, domain header not relevant.
 927:            proxyAuthentication.addToCache();
 928:            }
 929:    
 930:	              if (respCode == HTTP_OK) {
 931:            break;
 932:            }
 933:            // we don't know how to deal with other response code
 934:            // so disconnect and report error
 935:            disconnectInternal();
 936:            break;
 937:        } while (retryTunnel < maxRedirects);
 938:    
 939:	          if (retryTunnel >= maxRedirects || (respCode != HTTP_OK)) {
 940:            throw new IOException("Unable to tunnel through proxy."+
 941:                      " Proxy returns \"" +
 942:                      statusLine + "\"");
 943:        }
 944:    } finally  {
 945:	          if (respCode == HTTP_PROXY_AUTH && proxyAuthentication != null) {
 946:        proxyAuthentication.endAuthRequest();
 947:        } 
 948:    }
 949:    // remove tunneling related requests
 950:    int i;
 951:    if ((i = requests.getKey("Proxy-authorization")) >= 0)
 952:        requests.set(i, null, null);
 953:
 954:    // reset responses
 955:    responses.reset();
 956:    }
 957:
 958:    /**
 959:     * send a CONNECT request for establishing a tunnel to proxy server
 960:     */
 961:	      private void sendCONNECTRequest() throws IOException {
 962:    int port = url.getPort();
 963:	      if (port == -1) {
 964:        port = url.getDefaultPort();
 965:    }
 966:    requests.prepend("CONNECT " + url.getHost() + ":"
 967:             + port + " " + httpVersion, null);
 968:    requests.setIfNotSet("User-Agent", userAgent);
 969:
 970:    String host = url.getHost();
 971:	      if (port != -1 && port != 80) {
 972:        host += ":" + String.valueOf(port);
 973:    }
 974:    requests.setIfNotSet("Host", host);
 975:
 976:    // Not really necessary for a tunnel, but can't hurt
 977:    requests.setIfNotSet("Accept", acceptString);
 978:
 979:    setPreemptiveProxyAuthentication(requests);
 980:    http.writeRequests(requests, null);
 981:    // remove CONNECT header
 982:    requests.set(0, null, null);
 983:    }
 984:
 985:    /**
 986:     * Sets pre-emptive proxy authentication in header
 987:     */
 988:	      private void setPreemptiveProxyAuthentication(MessageHeader requests) {
 989:    AuthenticationInfo pauth
 990:        = AuthenticationInfo.getProxyAuth(http.getProxyHostUsed(),
 991:                          http.getProxyPortUsed());
 992:	      if (pauth != null && pauth.supportsPreemptiveAuthorization()) {
 993:        // Sets "Proxy-authorization"
 994:        requests.setIfNotSet(pauth.getHeaderName(), 
 995:                 pauth.getHeaderValue(url,method));
 996:        currentProxyCredentials = pauth;
 997:    }
 998:    }
 999:
1000:    /**
1001:     * Gets the authentication for an HTTP proxy, and applies it to
1002:     * the connection.
1003:     */
1004:	      private AuthenticationInfo getHttpProxyAuthentication (AuthenticationHeader authhdr) {
1005:    /* get authorization from authenticator */
1006:    AuthenticationInfo ret = null;
1007:    String raw = authhdr.raw();
1008:    String host = http.getProxyHostUsed();
1009:    int port = http.getProxyPortUsed();
1010:	      if (host != null && authhdr.isPresent()) {
1011:        HeaderParser p = authhdr.headerParser();
1012:        String realm = p.findValue("realm");
1013:        String scheme = authhdr.scheme();
1014:        char schemeID;
1015:	          if ("basic".equalsIgnoreCase(scheme)) {
1016:        schemeID = BasicAuthentication.BASIC_AUTH;
1017:        } else if ("digest".equalsIgnoreCase(scheme)) {
1018:        schemeID = DigestAuthentication.DIGEST_AUTH;
1019:
1020:        } else {
1021:        schemeID = 0;
1022:        }
1023:        if (realm == null)
1024:        realm = "";
1025:        ret = AuthenticationInfo.getProxyAuth(host, port, realm, schemeID);
1026:	          if (ret == null) {
1027:	              if (schemeID == BasicAuthentication.BASIC_AUTH) {
1028:            InetAddress addr = null;
1029:	              try {
1030:                final String finalHost = host;
1031:                addr = (InetAddress)
1032:                java.security.AccessController.doPrivileged
1033:	                      (new java.security.PrivilegedExceptionAction() {
1034:                    public Object run()
1035:	                      throws UnknownHostException {
1036:                    return InetAddress.getByName(finalHost);
1037:                    }
1038:                });
1039:            } catch (PrivilegedActionException ignored) {
1040:                // User will have an unknown host.
1041:            }
1042:            PasswordAuthentication a =
1043:                privilegedRequestPasswordAuthentication(
1044:                        host, addr, port, "http", realm, scheme);
1045:	              if (a != null) {
1046:                ret = new BasicAuthentication(true, host, port, realm, a);
1047:            }
1048:            } else if (schemeID == DigestAuthentication.DIGEST_AUTH) {
1049:            PasswordAuthentication a = 
1050:                privilegedRequestPasswordAuthentication(
1051:                        host, null, port, url.getProtocol(),
1052:                        realm, scheme);
1053:	              if (a != null) {
1054:                DigestAuthentication.Parameters params = 
1055:                new DigestAuthentication.Parameters();
1056:                ret = new DigestAuthentication(true, host, port, realm, 
1057:                                scheme, a, params);
1058:            }
1059:            } 
1060:        }
1061:        // For backwards compatibility, we also try defaultAuth
1062:
1063:        if (ret == null && defaultAuth != null
1064:	          && defaultAuth.schemeSupported(scheme)) {
1065:	          try {
1066:            URL u = new URL("http", host, port, "/");
1067:            String a = defaultAuth.authString(u, scheme, realm);
1068:	              if (a != null) {
1069:            ret = new BasicAuthentication (true, host, port, realm, a);
1070:            // not in cache by default - cache on success
1071:            }
1072:        } catch (MalformedURLException ignored) {
1073:        }
1074:        }
1075:	          if (ret != null) {
1076:	          if (!ret.setHeaders(this, p, raw)) {
1077:            ret = null;
1078:        }
1079:        }
1080:    }
1081:    return ret;
1082:    }
1083:
1084:    /**
1085:     * Gets the authentication for an HTTP server, and applies it to
1086:     * the connection.
1087:     */
1088:	      private AuthenticationInfo getServerAuthentication (AuthenticationHeader authhdr) {
1089:    /* get authorization from authenticator */
1090:    AuthenticationInfo ret = null;
1091:    String raw = authhdr.raw();
1092:    /* When we get an NTLM auth from cache, don't set any special headers */
1093:	      if (authhdr.isPresent()) {
1094:        HeaderParser p = authhdr.headerParser();
1095:        String realm = p.findValue("realm");
1096:        String scheme = authhdr.scheme();
1097:        char schemeID;
1098:	          if ("basic".equalsIgnoreCase(scheme)) {
1099:        schemeID = BasicAuthentication.BASIC_AUTH;
1100:        } else if ("digest".equalsIgnoreCase(scheme)) {
1101:        schemeID = DigestAuthentication.DIGEST_AUTH;
1102: 
1103:        } else {
1104:        schemeID = 0;
1105:        }
1106:        domain = p.findValue ("domain");
1107:        if (realm == null)
1108:        realm = "";
1109:        ret = AuthenticationInfo.getServerAuth(url, realm, schemeID);
1110:        InetAddress addr = null;
1111:	          if (ret == null) {
1112:	          try {
1113:            addr = InetAddress.getByName(url.getHost());
1114:        } catch (UnknownHostException ignored) {
1115:            // User will have addr = null
1116:        }
1117:        }
1118:        // replacing -1 with default port for a protocol
1119:        int port = url.getPort();
1120:	          if (port == -1) {
1121:        port = url.getDefaultPort();
1122:        }
1123:	          if (ret == null) {
1124:	              if (schemeID == BasicAuthentication.BASIC_AUTH) {
1125:            PasswordAuthentication a = 
1126:                privilegedRequestPasswordAuthentication(
1127:                        url.getHost(), addr, port, url.getProtocol(),
1128:                        realm, scheme);
1129:	              if (a != null) {
1130:                ret = new BasicAuthentication(false, url, realm, a);
1131:            }
1132:            }
1133:    
1134:	              if (schemeID == DigestAuthentication.DIGEST_AUTH) {
1135:            PasswordAuthentication a = 
1136:                privilegedRequestPasswordAuthentication(
1137:                        url.getHost(), addr, port, url.getProtocol(),
1138:                        realm, scheme);
1139:	              if (a != null) {
1140:                digestparams = new DigestAuthentication.Parameters();
1141:                ret = new DigestAuthentication(false, url, realm, scheme, a, digestparams);
1142:            }
1143:            }
1144:        }
1145:
1146:        // For backwards compatibility, we also try defaultAuth
1147:
1148:        if (ret == null && defaultAuth != null
1149:	          && defaultAuth.schemeSupported(scheme)) {
1150:        String a = defaultAuth.authString(url, scheme, realm);
1151:	          if (a != null) {
1152:            ret = new BasicAuthentication (false, url, realm, a); 
1153:            // not in cache by default - cache on success
1154:        }
1155:        }
1156:
1157:	          if (ret != null ) {
1158:	          if (!ret.setHeaders(this, p, raw)) {
1159:            ret = null;
1160:        }
1161:        }
1162:    }
1163:    return ret;
1164:    }
1165:
1166:    /* inclose will be true if called from close(), in which case we
1167:     * force the call to check because this is the last chance to do so.
1168:     * If not in close(), then the authentication info could arrive in a trailer
1169:     * field, which we have not read yet.
1170:     */
1171:	      private void checkResponseCredentials (boolean inClose) throws IOException {
1172:	      try {
1173:        if (!needToCheck)
1174:            return;
1175:	          if (validateProxy && currentProxyCredentials != null) {
1176:            String raw = responses.findValue ("Proxy-Authentication-Info");
1177:	          if (inClose || (raw != null)) {
1178:                currentProxyCredentials.checkResponse (raw, method, url);
1179:                currentProxyCredentials = null;
1180:        }
1181:        }
1182:	          if (validateServer && currentServerCredentials != null) {
1183:            String raw = responses.findValue ("Authentication-Info");
1184:	          if (inClose || (raw != null)) {
1185:                currentServerCredentials.checkResponse (raw, method, url);
1186:                currentServerCredentials = null;
1187:        }
1188:        }
1189:	          if ((currentServerCredentials==null) && (currentProxyCredentials == null)) {
1190:        needToCheck = false;
1191:        }
1192:    } catch (IOException e) {
1193:        disconnectInternal();
1194:        connected = false;
1195:        throw e;
1196:    }
1197:    }
1198:
1199:    /* Tells us whether to follow a redirect.  If so, it
1200:     * closes the connection (break any keep-alive) and
1201:     * resets the url, re-connects, and resets the request
1202:     * property.
1203:     */
1204:	      private boolean followRedirect() throws IOException {
1205:	      if (!getInstanceFollowRedirects()) {
1206:        return false;
1207:    }
1208:
1209:    int stat = getResponseCode();
1210:    if (stat < 300 || stat > 307 || stat == 306 
1211:	                  || stat == HTTP_NOT_MODIFIED) {
1212:        return false;
1213:    }
1214:    String loc = getHeaderField("Location");
1215:	      if (loc == null) { 
1216:        /* this should be present - if not, we have no choice
1217:         * but to go forward w/ the response we got
1218:         */
1219:        return false;
1220:    }
1221:    URL locUrl;
1222:	      try {
1223:        locUrl = new URL(loc);
1224:	          if (!url.getProtocol().equalsIgnoreCase(locUrl.getProtocol())) {
1225:        return false;
1226:        }
1227:
1228:    } catch (MalformedURLException mue) {
1229:      // treat loc as a relative URI to conform to popular browsers
1230:      locUrl = new URL(url, loc);
1231:    }
1232:    disconnectInternal();
1233:    // clear out old response headers!!!!
1234:    responses = new MessageHeader();
1235:	      if (stat == HTTP_USE_PROXY) {
1236:        /* This means we must re-request the resource through the
1237:         * proxy denoted in the "Location:" field of the response.
1238:         * Judging by the spec, the string in the Location header
1239:         * _should_ denote a URL - let's hope for "http://my.proxy.org"
1240:         * Make a new HttpClient to the proxy, using HttpClient's
1241:         * Instance-specific proxy fields, but note we're still fetching
1242:         * the same URL.
1243:         */
1244:        setProxiedClient (url, locUrl.getHost(), locUrl.getPort());
1245:        requests.set(0, method + " " + http.getURLFile()+" "  + 
1246:                 httpVersion, null);
1247:        connected = true;
1248:    } else {
1249:        // maintain previous headers, just change the name
1250:        // of the file we're getting
1251:        url = locUrl;
1252:	          if (method.equals("POST") && !Boolean.getBoolean("http.strictPostRedirect") && (stat!=307)) {
1253:        /* The HTTP/1.1 spec says that a redirect from a POST 
1254:         * *should not* be immediately turned into a GET, and
1255:         * that some HTTP/1.0 clients incorrectly did this.
1256:         * Correct behavior redirects a POST to another POST.
1257:         * Unfortunately, since most browsers have this incorrect
1258:         * behavior, the web works this way now.  Typical usage
1259:         * seems to be:
1260:         *   POST a login code or passwd to a web page.
1261:         *   after validation, the server redirects to another
1262:         *     (welcome) page
1263:         *   The second request is (erroneously) expected to be GET
1264:         * 
1265:         * We will do the incorrect thing (POST-->GET) by default.
1266:         * We will provide the capability to do the "right" thing
1267:         * (POST-->POST) by a system property, "http.strictPostRedirect=true"
1268:         */
1269:
1270:        requests = new MessageHeader();
1271:        setRequests = false;
1272:        setRequestMethod("GET");
1273:        poster = null;
1274:        if (!checkReuseConnection())
1275:            connect();
1276:        } else {
1277:        if (!checkReuseConnection())
1278:            connect();
1279:        requests.set(0, method + " " + http.getURLFile()+" "  + 
1280:                 httpVersion, null);
1281:        requests.set("Host", url.getHost() + ((url.getPort() == -1 || url.getPort() == 80) ?
1282:                                         "": ":"+String.valueOf(url.getPort())));
1283:        }
1284:    }
1285:    return true;
1286:    }
1287:
1288:    /* dummy byte buffer for reading off socket prior to closing */
1289:    byte[] cdata = new byte [128];
1290:
1291:    /**
1292:     * Reset (without disconnecting the TCP conn) in order to do another transaction with this instance
1293:     */
1294:	      private void reset() throws IOException {
1295:    http.reuse = true;
1296:    /* must save before calling close */
1297:    reuseClient = http;
1298:    InputStream is = http.getInputStream();
1299:	      try {
1300:        /* we want to read the rest of the response without using the
1301:         * hurry mechanism, because that would close the connection
1302:           * if everything is not available immediately
1303:         */
1304:        if ((is instanceof ChunkedInputStream) || 
1305:	          (is instanceof MeteredStream)) {
1306:        /* reading until eof will not block */
1307:            while (is.read (cdata) > 0) {}
1308:        } else { 
1309:        /* raw stream, which will block on read, so only read
1310:         * the expected number of bytes, probably 0
1311:         */
1312:        int cl = 0, n=0;
1313:	          try {
1314:                cl = Integer.parseInt (responses.findValue ("Content-Length"));
1315:        } catch (Exception e) {}
1316:	              for (int i=0; i<cl; ) {
1317:	              if ((n = is.read (cdata)) == -1) {
1318:                break;
1319:            } else {
1320:                i+= n;
1321:            }
1322:            }
1323:        }
1324:    } catch (IOException e) {
1325:        http.reuse = false;
1326:        reuseClient = null;
1327:        disconnectInternal();
1328:        return;
1329:    } 
1330:	      try {
1331:	          if (is instanceof MeteredStream) {
1332:        is.close();
1333:        }
1334:    } catch (IOException e) { }
1335:    responseCode = -1;
1336:    responses = new MessageHeader();
1337:    connected = false;
1338:    }
1339:
1340:    /**
1341:     * Disconnect from the server (for internal use)
1342:     */
1343:	      private void disconnectInternal() {
1344:    responseCode = -1;
1345:	          if (pe != null) {
1346:            ProgressData.pdata.unregister(pe);
1347:        }
1348:	      if (http != null) {
1349:        http.closeServer();
1350:            http = null;
1351:            connected = false;
1352:        }
1353:    }
1354:
1355:    /**
1356:     * Disconnect from the server (public API)
1357:     */
1358:	      public void disconnect() {
1359:
1360:    responseCode = -1;
1361:	      if (pe != null) {
1362:        ProgressData.pdata.unregister(pe);
1363:    }
1364:	      if (http != null) {
1365:        /*
1366:         * If we have an input stream this means we received a response
1367:         * from the server. That stream may have been read to EOF and
1368:         * dependening on the stream type may already be closed or the
1369:         * the http client may be returned to the keep-alive cache.
1370:         * If the http client has been returned to the keep-alive cache
1371:         * it may be closed (idle timeout) or may be allocated to 
1372:         * another request.
1373:         *
1374:         * In other to avoid timing issues we close the input stream
1375:         * which will either close the underlying connection or return
1376:         * the client to the cache. If there's a possibility that the
1377:         * client has been returned to the cache (ie: stream is a keep
1378:         * alive stream or a chunked input stream) then we remove an
1379:         * idle connection to the server. Note that this approach
1380:         * can be considered an approximation in that we may close a
1381:           * different idle connection to that used by the request.
1382:         * Additionally it's possible that we close two connections
1383:         * - the first becuase it wasn't an EOF (and couldn't be
1384:         * hurried) - the second, another idle connection to the
1385:         * same server. The is okay because "disconnect" is an
1386:         * indication that the application doesn't intend to access
1387:         * this http server for a while.
1388:         */
1389:
1390:	          if (inputStream != null) {
1391:        HttpClient hc = http;
1392:
1393:        // un-synchronized 
1394:        boolean ka = hc.isKeepingAlive();
1395:
1396:	          try {
1397:            inputStream.close();
1398:        } catch (IOException ioe) { }
1399:
1400:        // if the connection is persistent it may have been closed
1401:        // or returned to the keep-alive cache. If it's been returned
1402:        // to the keep-alive cache then we would like to close it
1403:        // but it may have been allocated
1404:
1405:	          if (ka) {
1406:            hc.closeIdleConnection();
1407:        }
1408:        
1409:
1410:        } else {
1411:            http.closeServer();
1412:        }
1413:
1414:        //        poster = null;
1415:        http = null;
1416:        connected = false;
1417:    }
1418:    }
1419:
1420:	      public boolean usingProxy() {
1421:	      if (http != null) {
1422:        return (http.getProxyHostUsed() != null);
1423:    }
1424:    return false;
1425:    }
1426:
1427:    /**
1428:     * Gets a header field by name. Returns null if not known.
1429:     * @param name the name of the header field
1430:     */
1431:	      public String getHeaderField(String name) {
1432:	      try {
1433:        getInputStream();
1434:    } catch (IOException e) {}
1435:    return responses.findValue(name);
1436:    }
1437:
1438:    /**
1439:     * Returns an unmodifiable Map of the header fields.
1440:     * The Map keys are Strings that represent the
1441:     * response-header field names. Each Map value is an
1442:     * unmodifiable List of Strings that represents 
1443:     * the corresponding field values.
1444:     *
1445:     * @return a Map of header fields
1446:     * @since 1.4
1447:     */
1448:	      public Map getHeaderFields() {
1449:	      try {
1450:        getInputStream();
1451:    } catch (IOException e) {}
1452:        return responses.getHeaders();
1453:    }
1454:
1455:    /**
1456:     * Gets a header field by index. Returns null if not known.
1457:     * @param n the index of the header field
1458:     */
1459:	      public String getHeaderField(int n) {
1460:	      try {
1461:        getInputStream();
1462:    } catch (IOException e) {}
1463:    return responses.getValue(n);
1464:    }
1465:
1466:    /**
1467:     * Gets a header field by index. Returns null if not known.
1468:     * @param n the index of the header field
1469:     */
1470:	      public String getHeaderFieldKey(int n) {
1471:	      try {
1472:        getInputStream();
1473:    } catch (IOException e) {}
1474:    return responses.getKey(n);
1475:    }
1476:
1477:    /**
1478:     * Sets request property. If a property with the key already
1479:     * exists, overwrite its value with the new value.
1480:     * @param value the value to be set
1481:     */
1482:	      public void setRequestProperty(String key, String value) {
1483:    super.setRequestProperty(key, value);
1484:    checkMessageHeader(key, value);
1485:    requests.set(key, value);
1486:    }
1487:
1488:    /**
1489:     * Adds a general request property specified by a
1490:     * key-value pair.  This method will not overwrite
1491:     * existing values associated with the same key.
1492:     *
1493:     * @param   key     the keyword by which the request is known
1494:     *                  (e.g., "<code>accept</code>").
1495:     * @param   value  the value associated with it.
1496:     * @see #getRequestProperties(java.lang.String)
1497:     * @since 1.4
1498:     */
1499:	      public void addRequestProperty(String key, String value) {
1500:    super.addRequestProperty(key, value);
1501:    checkMessageHeader(key, value);
1502:    requests.add(key, value);
1503:    }
1504:
1505:    //
1506:    // Set a property for authentication.  This can safely disregard
1507:    // the connected test.
1508:    //
1509:	      void setAuthenticationProperty(String key, String value) {
1510:    checkMessageHeader(key, value);
1511:    requests.set(key, value);
1512:    }
1513:
1514:	      public String getRequestProperty (String key) {
1515:    // don't return headers containing security sensitive information
1516:	      if (key != null) {
1517:	          for (int i=0; i < EXCLUDE_HEADERS.length; i++) {
1518:	          if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) {
1519:            return null;
1520:        }
1521:        }
1522:    }
1523:    return requests.findValue(key);
1524:    }
1525:
1526:    /**
1527:     * Returns an unmodifiable Map of general request
1528:     * properties for this connection. The Map keys
1529:     * are Strings that represent the request-header
1530:     * field names. Each Map value is a unmodifiable List 
1531:     * of Strings that represents the corresponding 
1532:     * field values.
1533:     *
1534:     * @return  a Map of the general request properties for this connection.
1535:     * @throws IllegalStateException if already connected
1536:     * @since 1.4
1537:     */
1538:	      public Map getRequestProperties() {
1539:        if (connected)
1540:            throw new IllegalStateException("Already connected");
1541:
1542:    // exclude headers containing security-sensitive info
1543:    return requests.getHeaders(EXCLUDE_HEADERS);
1544:    }
1545:
1546:	      public void finalize() {
1547:    // this should do nothing.  The stream finalizer will close 
1548:    // the fd
1549:    }
1550:
1551:	      String getMethod() {
1552:    return method;
1553:    }
1554:
1555:    /* The purpose of this wrapper is just to capture the close() call
1556:     * so we can check authentication information that may have
1557:     * arrived in a Trailer field
1558:     */
1559:	      class HttpInputStream extends FilterInputStream {
1560:
1561:	          public HttpInputStream (InputStream is) {
1562:        super (is);
1563:        }
1564:
1565:	          public void close () throws IOException {
1566:	          try {
1567:        super.close ();
1568:        } finally {
1569:        HttpURLConnection.this.http = null;
1570:            checkResponseCredentials (true);
1571:        } 
1572:        }
1573:    }
1574:}
1575:
1576:/** An input stream that just returns EOF.  This is for
1577: * HTTP URLConnections that are KeepAlive && use the
1578: * HEAD method - i.e., stream not dead, but nothing to be read.
1579: */
1580:
1581:	  class EmptyInputStream extends InputStream {
1582:
1583:	      public int available() {
1584:    return 0;
1585:    }
1586:
1587:	      public int read() {
1588:    return -1;
1589:    }
1590:}