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