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