rev 14210 : 8154231: Simplify access to System properties from JDK code
Reviewed-by: rriggs

   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 jdk.internal.misc.JavaNetHttpCookieAccess;
  69 import jdk.internal.misc.SharedSecrets;
  70 import sun.net.*;
  71 import sun.net.www.*;
  72 import sun.net.www.http.HttpClient;
  73 import sun.net.www.http.PosterOutputStream;
  74 import sun.net.www.http.ChunkedInputStream;
  75 import sun.net.www.http.ChunkedOutputStream;
  76 import sun.util.logging.PlatformLogger;
  77 import java.text.SimpleDateFormat;
  78 import java.util.TimeZone;
  79 import java.net.MalformedURLException;
  80 import java.nio.ByteBuffer;

  81 import static sun.net.www.protocol.http.AuthScheme.BASIC;
  82 import static sun.net.www.protocol.http.AuthScheme.DIGEST;
  83 import static sun.net.www.protocol.http.AuthScheme.NTLM;
  84 import static sun.net.www.protocol.http.AuthScheme.NEGOTIATE;
  85 import static sun.net.www.protocol.http.AuthScheme.KERBEROS;
  86 import static sun.net.www.protocol.http.AuthScheme.UNKNOWN;


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