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