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