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