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