1 /*
   2  * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.net.www.protocol.http;
  27 
  28 import java.lang.reflect.Constructor;
  29 import java.net.URL;
  30 import java.net.URLConnection;
  31 import java.net.ProtocolException;
  32 import java.net.HttpRetryException;
  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.net.Proxy;
  39 import java.net.ProxySelector;
  40 import java.net.URI;
  41 import java.net.InetSocketAddress;
  42 import java.net.CookieHandler;
  43 import java.net.ResponseCache;
  44 import java.net.CacheResponse;
  45 import java.net.SecureCacheResponse;
  46 import java.net.CacheRequest;
  47 import java.net.Authenticator.RequestorType;
  48 import java.io.*;
  49 import java.util.Date;
  50 import java.util.Map;
  51 import java.util.List;
  52 import java.util.Locale;
  53 import java.util.StringTokenizer;
  54 import java.util.Iterator;
  55 import java.util.HashSet;
  56 import java.util.HashMap;
  57 import java.util.Set;
  58 import sun.net.*;
  59 import sun.net.www.*;
  60 import sun.net.www.http.HttpClient;
  61 import sun.net.www.http.PosterOutputStream;
  62 import sun.net.www.http.ChunkedInputStream;
  63 import sun.net.www.http.ChunkedOutputStream;
  64 import sun.net.www.http.HttpCapture;
  65 import java.text.SimpleDateFormat;
  66 import java.util.TimeZone;
  67 import java.net.MalformedURLException;
  68 import java.nio.ByteBuffer;
  69 import static sun.net.www.protocol.http.AuthScheme.BASIC;
  70 import static sun.net.www.protocol.http.AuthScheme.DIGEST;
  71 import static sun.net.www.protocol.http.AuthScheme.NTLM;
  72 import static sun.net.www.protocol.http.AuthScheme.NEGOTIATE;
  73 import static sun.net.www.protocol.http.AuthScheme.KERBEROS;
  74 import static sun.net.www.protocol.http.AuthScheme.UNKNOWN;
  75 
  76 /**
  77  * A class to represent an HTTP connection to a remote object.
  78  */
  79 
  80 
  81 public class HttpURLConnection extends java.net.HttpURLConnection {
  82 
  83     static final String version;
  84     public static final String userAgent;
  85 
  86     /* max # of allowed re-directs */
  87     static final int defaultmaxRedirects = 20;
  88     static final int maxRedirects;
  89 
  90     /* Not all servers support the (Proxy)-Authentication-Info headers.
  91      * By default, we don't require them to be sent
  92      */
  93     static final boolean validateProxy;
  94     static final boolean validateServer;
  95 
  96     private StreamingOutputStream strOutputStream;
  97     private final static String RETRY_MSG1 =
  98         "cannot retry due to proxy authentication, in streaming mode";
  99     private final static String RETRY_MSG2 =
 100         "cannot retry due to server authentication, in streaming mode";
 101     private final static String RETRY_MSG3 =
 102         "cannot retry due to redirection, in streaming mode";
 103 
 104     /*
 105      * System properties related to error stream handling:
 106      *
 107      * sun.net.http.errorstream.enableBuffering = <boolean>
 108      *
 109      * With the above system property set to true (default is false),
 110      * when the response code is >=400, the HTTP handler will try to
 111      * buffer the response body (up to a certain amount and within a
 112      * time limit). Thus freeing up the underlying socket connection
 113      * for reuse. The rationale behind this is that usually when the
 114      * server responds with a >=400 error (client error or server
 115      * error, such as 404 file not found), the server will send a
 116      * small response body to explain who to contact and what to do to
 117      * recover. With this property set to true, even if the
 118      * application doesn't call getErrorStream(), read the response
 119      * body, and then call close(), the underlying socket connection
 120      * can still be kept-alive and reused. The following two system
 121      * properties provide further control to the error stream
 122      * buffering behaviour.
 123      *
 124      * sun.net.http.errorstream.timeout = <int>
 125      *     the timeout (in millisec) waiting the error stream
 126      *     to be buffered; default is 300 ms
 127      *
 128      * sun.net.http.errorstream.bufferSize = <int>
 129      *     the size (in bytes) to use for the buffering the error stream;
 130      *     default is 4k
 131      */
 132 
 133 
 134     /* Should we enable buffering of error streams? */
 135     private static boolean enableESBuffer = false;
 136 
 137     /* timeout waiting for read for buffered error stream;
 138      */
 139     private static int timeout4ESBuffer = 0;
 140 
 141     /* buffer size for buffered error stream;
 142     */
 143     private static int bufSize4ES = 0;
 144 
 145     /*
 146      * Restrict setting of request headers through the public api
 147      * consistent with JavaScript XMLHttpRequest2 with a few
 148      * exceptions. Disallowed headers are silently ignored for
 149      * backwards compatibility reasons rather than throwing a
 150      * SecurityException. For example, some applets set the
 151      * Host header since old JREs did not implement HTTP 1.1.
 152      * Additionally, any header starting with Sec- is
 153      * disallowed.
 154      *
 155      * The following headers are allowed for historical reasons:
 156      *
 157      * Accept-Charset, Accept-Encoding, Cookie, Cookie2, Date,
 158      * Referer, TE, User-Agent, headers beginning with Proxy-.
 159      *
 160      * The following headers are allowed in a limited form:
 161      *
 162      * Connection: close
 163      *
 164      * See http://www.w3.org/TR/XMLHttpRequest2.
 165      */
 166     private static final boolean allowRestrictedHeaders;
 167 
 168     private static final Set<String> restrictedHeaderSet;
 169 
 170     private static final String[] restrictedHeaders = {
 171         /* Restricted by XMLHttpRequest2 */
 172         //"Accept-Charset",
 173         //"Accept-Encoding",
 174         "Access-Control-Request-Headers",
 175         "Access-Control-Request-Method",
 176         "Connection", /* close is allowed */
 177         "Content-Length",
 178         //"Cookie",
 179         //"Cookie2",
 180         "Content-Transfer-Encoding",
 181         //"Date",
 182         "Expect",
 183         "Host",
 184         "Keep-Alive",
 185         "Origin",
 186         // "Referer", 
 187         // "TE",
 188         "Trailer",
 189         "Transfer-Encoding",
 190         "Upgrade",
 191         //"User-Agent",
 192         "Via"
 193     };
 194 
 195     static {
 196         maxRedirects = java.security.AccessController.doPrivileged(
 197             new sun.security.action.GetIntegerAction(
 198                 "http.maxRedirects", defaultmaxRedirects)).intValue();
 199         version = java.security.AccessController.doPrivileged(
 200                     new sun.security.action.GetPropertyAction("java.version"));
 201         String agent = java.security.AccessController.doPrivileged(
 202                     new sun.security.action.GetPropertyAction("http.agent"));
 203         if (agent == null) {
 204             agent = "Java/"+version;
 205         } else {
 206             agent = agent + " Java/"+version;
 207         }
 208         userAgent = agent;
 209         validateProxy = java.security.AccessController.doPrivileged(
 210                 new sun.security.action.GetBooleanAction(
 211                     "http.auth.digest.validateProxy")).booleanValue();
 212         validateServer = java.security.AccessController.doPrivileged(
 213                 new sun.security.action.GetBooleanAction(
 214                     "http.auth.digest.validateServer")).booleanValue();
 215 
 216         enableESBuffer = java.security.AccessController.doPrivileged(
 217                 new sun.security.action.GetBooleanAction(
 218                     "sun.net.http.errorstream.enableBuffering")).booleanValue();
 219         timeout4ESBuffer = java.security.AccessController.doPrivileged(
 220                 new sun.security.action.GetIntegerAction(
 221                     "sun.net.http.errorstream.timeout", 300)).intValue();
 222         if (timeout4ESBuffer <= 0) {
 223             timeout4ESBuffer = 300; // use the default
 224         }
 225 
 226         bufSize4ES = java.security.AccessController.doPrivileged(
 227                 new sun.security.action.GetIntegerAction(
 228                     "sun.net.http.errorstream.bufferSize", 4096)).intValue();
 229         if (bufSize4ES <= 0) {
 230             bufSize4ES = 4096; // use the default
 231         }
 232 
 233         allowRestrictedHeaders = ((Boolean)java.security.AccessController.doPrivileged( 
 234                 new sun.security.action.GetBooleanAction( 
 235                     "sun.net.http.allowRestrictedHeaders"))).booleanValue(); 
 236         if (!allowRestrictedHeaders) { 
 237             restrictedHeaderSet = new HashSet<String>(restrictedHeaders.length); 
 238             for (int i=0; i < restrictedHeaders.length; i++) { 
 239                 restrictedHeaderSet.add(restrictedHeaders[i].toLowerCase()); 
 240             }
 241         } else {
 242             restrictedHeaderSet = null;
 243         }
 244     }
 245 
 246     static final String httpVersion = "HTTP/1.1";
 247     static final String acceptString =
 248         "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2";
 249 
 250     // the following http request headers should NOT have their values
 251     // returned for security reasons.
 252     private static final String[] EXCLUDE_HEADERS = {
 253             "Proxy-Authorization",
 254             "Authorization"
 255     };
 256 
 257     // also exclude system cookies when any might be set
 258     private static final String[] EXCLUDE_HEADERS2= {
 259             "Proxy-Authorization", 
 260             "Authorization",
 261             "Cookie",
 262             "Cookie2"
 263     };
 264 
 265     protected HttpClient http;
 266     protected Handler handler;
 267     protected Proxy instProxy;
 268 
 269     private CookieHandler cookieHandler;
 270     private ResponseCache cacheHandler;
 271 
 272     // the cached response, and cached response headers and body
 273     protected CacheResponse cachedResponse;
 274     private MessageHeader cachedHeaders;
 275     private InputStream cachedInputStream;
 276 
 277     /* output stream to server */
 278     protected PrintStream ps = null;
 279 
 280 
 281     /* buffered error stream */
 282     private InputStream errorStream = null;
 283 
 284     /* User set Cookies */
 285     private boolean setUserCookies = true;
 286     private String userCookies = null;
 287     private String userCookies2 = null;
 288 
 289     /* We only have a single static authenticator for now.
 290      * REMIND:  backwards compatibility with JDK 1.1.  Should be
 291      * eliminated for JDK 2.0.
 292      */
 293     private static HttpAuthenticator defaultAuth;
 294 
 295     /* all the headers we send
 296      * NOTE: do *NOT* dump out the content of 'requests' in the
 297      * output or stacktrace since it may contain security-sensitive
 298      * headers such as those defined in EXCLUDE_HEADERS.
 299      */
 300     private MessageHeader requests;
 301 
 302     /* The following two fields are only used with Digest Authentication */
 303     String domain;      /* The list of authentication domains */
 304     DigestAuthentication.Parameters digestparams;
 305 
 306     /* Current credentials in use */
 307     AuthenticationInfo  currentProxyCredentials = null;
 308     AuthenticationInfo  currentServerCredentials = null;
 309     boolean             needToCheck = true;
 310     private boolean doingNTLM2ndStage = false; /* doing the 2nd stage of an NTLM server authentication */
 311     private boolean doingNTLMp2ndStage = false; /* doing the 2nd stage of an NTLM proxy authentication */
 312 
 313     /* try auth without calling Authenticator. Used for transparent NTLM authentication */
 314     private boolean tryTransparentNTLMServer = true;
 315     private boolean tryTransparentNTLMProxy = true;
 316     private boolean useProxyResponseCode = false;
 317 
 318     /* Used by Windows specific code */
 319     Object authObj;
 320 
 321     /* Set if the user is manually setting the Authorization or Proxy-Authorization headers */
 322     boolean isUserServerAuth;
 323     boolean isUserProxyAuth;
 324 
 325     /* Progress source */
 326     protected ProgressSource pi;
 327 
 328     /* all the response headers we get back */
 329     private MessageHeader responses;
 330     /* the stream _from_ the server */
 331     private InputStream inputStream = null;
 332     /* post stream _to_ the server, if any */
 333     private PosterOutputStream poster = null;
 334 
 335     /* Indicates if the std. request headers have been set in requests. */
 336     private boolean setRequests=false;
 337 
 338     /* Indicates whether a request has already failed or not */
 339     private boolean failedOnce=false;
 340 
 341     /* Remembered Exception, we will throw it again if somebody
 342        calls getInputStream after disconnect */
 343     private Exception rememberedException = null;
 344 
 345     /* If we decide we want to reuse a client, we put it here */
 346     private HttpClient reuseClient = null;
 347 
 348     /* Redefine timeouts from java.net.URLConnection as we nee -1 to mean
 349      * not set. This is to ensure backward compatibility.
 350      */
 351     private int connectTimeout = -1;
 352     private int readTimeout = -1;
 353 
 354     /*
 355      * privileged request password authentication
 356      *
 357      */
 358     private static PasswordAuthentication
 359     privilegedRequestPasswordAuthentication(
 360                             final String host,
 361                             final InetAddress addr,
 362                             final int port,
 363                             final String protocol,
 364                             final String prompt,
 365                             final String scheme,
 366                             final URL url,
 367                             final RequestorType authType) {
 368          return java.security.AccessController.doPrivileged(
 369              new java.security.PrivilegedAction<PasswordAuthentication>() {
 370                  public PasswordAuthentication run() {
 371                     if (HttpCapture.isLoggable("FINEST")) {
 372                         HttpCapture.finest("Requesting Authentication: host =" + host + " url = " + url);
 373                     }
 374                     PasswordAuthentication pass = Authenticator.requestPasswordAuthentication(
 375                         host, addr, port, protocol,
 376                         prompt, scheme, url, authType);
 377                     if (HttpCapture.isLoggable("FINEST")) {
 378                         HttpCapture.finest("Authentication returned: " + (pass != null ? pass.toString() : "null"));
 379                     }
 380                     return pass;
 381                 }
 382             });
 383     }
 384 
 385     private boolean isRestrictedHeader(String key, String value) {
 386         if (allowRestrictedHeaders) {
 387             return false;
 388         }
 389 
 390         key = key.toLowerCase();
 391         if (restrictedHeaderSet.contains(key)) {
 392             /*
 393              * Exceptions to restricted headers:
 394              *
 395              * Allow "Connection: close".
 396              */
 397             if (key.equals("connection") && value.equalsIgnoreCase("close")) {
 398                 return false;
 399             }
 400             return true;
 401         } else if (key.startsWith("sec-")) {
 402             return true;
 403         }
 404         return false;
 405     }
 406 
 407     /*
 408      * Checks the validity of http message header and whether the header
 409      * is restricted and throws IllegalArgumentException if invalid or
 410      * restricted.
 411      */
 412     private boolean isExternalMessageHeaderAllowed(String key, String value) {
 413         checkMessageHeader(key, value);
 414         if (!isRestrictedHeader(key, value)) {
 415             return true;
 416         }
 417         return false;
 418     }
 419 
 420     /*
 421      * checks the validity of http message header and throws
 422      * IllegalArgumentException if invalid.
 423      */
 424     private void checkMessageHeader(String key, String value) {
 425         char LF = '\n';
 426         int index = key.indexOf(LF);
 427         int index1 = key.indexOf(':');
 428         if (index != -1 || index1 != -1) {
 429             throw new IllegalArgumentException(
 430                 "Illegal character(s) in message header field: " + key);
 431         }
 432         else {
 433             if (value == null) {
 434                 return;
 435             }
 436 
 437             index = value.indexOf(LF);
 438             while (index != -1) {
 439                 index++;
 440                 if (index < value.length()) {
 441                     char c = value.charAt(index);
 442                     if ((c==' ') || (c=='\t')) {
 443                         // ok, check the next occurrence
 444                         index = value.indexOf(LF, index);
 445                         continue;
 446                     }
 447                 }
 448                 throw new IllegalArgumentException(
 449                     "Illegal character(s) in message header value: " + value);
 450             }
 451         }
 452     }
 453 
 454     /* adds the standard key/val pairs to reqests if necessary & write to
 455      * given PrintStream
 456      */
 457     private void writeRequests() throws IOException {
 458         /* print all message headers in the MessageHeader
 459          * onto the wire - all the ones we've set and any
 460          * others that have been set
 461          */
 462         // send any pre-emptive authentication
 463         if (http.usingProxy) {
 464             setPreemptiveProxyAuthentication(requests);
 465         }
 466         if (!setRequests) {
 467 
 468             /* We're very particular about the order in which we
 469              * set the request headers here.  The order should not
 470              * matter, but some careless CGI programs have been
 471              * written to expect a very particular order of the
 472              * standard headers.  To name names, the order in which
 473              * Navigator3.0 sends them.  In particular, we make *sure*
 474              * to send Content-type: <> and Content-length:<> second
 475              * to last and last, respectively, in the case of a POST
 476              * request.
 477              */
 478             if (!failedOnce)
 479                 requests.prepend(method + " " + http.getURLFile()+" "  +
 480                                  httpVersion, null);
 481             if (!getUseCaches()) {
 482                 requests.setIfNotSet ("Cache-Control", "no-cache");
 483                 requests.setIfNotSet ("Pragma", "no-cache");
 484             }
 485             requests.setIfNotSet("User-Agent", userAgent);
 486             int port = url.getPort();
 487             String host = url.getHost();
 488             if (port != -1 && port != url.getDefaultPort()) {
 489                 host += ":" + String.valueOf(port);
 490             }
 491             requests.setIfNotSet("Host", host);
 492             requests.setIfNotSet("Accept", acceptString);
 493 
 494             /*
 495              * For HTTP/1.1 the default behavior is to keep connections alive.
 496              * However, we may be talking to a 1.0 server so we should set
 497              * keep-alive just in case, except if we have encountered an error
 498              * or if keep alive is disabled via a system property
 499              */
 500 
 501             // Try keep-alive only on first attempt
 502             if (!failedOnce && http.getHttpKeepAliveSet()) {
 503                 if (http.usingProxy) {
 504                     requests.setIfNotSet("Proxy-Connection", "keep-alive");
 505                 } else {
 506                     requests.setIfNotSet("Connection", "keep-alive");
 507                 }
 508             } else {
 509                 /*
 510                  * RFC 2616 HTTP/1.1 section 14.10 says:
 511                  * HTTP/1.1 applications that do not support persistent
 512                  * connections MUST include the "close" connection option
 513                  * in every message
 514                  */
 515                 requests.setIfNotSet("Connection", "close");
 516             }
 517             // Set modified since if necessary
 518             long modTime = getIfModifiedSince();
 519             if (modTime != 0 ) {
 520                 Date date = new Date(modTime);
 521                 //use the preferred date format according to RFC 2068(HTTP1.1),
 522                 // RFC 822 and RFC 1123
 523                 SimpleDateFormat fo =
 524                   new SimpleDateFormat ("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
 525                 fo.setTimeZone(TimeZone.getTimeZone("GMT"));
 526                 requests.setIfNotSet("If-Modified-Since", fo.format(date));
 527             }
 528             // check for preemptive authorization
 529             AuthenticationInfo sauth = AuthenticationInfo.getServerAuth(url);
 530             if (sauth != null && sauth.supportsPreemptiveAuthorization() ) {
 531                 // Sets "Authorization"
 532                 requests.setIfNotSet(sauth.getHeaderName(), sauth.getHeaderValue(url,method));
 533                 currentServerCredentials = sauth;
 534             }
 535 
 536             if (!method.equals("PUT") && (poster != null || streaming())) {
 537                 requests.setIfNotSet ("Content-type",
 538                         "application/x-www-form-urlencoded");
 539             }
 540 
 541             boolean chunked = false;
 542 
 543             if (streaming()) {
 544                 if (chunkLength != -1) {
 545                     requests.set ("Transfer-Encoding", "chunked");
 546                     chunked = true;
 547                 } else {
 548                     requests.set ("Content-Length", String.valueOf(fixedContentLength));
 549                 }
 550             } else if (poster != null) {
 551                 /* add Content-Length & POST/PUT data */
 552                 synchronized (poster) {
 553                     /* close it, so no more data can be added */
 554                     poster.close();
 555                     requests.set("Content-Length",
 556                                  String.valueOf(poster.size()));
 557                 }
 558             }
 559 
 560             if (!chunked) {
 561                 if (requests.findValue("Transfer-Encoding") != null) {
 562                     requests.remove("Transfer-Encoding");
 563                     if (HttpCapture.isLoggable("WARNING")) {
 564                         HttpCapture.warning(
 565                             "use streaming mode for chunked encoding");
 566                     }
 567                 }
 568             }
 569 
 570             // get applicable cookies based on the uri and request headers
 571             // add them to the existing request headers
 572             setCookieHeader();
 573 
 574             setRequests=true;
 575         }
 576         if (HttpCapture.isLoggable("FINE")) {
 577             HttpCapture.fine(requests.toString());
 578         }
 579         http.writeRequests(requests, poster, streaming());
 580         if (ps.checkError()) {
 581             String proxyHost = http.getProxyHostUsed();
 582             int proxyPort = http.getProxyPortUsed();
 583             disconnectInternal();
 584             if (failedOnce) {
 585                 throw new IOException("Error writing to server");
 586             } else { // try once more
 587                 failedOnce=true;
 588                 if (proxyHost != null) {
 589                     setProxiedClient(url, proxyHost, proxyPort);
 590                 } else {
 591                     setNewClient (url);
 592                 }
 593                 ps = (PrintStream) http.getOutputStream();
 594                 connected=true;
 595                 responses = new MessageHeader();
 596                 setRequests=false;
 597                 writeRequests();
 598             }
 599         }
 600     }
 601 
 602 
 603     /**
 604      * Create a new HttpClient object, bypassing the cache of
 605      * HTTP client objects/connections.
 606      *
 607      * @param url       the URL being accessed
 608      */
 609     protected void setNewClient (URL url)
 610     throws IOException {
 611         setNewClient(url, false);
 612     }
 613 
 614     /**
 615      * Obtain a HttpsClient object. Use the cached copy if specified.
 616      *
 617      * @param url       the URL being accessed
 618      * @param useCache  whether the cached connection should be used
 619      *        if present
 620      */
 621     protected void setNewClient (URL url, boolean useCache)
 622         throws IOException {
 623         http = HttpClient.New(url, null, -1, useCache, connectTimeout);
 624         http.setReadTimeout(readTimeout);
 625     }
 626 
 627 
 628     /**
 629      * Create a new HttpClient object, set up so that it uses
 630      * per-instance proxying to the given HTTP proxy.  This
 631      * bypasses the cache of HTTP client objects/connections.
 632      *
 633      * @param url       the URL being accessed
 634      * @param proxyHost the proxy host to use
 635      * @param proxyPort the proxy port to use
 636      */
 637     protected void setProxiedClient (URL url, String proxyHost, int proxyPort)
 638     throws IOException {
 639         setProxiedClient(url, proxyHost, proxyPort, false);
 640     }
 641 
 642     /**
 643      * Obtain a HttpClient object, set up so that it uses per-instance
 644      * proxying to the given HTTP proxy. Use the cached copy of HTTP
 645      * client objects/connections if specified.
 646      *
 647      * @param url       the URL being accessed
 648      * @param proxyHost the proxy host to use
 649      * @param proxyPort the proxy port to use
 650      * @param useCache  whether the cached connection should be used
 651      *        if present
 652      */
 653     protected void setProxiedClient (URL url,
 654                                            String proxyHost, int proxyPort,
 655                                            boolean useCache)
 656         throws IOException {
 657         proxiedConnect(url, proxyHost, proxyPort, useCache);
 658     }
 659 
 660     protected void proxiedConnect(URL url,
 661                                            String proxyHost, int proxyPort,
 662                                            boolean useCache)
 663         throws IOException {
 664         http = HttpClient.New (url, proxyHost, proxyPort, useCache, connectTimeout);
 665         http.setReadTimeout(readTimeout);
 666     }
 667 
 668     protected HttpURLConnection(URL u, Handler handler)
 669     throws IOException {
 670         // we set proxy == null to distinguish this case with the case
 671         // when per connection proxy is set
 672         this(u, null, handler);
 673     }
 674 
 675     public HttpURLConnection(URL u, String host, int port) {
 676         this(u, new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(host, port)));
 677     }
 678 
 679     /** this constructor is used by other protocol handlers such as ftp
 680         that want to use http to fetch urls on their behalf.*/
 681     public HttpURLConnection(URL u, Proxy p) {
 682         this(u, p, new Handler());
 683     }
 684 
 685     protected HttpURLConnection(URL u, Proxy p, Handler handler) {
 686         super(u);
 687         requests = new MessageHeader();
 688         responses = new MessageHeader();
 689         this.handler = handler;
 690         instProxy = p;
 691         if (instProxy instanceof sun.net.ApplicationProxy) {
 692             /* Application set Proxies should not have access to cookies
 693              * in a secure environment unless explicitly allowed. */
 694             try {
 695                 cookieHandler = CookieHandler.getDefault();
 696             } catch (SecurityException se) { /* swallow exception */ }
 697         } else {
 698             cookieHandler = java.security.AccessController.doPrivileged(
 699                 new java.security.PrivilegedAction<CookieHandler>() {
 700                 public CookieHandler run() {
 701                     return CookieHandler.getDefault();
 702                 }
 703             });
 704         }
 705         cacheHandler = java.security.AccessController.doPrivileged(
 706             new java.security.PrivilegedAction<ResponseCache>() {
 707             public ResponseCache run() {
 708                 return ResponseCache.getDefault();
 709             }
 710         });
 711     }
 712 
 713     /**
 714      * @deprecated.  Use java.net.Authenticator.setDefault() instead.
 715      */
 716     public static void setDefaultAuthenticator(HttpAuthenticator a) {
 717         defaultAuth = a;
 718     }
 719 
 720     /**
 721      * opens a stream allowing redirects only to the same host.
 722      */
 723     public static InputStream openConnectionCheckRedirects(URLConnection c)
 724         throws IOException
 725     {
 726         boolean redir;
 727         int redirects = 0;
 728         InputStream in;
 729 
 730         do {
 731             if (c instanceof HttpURLConnection) {
 732                 ((HttpURLConnection) c).setInstanceFollowRedirects(false);
 733             }
 734 
 735             // We want to open the input stream before
 736             // getting headers, because getHeaderField()
 737             // et al swallow IOExceptions.
 738             in = c.getInputStream();
 739             redir = false;
 740 
 741             if (c instanceof HttpURLConnection) {
 742                 HttpURLConnection http = (HttpURLConnection) c;
 743                 int stat = http.getResponseCode();
 744                 if (stat >= 300 && stat <= 307 && stat != 306 &&
 745                         stat != HttpURLConnection.HTTP_NOT_MODIFIED) {
 746                     URL base = http.getURL();
 747                     String loc = http.getHeaderField("Location");
 748                     URL target = null;
 749                     if (loc != null) {
 750                         target = new URL(base, loc);
 751                     }
 752                     http.disconnect();
 753                     if (target == null
 754                         || !base.getProtocol().equals(target.getProtocol())
 755                         || base.getPort() != target.getPort()
 756                         || !hostsEqual(base, target)
 757                         || redirects >= 5)
 758                     {
 759                         throw new SecurityException("illegal URL redirect");
 760                     }
 761                     redir = true;
 762                     c = target.openConnection();
 763                     redirects++;
 764                 }
 765             }
 766         } while (redir);
 767         return in;
 768     }
 769 
 770 
 771     //
 772     // Same as java.net.URL.hostsEqual
 773     //
 774     private static boolean hostsEqual(URL u1, URL u2) {
 775         final String h1 = u1.getHost();
 776         final String h2 = u2.getHost();
 777 
 778         if (h1 == null) {
 779             return h2 == null;
 780         } else if (h2 == null) {
 781             return false;
 782         } else if (h1.equalsIgnoreCase(h2)) {
 783             return true;
 784         }
 785         // Have to resolve addresses before comparing, otherwise
 786         // names like tachyon and tachyon.eng would compare different
 787         final boolean result[] = {false};
 788 
 789         java.security.AccessController.doPrivileged(
 790             new java.security.PrivilegedAction<Void>() {
 791             public Void run() {
 792                 try {
 793                     InetAddress a1 = InetAddress.getByName(h1);
 794                     InetAddress a2 = InetAddress.getByName(h2);
 795                     result[0] = a1.equals(a2);
 796                 } catch(UnknownHostException e) {
 797                 } catch(SecurityException e) {
 798                 }
 799                 return null;
 800             }
 801         });
 802 
 803         return result[0];
 804     }
 805 
 806     // overridden in HTTPS subclass
 807 
 808     public void connect() throws IOException {
 809         plainConnect();
 810     }
 811 
 812     private boolean checkReuseConnection () {
 813         if (connected) {
 814             return true;
 815         }
 816         if (reuseClient != null) {
 817             http = reuseClient;
 818             http.setReadTimeout(getReadTimeout());
 819             http.reuse = false;
 820             reuseClient = null;
 821             connected = true;
 822             return true;
 823         }
 824         return false;
 825     }
 826 
 827     protected void plainConnect()  throws IOException {
 828         if (connected) {
 829             return;
 830         }
 831         // try to see if request can be served from local cache
 832         if (cacheHandler != null && getUseCaches()) {
 833             try {
 834                 URI uri = ParseUtil.toURI(url);
 835                 if (uri != null) {
 836                     cachedResponse = cacheHandler.get(uri, getRequestMethod(), requests.getHeaders(EXCLUDE_HEADERS));
 837                     if ("https".equalsIgnoreCase(uri.getScheme())
 838                         && !(cachedResponse instanceof SecureCacheResponse)) {
 839                         cachedResponse = null;
 840                     }
 841                     if (HttpCapture.isLoggable("FINEST")) {
 842                         HttpCapture.finest("Cache Request for " + uri + " / " + getRequestMethod());
 843                         HttpCapture.finest("From cache: " + (cachedResponse != null ? cachedResponse.toString() : "null"));
 844                     }
 845                     if (cachedResponse != null) {
 846                         cachedHeaders = mapToMessageHeader(cachedResponse.getHeaders());
 847                         cachedInputStream = cachedResponse.getBody();
 848                     }
 849                 }
 850             } catch (IOException ioex) {
 851                 // ignore and commence normal connection
 852             }
 853             if (cachedHeaders != null && cachedInputStream != null) {
 854                 connected = true;
 855                 return;
 856             } else {
 857                 cachedResponse = null;
 858             }
 859         }
 860         try {
 861             /* Try to open connections using the following scheme,
 862              * return on the first one that's successful:
 863              * 1) if (instProxy != null)
 864              *        connect to instProxy; raise exception if failed
 865              * 2) else use system default ProxySelector
 866              * 3) is 2) fails, make direct connection
 867              */
 868 
 869             if (instProxy == null) { // no instance Proxy is set
 870                 /**
 871                  * Do we have to use a proxy?
 872                  */
 873                 ProxySelector sel =
 874                     java.security.AccessController.doPrivileged(
 875                              new java.security.PrivilegedAction<ProxySelector>() {
 876                                  public ProxySelector run() {
 877                                      return ProxySelector.getDefault();
 878                                  }
 879                              });
 880                 if (sel != null) {
 881                     URI uri = sun.net.www.ParseUtil.toURI(url);
 882                     if (HttpCapture.isLoggable("FINEST")) {
 883                         HttpCapture.finest("ProxySelector Request for " + uri);
 884                     }
 885                     Iterator<Proxy> it = sel.select(uri).iterator();
 886                     Proxy p;
 887                     while (it.hasNext()) {
 888                         p = it.next();
 889                         try {
 890                             if (!failedOnce) {
 891                                 http = getNewHttpClient(url, p, connectTimeout);
 892                                 http.setReadTimeout(readTimeout);
 893                             } else {
 894                                 // make sure to construct new connection if first
 895                                 // attempt failed
 896                                 http = getNewHttpClient(url, p, connectTimeout, false);
 897                                 http.setReadTimeout(readTimeout);
 898                             }
 899                             if (HttpCapture.isLoggable("FINEST")) {
 900                                 if (p != null) {
 901                                     HttpCapture.finest("Proxy used: " + p.toString());
 902                                 }
 903                             }
 904                             break;
 905                         } catch (IOException ioex) {
 906                             if (p != Proxy.NO_PROXY) {
 907                                 sel.connectFailed(uri, p.address(), ioex);
 908                                 if (!it.hasNext()) {
 909                                     // fallback to direct connection
 910                                     http = getNewHttpClient(url, null, connectTimeout, false);
 911                                     http.setReadTimeout(readTimeout);
 912                                     break;
 913                                 }
 914                             } else {
 915                                 throw ioex;
 916                             }
 917                             continue;
 918                         }
 919                     }
 920                 } else {
 921                     // No proxy selector, create http client with no proxy
 922                     if (!failedOnce) {
 923                         http = getNewHttpClient(url, null, connectTimeout);
 924                         http.setReadTimeout(readTimeout);
 925                     } else {
 926                         // make sure to construct new connection if first
 927                         // attempt failed
 928                         http = getNewHttpClient(url, null, connectTimeout, false);
 929                         http.setReadTimeout(readTimeout);
 930                     }
 931                 }
 932             } else {
 933                 if (!failedOnce) {
 934                     http = getNewHttpClient(url, instProxy, connectTimeout);
 935                     http.setReadTimeout(readTimeout);
 936                 } else {
 937                     // make sure to construct new connection if first
 938                     // attempt failed
 939                     http = getNewHttpClient(url, instProxy, connectTimeout, false);
 940                     http.setReadTimeout(readTimeout);
 941                 }
 942             }
 943 
 944             ps = (PrintStream)http.getOutputStream();
 945         } catch (IOException e) {
 946             throw e;
 947         }
 948         // constructor to HTTP client calls openserver
 949         connected = true;
 950     }
 951 
 952     // subclass HttpsClient will overwrite & return an instance of HttpsClient
 953     protected HttpClient getNewHttpClient(URL url, Proxy p, int connectTimeout)
 954         throws IOException {
 955         return HttpClient.New(url, p, connectTimeout);
 956     }
 957 
 958     // subclass HttpsClient will overwrite & return an instance of HttpsClient
 959     protected HttpClient getNewHttpClient(URL url, Proxy p,
 960                                           int connectTimeout, boolean useCache)
 961         throws IOException {
 962         return HttpClient.New(url, p, connectTimeout, useCache);
 963     }
 964 
 965     private void expect100Continue() throws IOException {
 966             // Expect: 100-Continue was set, so check the return code for
 967             // Acceptance
 968             int oldTimeout = http.getReadTimeout();
 969             boolean enforceTimeOut = false;
 970             boolean timedOut = false;
 971             if (oldTimeout <= 0) {
 972                 // 5s read timeout in case the server doesn't understand
 973                 // Expect: 100-Continue
 974                 http.setReadTimeout(5000);
 975                 enforceTimeOut = true;
 976             }
 977 
 978             try {
 979                 http.parseHTTP(responses, pi, this);
 980             } catch (SocketTimeoutException se) {
 981                 if (!enforceTimeOut) {
 982                     throw se;
 983                 }
 984                 timedOut = true;
 985                 http.setIgnoreContinue(true);
 986             }
 987             if (!timedOut) {
 988                 // Can't use getResponseCode() yet
 989                 String resp = responses.getValue(0);
 990                 // Parse the response which is of the form:
 991                 // HTTP/1.1 417 Expectation Failed
 992                 // HTTP/1.1 100 Continue
 993                 if (resp != null && resp.startsWith("HTTP/")) {
 994                     String[] sa = resp.split("\\s+");
 995                     responseCode = -1;
 996                     try {
 997                         // Response code is 2nd token on the line
 998                         if (sa.length > 1)
 999                             responseCode = Integer.parseInt(sa[1]);
1000                     } catch (NumberFormatException numberFormatException) {
1001                     }
1002                 }
1003                 if (responseCode != 100) {
1004                     throw new ProtocolException("Server rejected operation");
1005                 }
1006             }
1007             if (oldTimeout > 0) {
1008                 http.setReadTimeout(oldTimeout);
1009             }
1010             responseCode = -1;
1011             responses.reset();
1012             // Proceed
1013     }
1014 
1015     /*
1016      * Allowable input/output sequences:
1017      * [interpreted as POST/PUT]
1018      * - get output, [write output,] get input, [read input]
1019      * - get output, [write output]
1020      * [interpreted as GET]
1021      * - get input, [read input]
1022      * Disallowed:
1023      * - get input, [read input,] get output, [write output]
1024      */
1025 
1026     @Override
1027     public synchronized OutputStream getOutputStream() throws IOException {
1028 
1029         try {
1030             if (!doOutput) {
1031                 throw new ProtocolException("cannot write to a URLConnection"
1032                                + " if doOutput=false - call setDoOutput(true)");
1033             }
1034 
1035             if (method.equals("GET")) {
1036                 method = "POST"; // Backward compatibility
1037             }
1038             if (!"POST".equals(method) && !"PUT".equals(method) &&
1039                 "http".equals(url.getProtocol())) {
1040                 throw new ProtocolException("HTTP method " + method +
1041                                             " doesn't support output");
1042             }
1043 
1044             // if there's already an input stream open, throw an exception
1045             if (inputStream != null) {
1046                 throw new ProtocolException("Cannot write output after reading input.");
1047             }
1048 
1049             if (!checkReuseConnection())
1050                 connect();
1051 
1052             boolean expectContinue = false;
1053             String expects = requests.findValue("Expect");
1054             if ("100-Continue".equalsIgnoreCase(expects)) {
1055                 http.setIgnoreContinue(false);
1056                 expectContinue = true;
1057             }
1058 
1059             if (streaming() && strOutputStream == null) {
1060                 writeRequests();
1061             }
1062 
1063             if (expectContinue) {
1064                 expect100Continue();
1065             }
1066             ps = (PrintStream)http.getOutputStream();
1067             if (streaming()) {
1068                 if (strOutputStream == null) {
1069                     if (fixedContentLength != -1) {
1070                         strOutputStream = new StreamingOutputStream (ps, fixedContentLength);
1071                     } else if (chunkLength != -1) {
1072                         strOutputStream =
1073                             new StreamingOutputStream (new ChunkedOutputStream (ps, chunkLength), -1);
1074                     }
1075                 }
1076                 return strOutputStream;
1077             } else {
1078                 if (poster == null) {
1079                     poster = new PosterOutputStream();
1080                 }
1081                 return poster;
1082             }
1083         } catch (RuntimeException e) {
1084             disconnectInternal();
1085             throw e;
1086         } catch (ProtocolException e) {
1087             // Save the response code which may have been set while enforcing
1088             // the 100-continue. disconnectInternal() forces it to -1
1089             int i = responseCode;
1090             disconnectInternal();
1091             responseCode = i;
1092             throw e;
1093         } catch (IOException e) {
1094             disconnectInternal();
1095             throw e;
1096         }
1097     }
1098 
1099     private boolean streaming () {
1100         return (fixedContentLength != -1) || (chunkLength != -1);
1101     }
1102 
1103     /*
1104      * get applicable cookies based on the uri and request headers
1105      * add them to the existing request headers
1106      */
1107     private void setCookieHeader() throws IOException {
1108         if (cookieHandler != null) {
1109             // we only want to capture the user defined Cookies once, as
1110             // they cannot be changed by user code after we are connected,
1111             // only internally.
1112             synchronized (this) {
1113                 if (setUserCookies) {
1114                     int k = requests.getKey("Cookie");
1115                     if ( k != -1)
1116                         userCookies = requests.getValue(k);
1117                     k = requests.getKey("Cookie2");
1118                     if ( k != -1)
1119                         userCookies2 = requests.getValue(k);
1120                     setUserCookies = false;
1121                 }
1122             }
1123 
1124             // remove old Cookie header before setting new one.
1125             requests.remove("Cookie");
1126             requests.remove("Cookie2");
1127 
1128             URI uri = ParseUtil.toURI(url);
1129             if (uri != null) {
1130                 if (HttpCapture.isLoggable("FINEST")) {
1131                     HttpCapture.finest("CookieHandler request for " + uri);
1132                 }
1133                 Map<String, List<String>> cookies
1134                     = cookieHandler.get(
1135                         uri, requests.getHeaders(EXCLUDE_HEADERS));
1136                 if (!cookies.isEmpty()) {
1137                     if (HttpCapture.isLoggable("FINEST")) {
1138                         HttpCapture.finest("Cookies retrieved: " + cookies.toString());
1139                     }
1140                     for (Map.Entry<String, List<String>> entry :
1141                              cookies.entrySet()) {
1142                         String key = entry.getKey();
1143                         // ignore all entries that don't have "Cookie"
1144                         // or "Cookie2" as keys
1145                         if (!"Cookie".equalsIgnoreCase(key) &&
1146                             !"Cookie2".equalsIgnoreCase(key)) {
1147                             continue;
1148                         }
1149                         List<String> l = entry.getValue();
1150                         if (l != null && !l.isEmpty()) {
1151                             StringBuilder cookieValue = new StringBuilder();
1152                             for (String value : l) {
1153                                 cookieValue.append(value).append("; ");
1154                             }
1155                             // strip off the trailing '; '
1156                             try {
1157                                 requests.add(key, cookieValue.substring(0, cookieValue.length() - 2));
1158                             } catch (StringIndexOutOfBoundsException ignored) {
1159                                 // no-op
1160                             }
1161                         }
1162                     }
1163                 }
1164             }
1165             if (userCookies != null) {
1166                 int k;
1167                 if ((k = requests.getKey("Cookie")) != -1)
1168                     requests.set("Cookie", requests.getValue(k) + ";" + userCookies);
1169                 else
1170                     requests.set("Cookie", userCookies);
1171             }
1172             if (userCookies2 != null) {
1173                 int k;
1174                 if ((k = requests.getKey("Cookie2")) != -1)
1175                     requests.set("Cookie2", requests.getValue(k) + ";" + userCookies2);
1176                 else
1177                     requests.set("Cookie2", userCookies2);
1178             }
1179 
1180         } // end of getting cookies
1181     }
1182 
1183     @Override
1184     @SuppressWarnings("empty-statement")
1185     public synchronized InputStream getInputStream() throws IOException {
1186 
1187         if (!doInput) {
1188             throw new ProtocolException("Cannot read from URLConnection"
1189                    + " if doInput=false (call setDoInput(true))");
1190         }
1191 
1192         if (rememberedException != null) {
1193             if (rememberedException instanceof RuntimeException)
1194                 throw new RuntimeException(rememberedException);
1195             else {
1196                 throw getChainedException((IOException)rememberedException);
1197             }
1198         }
1199 
1200         if (inputStream != null) {
1201             return inputStream;
1202         }
1203 
1204         if (streaming() ) {
1205             if (strOutputStream == null) {
1206                 getOutputStream();
1207             }
1208             /* make sure stream is closed */
1209             strOutputStream.close ();
1210             if (!strOutputStream.writtenOK()) {
1211                 throw new IOException ("Incomplete output stream");
1212             }
1213         }
1214 
1215         int redirects = 0;
1216         int respCode = 0;
1217         int cl = -1;
1218         AuthenticationInfo serverAuthentication = null;
1219         AuthenticationInfo proxyAuthentication = null;
1220         AuthenticationHeader srvHdr = null;
1221 
1222         // If the user has set either of these headers then do not remove them
1223         isUserServerAuth = requests.getKey("Authorization") != -1;
1224         isUserProxyAuth = requests.getKey("Proxy-Authorization") != -1;
1225 
1226         try {
1227             do {
1228                 if (!checkReuseConnection())
1229                     connect();
1230 
1231                 if (cachedInputStream != null) {
1232                     return cachedInputStream;
1233                 }
1234 
1235                 // Check if URL should be metered
1236                 boolean meteredInput = ProgressMonitor.getDefault().shouldMeterInput(url, method);
1237 
1238                 if (meteredInput)   {
1239                     pi = new ProgressSource(url, method);
1240                     pi.beginTracking();
1241                 }
1242 
1243                 /* REMIND: This exists to fix the HttpsURLConnection subclass.
1244                  * Hotjava needs to run on JDK1.1FCS.  Do proper fix once a
1245                  * proper solution for SSL can be found.
1246                  */
1247                 ps = (PrintStream)http.getOutputStream();
1248 
1249                 if (!streaming()) {
1250                     writeRequests();
1251                 }
1252                 http.parseHTTP(responses, pi, this);
1253                 if (HttpCapture.isLoggable("FINE")) {
1254                     HttpCapture.fine(responses.toString());
1255                 }
1256                 inputStream = http.getInputStream();
1257 
1258                 respCode = getResponseCode();
1259                 if (respCode == HTTP_PROXY_AUTH) {
1260                     if (streaming()) {
1261                         disconnectInternal();
1262                         throw new HttpRetryException (
1263                             RETRY_MSG1, HTTP_PROXY_AUTH);
1264                     }
1265 
1266                     // changes: add a 3rd parameter to the constructor of
1267                     // AuthenticationHeader, so that NegotiateAuthentication.
1268                     // isSupported can be tested.
1269                     // The other 2 appearances of "new AuthenticationHeader" is
1270                     // altered in similar ways.
1271 
1272                     AuthenticationHeader authhdr = new AuthenticationHeader (
1273                         "Proxy-Authenticate", responses,
1274                             new HttpCallerInfo(url, http.getProxyHostUsed(),
1275                                 http.getProxyPortUsed())
1276                     );
1277 
1278                     if (!doingNTLMp2ndStage) {
1279                         proxyAuthentication =
1280                             resetProxyAuthentication(proxyAuthentication, authhdr);
1281                         if (proxyAuthentication != null) {
1282                             redirects++;
1283                             disconnectInternal();
1284                             continue;
1285                         }
1286                     } else {
1287                         /* in this case, only one header field will be present */
1288                         String raw = responses.findValue ("Proxy-Authenticate");
1289                         reset ();
1290                         if (!proxyAuthentication.setHeaders(this,
1291                                                         authhdr.headerParser(), raw)) {
1292                             disconnectInternal();
1293                             throw new IOException ("Authentication failure");
1294                         }
1295                         if (serverAuthentication != null && srvHdr != null &&
1296                                 !serverAuthentication.setHeaders(this,
1297                                                         srvHdr.headerParser(), raw)) {
1298                             disconnectInternal ();
1299                             throw new IOException ("Authentication failure");
1300                         }
1301                         authObj = null;
1302                         doingNTLMp2ndStage = false;
1303                         continue;
1304                     }
1305                 }
1306 
1307                 // cache proxy authentication info
1308                 if (proxyAuthentication != null) {
1309                     // cache auth info on success, domain header not relevant.
1310                     proxyAuthentication.addToCache();
1311                 }
1312 
1313                 if (respCode == HTTP_UNAUTHORIZED) {
1314                     if (streaming()) {
1315                         disconnectInternal();
1316                         throw new HttpRetryException (
1317                             RETRY_MSG2, HTTP_UNAUTHORIZED);
1318                     }
1319 
1320                     srvHdr = new AuthenticationHeader (
1321                             "WWW-Authenticate", responses,
1322                             new HttpCallerInfo(url)
1323                     );
1324 
1325                     String raw = srvHdr.raw();
1326                     if (!doingNTLM2ndStage) {
1327                         if ((serverAuthentication != null)&&
1328                             serverAuthentication.getAuthScheme() != NTLM) {
1329                             if (serverAuthentication.isAuthorizationStale (raw)) {
1330                                 /* we can retry with the current credentials */
1331                                 disconnectInternal();
1332                                 redirects++;
1333                                 requests.set(serverAuthentication.getHeaderName(),
1334                                             serverAuthentication.getHeaderValue(url, method));
1335                                 currentServerCredentials = serverAuthentication;
1336                                 setCookieHeader();
1337                                 continue;
1338                             } else {
1339                                 serverAuthentication.removeFromCache();
1340                             }
1341                         }
1342                         serverAuthentication = getServerAuthentication(srvHdr);
1343                         currentServerCredentials = serverAuthentication;
1344 
1345                         if (serverAuthentication != null) {
1346                             disconnectInternal();
1347                             redirects++; // don't let things loop ad nauseum
1348                             setCookieHeader();
1349                             continue;
1350                         }
1351                     } else {
1352                         reset ();
1353                         /* header not used for ntlm */
1354                         if (!serverAuthentication.setHeaders(this, null, raw)) {
1355                             disconnectInternal();
1356                             throw new IOException ("Authentication failure");
1357                         }
1358                         doingNTLM2ndStage = false;
1359                         authObj = null;
1360                         setCookieHeader();
1361                         continue;
1362                     }
1363                 }
1364                 // cache server authentication info
1365                 if (serverAuthentication != null) {
1366                     // cache auth info on success
1367                     if (!(serverAuthentication instanceof DigestAuthentication) ||
1368                         (domain == null)) {
1369                         if (serverAuthentication instanceof BasicAuthentication) {
1370                             // check if the path is shorter than the existing entry
1371                             String npath = AuthenticationInfo.reducePath (url.getPath());
1372                             String opath = serverAuthentication.path;
1373                             if (!opath.startsWith (npath) || npath.length() >= opath.length()) {
1374                                 /* npath is longer, there must be a common root */
1375                                 npath = BasicAuthentication.getRootPath (opath, npath);
1376                             }
1377                             // remove the entry and create a new one
1378                             BasicAuthentication a =
1379                                 (BasicAuthentication) serverAuthentication.clone();
1380                             serverAuthentication.removeFromCache();
1381                             a.path = npath;
1382                             serverAuthentication = a;
1383                         }
1384                         serverAuthentication.addToCache();
1385                     } else {
1386                         // what we cache is based on the domain list in the request
1387                         DigestAuthentication srv = (DigestAuthentication)
1388                             serverAuthentication;
1389                         StringTokenizer tok = new StringTokenizer (domain," ");
1390                         String realm = srv.realm;
1391                         PasswordAuthentication pw = srv.pw;
1392                         digestparams = srv.params;
1393                         while (tok.hasMoreTokens()) {
1394                             String path = tok.nextToken();
1395                             try {
1396                                 /* path could be an abs_path or a complete URI */
1397                                 URL u = new URL (url, path);
1398                                 DigestAuthentication d = new DigestAuthentication (
1399                                                    false, u, realm, "Digest", pw, digestparams);
1400                                 d.addToCache ();
1401                             } catch (Exception e) {}
1402                         }
1403                     }
1404                 }
1405 
1406                 // some flags should be reset to its initialized form so that
1407                 // even after a redirect the necessary checks can still be
1408                 // preformed.
1409 
1410                 //serverAuthentication = null;
1411                 doingNTLMp2ndStage = false;
1412                 doingNTLM2ndStage = false;
1413                 if (!isUserServerAuth)
1414                     requests.remove("Authorization");
1415                 if (!isUserProxyAuth)
1416                     requests.remove("Proxy-Authorization");
1417 
1418                 if (respCode == HTTP_OK) {
1419                     checkResponseCredentials (false);
1420                 } else {
1421                     needToCheck = false;
1422                 }
1423 
1424                 // a flag need to clean
1425                 needToCheck = true;
1426 
1427                 if (followRedirect()) {
1428                     /* if we should follow a redirect, then the followRedirects()
1429                      * method will disconnect() and re-connect us to the new
1430                      * location
1431                      */
1432                     redirects++;
1433 
1434                     // redirecting HTTP response may have set cookie, so
1435                     // need to re-generate request header
1436                     setCookieHeader();
1437 
1438                     continue;
1439                 }
1440 
1441                 try {
1442                     cl = Integer.parseInt(responses.findValue("content-length"));
1443                 } catch (Exception exc) { };
1444 
1445                 if (method.equals("HEAD") || cl == 0 ||
1446                     respCode == HTTP_NOT_MODIFIED ||
1447                     respCode == HTTP_NO_CONTENT) {
1448 
1449                     if (pi != null) {
1450                         pi.finishTracking();
1451                         pi = null;
1452                     }
1453                     http.finished();
1454                     http = null;
1455                     inputStream = new EmptyInputStream();
1456                     connected = false;
1457                 }
1458 
1459                 if (respCode == 200 || respCode == 203 || respCode == 206 ||
1460                     respCode == 300 || respCode == 301 || respCode == 410) {
1461                     if (cacheHandler != null && getUseCaches()) {
1462                         // give cache a chance to save response in cache
1463                         URI uri = ParseUtil.toURI(url);
1464                         if (uri != null) {
1465                             URLConnection uconn = this;
1466                             if ("https".equalsIgnoreCase(uri.getScheme())) {
1467                                 try {
1468                                 // use reflection to get to the public
1469                                 // HttpsURLConnection instance saved in
1470                                 // DelegateHttpsURLConnection
1471                                 uconn = (URLConnection)this.getClass().getField("httpsURLConnection").get(this);
1472                                 } catch (IllegalAccessException iae) {
1473                                     // ignored; use 'this'
1474                                 } catch (NoSuchFieldException nsfe) {
1475                                     // ignored; use 'this'
1476                                 }
1477                             }
1478                             CacheRequest cacheRequest =
1479                                 cacheHandler.put(uri, uconn);
1480                             if (cacheRequest != null && http != null) {
1481                                 http.setCacheRequest(cacheRequest);
1482                                 inputStream = new HttpInputStream(inputStream, cacheRequest);
1483                             }
1484                         }
1485                     }
1486                 }
1487 
1488                 if (!(inputStream instanceof HttpInputStream)) {
1489                     inputStream = new HttpInputStream(inputStream);
1490                 }
1491 
1492                 if (respCode >= 400) {
1493                     if (respCode == 404 || respCode == 410) {
1494                         throw new FileNotFoundException(url.toString());
1495                     } else {
1496                         throw new java.io.IOException("Server returned HTTP" +
1497                               " response code: " + respCode + " for URL: " +
1498                               url.toString());
1499                     }
1500                 }
1501                 poster = null;
1502                 strOutputStream = null;
1503                 return inputStream;
1504             } while (redirects < maxRedirects);
1505 
1506             throw new ProtocolException("Server redirected too many " +
1507                                         " times ("+ redirects + ")");
1508         } catch (RuntimeException e) {
1509             disconnectInternal();
1510             rememberedException = e;
1511             throw e;
1512         } catch (IOException e) {
1513             rememberedException = e;
1514 
1515             // buffer the error stream if bytes < 4k
1516             // and it can be buffered within 1 second
1517             String te = responses.findValue("Transfer-Encoding");
1518             if (http != null && http.isKeepingAlive() && enableESBuffer &&
1519                 (cl > 0 || (te != null && te.equalsIgnoreCase("chunked")))) {
1520                 errorStream = ErrorStream.getErrorStream(inputStream, cl, http);
1521             }
1522             throw e;
1523         } finally {
1524             if (respCode == HTTP_PROXY_AUTH && proxyAuthentication != null) {
1525                 proxyAuthentication.endAuthRequest();
1526             }
1527             else if (respCode == HTTP_UNAUTHORIZED && serverAuthentication != null) {
1528                 serverAuthentication.endAuthRequest();
1529             }
1530         }
1531     }
1532 
1533     /*
1534      * Creates a chained exception that has the same type as
1535      * original exception and with the same message. Right now,
1536      * there is no convenient APIs for doing so.
1537      */
1538     private IOException getChainedException(final IOException rememberedException) {
1539         try {
1540             final Object[] args = { rememberedException.getMessage() };
1541             IOException chainedException =
1542                 java.security.AccessController.doPrivileged(
1543                     new java.security.PrivilegedExceptionAction<IOException>() {
1544                         public IOException run() throws Exception {
1545                             return (IOException)
1546                                 rememberedException.getClass()
1547                                 .getConstructor(new Class[] { String.class })
1548                                 .newInstance(args);
1549                         }
1550                     });
1551             chainedException.initCause(rememberedException);
1552             return chainedException;
1553         } catch (Exception ignored) {
1554             return rememberedException;
1555         }
1556     }
1557 
1558     @Override
1559     public InputStream getErrorStream() {
1560         if (connected && responseCode >= 400) {
1561             // Client Error 4xx and Server Error 5xx
1562             if (errorStream != null) {
1563                 return errorStream;
1564             } else if (inputStream != null) {
1565                 return inputStream;
1566             }
1567         }
1568         return null;
1569     }
1570 
1571     /**
1572      * set or reset proxy authentication info in request headers
1573      * after receiving a 407 error. In the case of NTLM however,
1574      * receiving a 407 is normal and we just skip the stale check
1575      * because ntlm does not support this feature.
1576      */
1577     private AuthenticationInfo
1578         resetProxyAuthentication(AuthenticationInfo proxyAuthentication, AuthenticationHeader auth) {
1579         if ((proxyAuthentication != null )&&
1580              proxyAuthentication.getAuthScheme() != NTLM) {
1581             String raw = auth.raw();
1582             if (proxyAuthentication.isAuthorizationStale (raw)) {
1583                 /* we can retry with the current credentials */
1584                 requests.set (proxyAuthentication.getHeaderName(),
1585                               proxyAuthentication.getHeaderValue(
1586                                                      url, method));
1587                 currentProxyCredentials = proxyAuthentication;
1588                 return  proxyAuthentication;
1589             } else {
1590                 proxyAuthentication.removeFromCache();
1591             }
1592         }
1593         proxyAuthentication = getHttpProxyAuthentication(auth);
1594         currentProxyCredentials = proxyAuthentication;
1595         return  proxyAuthentication;
1596     }
1597 
1598     /**
1599      * establish a tunnel through proxy server
1600      */
1601     public synchronized void doTunneling() throws IOException {
1602         int retryTunnel = 0;
1603         String statusLine = "";
1604         int respCode = 0;
1605         AuthenticationInfo proxyAuthentication = null;
1606         String proxyHost = null;
1607         int proxyPort = -1;
1608 
1609         // save current requests so that they can be restored after tunnel is setup.
1610         MessageHeader savedRequests = requests;
1611         requests = new MessageHeader();
1612 
1613         try {
1614             do {
1615                 if (!checkReuseConnection()) {
1616                     proxiedConnect(url, proxyHost, proxyPort, false);
1617                 }
1618                 // send the "CONNECT" request to establish a tunnel
1619                 // through proxy server
1620                 sendCONNECTRequest();
1621                 responses.reset();
1622 
1623                 // There is no need to track progress in HTTP Tunneling,
1624                 // so ProgressSource is null.
1625                 http.parseHTTP(responses, null, this);
1626 
1627                 /* Log the response to the CONNECT */
1628                 if (HttpCapture.isLoggable("FINE")) {
1629                     HttpCapture.fine(responses.toString());
1630                 }
1631 
1632                 statusLine = responses.getValue(0);
1633                 StringTokenizer st = new StringTokenizer(statusLine);
1634                 st.nextToken();
1635                 respCode = Integer.parseInt(st.nextToken().trim());
1636                 if (respCode == HTTP_PROXY_AUTH) {
1637                     AuthenticationHeader authhdr = new AuthenticationHeader (
1638                         "Proxy-Authenticate", responses,
1639                             new HttpCallerInfo(url, http.getProxyHostUsed(),
1640                                 http.getProxyPortUsed())
1641                     );
1642                     if (!doingNTLMp2ndStage) {
1643                         proxyAuthentication =
1644                             resetProxyAuthentication(proxyAuthentication, authhdr);
1645                         if (proxyAuthentication != null) {
1646                             proxyHost = http.getProxyHostUsed();
1647                             proxyPort = http.getProxyPortUsed();
1648                             disconnectInternal();
1649                             retryTunnel++;
1650                             continue;
1651                         }
1652                     } else {
1653                         String raw = responses.findValue ("Proxy-Authenticate");
1654                         reset ();
1655                         if (!proxyAuthentication.setHeaders(this,
1656                                                 authhdr.headerParser(), raw)) {
1657                             disconnectInternal();
1658                             throw new IOException ("Authentication failure");
1659                         }
1660                         authObj = null;
1661                         doingNTLMp2ndStage = false;
1662                         continue;
1663                     }
1664                 }
1665                 // cache proxy authentication info
1666                 if (proxyAuthentication != null) {
1667                     // cache auth info on success, domain header not relevant.
1668                     proxyAuthentication.addToCache();
1669                 }
1670 
1671                 if (respCode == HTTP_OK) {
1672                     break;
1673                 }
1674                 // we don't know how to deal with other response code
1675                 // so disconnect and report error
1676                 disconnectInternal();
1677                 break;
1678             } while (retryTunnel < maxRedirects);
1679 
1680             if (retryTunnel >= maxRedirects || (respCode != HTTP_OK)) {
1681                 throw new IOException("Unable to tunnel through proxy."+
1682                                       " Proxy returns \"" +
1683                                       statusLine + "\"");
1684             }
1685         } finally  {
1686             if (respCode == HTTP_PROXY_AUTH && proxyAuthentication != null) {
1687                 proxyAuthentication.endAuthRequest();
1688             }
1689         }
1690 
1691         // restore original request headers
1692         requests = savedRequests;
1693 
1694         // reset responses
1695         responses.reset();
1696     }
1697 
1698     /**
1699      * send a CONNECT request for establishing a tunnel to proxy server
1700      */
1701     private void sendCONNECTRequest() throws IOException {
1702         int port = url.getPort();
1703 
1704         // setRequests == true indicates the std. request headers
1705         // have been set in (previous) requests.
1706         // so the first one must be the http method (GET, etc.).
1707         // we need to set it to CONNECT soon, remove this one first.
1708         // otherwise, there may have 2 http methods in headers
1709         if (setRequests) requests.set(0, null, null);
1710 
1711         requests.prepend("CONNECT " + url.getHost() + ":"
1712                          + (port != -1 ? port : url.getDefaultPort())
1713                          + " " + httpVersion, null);
1714         requests.setIfNotSet("User-Agent", userAgent);
1715 
1716         String host = url.getHost();
1717         if (port != -1 && port != url.getDefaultPort()) {
1718             host += ":" + String.valueOf(port);
1719         }
1720         requests.setIfNotSet("Host", host);
1721 
1722         // Not really necessary for a tunnel, but can't hurt
1723         requests.setIfNotSet("Accept", acceptString);
1724 
1725         setPreemptiveProxyAuthentication(requests);
1726 
1727          /* Log the CONNECT request */
1728         if (HttpCapture.isLoggable("FINE")) {
1729             HttpCapture.fine(requests.toString());
1730         }
1731 
1732         http.writeRequests(requests, null);
1733         // remove CONNECT header
1734         requests.set(0, null, null);
1735     }
1736 
1737     /**
1738      * Sets pre-emptive proxy authentication in header
1739      */
1740     private void setPreemptiveProxyAuthentication(MessageHeader requests) {
1741         AuthenticationInfo pauth
1742             = AuthenticationInfo.getProxyAuth(http.getProxyHostUsed(),
1743                                               http.getProxyPortUsed());
1744         if (pauth != null && pauth.supportsPreemptiveAuthorization()) {
1745             // Sets "Proxy-authorization"
1746             requests.set(pauth.getHeaderName(),
1747                                  pauth.getHeaderValue(url,method));
1748             currentProxyCredentials = pauth;
1749         }
1750     }
1751 
1752     /**
1753      * Gets the authentication for an HTTP proxy, and applies it to
1754      * the connection.
1755      */
1756     private AuthenticationInfo getHttpProxyAuthentication (AuthenticationHeader authhdr) {
1757         /* get authorization from authenticator */
1758         AuthenticationInfo ret = null;
1759         String raw = authhdr.raw();
1760         String host = http.getProxyHostUsed();
1761         int port = http.getProxyPortUsed();
1762         if (host != null && authhdr.isPresent()) {
1763             HeaderParser p = authhdr.headerParser();
1764             String realm = p.findValue("realm");
1765             String scheme = authhdr.scheme();
1766             AuthScheme authScheme = UNKNOWN;
1767             if ("basic".equalsIgnoreCase(scheme)) {
1768                 authScheme = BASIC;
1769             } else if ("digest".equalsIgnoreCase(scheme)) {
1770                 authScheme = DIGEST;
1771             } else if ("ntlm".equalsIgnoreCase(scheme)) {
1772                 authScheme = NTLM;
1773                 doingNTLMp2ndStage = true;
1774             } else if ("Kerberos".equalsIgnoreCase(scheme)) {
1775                 authScheme = KERBEROS;
1776                 doingNTLMp2ndStage = true;
1777             } else if ("Negotiate".equalsIgnoreCase(scheme)) {
1778                 authScheme = NEGOTIATE;
1779                 doingNTLMp2ndStage = true;
1780             }
1781 
1782             if (realm == null)
1783                 realm = "";
1784             ret = AuthenticationInfo.getProxyAuth(host,
1785                                                   port,
1786                                                   realm,
1787                                                   authScheme);
1788             if (ret == null) {
1789                 switch (authScheme) {
1790                 case BASIC:
1791                     InetAddress addr = null;
1792                     try {
1793                         final String finalHost = host;
1794                         addr =
1795                             java.security.AccessController.doPrivileged
1796                                 (new java.security.PrivilegedExceptionAction<InetAddress>() {
1797                                 public InetAddress run()
1798                                     throws java.net.UnknownHostException {
1799                                     return InetAddress.getByName(finalHost);
1800                                 }
1801                             });
1802                     } catch (java.security.PrivilegedActionException ignored) {
1803                         // User will have an unknown host.
1804                     }
1805                     PasswordAuthentication a =
1806                         privilegedRequestPasswordAuthentication(
1807                                     host, addr, port, "http",
1808                                     realm, scheme, url, RequestorType.PROXY);
1809                     if (a != null) {
1810                         ret = new BasicAuthentication(true, host, port, realm, a);
1811                     }
1812                     break;
1813                 case DIGEST:
1814                     a = privilegedRequestPasswordAuthentication(
1815                                     host, null, port, url.getProtocol(),
1816                                     realm, scheme, url, RequestorType.PROXY);
1817                     if (a != null) {
1818                         DigestAuthentication.Parameters params =
1819                             new DigestAuthentication.Parameters();
1820                         ret = new DigestAuthentication(true, host, port, realm,
1821                                                             scheme, a, params);
1822                     }
1823                     break;
1824                 case NTLM:
1825                     if (NTLMAuthenticationProxy.proxy.supported) {
1826                         /* tryTransparentNTLMProxy will always be true the first
1827                          * time around, but verify that the platform supports it
1828                          * otherwise don't try. */
1829                         if (tryTransparentNTLMProxy) {
1830                             tryTransparentNTLMProxy =
1831                                     NTLMAuthenticationProxy.proxy.supportsTransparentAuth;
1832                             /* If the platform supports transparent authentication
1833                              * then normally it's ok to do transparent auth to a proxy
1834                                          * because we generally trust proxies (chosen by the user)
1835                                          * But not in the case of 305 response where the server
1836                              * chose it. */
1837                             if (tryTransparentNTLMProxy && useProxyResponseCode) {
1838                                 tryTransparentNTLMProxy = false;
1839                             }
1840                         }
1841                         a = null;
1842                         if (tryTransparentNTLMProxy) {
1843                             HttpCapture.finest("Trying Transparent NTLM authentication");
1844                         } else {
1845                             a = privilegedRequestPasswordAuthentication(
1846                                                 host, null, port, url.getProtocol(),
1847                                                 "", scheme, url, RequestorType.PROXY);
1848                         }
1849                         /* If we are not trying transparent authentication then
1850                          * we need to have a PasswordAuthentication instance. For
1851                          * transparent authentication (Windows only) the username
1852                          * and password will be picked up from the current logged
1853                          * on users credentials.
1854                         */
1855                         if (tryTransparentNTLMProxy ||
1856                               (!tryTransparentNTLMProxy && a != null)) {
1857                             ret = NTLMAuthenticationProxy.proxy.create(true, host, port, a);
1858                         }
1859 
1860                         /* set to false so that we do not try again */
1861                         tryTransparentNTLMProxy = false;
1862                     }
1863                     break;
1864                 case NEGOTIATE:
1865                     ret = new NegotiateAuthentication(new HttpCallerInfo(authhdr.getHttpCallerInfo(), "Negotiate"));
1866                     break;
1867                 case KERBEROS:
1868                     ret = new NegotiateAuthentication(new HttpCallerInfo(authhdr.getHttpCallerInfo(), "Kerberos"));
1869                     break;
1870                 case UNKNOWN:
1871                     HttpCapture.finest("Unknown/Unsupported authentication scheme: " + scheme);
1872                 default:
1873                     throw new AssertionError("should not reach here");
1874                 }
1875             }
1876             // For backwards compatibility, we also try defaultAuth
1877             // REMIND:  Get rid of this for JDK2.0.
1878 
1879             if (ret == null && defaultAuth != null
1880                 && defaultAuth.schemeSupported(scheme)) {
1881                 try {
1882                     URL u = new URL("http", host, port, "/");
1883                     String a = defaultAuth.authString(u, scheme, realm);
1884                     if (a != null) {
1885                         ret = new BasicAuthentication (true, host, port, realm, a);
1886                         // not in cache by default - cache on success
1887                     }
1888                 } catch (java.net.MalformedURLException ignored) {
1889                 }
1890             }
1891             if (ret != null) {
1892                 if (!ret.setHeaders(this, p, raw)) {
1893                     ret = null;
1894                 }
1895             }
1896         }
1897         if (HttpCapture.isLoggable("FINER")) {
1898             HttpCapture.finer("Proxy Authentication for " + authhdr.toString() +" returned " + (ret != null ? ret.toString() : "null"));
1899         }
1900         return ret;
1901     }
1902 
1903     /**
1904      * Gets the authentication for an HTTP server, and applies it to
1905      * the connection.
1906      * @param authHdr the AuthenticationHeader which tells what auth scheme is
1907      * prefered.
1908      */
1909     private AuthenticationInfo getServerAuthentication (AuthenticationHeader authhdr) {
1910         /* get authorization from authenticator */
1911         AuthenticationInfo ret = null;
1912         String raw = authhdr.raw();
1913         /* When we get an NTLM auth from cache, don't set any special headers */
1914         if (authhdr.isPresent()) {
1915             HeaderParser p = authhdr.headerParser();
1916             String realm = p.findValue("realm");
1917             String scheme = authhdr.scheme();
1918             AuthScheme authScheme = UNKNOWN;
1919             if ("basic".equalsIgnoreCase(scheme)) {
1920                 authScheme = BASIC;
1921             } else if ("digest".equalsIgnoreCase(scheme)) {
1922                 authScheme = DIGEST;
1923             } else if ("ntlm".equalsIgnoreCase(scheme)) {
1924                 authScheme = NTLM;
1925                 doingNTLM2ndStage = true;
1926             } else if ("Kerberos".equalsIgnoreCase(scheme)) {
1927                 authScheme = KERBEROS;
1928                 doingNTLM2ndStage = true;
1929             } else if ("Negotiate".equalsIgnoreCase(scheme)) {
1930                 authScheme = NEGOTIATE;
1931                 doingNTLM2ndStage = true;
1932             }
1933 
1934             domain = p.findValue ("domain");
1935             if (realm == null)
1936                 realm = "";
1937             ret = AuthenticationInfo.getServerAuth(url, realm, authScheme);
1938             InetAddress addr = null;
1939             if (ret == null) {
1940                 try {
1941                     addr = InetAddress.getByName(url.getHost());
1942                 } catch (java.net.UnknownHostException ignored) {
1943                     // User will have addr = null
1944                 }
1945             }
1946             // replacing -1 with default port for a protocol
1947             int port = url.getPort();
1948             if (port == -1) {
1949                 port = url.getDefaultPort();
1950             }
1951             if (ret == null) {
1952                 switch(authScheme) {
1953                 case KERBEROS:
1954                     ret = new NegotiateAuthentication(new HttpCallerInfo(authhdr.getHttpCallerInfo(), "Kerberos"));
1955                     break;
1956                 case NEGOTIATE:
1957                     ret = new NegotiateAuthentication(new HttpCallerInfo(authhdr.getHttpCallerInfo(), "Negotiate"));
1958                     break;
1959                 case BASIC:
1960                     PasswordAuthentication a =
1961                         privilegedRequestPasswordAuthentication(
1962                             url.getHost(), addr, port, url.getProtocol(),
1963                             realm, scheme, url, RequestorType.SERVER);
1964                     if (a != null) {
1965                         ret = new BasicAuthentication(false, url, realm, a);
1966                     }
1967                     break;
1968                 case DIGEST:
1969                     a = privilegedRequestPasswordAuthentication(
1970                             url.getHost(), addr, port, url.getProtocol(),
1971                             realm, scheme, url, RequestorType.SERVER);
1972                     if (a != null) {
1973                         digestparams = new DigestAuthentication.Parameters();
1974                         ret = new DigestAuthentication(false, url, realm, scheme, a, digestparams);
1975                     }
1976                     break;
1977                 case NTLM:
1978                     if (NTLMAuthenticationProxy.proxy.supported) {
1979                         URL url1;
1980                         try {
1981                             url1 = new URL (url, "/"); /* truncate the path */
1982                         } catch (Exception e) {
1983                             url1 = url;
1984                         }
1985 
1986                         /* tryTransparentNTLMServer will always be true the first
1987                          * time around, but verify that the platform supports it
1988                          * otherwise don't try. */
1989                         if (tryTransparentNTLMServer) {
1990                             tryTransparentNTLMServer =
1991                                     NTLMAuthenticationProxy.proxy.supportsTransparentAuth;
1992                         }
1993                         a = null;
1994                         if (tryTransparentNTLMServer) {
1995                             HttpCapture.finest("Trying Transparent NTLM authentication");
1996                         } else {
1997                             a = privilegedRequestPasswordAuthentication(
1998                                 url.getHost(), addr, port, url.getProtocol(),
1999                                 "", scheme, url, RequestorType.SERVER);
2000                         }
2001 
2002                         /* If we are not trying transparent authentication then
2003                          * we need to have a PasswordAuthentication instance. For
2004                          * transparent authentication (Windows only) the username
2005                          * and password will be picked up from the current logged
2006                          * on users credentials.
2007                          */
2008                         if (tryTransparentNTLMServer ||
2009                               (!tryTransparentNTLMServer && a != null)) {
2010                             ret = NTLMAuthenticationProxy.proxy.create(false, url1, a);
2011                         }
2012 
2013                         /* set to false so that we do not try again */
2014                         tryTransparentNTLMServer = false;
2015                     }
2016                     break;
2017                 case UNKNOWN:
2018                     HttpCapture.finest("Unknown/Unsupported authentication scheme: " + scheme);
2019                 default:
2020                     throw new AssertionError("should not reach here");
2021                 }
2022             }
2023 
2024             // For backwards compatibility, we also try defaultAuth
2025             // REMIND:  Get rid of this for JDK2.0.
2026 
2027             if (ret == null && defaultAuth != null
2028                 && defaultAuth.schemeSupported(scheme)) {
2029                 String a = defaultAuth.authString(url, scheme, realm);
2030                 if (a != null) {
2031                     ret = new BasicAuthentication (false, url, realm, a);
2032                     // not in cache by default - cache on success
2033                 }
2034             }
2035 
2036             if (ret != null ) {
2037                 if (!ret.setHeaders(this, p, raw)) {
2038                     ret = null;
2039                 }
2040             }
2041         }
2042         if (HttpCapture.isLoggable("FINER")) {
2043             HttpCapture.finer("Server Authentication for " + authhdr.toString() +" returned " + (ret != null ? ret.toString() : "null"));
2044         }
2045         return ret;
2046     }
2047 
2048     /* inclose will be true if called from close(), in which case we
2049      * force the call to check because this is the last chance to do so.
2050      * If not in close(), then the authentication info could arrive in a trailer
2051      * field, which we have not read yet.
2052      */
2053     private void checkResponseCredentials (boolean inClose) throws IOException {
2054         try {
2055             if (!needToCheck)
2056                 return;
2057             if (validateProxy && currentProxyCredentials != null) {
2058                 String raw = responses.findValue ("Proxy-Authentication-Info");
2059                 if (inClose || (raw != null)) {
2060                     currentProxyCredentials.checkResponse (raw, method, url);
2061                     currentProxyCredentials = null;
2062                 }
2063             }
2064             if (validateServer && currentServerCredentials != null) {
2065                 String raw = responses.findValue ("Authentication-Info");
2066                 if (inClose || (raw != null)) {
2067                     currentServerCredentials.checkResponse (raw, method, url);
2068                     currentServerCredentials = null;
2069                 }
2070             }
2071             if ((currentServerCredentials==null) && (currentProxyCredentials == null)) {
2072                 needToCheck = false;
2073             }
2074         } catch (IOException e) {
2075             disconnectInternal();
2076             connected = false;
2077             throw e;
2078         }
2079     }
2080 
2081     /* Tells us whether to follow a redirect.  If so, it
2082      * closes the connection (break any keep-alive) and
2083      * resets the url, re-connects, and resets the request
2084      * property.
2085      */
2086     private boolean followRedirect() throws IOException {
2087         if (!getInstanceFollowRedirects()) {
2088             return false;
2089         }
2090 
2091         int stat = getResponseCode();
2092         if (stat < 300 || stat > 307 || stat == 306
2093                                 || stat == HTTP_NOT_MODIFIED) {
2094             return false;
2095         }
2096         String loc = getHeaderField("Location");
2097         if (loc == null) {
2098             /* this should be present - if not, we have no choice
2099              * but to go forward w/ the response we got
2100              */
2101             return false;
2102         }
2103         URL locUrl;
2104         try {
2105             locUrl = new URL(loc);
2106             if (!url.getProtocol().equalsIgnoreCase(locUrl.getProtocol())) {
2107                 return false;
2108             }
2109 
2110         } catch (MalformedURLException mue) {
2111           // treat loc as a relative URI to conform to popular browsers
2112           locUrl = new URL(url, loc);
2113         }
2114         disconnectInternal();
2115         if (streaming()) {
2116             throw new HttpRetryException (RETRY_MSG3, stat, loc);
2117         }
2118         if (HttpCapture.isLoggable("FINE")) {
2119             HttpCapture.fine("Redirected from " + url + " to " + locUrl);
2120         }
2121 
2122         // clear out old response headers!!!!
2123         responses = new MessageHeader();
2124         if (stat == HTTP_USE_PROXY) {
2125             /* This means we must re-request the resource through the
2126              * proxy denoted in the "Location:" field of the response.
2127              * Judging by the spec, the string in the Location header
2128              * _should_ denote a URL - let's hope for "http://my.proxy.org"
2129              * Make a new HttpClient to the proxy, using HttpClient's
2130              * Instance-specific proxy fields, but note we're still fetching
2131              * the same URL.
2132              */
2133             String proxyHost = locUrl.getHost();
2134             int proxyPort = locUrl.getPort();
2135 
2136             SecurityManager security = System.getSecurityManager();
2137             if (security != null) {
2138                 security.checkConnect(proxyHost, proxyPort);
2139             }
2140 
2141             setProxiedClient (url, proxyHost, proxyPort);
2142             requests.set(0, method + " " + http.getURLFile()+" "  +
2143                              httpVersion, null);
2144             connected = true;
2145             // need to remember this in case NTLM proxy authentication gets
2146             // used. We can't use transparent authentication when user
2147             // doesn't know about proxy.
2148             useProxyResponseCode = true;
2149         } else {
2150             // maintain previous headers, just change the name
2151             // of the file we're getting
2152             url = locUrl;
2153             if (method.equals("POST") && !Boolean.getBoolean("http.strictPostRedirect") && (stat!=307)) {
2154                 /* The HTTP/1.1 spec says that a redirect from a POST
2155                  * *should not* be immediately turned into a GET, and
2156                  * that some HTTP/1.0 clients incorrectly did this.
2157                  * Correct behavior redirects a POST to another POST.
2158                  * Unfortunately, since most browsers have this incorrect
2159                  * behavior, the web works this way now.  Typical usage
2160                  * seems to be:
2161                  *   POST a login code or passwd to a web page.
2162                  *   after validation, the server redirects to another
2163                  *     (welcome) page
2164                  *   The second request is (erroneously) expected to be GET
2165                  *
2166                  * We will do the incorrect thing (POST-->GET) by default.
2167                  * We will provide the capability to do the "right" thing
2168                  * (POST-->POST) by a system property, "http.strictPostRedirect=true"
2169                  */
2170 
2171                 requests = new MessageHeader();
2172                 setRequests = false;
2173                 setRequestMethod("GET");
2174                 poster = null;
2175                 if (!checkReuseConnection())
2176                     connect();
2177             } else {
2178                 if (!checkReuseConnection())
2179                     connect();
2180                 /* Even after a connect() call, http variable still can be
2181                  * null, if a ResponseCache has been installed and it returns
2182                  * a non-null CacheResponse instance. So check nullity before using it.
2183                  *
2184                  * And further, if http is null, there's no need to do anything
2185                  * about request headers because successive http session will use
2186                  * cachedInputStream/cachedHeaders anyway, which is returned by
2187                  * CacheResponse.
2188                  */
2189                 if (http != null) {
2190                     requests.set(0, method + " " + http.getURLFile()+" "  +
2191                                  httpVersion, null);
2192                     int port = url.getPort();
2193                     String host = url.getHost();
2194                     if (port != -1 && port != url.getDefaultPort()) {
2195                         host += ":" + String.valueOf(port);
2196                     }
2197                     requests.set("Host", host);
2198                 }
2199             }
2200         }
2201         return true;
2202     }
2203 
2204     /* dummy byte buffer for reading off socket prior to closing */
2205     byte[] cdata = new byte [128];
2206 
2207     /**
2208      * Reset (without disconnecting the TCP conn) in order to do another transaction with this instance
2209      */
2210     private void reset() throws IOException {
2211         http.reuse = true;
2212         /* must save before calling close */
2213         reuseClient = http;
2214         InputStream is = http.getInputStream();
2215         if (!method.equals("HEAD")) {
2216             try {
2217                 /* we want to read the rest of the response without using the
2218                  * hurry mechanism, because that would close the connection
2219                  * if everything is not available immediately
2220                  */
2221                 if ((is instanceof ChunkedInputStream) ||
2222                     (is instanceof MeteredStream)) {
2223                     /* reading until eof will not block */
2224                     while (is.read (cdata) > 0) {}
2225                 } else {
2226                     /* raw stream, which will block on read, so only read
2227                      * the expected number of bytes, probably 0
2228                      */
2229                     long cl = 0;
2230                     int n = 0;
2231                     String cls = responses.findValue ("Content-Length");
2232                     if (cls != null) {
2233                         try {
2234                             cl = Long.parseLong (cls);
2235                         } catch (NumberFormatException e) {
2236                             cl = 0;
2237                         }
2238                     }
2239                     for (long i=0; i<cl; ) {
2240                         if ((n = is.read (cdata)) == -1) {
2241                             break;
2242                         } else {
2243                             i+= n;
2244                         }
2245                     }
2246                 }
2247             } catch (IOException e) {
2248                 http.reuse = false;
2249                 reuseClient = null;
2250                 disconnectInternal();
2251                 return;
2252             }
2253             try {
2254                 if (is instanceof MeteredStream) {
2255                     is.close();
2256                 }
2257             } catch (IOException e) { }
2258         }
2259         responseCode = -1;
2260         responses = new MessageHeader();
2261         connected = false;
2262     }
2263 
2264     /**
2265      * Disconnect from the server (for internal use)
2266      */
2267     private void disconnectInternal() {
2268         responseCode = -1;
2269         if (pi != null) {
2270             pi.finishTracking();
2271             pi = null;
2272         }
2273         if (http != null) {
2274             http.closeServer();
2275             http = null;
2276             connected = false;
2277         }
2278     }
2279 
2280     /**
2281      * Disconnect from the server (public API)
2282      */
2283     public void disconnect() {
2284 
2285         responseCode = -1;
2286         if (pi != null) {
2287             pi.finishTracking();
2288             pi = null;
2289         }
2290 
2291         if (http != null) {
2292             /*
2293              * If we have an input stream this means we received a response
2294              * from the server. That stream may have been read to EOF and
2295              * dependening on the stream type may already be closed or the
2296              * the http client may be returned to the keep-alive cache.
2297              * If the http client has been returned to the keep-alive cache
2298              * it may be closed (idle timeout) or may be allocated to
2299              * another request.
2300              *
2301              * In other to avoid timing issues we close the input stream
2302              * which will either close the underlying connection or return
2303              * the client to the cache. If there's a possibility that the
2304              * client has been returned to the cache (ie: stream is a keep
2305              * alive stream or a chunked input stream) then we remove an
2306              * idle connection to the server. Note that this approach
2307              * can be considered an approximation in that we may close a
2308              * different idle connection to that used by the request.
2309              * Additionally it's possible that we close two connections
2310              * - the first becuase it wasn't an EOF (and couldn't be
2311              * hurried) - the second, another idle connection to the
2312              * same server. The is okay because "disconnect" is an
2313              * indication that the application doesn't intend to access
2314              * this http server for a while.
2315              */
2316 
2317             if (inputStream != null) {
2318                 HttpClient hc = http;
2319 
2320                 // un-synchronized
2321                 boolean ka = hc.isKeepingAlive();
2322 
2323                 try {
2324                     inputStream.close();
2325                 } catch (IOException ioe) { }
2326 
2327                 // if the connection is persistent it may have been closed
2328                 // or returned to the keep-alive cache. If it's been returned
2329                 // to the keep-alive cache then we would like to close it
2330                 // but it may have been allocated
2331 
2332                 if (ka) {
2333                     hc.closeIdleConnection();
2334                 }
2335 
2336 
2337             } else {
2338                 // We are deliberatly being disconnected so HttpClient
2339                 // should not try to resend the request no matter what stage
2340                 // of the connection we are in.
2341                 http.setDoNotRetry(true);
2342 
2343                 http.closeServer();
2344             }
2345 
2346             //      poster = null;
2347             http = null;
2348             connected = false;
2349         }
2350         cachedInputStream = null;
2351         if (cachedHeaders != null) {
2352             cachedHeaders.reset();
2353         }
2354     }
2355 
2356     public boolean usingProxy() {
2357         if (http != null) {
2358             return (http.getProxyHostUsed() != null);
2359         }
2360         return false;
2361     }
2362 
2363     /**
2364      * Gets a header field by name. Returns null if not known.
2365      * @param name the name of the header field
2366      */
2367     @Override
2368     public String getHeaderField(String name) {
2369         try {
2370             getInputStream();
2371         } catch (IOException e) {}
2372 
2373         if (cachedHeaders != null) {
2374             return cachedHeaders.findValue(name);
2375         }
2376 
2377         return responses.findValue(name);
2378     }
2379 
2380     /**
2381      * Returns an unmodifiable Map of the header fields.
2382      * The Map keys are Strings that represent the
2383      * response-header field names. Each Map value is an
2384      * unmodifiable List of Strings that represents
2385      * the corresponding field values.
2386      *
2387      * @return a Map of header fields
2388      * @since 1.4
2389      */
2390     @Override
2391     public Map<String, List<String>> getHeaderFields() {
2392         try {
2393             getInputStream();
2394         } catch (IOException e) {}
2395 
2396         if (cachedHeaders != null) {
2397             return cachedHeaders.getHeaders();
2398         }
2399 
2400         return responses.getHeaders();
2401     }
2402 
2403     /**
2404      * Gets a header field by index. Returns null if not known.
2405      * @param n the index of the header field
2406      */
2407     @Override
2408     public String getHeaderField(int n) {
2409         try {
2410             getInputStream();
2411         } catch (IOException e) {}
2412 
2413         if (cachedHeaders != null) {
2414            return cachedHeaders.getValue(n);
2415         }
2416         return responses.getValue(n);
2417     }
2418 
2419     /**
2420      * Gets a header field by index. Returns null if not known.
2421      * @param n the index of the header field
2422      */
2423     @Override
2424     public String getHeaderFieldKey(int n) {
2425         try {
2426             getInputStream();
2427         } catch (IOException e) {}
2428 
2429         if (cachedHeaders != null) {
2430             return cachedHeaders.getKey(n);
2431         }
2432 
2433         return responses.getKey(n);
2434     }
2435 
2436     /**
2437      * Sets request property. If a property with the key already
2438      * exists, overwrite its value with the new value.
2439      * @param value the value to be set
2440      */
2441     @Override
2442     public void setRequestProperty(String key, String value) {
2443         if (connected)
2444             throw new IllegalStateException("Already connected");
2445         if (key == null)
2446             throw new NullPointerException ("key is null");
2447 
2448         if (isExternalMessageHeaderAllowed(key, value)) {
2449             requests.set(key, value);
2450         }
2451     }
2452 
2453     /**
2454      * Adds a general request property specified by a
2455      * key-value pair.  This method will not overwrite
2456      * existing values associated with the same key.
2457      *
2458      * @param   key     the keyword by which the request is known
2459      *                  (e.g., "<code>accept</code>").
2460      * @param   value  the value associated with it.
2461      * @see #getRequestProperties(java.lang.String)
2462      * @since 1.4
2463      */
2464     @Override
2465     public void addRequestProperty(String key, String value) {
2466         if (connected)
2467             throw new IllegalStateException("Already connected");
2468         if (key == null)
2469             throw new NullPointerException ("key is null");
2470 
2471         if (isExternalMessageHeaderAllowed(key, value)) {
2472             requests.add(key, value);
2473         }
2474     }
2475 
2476     //
2477     // Set a property for authentication.  This can safely disregard
2478     // the connected test.
2479     //
2480     void setAuthenticationProperty(String key, String value) {
2481         checkMessageHeader(key, value);
2482         requests.set(key, value);
2483     }
2484 
2485     @Override
2486     public String getRequestProperty (String key) {
2487         if (key == null) {
2488             return null;
2489         }
2490         // don't return headers containing security sensitive information
2491         for (int i=0; i < EXCLUDE_HEADERS.length; i++) {
2492             if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) {
2493                 return null;
2494             }
2495         }
2496         if (!setUserCookies) {
2497             if (key.equalsIgnoreCase("Cookie")) {
2498                 return userCookies;
2499             }
2500             if (key.equalsIgnoreCase("Cookie2")) {
2501                 return userCookies2;
2502             }
2503         }
2504 
2505         return requests.findValue(key);
2506     }
2507 
2508     /**
2509      * Returns an unmodifiable Map of general request
2510      * properties for this connection. The Map keys
2511      * are Strings that represent the request-header
2512      * field names. Each Map value is a unmodifiable List
2513      * of Strings that represents the corresponding
2514      * field values.
2515      *
2516      * @return  a Map of the general request properties for this connection.
2517      * @throws IllegalStateException if already connected
2518      * @since 1.4
2519      */
2520     @Override
2521     public synchronized Map<String, List<String>> getRequestProperties() {
2522         if (connected)
2523             throw new IllegalStateException("Already connected");
2524 
2525         // exclude headers containing security-sensitive info
2526         if (setUserCookies) {
2527             return requests.getHeaders(EXCLUDE_HEADERS);
2528         }
2529         /*
2530          * The cookies in the requests message headers may have
2531          * been modified. Use the saved user cookies instead.
2532          */
2533         Map userCookiesMap = null;
2534         if (userCookies != null || userCookies2 != null) {
2535             userCookiesMap = new HashMap();
2536             if (userCookies != null) {
2537                 userCookiesMap.put("Cookie", userCookies);
2538             }
2539             if (userCookies2 != null) {
2540                 userCookiesMap.put("Cookie2", userCookies2);
2541             }
2542         }
2543         return requests.filterAndAddHeaders(EXCLUDE_HEADERS2, userCookiesMap);
2544     }
2545 
2546     @Override
2547     public void setConnectTimeout(int timeout) {
2548         if (timeout < 0)
2549             throw new IllegalArgumentException("timeouts can't be negative");
2550         connectTimeout = timeout;
2551     }
2552 
2553 
2554     /**
2555      * Returns setting for connect timeout.
2556      * <p>
2557      * 0 return implies that the option is disabled
2558      * (i.e., timeout of infinity).
2559      *
2560      * @return an <code>int</code> that indicates the connect timeout
2561      *         value in milliseconds
2562      * @see java.net.URLConnection#setConnectTimeout(int)
2563      * @see java.net.URLConnection#connect()
2564      * @since 1.5
2565      */
2566     @Override
2567     public int getConnectTimeout() {
2568         return (connectTimeout < 0 ? 0 : connectTimeout);
2569     }
2570 
2571     /**
2572      * Sets the read timeout to a specified timeout, in
2573      * milliseconds. A non-zero value specifies the timeout when
2574      * reading from Input stream when a connection is established to a
2575      * resource. If the timeout expires before there is data available
2576      * for read, a java.net.SocketTimeoutException is raised. A
2577      * timeout of zero is interpreted as an infinite timeout.
2578      *
2579      * <p> Some non-standard implementation of this method ignores the
2580      * specified timeout. To see the read timeout set, please call
2581      * getReadTimeout().
2582      *
2583      * @param timeout an <code>int</code> that specifies the timeout
2584      * value to be used in milliseconds
2585      * @throws IllegalArgumentException if the timeout parameter is negative
2586      *
2587      * @see java.net.URLConnectiongetReadTimeout()
2588      * @see java.io.InputStream#read()
2589      * @since 1.5
2590      */
2591     @Override
2592     public void setReadTimeout(int timeout) {
2593         if (timeout < 0)
2594             throw new IllegalArgumentException("timeouts can't be negative");
2595         readTimeout = timeout;
2596     }
2597 
2598     /**
2599      * Returns setting for read timeout. 0 return implies that the
2600      * option is disabled (i.e., timeout of infinity).
2601      *
2602      * @return an <code>int</code> that indicates the read timeout
2603      *         value in milliseconds
2604      *
2605      * @see java.net.URLConnection#setReadTimeout(int)
2606      * @see java.io.InputStream#read()
2607      * @since 1.5
2608      */
2609     @Override
2610     public int getReadTimeout() {
2611         return readTimeout < 0 ? 0 : readTimeout;
2612     }
2613 
2614     public CookieHandler getCookieHandler() {
2615         return cookieHandler;
2616     }
2617 
2618     String getMethod() {
2619         return method;
2620     }
2621 
2622     private MessageHeader mapToMessageHeader(Map<String, List<String>> map) {
2623         MessageHeader headers = new MessageHeader();
2624         if (map == null || map.isEmpty()) {
2625             return headers;
2626         }
2627         for (Map.Entry<String, List<String>> entry : map.entrySet()) {
2628             String key = entry.getKey();
2629             List<String> values = entry.getValue();
2630             for (String value : values) {
2631                 if (key == null) {
2632                     headers.prepend(key, value);
2633                 } else {
2634                     headers.add(key, value);
2635                 }
2636             }
2637         }
2638         return headers;
2639     }
2640 
2641     /* The purpose of this wrapper is just to capture the close() call
2642      * so we can check authentication information that may have
2643      * arrived in a Trailer field and to write data to a cache
2644      */
2645     class HttpInputStream extends FilterInputStream {
2646         private CacheRequest cacheRequest;
2647         private OutputStream outputStream;
2648         private boolean marked = false;
2649         private int inCache = 0;
2650         private int markCount = 0;
2651 
2652         public HttpInputStream (InputStream is) {
2653             super (is);
2654             this.cacheRequest = null;
2655             this.outputStream = null;
2656         }
2657 
2658         public HttpInputStream (InputStream is, CacheRequest cacheRequest) {
2659             super (is);
2660             this.cacheRequest = cacheRequest;
2661             try {
2662                 this.outputStream = cacheRequest.getBody();
2663             } catch (IOException ioex) {
2664                 this.cacheRequest.abort();
2665                 this.cacheRequest = null;
2666                 this.outputStream = null;
2667             }
2668         }
2669 
2670         /**
2671          * Marks the current position in this input stream. A subsequent
2672          * call to the <code>reset</code> method repositions this stream at
2673          * the last marked position so that subsequent reads re-read the same
2674          * bytes.
2675          * <p>
2676          * The <code>readlimit</code> argument tells this input stream to
2677          * allow that many bytes to be read before the mark position gets
2678          * invalidated.
2679          * <p>
2680          * This method simply performs <code>in.mark(readlimit)</code>.
2681          *
2682          * @param   readlimit   the maximum limit of bytes that can be read before
2683          *                      the mark position becomes invalid.
2684          * @see     java.io.FilterInputStream#in
2685          * @see     java.io.FilterInputStream#reset()
2686          */
2687         @Override
2688         public synchronized void mark(int readlimit) {
2689             super.mark(readlimit);
2690             if (cacheRequest != null) {
2691                 marked = true;
2692                 markCount = 0;
2693             }
2694         }
2695 
2696         /**
2697          * Repositions this stream to the position at the time the
2698          * <code>mark</code> method was last called on this input stream.
2699          * <p>
2700          * This method
2701          * simply performs <code>in.reset()</code>.
2702          * <p>
2703          * Stream marks are intended to be used in
2704          * situations where you need to read ahead a little to see what's in
2705          * the stream. Often this is most easily done by invoking some
2706          * general parser. If the stream is of the type handled by the
2707          * parse, it just chugs along happily. If the stream is not of
2708          * that type, the parser should toss an exception when it fails.
2709          * If this happens within readlimit bytes, it allows the outer
2710          * code to reset the stream and try another parser.
2711          *
2712          * @exception  IOException  if the stream has not been marked or if the
2713          *               mark has been invalidated.
2714          * @see        java.io.FilterInputStream#in
2715          * @see        java.io.FilterInputStream#mark(int)
2716          */
2717         @Override
2718         public synchronized void reset() throws IOException {
2719             super.reset();
2720             if (cacheRequest != null) {
2721                 marked = false;
2722                 inCache += markCount;
2723             }
2724         }
2725 
2726         @Override
2727         public int read() throws IOException {
2728             try {
2729                 byte[] b = new byte[1];
2730                 int ret = read(b);
2731                 return (ret == -1? ret : (b[0] & 0x00FF));
2732             } catch (IOException ioex) {
2733                 if (cacheRequest != null) {
2734                     cacheRequest.abort();
2735                 }
2736                 throw ioex;
2737             }
2738         }
2739 
2740         @Override
2741         public int read(byte[] b) throws IOException {
2742             return read(b, 0, b.length);
2743         }
2744 
2745         @Override
2746         public int read(byte[] b, int off, int len) throws IOException {
2747             try {
2748                 int newLen = super.read(b, off, len);
2749                 int nWrite;
2750                 // write to cache
2751                 if (inCache > 0) {
2752                     if (inCache >= newLen) {
2753                         inCache -= newLen;
2754                         nWrite = 0;
2755                     } else {
2756                         nWrite = newLen - inCache;
2757                         inCache = 0;
2758                     }
2759                 } else {
2760                     nWrite = newLen;
2761                 }
2762                 if (nWrite > 0 && outputStream != null)
2763                     outputStream.write(b, off + (newLen-nWrite), nWrite);
2764                 if (marked) {
2765                     markCount += newLen;
2766                 }
2767                 return newLen;
2768             } catch (IOException ioex) {
2769                 if (cacheRequest != null) {
2770                     cacheRequest.abort();
2771                 }
2772                 throw ioex;
2773             }
2774         }
2775 
2776         @Override
2777         public void close () throws IOException {
2778             try {
2779                 if (outputStream != null) {
2780                     if (read() != -1) {
2781                         cacheRequest.abort();
2782                     } else {
2783                         outputStream.close();
2784                     }
2785                 }
2786                 super.close ();
2787             } catch (IOException ioex) {
2788                 if (cacheRequest != null) {
2789                     cacheRequest.abort();
2790                 }
2791                 throw ioex;
2792             } finally {
2793                 HttpURLConnection.this.http = null;
2794                 checkResponseCredentials (true);
2795             }
2796         }
2797 
2798         /* same implementation as InputStream.skip */
2799 
2800         private byte[] skipBuffer;
2801         private static final int SKIP_BUFFER_SIZE = 8096;
2802 
2803 
2804         public long skip(long n) throws IOException {
2805 
2806             long remaining = n;
2807             int nr;
2808             if (skipBuffer == null)
2809                 skipBuffer = new byte[SKIP_BUFFER_SIZE];
2810 
2811             byte[] localSkipBuffer = skipBuffer;
2812 
2813             if (n <= 0) {
2814                 return 0;
2815             }
2816 
2817             while (remaining > 0) {
2818                 nr = read(localSkipBuffer, 0,
2819                           (int) Math.min(SKIP_BUFFER_SIZE, remaining));
2820                 if (nr < 0) {
2821                     break;
2822                 }
2823                 remaining -= nr;
2824             }
2825 
2826             return n - remaining;
2827         }
2828     }
2829 
2830     class StreamingOutputStream extends FilterOutputStream {
2831 
2832         int expected;
2833         int written;
2834         boolean closed;
2835         boolean error;
2836         IOException errorExcp;
2837 
2838         /**
2839          * expectedLength == -1 if the stream is chunked
2840          * expectedLength > 0 if the stream is fixed content-length
2841          *    In the 2nd case, we make sure the expected number of
2842          *    of bytes are actually written
2843          */
2844         StreamingOutputStream (OutputStream os, int expectedLength) {
2845             super (os);
2846             expected = expectedLength;
2847             written = 0;
2848             closed = false;
2849             error = false;
2850         }
2851 
2852         @Override
2853         public void write (int b) throws IOException {
2854             checkError();
2855             written ++;
2856             if (expected != -1 && written > expected) {
2857                 throw new IOException ("too many bytes written");
2858             }
2859             out.write (b);
2860         }
2861 
2862         @Override
2863         public void write (byte[] b) throws IOException {
2864             write (b, 0, b.length);
2865         }
2866 
2867         @Override
2868         public void write (byte[] b, int off, int len) throws IOException {
2869             checkError();
2870             written += len;
2871             if (expected != -1 && written > expected) {
2872                 out.close ();
2873                 throw new IOException ("too many bytes written");
2874             }
2875             out.write (b, off, len);
2876         }
2877 
2878         void checkError () throws IOException {
2879             if (closed) {
2880                 throw new IOException ("Stream is closed");
2881             }
2882             if (error) {
2883                 throw errorExcp;
2884             }
2885             if (((PrintStream)out).checkError()) {
2886                 throw new IOException("Error writing request body to server");
2887             }
2888         }
2889 
2890         /* this is called to check that all the bytes
2891          * that were supposed to be written were written
2892          * and that the stream is now closed().
2893          */
2894         boolean writtenOK () {
2895             return closed && ! error;
2896         }
2897 
2898         @Override
2899         public void close () throws IOException {
2900             if (closed) {
2901                 return;
2902             }
2903             closed = true;
2904             if (expected != -1) {
2905                 /* not chunked */
2906                 if (written != expected) {
2907                     error = true;
2908                     errorExcp = new IOException ("insufficient data written");
2909                     out.close ();
2910                     throw errorExcp;
2911                 }
2912                 super.flush(); /* can't close the socket */
2913             } else {
2914                 /* chunked */
2915                 super.close (); /* force final chunk to be written */
2916                 /* trailing \r\n */
2917                 OutputStream o = http.getOutputStream();
2918                 o.write ('\r');
2919                 o.write ('\n');
2920                 o.flush();
2921             }
2922         }
2923     }
2924 
2925 
2926     static class ErrorStream extends InputStream {
2927         ByteBuffer buffer;
2928         InputStream is;
2929 
2930         private ErrorStream(ByteBuffer buf) {
2931             buffer = buf;
2932             is = null;
2933         }
2934 
2935         private ErrorStream(ByteBuffer buf, InputStream is) {
2936             buffer = buf;
2937             this.is = is;
2938         }
2939 
2940         // when this method is called, it's either the case that cl > 0, or
2941         // if chunk-encoded, cl = -1; in other words, cl can't be 0
2942         public static InputStream getErrorStream(InputStream is, int cl, HttpClient http) {
2943 
2944             // cl can't be 0; this following is here for extra precaution
2945             if (cl == 0) {
2946                 return null;
2947             }
2948 
2949             try {
2950                 // set SO_TIMEOUT to 1/5th of the total timeout
2951                 // remember the old timeout value so that we can restore it
2952                 int oldTimeout = http.getReadTimeout();
2953                 http.setReadTimeout(timeout4ESBuffer/5);
2954 
2955                 int expected = 0;
2956                 boolean isChunked = false;
2957                 // the chunked case
2958                 if (cl < 0) {
2959                     expected = bufSize4ES;
2960                     isChunked = true;
2961                 } else {
2962                     expected = cl;
2963                 }
2964                 if (expected <= bufSize4ES) {
2965                     byte[] buffer = new byte[expected];
2966                     int count = 0, time = 0, len = 0;
2967                     do {
2968                         try {
2969                             len = is.read(buffer, count,
2970                                              buffer.length - count);
2971                             if (len < 0) {
2972                                 if (cl < 0) {
2973                                     // chunked ended
2974                                     // if chunked ended prematurely,
2975                                     // an IOException would be thrown
2976                                     break;
2977                                 }
2978                                 // the server sends less than cl bytes of data
2979                                 throw new IOException("the server closes"+
2980                                                       " before sending "+cl+
2981                                                       " bytes of data");
2982                             }
2983                             count += len;
2984                         } catch (SocketTimeoutException ex) {
2985                             time += timeout4ESBuffer/5;
2986                         }
2987                     } while (count < expected && time < timeout4ESBuffer);
2988 
2989                     // reset SO_TIMEOUT to old value
2990                     http.setReadTimeout(oldTimeout);
2991 
2992                     // if count < cl at this point, we will not try to reuse
2993                     // the connection
2994                     if (count == 0) {
2995                         // since we haven't read anything,
2996                         // we will return the underlying
2997                         // inputstream back to the application
2998                         return null;
2999                     }  else if ((count == expected && !(isChunked)) || (isChunked && len <0)) {
3000                         // put the connection into keep-alive cache
3001                         // the inputstream will try to do the right thing
3002                         is.close();
3003                         return new ErrorStream(ByteBuffer.wrap(buffer, 0, count));
3004                     } else {
3005                         // we read part of the response body
3006                         return new ErrorStream(
3007                                       ByteBuffer.wrap(buffer, 0, count), is);
3008                     }
3009                 }
3010                 return null;
3011             } catch (IOException ioex) {
3012                 // ioex.printStackTrace();
3013                 return null;
3014             }
3015         }
3016 
3017         @Override
3018         public int available() throws IOException {
3019             if (is == null) {
3020                 return buffer.remaining();
3021             } else {
3022                 return buffer.remaining()+is.available();
3023             }
3024         }
3025 
3026         public int read() throws IOException {
3027             byte[] b = new byte[1];
3028             int ret = read(b);
3029             return (ret == -1? ret : (b[0] & 0x00FF));
3030         }
3031 
3032         @Override
3033         public int read(byte[] b) throws IOException {
3034             return read(b, 0, b.length);
3035         }
3036 
3037         @Override
3038         public int read(byte[] b, int off, int len) throws IOException {
3039             int rem = buffer.remaining();
3040             if (rem > 0) {
3041                 int ret = rem < len? rem : len;
3042                 buffer.get(b, off, ret);
3043                 return ret;
3044             } else {
3045                 if (is == null) {
3046                     return -1;
3047                 } else {
3048                     return is.read(b, off, len);
3049                 }
3050             }
3051         }
3052 
3053         @Override
3054         public void close() throws IOException {
3055             buffer = null;
3056             if (is != null) {
3057                 is.close();
3058             }
3059         }
3060     }
3061 }
3062 
3063 /** An input stream that just returns EOF.  This is for
3064  * HTTP URLConnections that are KeepAlive && use the
3065  * HEAD method - i.e., stream not dead, but nothing to be read.
3066  */
3067 
3068 class EmptyInputStream extends InputStream {
3069 
3070     @Override
3071     public int available() {
3072         return 0;
3073     }
3074 
3075     public int read() {
3076         return -1;
3077     }
3078 }