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