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                             /* If the platform supports transparent authentication
2177                              * then check if we are in a secure environment
2178                              * whether, or not, we should try transparent authentication.*/
2179                             if (tryTransparentNTLMServer) {
2180                                 tryTransparentNTLMServer =
2181                                         NTLMAuthenticationProxy.proxy.isTrustedSite(url);
2182                             }
2183                         }
2184                         a = null;
2185                         if (tryTransparentNTLMServer) {
2186                             logger.finest("Trying Transparent NTLM authentication");
2187                         } else {
2188                             a = privilegedRequestPasswordAuthentication(
2189                                 url.getHost(), addr, port, url.getProtocol(),
2190                                 "", scheme, url, RequestorType.SERVER);
2191                         }
2192 
2193                         /* If we are not trying transparent authentication then
2194                          * we need to have a PasswordAuthentication instance. For
2195                          * transparent authentication (Windows only) the username
2196                          * and password will be picked up from the current logged
2197                          * on users credentials.
2198                          */
2199                         if (tryTransparentNTLMServer ||
2200                               (!tryTransparentNTLMServer && a != null)) {
2201                             ret = NTLMAuthenticationProxy.proxy.create(false, url1, a);
2202                         }
2203 
2204                         /* set to false so that we do not try again */
2205                         tryTransparentNTLMServer = false;
2206                     }
2207                     break;
2208                 case UNKNOWN:
2209                     logger.finest("Unknown/Unsupported authentication scheme: " + scheme);
2210                 default:
2211                     throw new AssertionError("should not reach here");
2212                 }
2213             }
2214 
2215             // For backwards compatibility, we also try defaultAuth
2216             // REMIND:  Get rid of this for JDK2.0.
2217 
2218             if (ret == null && defaultAuth != null
2219                 && defaultAuth.schemeSupported(scheme)) {
2220                 String a = defaultAuth.authString(url, scheme, realm);
2221                 if (a != null) {
2222                     ret = new BasicAuthentication (false, url, realm, a);
2223                     // not in cache by default - cache on success
2224                 }
2225             }
2226 
2227             if (ret != null ) {
2228                 if (!ret.setHeaders(this, p, raw)) {
2229                     ret = null;
2230                 }
2231             }
2232         }
2233         if (logger.isLoggable(PlatformLogger.FINER)) {
2234             logger.finer("Server Authentication for " + authhdr.toString() +" returned " + (ret != null ? ret.toString() : "null"));
2235         }
2236         return ret;
2237     }
2238 
2239     /* inclose will be true if called from close(), in which case we
2240      * force the call to check because this is the last chance to do so.
2241      * If not in close(), then the authentication info could arrive in a trailer
2242      * field, which we have not read yet.
2243      */
2244     private void checkResponseCredentials (boolean inClose) throws IOException {
2245         try {
2246             if (!needToCheck)
2247                 return;
2248             if ((validateProxy && currentProxyCredentials != null) &&
2249                 (currentProxyCredentials instanceof DigestAuthentication)) {
2250                 String raw = responses.findValue ("Proxy-Authentication-Info");
2251                 if (inClose || (raw != null)) {
2252                     DigestAuthentication da = (DigestAuthentication)
2253                         currentProxyCredentials;
2254                     da.checkResponse (raw, method, getRequestURI());
2255                     currentProxyCredentials = null;
2256                 }
2257             }
2258             if ((validateServer && currentServerCredentials != null) &&
2259                 (currentServerCredentials instanceof DigestAuthentication)) {
2260                 String raw = responses.findValue ("Authentication-Info");
2261                 if (inClose || (raw != null)) {
2262                     DigestAuthentication da = (DigestAuthentication)
2263                         currentServerCredentials;
2264                     da.checkResponse (raw, method, url);
2265                     currentServerCredentials = null;
2266                 }
2267             }
2268             if ((currentServerCredentials==null) && (currentProxyCredentials == null)) {
2269                 needToCheck = false;
2270             }
2271         } catch (IOException e) {
2272             disconnectInternal();
2273             connected = false;
2274             throw e;
2275         }
2276     }
2277 
2278    /* The request URI used in the request line for this request.
2279     * Also, needed for digest authentication
2280     */
2281 
2282     String requestURI = null;
2283 
2284     String getRequestURI() throws IOException {
2285         if (requestURI == null) {
2286             requestURI = http.getURLFile();
2287         }
2288         return requestURI;
2289     }
2290 
2291     /* Tells us whether to follow a redirect.  If so, it
2292      * closes the connection (break any keep-alive) and
2293      * resets the url, re-connects, and resets the request
2294      * property.
2295      */
2296     private boolean followRedirect() throws IOException {
2297         if (!getInstanceFollowRedirects()) {
2298             return false;
2299         }
2300 
2301         int stat = getResponseCode();
2302         if (stat < 300 || stat > 307 || stat == 306
2303                                 || stat == HTTP_NOT_MODIFIED) {
2304             return false;
2305         }
2306         String loc = getHeaderField("Location");
2307         if (loc == null) {
2308             /* this should be present - if not, we have no choice
2309              * but to go forward w/ the response we got
2310              */
2311             return false;
2312         }
2313         URL locUrl;
2314         try {
2315             locUrl = new URL(loc);
2316             if (!url.getProtocol().equalsIgnoreCase(locUrl.getProtocol())) {
2317                 return false;
2318             }
2319 
2320         } catch (MalformedURLException mue) {
2321           // treat loc as a relative URI to conform to popular browsers
2322           locUrl = new URL(url, loc);
2323         }
2324         disconnectInternal();
2325         if (streaming()) {
2326             throw new HttpRetryException (RETRY_MSG3, stat, loc);
2327         }
2328         if (logger.isLoggable(PlatformLogger.FINE)) {
2329             logger.fine("Redirected from " + url + " to " + locUrl);
2330         }
2331 
2332         // clear out old response headers!!!!
2333         responses = new MessageHeader();
2334         if (stat == HTTP_USE_PROXY) {
2335             /* This means we must re-request the resource through the
2336              * proxy denoted in the "Location:" field of the response.
2337              * Judging by the spec, the string in the Location header
2338              * _should_ denote a URL - let's hope for "http://my.proxy.org"
2339              * Make a new HttpClient to the proxy, using HttpClient's
2340              * Instance-specific proxy fields, but note we're still fetching
2341              * the same URL.
2342              */
2343             String proxyHost = locUrl.getHost();
2344             int proxyPort = locUrl.getPort();
2345 
2346             SecurityManager security = System.getSecurityManager();
2347             if (security != null) {
2348                 security.checkConnect(proxyHost, proxyPort);
2349             }
2350 
2351             setProxiedClient (url, proxyHost, proxyPort);
2352             requests.set(0, method + " " + getRequestURI()+" "  +
2353                              httpVersion, null);
2354             connected = true;
2355         } else {
2356             // maintain previous headers, just change the name
2357             // of the file we're getting
2358             url = locUrl;
2359             requestURI = null; // force it to be recalculated
2360             if (method.equals("POST") && !Boolean.getBoolean("http.strictPostRedirect") && (stat!=307)) {
2361                 /* The HTTP/1.1 spec says that a redirect from a POST
2362                  * *should not* be immediately turned into a GET, and
2363                  * that some HTTP/1.0 clients incorrectly did this.
2364                  * Correct behavior redirects a POST to another POST.
2365                  * Unfortunately, since most browsers have this incorrect
2366                  * behavior, the web works this way now.  Typical usage
2367                  * seems to be:
2368                  *   POST a login code or passwd to a web page.
2369                  *   after validation, the server redirects to another
2370                  *     (welcome) page
2371                  *   The second request is (erroneously) expected to be GET
2372                  *
2373                  * We will do the incorrect thing (POST-->GET) by default.
2374                  * We will provide the capability to do the "right" thing
2375                  * (POST-->POST) by a system property, "http.strictPostRedirect=true"
2376                  */
2377 
2378                 requests = new MessageHeader();
2379                 setRequests = false;
2380                 setRequestMethod("GET");
2381                 poster = null;
2382                 if (!checkReuseConnection())
2383                     connect();
2384             } else {
2385                 if (!checkReuseConnection())
2386                     connect();
2387                 /* Even after a connect() call, http variable still can be
2388                  * null, if a ResponseCache has been installed and it returns
2389                  * a non-null CacheResponse instance. So check nullity before using it.
2390                  *
2391                  * And further, if http is null, there's no need to do anything
2392                  * about request headers because successive http session will use
2393                  * cachedInputStream/cachedHeaders anyway, which is returned by
2394                  * CacheResponse.
2395                  */
2396                 if (http != null) {
2397                     requests.set(0, method + " " + getRequestURI()+" "  +
2398                                  httpVersion, null);
2399                     int port = url.getPort();
2400                     String host = url.getHost();
2401                     if (port != -1 && port != url.getDefaultPort()) {
2402                         host += ":" + String.valueOf(port);
2403                     }
2404                     requests.set("Host", host);
2405                 }
2406             }
2407         }
2408         return true;
2409     }
2410 
2411     /* dummy byte buffer for reading off socket prior to closing */
2412     byte[] cdata = new byte [128];
2413 
2414     /**
2415      * Reset (without disconnecting the TCP conn) in order to do another transaction with this instance
2416      */
2417     private void reset() throws IOException {
2418         http.reuse = true;
2419         /* must save before calling close */
2420         reuseClient = http;
2421         InputStream is = http.getInputStream();
2422         if (!method.equals("HEAD")) {
2423             try {
2424                 /* we want to read the rest of the response without using the
2425                  * hurry mechanism, because that would close the connection
2426                  * if everything is not available immediately
2427                  */
2428                 if ((is instanceof ChunkedInputStream) ||
2429                     (is instanceof MeteredStream)) {
2430                     /* reading until eof will not block */
2431                     while (is.read (cdata) > 0) {}
2432                 } else {
2433                     /* raw stream, which will block on read, so only read
2434                      * the expected number of bytes, probably 0
2435                      */
2436                     long cl = 0;
2437                     int n = 0;
2438                     String cls = responses.findValue ("Content-Length");
2439                     if (cls != null) {
2440                         try {
2441                             cl = Long.parseLong (cls);
2442                         } catch (NumberFormatException e) {
2443                             cl = 0;
2444                         }
2445                     }
2446                     for (long i=0; i<cl; ) {
2447                         if ((n = is.read (cdata)) == -1) {
2448                             break;
2449                         } else {
2450                             i+= n;
2451                         }
2452                     }
2453                 }
2454             } catch (IOException e) {
2455                 http.reuse = false;
2456                 reuseClient = null;
2457                 disconnectInternal();
2458                 return;
2459             }
2460             try {
2461                 if (is instanceof MeteredStream) {
2462                     is.close();
2463                 }
2464             } catch (IOException e) { }
2465         }
2466         responseCode = -1;
2467         responses = new MessageHeader();
2468         connected = false;
2469     }
2470 
2471     /**
2472      * Disconnect from the web server at the first 401 error. Do not
2473      * disconnect when using a proxy, a good proxy should have already
2474      * closed the connection to the web server.
2475      */
2476     private void disconnectWeb() throws IOException {
2477         if (usingProxy() && http.isKeepingAlive()) {
2478             responseCode = -1;
2479             // clean up, particularly, skip the content part
2480             // of a 401 error response
2481             reset();
2482         } else {
2483             disconnectInternal();
2484         }
2485     }
2486 
2487     /**
2488      * Disconnect from the server (for internal use)
2489      */
2490     private void disconnectInternal() {
2491         responseCode = -1;
2492         inputStream = null;
2493         if (pi != null) {
2494             pi.finishTracking();
2495             pi = null;
2496         }
2497         if (http != null) {
2498             http.closeServer();
2499             http = null;
2500             connected = false;
2501         }
2502     }
2503 
2504     /**
2505      * Disconnect from the server (public API)
2506      */
2507     public void disconnect() {
2508 
2509         responseCode = -1;
2510         if (pi != null) {
2511             pi.finishTracking();
2512             pi = null;
2513         }
2514 
2515         if (http != null) {
2516             /*
2517              * If we have an input stream this means we received a response
2518              * from the server. That stream may have been read to EOF and
2519              * dependening on the stream type may already be closed or the
2520              * the http client may be returned to the keep-alive cache.
2521              * If the http client has been returned to the keep-alive cache
2522              * it may be closed (idle timeout) or may be allocated to
2523              * another request.
2524              *
2525              * In other to avoid timing issues we close the input stream
2526              * which will either close the underlying connection or return
2527              * the client to the cache. If there's a possibility that the
2528              * client has been returned to the cache (ie: stream is a keep
2529              * alive stream or a chunked input stream) then we remove an
2530              * idle connection to the server. Note that this approach
2531              * can be considered an approximation in that we may close a
2532              * different idle connection to that used by the request.
2533              * Additionally it's possible that we close two connections
2534              * - the first becuase it wasn't an EOF (and couldn't be
2535              * hurried) - the second, another idle connection to the
2536              * same server. The is okay because "disconnect" is an
2537              * indication that the application doesn't intend to access
2538              * this http server for a while.
2539              */
2540 
2541             if (inputStream != null) {
2542                 HttpClient hc = http;
2543 
2544                 // un-synchronized
2545                 boolean ka = hc.isKeepingAlive();
2546 
2547                 try {
2548                     inputStream.close();
2549                 } catch (IOException ioe) { }
2550 
2551                 // if the connection is persistent it may have been closed
2552                 // or returned to the keep-alive cache. If it's been returned
2553                 // to the keep-alive cache then we would like to close it
2554                 // but it may have been allocated
2555 
2556                 if (ka) {
2557                     hc.closeIdleConnection();
2558                 }
2559 
2560 
2561             } else {
2562                 // We are deliberatly being disconnected so HttpClient
2563                 // should not try to resend the request no matter what stage
2564                 // of the connection we are in.
2565                 http.setDoNotRetry(true);
2566 
2567                 http.closeServer();
2568             }
2569 
2570             //      poster = null;
2571             http = null;
2572             connected = false;
2573         }
2574         cachedInputStream = null;
2575         if (cachedHeaders != null) {
2576             cachedHeaders.reset();
2577         }
2578     }
2579 
2580     public boolean usingProxy() {
2581         if (http != null) {
2582             return (http.getProxyHostUsed() != null);
2583         }
2584         return false;
2585     }
2586 
2587     /**
2588      * Gets a header field by name. Returns null if not known.
2589      * @param name the name of the header field
2590      */
2591     @Override
2592     public String getHeaderField(String name) {
2593         try {
2594             getInputStream();
2595         } catch (IOException e) {}
2596 
2597         if (cachedHeaders != null) {
2598             return cachedHeaders.findValue(name);
2599         }
2600 
2601         return responses.findValue(name);
2602     }
2603 
2604     /**
2605      * Returns an unmodifiable Map of the header fields.
2606      * The Map keys are Strings that represent the
2607      * response-header field names. Each Map value is an
2608      * unmodifiable List of Strings that represents
2609      * the corresponding field values.
2610      *
2611      * @return a Map of header fields
2612      * @since 1.4
2613      */
2614     @Override
2615     public Map<String, List<String>> getHeaderFields() {
2616         try {
2617             getInputStream();
2618         } catch (IOException e) {}
2619 
2620         if (cachedHeaders != null) {
2621             return cachedHeaders.getHeaders();
2622         }
2623 
2624         return responses.getHeaders();
2625     }
2626 
2627     /**
2628      * Gets a header field by index. Returns null if not known.
2629      * @param n the index of the header field
2630      */
2631     @Override
2632     public String getHeaderField(int n) {
2633         try {
2634             getInputStream();
2635         } catch (IOException e) {}
2636 
2637         if (cachedHeaders != null) {
2638            return cachedHeaders.getValue(n);
2639         }
2640         return responses.getValue(n);
2641     }
2642 
2643     /**
2644      * Gets a header field by index. Returns null if not known.
2645      * @param n the index of the header field
2646      */
2647     @Override
2648     public String getHeaderFieldKey(int n) {
2649         try {
2650             getInputStream();
2651         } catch (IOException e) {}
2652 
2653         if (cachedHeaders != null) {
2654             return cachedHeaders.getKey(n);
2655         }
2656 
2657         return responses.getKey(n);
2658     }
2659 
2660     /**
2661      * Sets request property. If a property with the key already
2662      * exists, overwrite its value with the new value.
2663      * @param value the value to be set
2664      */
2665     @Override
2666     public void setRequestProperty(String key, String value) {
2667         if (connected)
2668             throw new IllegalStateException("Already connected");
2669         if (key == null)
2670             throw new NullPointerException ("key is null");
2671 
2672         if (isExternalMessageHeaderAllowed(key, value)) {
2673             requests.set(key, value);
2674         }
2675     }
2676 
2677     /**
2678      * Adds a general request property specified by a
2679      * key-value pair.  This method will not overwrite
2680      * existing values associated with the same key.
2681      *
2682      * @param   key     the keyword by which the request is known
2683      *                  (e.g., "<code>accept</code>").
2684      * @param   value  the value associated with it.
2685      * @see #getRequestProperties(java.lang.String)
2686      * @since 1.4
2687      */
2688     @Override
2689     public void addRequestProperty(String key, String value) {
2690         if (connected)
2691             throw new IllegalStateException("Already connected");
2692         if (key == null)
2693             throw new NullPointerException ("key is null");
2694 
2695         if (isExternalMessageHeaderAllowed(key, value)) {
2696             requests.add(key, value);
2697         }
2698     }
2699 
2700     //
2701     // Set a property for authentication.  This can safely disregard
2702     // the connected test.
2703     //
2704     public void setAuthenticationProperty(String key, String value) {
2705         checkMessageHeader(key, value);
2706         requests.set(key, value);
2707     }
2708 
2709     @Override
2710     public synchronized String getRequestProperty (String key) {
2711         if (key == null) {
2712             return null;
2713         }
2714 
2715         // don't return headers containing security sensitive information
2716         for (int i=0; i < EXCLUDE_HEADERS.length; i++) {
2717             if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) {
2718                 return null;
2719             }
2720         }
2721         if (!setUserCookies) {
2722             if (key.equalsIgnoreCase("Cookie")) {
2723                 return userCookies;
2724             }
2725             if (key.equalsIgnoreCase("Cookie2")) {
2726                 return userCookies2;
2727             }
2728         }
2729         return requests.findValue(key);
2730     }
2731 
2732     /**
2733      * Returns an unmodifiable Map of general request
2734      * properties for this connection. The Map keys
2735      * are Strings that represent the request-header
2736      * field names. Each Map value is a unmodifiable List
2737      * of Strings that represents the corresponding
2738      * field values.
2739      *
2740      * @return  a Map of the general request properties for this connection.
2741      * @throws IllegalStateException if already connected
2742      * @since 1.4
2743      */
2744     @Override
2745     public synchronized Map<String, List<String>> getRequestProperties() {
2746         if (connected)
2747             throw new IllegalStateException("Already connected");
2748 
2749         // exclude headers containing security-sensitive info
2750         if (setUserCookies) {
2751             return requests.getHeaders(EXCLUDE_HEADERS);
2752         }
2753         /*
2754          * The cookies in the requests message headers may have
2755          * been modified. Use the saved user cookies instead.
2756          */
2757         Map userCookiesMap = null;
2758         if (userCookies != null || userCookies2 != null) {
2759             userCookiesMap = new HashMap();
2760             if (userCookies != null) {
2761                 userCookiesMap.put("Cookie", userCookies);
2762             }
2763             if (userCookies2 != null) {
2764                 userCookiesMap.put("Cookie2", userCookies2);
2765             }
2766         }
2767         return requests.filterAndAddHeaders(EXCLUDE_HEADERS2, userCookiesMap);
2768     }
2769 
2770     @Override
2771     public void setConnectTimeout(int timeout) {
2772         if (timeout < 0)
2773             throw new IllegalArgumentException("timeouts can't be negative");
2774         connectTimeout = timeout;
2775     }
2776 
2777 
2778     /**
2779      * Returns setting for connect timeout.
2780      * <p>
2781      * 0 return implies that the option is disabled
2782      * (i.e., timeout of infinity).
2783      *
2784      * @return an <code>int</code> that indicates the connect timeout
2785      *         value in milliseconds
2786      * @see java.net.URLConnection#setConnectTimeout(int)
2787      * @see java.net.URLConnection#connect()
2788      * @since 1.5
2789      */
2790     @Override
2791     public int getConnectTimeout() {
2792         return (connectTimeout < 0 ? 0 : connectTimeout);
2793     }
2794 
2795     /**
2796      * Sets the read timeout to a specified timeout, in
2797      * milliseconds. A non-zero value specifies the timeout when
2798      * reading from Input stream when a connection is established to a
2799      * resource. If the timeout expires before there is data available
2800      * for read, a java.net.SocketTimeoutException is raised. A
2801      * timeout of zero is interpreted as an infinite timeout.
2802      *
2803      * <p> Some non-standard implementation of this method ignores the
2804      * specified timeout. To see the read timeout set, please call
2805      * getReadTimeout().
2806      *
2807      * @param timeout an <code>int</code> that specifies the timeout
2808      * value to be used in milliseconds
2809      * @throws IllegalArgumentException if the timeout parameter is negative
2810      *
2811      * @see java.net.URLConnectiongetReadTimeout()
2812      * @see java.io.InputStream#read()
2813      * @since 1.5
2814      */
2815     @Override
2816     public void setReadTimeout(int timeout) {
2817         if (timeout < 0)
2818             throw new IllegalArgumentException("timeouts can't be negative");
2819         readTimeout = timeout;
2820     }
2821 
2822     /**
2823      * Returns setting for read timeout. 0 return implies that the
2824      * option is disabled (i.e., timeout of infinity).
2825      *
2826      * @return an <code>int</code> that indicates the read timeout
2827      *         value in milliseconds
2828      *
2829      * @see java.net.URLConnection#setReadTimeout(int)
2830      * @see java.io.InputStream#read()
2831      * @since 1.5
2832      */
2833     @Override
2834     public int getReadTimeout() {
2835         return readTimeout < 0 ? 0 : readTimeout;
2836     }
2837 
2838     String getMethod() {
2839         return method;
2840     }
2841 
2842     private MessageHeader mapToMessageHeader(Map<String, List<String>> map) {
2843         MessageHeader headers = new MessageHeader();
2844         if (map == null || map.isEmpty()) {
2845             return headers;
2846         }
2847         for (Map.Entry<String, List<String>> entry : map.entrySet()) {
2848             String key = entry.getKey();
2849             List<String> values = entry.getValue();
2850             for (String value : values) {
2851                 if (key == null) {
2852                     headers.prepend(key, value);
2853                 } else {
2854                     headers.add(key, value);
2855                 }
2856             }
2857         }
2858         return headers;
2859     }
2860 
2861     /* The purpose of this wrapper is just to capture the close() call
2862      * so we can check authentication information that may have
2863      * arrived in a Trailer field
2864      */
2865     class HttpInputStream extends FilterInputStream {
2866         private CacheRequest cacheRequest;
2867         private OutputStream outputStream;
2868         private boolean marked = false;
2869         private int inCache = 0;
2870         private int markCount = 0;
2871 
2872         public HttpInputStream (InputStream is) {
2873             super (is);
2874             this.cacheRequest = null;
2875             this.outputStream = null;
2876         }
2877 
2878         public HttpInputStream (InputStream is, CacheRequest cacheRequest) {
2879             super (is);
2880             this.cacheRequest = cacheRequest;
2881             try {
2882                 this.outputStream = cacheRequest.getBody();
2883             } catch (IOException ioex) {
2884                 this.cacheRequest.abort();
2885                 this.cacheRequest = null;
2886                 this.outputStream = null;
2887             }
2888         }
2889 
2890         /**
2891          * Marks the current position in this input stream. A subsequent
2892          * call to the <code>reset</code> method repositions this stream at
2893          * the last marked position so that subsequent reads re-read the same
2894          * bytes.
2895          * <p>
2896          * The <code>readlimit</code> argument tells this input stream to
2897          * allow that many bytes to be read before the mark position gets
2898          * invalidated.
2899          * <p>
2900          * This method simply performs <code>in.mark(readlimit)</code>.
2901          *
2902          * @param   readlimit   the maximum limit of bytes that can be read before
2903          *                      the mark position becomes invalid.
2904          * @see     java.io.FilterInputStream#in
2905          * @see     java.io.FilterInputStream#reset()
2906          */
2907         @Override
2908         public synchronized void mark(int readlimit) {
2909             super.mark(readlimit);
2910             if (cacheRequest != null) {
2911                 marked = true;
2912                 markCount = 0;
2913             }
2914         }
2915 
2916         /**
2917          * Repositions this stream to the position at the time the
2918          * <code>mark</code> method was last called on this input stream.
2919          * <p>
2920          * This method
2921          * simply performs <code>in.reset()</code>.
2922          * <p>
2923          * Stream marks are intended to be used in
2924          * situations where you need to read ahead a little to see what's in
2925          * the stream. Often this is most easily done by invoking some
2926          * general parser. If the stream is of the type handled by the
2927          * parse, it just chugs along happily. If the stream is not of
2928          * that type, the parser should toss an exception when it fails.
2929          * If this happens within readlimit bytes, it allows the outer
2930          * code to reset the stream and try another parser.
2931          *
2932          * @exception  IOException  if the stream has not been marked or if the
2933          *               mark has been invalidated.
2934          * @see        java.io.FilterInputStream#in
2935          * @see        java.io.FilterInputStream#mark(int)
2936          */
2937         @Override
2938         public synchronized void reset() throws IOException {
2939             super.reset();
2940             if (cacheRequest != null) {
2941                 marked = false;
2942                 inCache += markCount;
2943             }
2944         }
2945 
2946         @Override
2947         public int read() throws IOException {
2948             try {
2949                 byte[] b = new byte[1];
2950                 int ret = read(b);
2951                 return (ret == -1? ret : (b[0] & 0x00FF));
2952             } catch (IOException ioex) {
2953                 if (cacheRequest != null) {
2954                     cacheRequest.abort();
2955                 }
2956                 throw ioex;
2957             }
2958         }
2959 
2960         @Override
2961         public int read(byte[] b) throws IOException {
2962             return read(b, 0, b.length);
2963         }
2964 
2965         @Override
2966         public int read(byte[] b, int off, int len) throws IOException {
2967             try {
2968                 int newLen = super.read(b, off, len);
2969                 int nWrite;
2970                 // write to cache
2971                 if (inCache > 0) {
2972                     if (inCache >= newLen) {
2973                         inCache -= newLen;
2974                         nWrite = 0;
2975                     } else {
2976                         nWrite = newLen - inCache;
2977                         inCache = 0;
2978                     }
2979                 } else {
2980                     nWrite = newLen;
2981                 }
2982                 if (nWrite > 0 && outputStream != null)
2983                     outputStream.write(b, off + (newLen-nWrite), nWrite);
2984                 if (marked) {
2985                     markCount += newLen;
2986                 }
2987                 return newLen;
2988             } catch (IOException ioex) {
2989                 if (cacheRequest != null) {
2990                     cacheRequest.abort();
2991                 }
2992                 throw ioex;
2993             }
2994         }
2995 
2996         /* skip() calls read() in order to ensure that entire response gets
2997          * cached. same implementation as InputStream.skip */
2998 
2999         private byte[] skipBuffer;
3000         private static final int SKIP_BUFFER_SIZE = 8096;
3001 
3002         @Override
3003         public long skip (long n) throws IOException {
3004 
3005             long remaining = n;
3006             int nr;
3007             if (skipBuffer == null)
3008                 skipBuffer = new byte[SKIP_BUFFER_SIZE];
3009 
3010             byte[] localSkipBuffer = skipBuffer;
3011 
3012             if (n <= 0) {
3013                 return 0;
3014             }
3015 
3016             while (remaining > 0) {
3017                 nr = read(localSkipBuffer, 0,
3018                           (int) Math.min(SKIP_BUFFER_SIZE, remaining));
3019                 if (nr < 0) {
3020                     break;
3021                 }
3022                 remaining -= nr;
3023             }
3024 
3025             return n - remaining;
3026         }
3027 
3028         @Override
3029         public void close () throws IOException {
3030             try {
3031                 if (outputStream != null) {
3032                     if (read() != -1) {
3033                         cacheRequest.abort();
3034                     } else {
3035                         outputStream.close();
3036                     }
3037                 }
3038                 super.close ();
3039             } catch (IOException ioex) {
3040                 if (cacheRequest != null) {
3041                     cacheRequest.abort();
3042                 }
3043                 throw ioex;
3044             } finally {
3045                 HttpURLConnection.this.http = null;
3046                 checkResponseCredentials (true);
3047             }
3048         }
3049     }
3050 
3051     class StreamingOutputStream extends FilterOutputStream {
3052 
3053         long expected;
3054         long written;
3055         boolean closed;
3056         boolean error;
3057         IOException errorExcp;
3058 
3059         /**
3060          * expectedLength == -1 if the stream is chunked
3061          * expectedLength > 0 if the stream is fixed content-length
3062          *    In the 2nd case, we make sure the expected number of
3063          *    of bytes are actually written
3064          */
3065         StreamingOutputStream (OutputStream os, long expectedLength) {
3066             super (os);
3067             expected = expectedLength;
3068             written = 0L;
3069             closed = false;
3070             error = false;
3071         }
3072 
3073         @Override
3074         public void write (int b) throws IOException {
3075             checkError();
3076             written ++;
3077             if (expected != -1L && written > expected) {
3078                 throw new IOException ("too many bytes written");
3079             }
3080             out.write (b);
3081         }
3082 
3083         @Override
3084         public void write (byte[] b) throws IOException {
3085             write (b, 0, b.length);
3086         }
3087 
3088         @Override
3089         public void write (byte[] b, int off, int len) throws IOException {
3090             checkError();
3091             written += len;
3092             if (expected != -1L && written > expected) {
3093                 out.close ();
3094                 throw new IOException ("too many bytes written");
3095             }
3096             out.write (b, off, len);
3097         }
3098 
3099         void checkError () throws IOException {
3100             if (closed) {
3101                 throw new IOException ("Stream is closed");
3102             }
3103             if (error) {
3104                 throw errorExcp;
3105             }
3106             if (((PrintStream)out).checkError()) {
3107                 throw new IOException("Error writing request body to server");
3108             }
3109         }
3110 
3111         /* this is called to check that all the bytes
3112          * that were supposed to be written were written
3113          * and that the stream is now closed().
3114          */
3115         boolean writtenOK () {
3116             return closed && ! error;
3117         }
3118 
3119         @Override
3120         public void close () throws IOException {
3121             if (closed) {
3122                 return;
3123             }
3124             closed = true;
3125             if (expected != -1L) {
3126                 /* not chunked */
3127                 if (written != expected) {
3128                     error = true;
3129                     errorExcp = new IOException ("insufficient data written");
3130                     out.close ();
3131                     throw errorExcp;
3132                 }
3133                 super.flush(); /* can't close the socket */
3134             } else {
3135                 /* chunked */
3136                 super.close (); /* force final chunk to be written */
3137                 /* trailing \r\n */
3138                 OutputStream o = http.getOutputStream();
3139                 o.write ('\r');
3140                 o.write ('\n');
3141                 o.flush();
3142             }
3143         }
3144     }
3145 
3146 
3147     static class ErrorStream extends InputStream {
3148         ByteBuffer buffer;
3149         InputStream is;
3150 
3151         private ErrorStream(ByteBuffer buf) {
3152             buffer = buf;
3153             is = null;
3154         }
3155 
3156         private ErrorStream(ByteBuffer buf, InputStream is) {
3157             buffer = buf;
3158             this.is = is;
3159         }
3160 
3161         // when this method is called, it's either the case that cl > 0, or
3162         // if chunk-encoded, cl = -1; in other words, cl can't be 0
3163         public static InputStream getErrorStream(InputStream is, long cl, HttpClient http) {
3164 
3165             // cl can't be 0; this following is here for extra precaution
3166             if (cl == 0) {
3167                 return null;
3168             }
3169 
3170             try {
3171                 // set SO_TIMEOUT to 1/5th of the total timeout
3172                 // remember the old timeout value so that we can restore it
3173                 int oldTimeout = http.getReadTimeout();
3174                 http.setReadTimeout(timeout4ESBuffer/5);
3175 
3176                 long expected = 0;
3177                 boolean isChunked = false;
3178                 // the chunked case
3179                 if (cl < 0) {
3180                     expected = bufSize4ES;
3181                     isChunked = true;
3182                 } else {
3183                     expected = cl;
3184                 }
3185                 if (expected <= bufSize4ES) {
3186                     int exp = (int) expected;
3187                     byte[] buffer = new byte[exp];
3188                     int count = 0, time = 0, len = 0;
3189                     do {
3190                         try {
3191                             len = is.read(buffer, count,
3192                                              buffer.length - count);
3193                             if (len < 0) {
3194                                 if (isChunked) {
3195                                     // chunked ended
3196                                     // if chunked ended prematurely,
3197                                     // an IOException would be thrown
3198                                     break;
3199                                 }
3200                                 // the server sends less than cl bytes of data
3201                                 throw new IOException("the server closes"+
3202                                                       " before sending "+cl+
3203                                                       " bytes of data");
3204                             }
3205                             count += len;
3206                         } catch (SocketTimeoutException ex) {
3207                             time += timeout4ESBuffer/5;
3208                         }
3209                     } while (count < exp && time < timeout4ESBuffer);
3210 
3211                     // reset SO_TIMEOUT to old value
3212                     http.setReadTimeout(oldTimeout);
3213 
3214                     // if count < cl at this point, we will not try to reuse
3215                     // the connection
3216                     if (count == 0) {
3217                         // since we haven't read anything,
3218                         // we will return the underlying
3219                         // inputstream back to the application
3220                         return null;
3221                     }  else if ((count == expected && !(isChunked)) || (isChunked && len <0)) {
3222                         // put the connection into keep-alive cache
3223                         // the inputstream will try to do the right thing
3224                         is.close();
3225                         return new ErrorStream(ByteBuffer.wrap(buffer, 0, count));
3226                     } else {
3227                         // we read part of the response body
3228                         return new ErrorStream(
3229                                       ByteBuffer.wrap(buffer, 0, count), is);
3230                     }
3231                 }
3232                 return null;
3233             } catch (IOException ioex) {
3234                 // ioex.printStackTrace();
3235                 return null;
3236             }
3237         }
3238 
3239         @Override
3240         public int available() throws IOException {
3241             if (is == null) {
3242                 return buffer.remaining();
3243             } else {
3244                 return buffer.remaining()+is.available();
3245             }
3246         }
3247 
3248         public int read() throws IOException {
3249             byte[] b = new byte[1];
3250             int ret = read(b);
3251             return (ret == -1? ret : (b[0] & 0x00FF));
3252         }
3253 
3254         @Override
3255         public int read(byte[] b) throws IOException {
3256             return read(b, 0, b.length);
3257         }
3258 
3259         @Override
3260         public int read(byte[] b, int off, int len) throws IOException {
3261             int rem = buffer.remaining();
3262             if (rem > 0) {
3263                 int ret = rem < len? rem : len;
3264                 buffer.get(b, off, ret);
3265                 return ret;
3266             } else {
3267                 if (is == null) {
3268                     return -1;
3269                 } else {
3270                     return is.read(b, off, len);
3271                 }
3272             }
3273         }
3274 
3275         @Override
3276         public void close() throws IOException {
3277             buffer = null;
3278             if (is != null) {
3279                 is.close();
3280             }
3281         }
3282     }
3283 }
3284 
3285 /** An input stream that just returns EOF.  This is for
3286  * HTTP URLConnections that are KeepAlive && use the
3287  * HEAD method - i.e., stream not dead, but nothing to be read.
3288  */
3289 
3290 class EmptyInputStream extends InputStream {
3291 
3292     @Override
3293     public int available() {
3294         return 0;
3295     }
3296 
3297     public int read() {
3298         return -1;
3299     }
3300 }