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