1 /*
   2  * Copyright (c) 1994, 2016, 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.http;
  27 
  28 import java.io.*;
  29 import java.net.*;
  30 import java.util.Locale;
  31 import java.util.Objects;
  32 import java.util.Properties;
  33 import sun.net.NetworkClient;
  34 import sun.net.ProgressSource;
  35 import sun.net.www.MessageHeader;
  36 import sun.net.www.HeaderParser;
  37 import sun.net.www.MeteredStream;
  38 import sun.net.www.ParseUtil;
  39 import sun.net.www.protocol.http.AuthenticatorKeys;
  40 import sun.net.www.protocol.http.HttpURLConnection;
  41 import sun.util.logging.PlatformLogger;
  42 import static sun.net.www.protocol.http.HttpURLConnection.TunnelState.*;
  43 import sun.security.action.GetPropertyAction;
  44 
  45 /**
  46  * @author Herb Jellinek
  47  * @author Dave Brown
  48  */
  49 public class HttpClient extends NetworkClient {
  50     // whether this httpclient comes from the cache
  51     protected boolean cachedHttpClient = false;
  52 
  53     protected boolean inCache;
  54 
  55     // Http requests we send
  56     MessageHeader requests;
  57 
  58     // Http data we send with the headers
  59     PosterOutputStream poster = null;
  60 
  61     // true if we are in streaming mode (fixed length or chunked)
  62     boolean streaming;
  63 
  64     // if we've had one io error
  65     boolean failedOnce = false;
  66 
  67     /** Response code for CONTINUE */
  68     private boolean ignoreContinue = true;
  69     private static final int    HTTP_CONTINUE = 100;
  70 
  71     /** Default port number for http daemons. REMIND: make these private */
  72     static final int    httpPortNumber = 80;
  73 
  74     /** return default port number (subclasses may override) */
  75     protected int getDefaultPort () { return httpPortNumber; }
  76 
  77     private static int getDefaultPort(String proto) {
  78         if ("http".equalsIgnoreCase(proto))
  79             return 80;
  80         if ("https".equalsIgnoreCase(proto))
  81             return 443;
  82         return -1;
  83     }
  84 
  85     /* All proxying (generic as well as instance-specific) may be
  86      * disabled through use of this flag
  87      */
  88     protected boolean proxyDisabled;
  89 
  90     // are we using proxy in this instance?
  91     public boolean usingProxy = false;
  92     // target host, port for the URL
  93     protected String host;
  94     protected int port;
  95 
  96     /* where we cache currently open, persistent connections */
  97     protected static KeepAliveCache kac = new KeepAliveCache();
  98 
  99     private static boolean keepAliveProp = true;
 100 
 101     // retryPostProp is true by default so as to preserve behavior
 102     // from previous releases.
 103     private static boolean retryPostProp = true;
 104 
 105     /* Value of the system property jdk.ntlm.cache;
 106        if false, then NTLM connections will not be cached.
 107        The default value is 'true'. */
 108     private static final boolean cacheNTLMProp;
 109     /* Value of the system property jdk.spnego.cache;
 110        if false, then connections authentified using the Negotiate/Kerberos
 111        scheme will not be cached.
 112        The default value is 'true'. */
 113     private static final boolean cacheSPNEGOProp;
 114 
 115     volatile boolean keepingAlive;    /* this is a keep-alive connection */
 116     volatile boolean disableKeepAlive;/* keep-alive has been disabled for this
 117                                          connection - this will be used when
 118                                          recomputing the value of keepingAlive */
 119     int keepAliveConnections = -1;    /* number of keep-alives left */
 120 
 121     /**Idle timeout value, in milliseconds. Zero means infinity,
 122      * iff keepingAlive=true.
 123      * Unfortunately, we can't always believe this one.  If I'm connected
 124      * through a Netscape proxy to a server that sent me a keep-alive
 125      * time of 15 sec, the proxy unilaterally terminates my connection
 126      * after 5 sec.  So we have to hard code our effective timeout to
 127      * 4 sec for the case where we're using a proxy. *SIGH*
 128      */
 129     int keepAliveTimeout = 0;
 130 
 131     /** whether the response is to be cached */
 132     private CacheRequest cacheRequest = null;
 133 
 134     /** Url being fetched. */
 135     protected URL       url;
 136 
 137     /* if set, the client will be reused and must not be put in cache */
 138     public boolean reuse = false;
 139 
 140     // Traffic capture tool, if configured. See HttpCapture class for info
 141     private HttpCapture capture = null;
 142 
 143     private static final PlatformLogger logger = HttpURLConnection.getHttpLogger();
 144     private static void logFinest(String msg) {
 145         if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
 146             logger.finest(msg);
 147         }
 148     }
 149 
 150     protected volatile String authenticatorKey;
 151 
 152     /**
 153      * A NOP method kept for backwards binary compatibility
 154      * @deprecated -- system properties are no longer cached.
 155      */
 156     @Deprecated
 157     public static synchronized void resetProperties() {
 158     }
 159 
 160     int getKeepAliveTimeout() {
 161         return keepAliveTimeout;
 162     }
 163 
 164     static {
 165         Properties props = GetPropertyAction.privilegedGetProperties();
 166         String keepAlive = props.getProperty("http.keepAlive");
 167         String retryPost = props.getProperty("sun.net.http.retryPost");
 168         String cacheNTLM = props.getProperty("jdk.ntlm.cache");
 169         String cacheSPNEGO = props.getProperty("jdk.spnego.cache");
 170 
 171         if (keepAlive != null) {
 172             keepAliveProp = Boolean.parseBoolean(keepAlive);
 173         } else {
 174             keepAliveProp = true;
 175         }
 176 
 177         if (retryPost != null) {
 178             retryPostProp = Boolean.parseBoolean(retryPost);
 179         } else {
 180             retryPostProp = true;
 181         }
 182 
 183         if (cacheNTLM != null) {
 184             cacheNTLMProp = Boolean.parseBoolean(cacheNTLM);
 185         } else {
 186             cacheNTLMProp = true;
 187         }
 188 
 189         if (cacheSPNEGO != null) {
 190             cacheSPNEGOProp = Boolean.parseBoolean(cacheSPNEGO);
 191         } else {
 192             cacheSPNEGOProp = true;
 193         }
 194     }
 195 
 196     /**
 197      * @return true iff http keep alive is set (i.e. enabled).  Defaults
 198      *          to true if the system property http.keepAlive isn't set.
 199      */
 200     public boolean getHttpKeepAliveSet() {
 201         return keepAliveProp;
 202     }
 203 
 204 
 205     protected HttpClient() {
 206     }
 207 
 208     private HttpClient(URL url)
 209     throws IOException {
 210         this(url, (String)null, -1, false);
 211     }
 212 
 213     protected HttpClient(URL url,
 214                          boolean proxyDisabled) throws IOException {
 215         this(url, null, -1, proxyDisabled);
 216     }
 217 
 218     /* This package-only CTOR should only be used for FTP piggy-backed on HTTP
 219      * HTTP URL's that use this won't take advantage of keep-alive.
 220      * Additionally, this constructor may be used as a last resort when the
 221      * first HttpClient gotten through New() failed (probably b/c of a
 222      * Keep-Alive mismatch).
 223      *
 224      * XXX That documentation is wrong ... it's not package-private any more
 225      */
 226     public HttpClient(URL url, String proxyHost, int proxyPort)
 227     throws IOException {
 228         this(url, proxyHost, proxyPort, false);
 229     }
 230 
 231     protected HttpClient(URL url, Proxy p, int to) throws IOException {
 232         proxy = (p == null) ? Proxy.NO_PROXY : p;
 233         this.host = url.getHost();
 234         this.url = url;
 235         port = url.getPort();
 236         if (port == -1) {
 237             port = getDefaultPort();
 238         }
 239         setConnectTimeout(to);
 240 
 241         capture = HttpCapture.getCapture(url);
 242         openServer();
 243     }
 244 
 245     protected static Proxy newHttpProxy(String proxyHost, int proxyPort,
 246                                       String proto) {
 247         if (proxyHost == null || proto == null)
 248             return Proxy.NO_PROXY;
 249         int pport = proxyPort < 0 ? getDefaultPort(proto) : proxyPort;
 250         InetSocketAddress saddr = InetSocketAddress.createUnresolved(proxyHost, pport);
 251         return new Proxy(Proxy.Type.HTTP, saddr);
 252     }
 253 
 254     /*
 255      * This constructor gives "ultimate" flexibility, including the ability
 256      * to bypass implicit proxying.  Sometimes we need to be using tunneling
 257      * (transport or network level) instead of proxying (application level),
 258      * for example when we don't want the application level data to become
 259      * visible to third parties.
 260      *
 261      * @param url               the URL to which we're connecting
 262      * @param proxy             proxy to use for this URL (e.g. forwarding)
 263      * @param proxyPort         proxy port to use for this URL
 264      * @param proxyDisabled     true to disable default proxying
 265      */
 266     private HttpClient(URL url, String proxyHost, int proxyPort,
 267                        boolean proxyDisabled)
 268         throws IOException {
 269         this(url, proxyDisabled ? Proxy.NO_PROXY :
 270              newHttpProxy(proxyHost, proxyPort, "http"), -1);
 271     }
 272 
 273     public HttpClient(URL url, String proxyHost, int proxyPort,
 274                        boolean proxyDisabled, int to)
 275         throws IOException {
 276         this(url, proxyDisabled ? Proxy.NO_PROXY :
 277              newHttpProxy(proxyHost, proxyPort, "http"), to);
 278     }
 279 
 280     /* This class has no public constructor for HTTP.  This method is used to
 281      * get an HttpClient to the specified URL.  If there's currently an
 282      * active HttpClient to that server/port, you'll get that one.
 283      */
 284     public static HttpClient New(URL url)
 285     throws IOException {
 286         return HttpClient.New(url, Proxy.NO_PROXY, -1, true, null);
 287     }
 288 
 289     public static HttpClient New(URL url, boolean useCache)
 290         throws IOException {
 291         return HttpClient.New(url, Proxy.NO_PROXY, -1, useCache, null);
 292     }
 293 
 294     public static HttpClient New(URL url, Proxy p, int to, boolean useCache,
 295         HttpURLConnection httpuc) throws IOException
 296     {
 297         if (p == null) {
 298             p = Proxy.NO_PROXY;
 299         }
 300         HttpClient ret = null;
 301         /* see if one's already around */
 302         if (useCache) {
 303             ret = kac.get(url, null);
 304             if (ret != null && httpuc != null &&
 305                 httpuc.streaming() &&
 306                 httpuc.getRequestMethod() == "POST") {
 307                 if (!ret.available()) {
 308                     ret.inCache = false;
 309                     ret.closeServer();
 310                     ret = null;
 311                 }
 312             }
 313             if (ret != null) {
 314                 String ak = httpuc == null ? AuthenticatorKeys.DEFAULT
 315                      : httpuc.getAuthenticatorKey();
 316                 boolean compatible = Objects.equals(ret.proxy, p)
 317                      && Objects.equals(ret.getAuthenticatorKey(), ak);
 318                 if (compatible) {
 319                     synchronized (ret) {
 320                         ret.cachedHttpClient = true;
 321                         assert ret.inCache;
 322                         ret.inCache = false;
 323                         if (httpuc != null && ret.needsTunneling())
 324                             httpuc.setTunnelState(TUNNELING);
 325                         logFinest("KeepAlive stream retrieved from the cache, " + ret);
 326                     }
 327                 } else {
 328                     // We cannot return this connection to the cache as it's
 329                     // KeepAliveTimeout will get reset. We simply close the connection.
 330                     // This should be fine as it is very rare that a connection
 331                     // to the same host will not use the same proxy.
 332                     synchronized(ret) {
 333                         ret.inCache = false;
 334                         ret.closeServer();
 335                     }
 336                     ret = null;
 337                 }
 338             }
 339         }
 340         if (ret == null) {
 341             ret = new HttpClient(url, p, to);
 342             if (httpuc != null) {
 343                 ret.authenticatorKey = httpuc.getAuthenticatorKey();
 344             }
 345         } else {
 346             SecurityManager security = System.getSecurityManager();
 347             if (security != null) {
 348                 if (ret.proxy == Proxy.NO_PROXY || ret.proxy == null) {
 349                     security.checkConnect(InetAddress.getByName(url.getHost()).getHostAddress(), url.getPort());
 350                 } else {
 351                     security.checkConnect(url.getHost(), url.getPort());
 352                 }
 353             }
 354             ret.url = url;
 355         }
 356         return ret;
 357     }
 358 
 359     public static HttpClient New(URL url, Proxy p, int to,
 360         HttpURLConnection httpuc) throws IOException
 361     {
 362         return New(url, p, to, true, httpuc);
 363     }
 364 
 365     public static HttpClient New(URL url, String proxyHost, int proxyPort,
 366                                  boolean useCache)
 367         throws IOException {
 368         return New(url, newHttpProxy(proxyHost, proxyPort, "http"),
 369             -1, useCache, null);
 370     }
 371 
 372     public static HttpClient New(URL url, String proxyHost, int proxyPort,
 373                                  boolean useCache, int to,
 374                                  HttpURLConnection httpuc)
 375         throws IOException {
 376         return New(url, newHttpProxy(proxyHost, proxyPort, "http"),
 377             to, useCache, httpuc);
 378     }
 379 
 380     public final String getAuthenticatorKey() {
 381         String k = authenticatorKey;
 382         if (k == null) return AuthenticatorKeys.DEFAULT;
 383         return k;
 384     }
 385 
 386     /* return it to the cache as still usable, if:
 387      * 1) It's keeping alive, AND
 388      * 2) It still has some connections left, AND
 389      * 3) It hasn't had a error (PrintStream.checkError())
 390      * 4) It hasn't timed out
 391      *
 392      * If this client is not keepingAlive, it should have been
 393      * removed from the cache in the parseHeaders() method.
 394      */
 395 
 396     public void finished() {
 397         if (reuse) /* will be reused */
 398             return;
 399         keepAliveConnections--;
 400         poster = null;
 401         if (keepAliveConnections > 0 && isKeepingAlive() &&
 402                !(serverOutput.checkError())) {
 403             /* This connection is keepingAlive && still valid.
 404              * Return it to the cache.
 405              */
 406             putInKeepAliveCache();
 407         } else {
 408             closeServer();
 409         }
 410     }
 411 
 412     protected synchronized boolean available() {
 413         boolean available = true;
 414         int old = -1;
 415 
 416         try {
 417             try {
 418                 old = serverSocket.getSoTimeout();
 419                 serverSocket.setSoTimeout(1);
 420                 BufferedInputStream tmpbuf =
 421                         new BufferedInputStream(serverSocket.getInputStream());
 422                 int r = tmpbuf.read();
 423                 if (r == -1) {
 424                     logFinest("HttpClient.available(): " +
 425                             "read returned -1: not available");
 426                     available = false;
 427                 }
 428             } catch (SocketTimeoutException e) {
 429                 logFinest("HttpClient.available(): " +
 430                         "SocketTimeout: its available");
 431             } finally {
 432                 if (old != -1)
 433                     serverSocket.setSoTimeout(old);
 434             }
 435         } catch (IOException e) {
 436             logFinest("HttpClient.available(): " +
 437                         "SocketException: not available");
 438             available = false;
 439         }
 440         return available;
 441     }
 442 
 443     protected synchronized void putInKeepAliveCache() {
 444         if (inCache) {
 445             assert false : "Duplicate put to keep alive cache";
 446             return;
 447         }
 448         inCache = true;
 449         kac.put(url, null, this);
 450     }
 451 
 452     protected synchronized boolean isInKeepAliveCache() {
 453         return inCache;
 454     }
 455 
 456     /*
 457      * Close an idle connection to this URL (if it exists in the
 458      * cache).
 459      */
 460     public void closeIdleConnection() {
 461         HttpClient http = kac.get(url, null);
 462         if (http != null) {
 463             http.closeServer();
 464         }
 465     }
 466 
 467     /* We're very particular here about what our InputStream to the server
 468      * looks like for reasons that are apparent if you can decipher the
 469      * method parseHTTP().  That's why this method is overidden from the
 470      * superclass.
 471      */
 472     @Override
 473     public void openServer(String server, int port) throws IOException {
 474         serverSocket = doConnect(server, port);
 475         try {
 476             OutputStream out = serverSocket.getOutputStream();
 477             if (capture != null) {
 478                 out = new HttpCaptureOutputStream(out, capture);
 479             }
 480             serverOutput = new PrintStream(
 481                 new BufferedOutputStream(out),
 482                                          false, encoding);
 483         } catch (UnsupportedEncodingException e) {
 484             throw new InternalError(encoding+" encoding not found", e);
 485         }
 486         serverSocket.setTcpNoDelay(true);
 487     }
 488 
 489     /*
 490      * Returns true if the http request should be tunneled through proxy.
 491      * An example where this is the case is Https.
 492      */
 493     public boolean needsTunneling() {
 494         return false;
 495     }
 496 
 497     /*
 498      * Returns true if this httpclient is from cache
 499      */
 500     public synchronized boolean isCachedConnection() {
 501         return cachedHttpClient;
 502     }
 503 
 504     /*
 505      * Finish any work left after the socket connection is
 506      * established.  In the normal http case, it's a NO-OP. Subclass
 507      * may need to override this. An example is Https, where for
 508      * direct connection to the origin server, ssl handshake needs to
 509      * be done; for proxy tunneling, the socket needs to be converted
 510      * into an SSL socket before ssl handshake can take place.
 511      */
 512     public void afterConnect() throws IOException, UnknownHostException {
 513         // NO-OP. Needs to be overwritten by HttpsClient
 514     }
 515 
 516     /*
 517      * call openServer in a privileged block
 518      */
 519     private synchronized void privilegedOpenServer(final InetSocketAddress server)
 520          throws IOException
 521     {
 522         try {
 523             java.security.AccessController.doPrivileged(
 524                 new java.security.PrivilegedExceptionAction<>() {
 525                     public Void run() throws IOException {
 526                     openServer(server.getHostString(), server.getPort());
 527                     return null;
 528                 }
 529             });
 530         } catch (java.security.PrivilegedActionException pae) {
 531             throw (IOException) pae.getException();
 532         }
 533     }
 534 
 535     /*
 536      * call super.openServer
 537      */
 538     private void superOpenServer(final String proxyHost,
 539                                  final int proxyPort)
 540         throws IOException, UnknownHostException
 541     {
 542         super.openServer(proxyHost, proxyPort);
 543     }
 544 
 545     /*
 546      */
 547     protected synchronized void openServer() throws IOException {
 548 
 549         SecurityManager security = System.getSecurityManager();
 550 
 551         if (security != null) {
 552             security.checkConnect(host, port);
 553         }
 554 
 555         if (keepingAlive) { // already opened
 556             return;
 557         }
 558 
 559         if (url.getProtocol().equals("http") ||
 560             url.getProtocol().equals("https") ) {
 561 
 562             if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
 563                 sun.net.www.URLConnection.setProxiedHost(host);
 564                 privilegedOpenServer((InetSocketAddress) proxy.address());
 565                 usingProxy = true;
 566                 return;
 567             } else {
 568                 // make direct connection
 569                 openServer(host, port);
 570                 usingProxy = false;
 571                 return;
 572             }
 573 
 574         } else {
 575             /* we're opening some other kind of url, most likely an
 576              * ftp url.
 577              */
 578             if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) {
 579                 sun.net.www.URLConnection.setProxiedHost(host);
 580                 privilegedOpenServer((InetSocketAddress) proxy.address());
 581                 usingProxy = true;
 582                 return;
 583             } else {
 584                 // make direct connection
 585                 super.openServer(host, port);
 586                 usingProxy = false;
 587                 return;
 588             }
 589         }
 590     }
 591 
 592     public String getURLFile() throws IOException {
 593 
 594         String fileName;
 595 
 596         /**
 597          * proxyDisabled is set by subclass HttpsClient!
 598          */
 599         if (usingProxy && !proxyDisabled) {
 600             // Do not use URLStreamHandler.toExternalForm as the fragment
 601             // should not be part of the RequestURI. It should be an
 602             // absolute URI which does not have a fragment part.
 603             StringBuilder result = new StringBuilder(128);
 604             result.append(url.getProtocol());
 605             result.append(":");
 606             if (url.getAuthority() != null && !url.getAuthority().isEmpty()) {
 607                 result.append("//");
 608                 result.append(url.getAuthority());
 609             }
 610             if (url.getPath() != null) {
 611                 result.append(url.getPath());
 612             }
 613             if (url.getQuery() != null) {
 614                 result.append('?');
 615                 result.append(url.getQuery());
 616             }
 617 
 618             fileName = result.toString();
 619         } else {
 620             fileName = url.getFile();
 621 
 622             if ((fileName == null) || (fileName.isEmpty())) {
 623                 fileName = "/";
 624             } else if (fileName.charAt(0) == '?') {
 625                 /* HTTP/1.1 spec says in 5.1.2. about Request-URI:
 626                  * "Note that the absolute path cannot be empty; if
 627                  * none is present in the original URI, it MUST be
 628                  * given as "/" (the server root)."  So if the file
 629                  * name here has only a query string, the path is
 630                  * empty and we also have to add a "/".
 631                  */
 632                 fileName = "/" + fileName;
 633             }
 634         }
 635 
 636         if (fileName.indexOf('\n') == -1)
 637             return fileName;
 638         else
 639             throw new java.net.MalformedURLException("Illegal character in URL");
 640     }
 641 
 642     /**
 643      * @deprecated
 644      */
 645     @Deprecated
 646     public void writeRequests(MessageHeader head) {
 647         requests = head;
 648         requests.print(serverOutput);
 649         serverOutput.flush();
 650     }
 651 
 652     public void writeRequests(MessageHeader head,
 653                               PosterOutputStream pos) throws IOException {
 654         requests = head;
 655         requests.print(serverOutput);
 656         poster = pos;
 657         if (poster != null)
 658             poster.writeTo(serverOutput);
 659         serverOutput.flush();
 660     }
 661 
 662     public void writeRequests(MessageHeader head,
 663                               PosterOutputStream pos,
 664                               boolean streaming) throws IOException {
 665         this.streaming = streaming;
 666         writeRequests(head, pos);
 667     }
 668 
 669     /** Parse the first line of the HTTP request.  It usually looks
 670         something like: {@literal "HTTP/1.0 <number> comment\r\n"}. */
 671 
 672     public boolean parseHTTP(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
 673     throws IOException {
 674         /* If "HTTP/*" is found in the beginning, return true.  Let
 675          * HttpURLConnection parse the mime header itself.
 676          *
 677          * If this isn't valid HTTP, then we don't try to parse a header
 678          * out of the beginning of the response into the responses,
 679          * and instead just queue up the output stream to it's very beginning.
 680          * This seems most reasonable, and is what the NN browser does.
 681          */
 682 
 683         try {
 684             serverInput = serverSocket.getInputStream();
 685             if (capture != null) {
 686                 serverInput = new HttpCaptureInputStream(serverInput, capture);
 687             }
 688             serverInput = new BufferedInputStream(serverInput);
 689             return (parseHTTPHeader(responses, pi, httpuc));
 690         } catch (SocketTimeoutException stex) {
 691             // We don't want to retry the request when the app. sets a timeout
 692             // but don't close the server if timeout while waiting for 100-continue
 693             if (ignoreContinue) {
 694                 closeServer();
 695             }
 696             throw stex;
 697         } catch (IOException e) {
 698             closeServer();
 699             cachedHttpClient = false;
 700             if (!failedOnce && requests != null) {
 701                 failedOnce = true;
 702                 if (getRequestMethod().equals("CONNECT")
 703                     || streaming
 704                     || (httpuc.getRequestMethod().equals("POST")
 705                         && !retryPostProp)) {
 706                     // do not retry the request
 707                 }  else {
 708                     // try once more
 709                     openServer();
 710                     if (needsTunneling()) {
 711                         MessageHeader origRequests = requests;
 712                         httpuc.doTunneling();
 713                         requests = origRequests;
 714                     }
 715                     afterConnect();
 716                     writeRequests(requests, poster);
 717                     return parseHTTP(responses, pi, httpuc);
 718                 }
 719             }
 720             throw e;
 721         }
 722 
 723     }
 724 
 725     private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
 726     throws IOException {
 727         /* If "HTTP/*" is found in the beginning, return true.  Let
 728          * HttpURLConnection parse the mime header itself.
 729          *
 730          * If this isn't valid HTTP, then we don't try to parse a header
 731          * out of the beginning of the response into the responses,
 732          * and instead just queue up the output stream to it's very beginning.
 733          * This seems most reasonable, and is what the NN browser does.
 734          */
 735 
 736         keepAliveConnections = -1;
 737         keepAliveTimeout = 0;
 738 
 739         boolean ret = false;
 740         byte[] b = new byte[8];
 741 
 742         try {
 743             int nread = 0;
 744             serverInput.mark(10);
 745             while (nread < 8) {
 746                 int r = serverInput.read(b, nread, 8 - nread);
 747                 if (r < 0) {
 748                     break;
 749                 }
 750                 nread += r;
 751             }
 752             String keep=null;
 753             String authenticate=null;
 754             ret = b[0] == 'H' && b[1] == 'T'
 755                     && b[2] == 'T' && b[3] == 'P' && b[4] == '/' &&
 756                 b[5] == '1' && b[6] == '.';
 757             serverInput.reset();
 758             if (ret) { // is valid HTTP - response started w/ "HTTP/1."
 759                 responses.parseHeader(serverInput);
 760 
 761                 // we've finished parsing http headers
 762                 // check if there are any applicable cookies to set (in cache)
 763                 CookieHandler cookieHandler = httpuc.getCookieHandler();
 764                 if (cookieHandler != null) {
 765                     URI uri = ParseUtil.toURI(url);
 766                     // NOTE: That cast from Map shouldn't be necessary but
 767                     // a bug in javac is triggered under certain circumstances
 768                     // So we do put the cast in as a workaround until
 769                     // it is resolved.
 770                     if (uri != null)
 771                         cookieHandler.put(uri, responses.getHeaders());
 772                 }
 773 
 774                 /* decide if we're keeping alive:
 775                  * This is a bit tricky.  There's a spec, but most current
 776                  * servers (10/1/96) that support this differ in dialects.
 777                  * If the server/client misunderstand each other, the
 778                  * protocol should fall back onto HTTP/1.0, no keep-alive.
 779                  */
 780                 if (usingProxy) { // not likely a proxy will return this
 781                     keep = responses.findValue("Proxy-Connection");
 782                     authenticate = responses.findValue("Proxy-Authenticate");
 783                 }
 784                 if (keep == null) {
 785                     keep = responses.findValue("Connection");
 786                     authenticate = responses.findValue("WWW-Authenticate");
 787                 }
 788 
 789                 // 'disableKeepAlive' starts with the value false.
 790                 // It can transition from false to true, but once true
 791                 // it stays true.
 792                 // If cacheNTLMProp is false, and disableKeepAlive is false,
 793                 // then we need to examine the response headers to figure out
 794                 // whether we are doing NTLM authentication. If we do NTLM,
 795                 // and cacheNTLMProp is false, than we can't keep this connection
 796                 // alive: we will switch disableKeepAlive to true.
 797                 boolean canKeepAlive = !disableKeepAlive;
 798                 if (canKeepAlive && (cacheNTLMProp == false || cacheSPNEGOProp == false)
 799                         && authenticate != null) {
 800                     authenticate = authenticate.toLowerCase(Locale.US);
 801                     if (cacheNTLMProp == false) {
 802                         canKeepAlive &= !authenticate.startsWith("ntlm ");
 803                     }
 804                     if (cacheSPNEGOProp == false) {
 805                         canKeepAlive &= !authenticate.startsWith("negotiate ");
 806                         canKeepAlive &= !authenticate.startsWith("kerberos ");
 807                     }
 808                 }
 809                 disableKeepAlive |= !canKeepAlive;
 810 
 811                 if (keep != null && keep.toLowerCase(Locale.US).equals("keep-alive")) {
 812                     /* some servers, notably Apache1.1, send something like:
 813                      * "Keep-Alive: timeout=15, max=1" which we should respect.
 814                      */
 815                     if (disableKeepAlive) {
 816                         keepAliveConnections = 1;
 817                     } else {
 818                         HeaderParser p = new HeaderParser(
 819                             responses.findValue("Keep-Alive"));
 820                         /* default should be larger in case of proxy */
 821                         keepAliveConnections = p.findInt("max", usingProxy?50:5);
 822                         keepAliveTimeout = p.findInt("timeout", usingProxy?60:5);
 823                     }
 824                 } else if (b[7] != '0') {
 825                     /*
 826                      * We're talking 1.1 or later. Keep persistent until
 827                      * the server says to close.
 828                      */
 829                     if (keep != null || disableKeepAlive) {
 830                         /*
 831                          * The only Connection token we understand is close.
 832                          * Paranoia: if there is any Connection header then
 833                          * treat as non-persistent.
 834                          */
 835                         keepAliveConnections = 1;
 836                     } else {
 837                         keepAliveConnections = 5;
 838                     }
 839                 }
 840             } else if (nread != 8) {
 841                 if (!failedOnce && requests != null) {
 842                     failedOnce = true;
 843                     if (getRequestMethod().equals("CONNECT")
 844                         || streaming
 845                         || (httpuc.getRequestMethod().equals("POST")
 846                             && !retryPostProp)) {
 847                         // do not retry the request
 848                     } else {
 849                         closeServer();
 850                         cachedHttpClient = false;
 851                         openServer();
 852                         if (needsTunneling()) {
 853                             MessageHeader origRequests = requests;
 854                             httpuc.doTunneling();
 855                             requests = origRequests;
 856                         }
 857                         afterConnect();
 858                         writeRequests(requests, poster);
 859                         return parseHTTP(responses, pi, httpuc);
 860                     }
 861                 }
 862                 throw new SocketException("Unexpected end of file from server");
 863             } else {
 864                 // we can't vouche for what this is....
 865                 responses.set("Content-type", "unknown/unknown");
 866             }
 867         } catch (IOException e) {
 868             throw e;
 869         }
 870 
 871         int code = -1;
 872         try {
 873             String resp;
 874             resp = responses.getValue(0);
 875             /* should have no leading/trailing LWS
 876              * expedite the typical case by assuming it has
 877              * form "HTTP/1.x <WS> 2XX <mumble>"
 878              */
 879             int ind;
 880             ind = resp.indexOf(' ');
 881             while(resp.charAt(ind) == ' ')
 882                 ind++;
 883             code = Integer.parseInt(resp, ind, ind + 3, 10);
 884         } catch (Exception e) {}
 885 
 886         if (code == HTTP_CONTINUE && ignoreContinue) {
 887             responses.reset();
 888             return parseHTTPHeader(responses, pi, httpuc);
 889         }
 890 
 891         long cl = -1;
 892 
 893         /*
 894          * Set things up to parse the entity body of the reply.
 895          * We should be smarter about avoid pointless work when
 896          * the HTTP method and response code indicate there will be
 897          * no entity body to parse.
 898          */
 899         String te = responses.findValue("Transfer-Encoding");
 900         if (te != null && te.equalsIgnoreCase("chunked")) {
 901             serverInput = new ChunkedInputStream(serverInput, this, responses);
 902 
 903             /*
 904              * If keep alive not specified then close after the stream
 905              * has completed.
 906              */
 907             if (keepAliveConnections <= 1) {
 908                 keepAliveConnections = 1;
 909                 keepingAlive = false;
 910             } else {
 911                 keepingAlive = !disableKeepAlive;
 912             }
 913             failedOnce = false;
 914         } else {
 915 
 916             /*
 917              * If it's a keep alive connection then we will keep
 918              * (alive if :-
 919              * 1. content-length is specified, or
 920              * 2. "Not-Modified" or "No-Content" responses - RFC 2616 states that
 921              *    204 or 304 response must not include a message body.
 922              */
 923             String cls = responses.findValue("content-length");
 924             if (cls != null) {
 925                 try {
 926                     cl = Long.parseLong(cls);
 927                 } catch (NumberFormatException e) {
 928                     cl = -1;
 929                 }
 930             }
 931             String requestLine = requests.getKey(0);
 932 
 933             if ((requestLine != null &&
 934                  (requestLine.startsWith("HEAD"))) ||
 935                 code == HttpURLConnection.HTTP_NOT_MODIFIED ||
 936                 code == HttpURLConnection.HTTP_NO_CONTENT) {
 937                 cl = 0;
 938             }
 939 
 940             if (keepAliveConnections > 1 &&
 941                 (cl >= 0 ||
 942                  code == HttpURLConnection.HTTP_NOT_MODIFIED ||
 943                  code == HttpURLConnection.HTTP_NO_CONTENT)) {
 944                 keepingAlive = !disableKeepAlive;
 945                 failedOnce = false;
 946             } else if (keepingAlive) {
 947                 /* Previously we were keeping alive, and now we're not.  Remove
 948                  * this from the cache (but only here, once) - otherwise we get
 949                  * multiple removes and the cache count gets messed up.
 950                  */
 951                 keepingAlive=false;
 952             }
 953         }
 954 
 955         /* wrap a KeepAliveStream/MeteredStream around it if appropriate */
 956 
 957         if (cl > 0) {
 958             // In this case, content length is well known, so it is okay
 959             // to wrap the input stream with KeepAliveStream/MeteredStream.
 960 
 961             if (pi != null) {
 962                 // Progress monitor is enabled
 963                 pi.setContentType(responses.findValue("content-type"));
 964             }
 965 
 966             // If disableKeepAlive == true, the client will not be returned
 967             // to the cache. But we still need to use a keepalive stream to
 968             // allow the multi-message authentication exchange on the connection
 969             boolean useKeepAliveStream = isKeepingAlive() || disableKeepAlive;
 970             if (useKeepAliveStream)   {
 971                 // Wrap KeepAliveStream if keep alive is enabled.
 972                 logFinest("KeepAlive stream used: " + url);
 973                 serverInput = new KeepAliveStream(serverInput, pi, cl, this);
 974                 failedOnce = false;
 975             }
 976             else        {
 977                 serverInput = new MeteredStream(serverInput, pi, cl);
 978             }
 979         }
 980         else if (cl == -1)  {
 981             // In this case, content length is unknown - the input
 982             // stream would simply be a regular InputStream or
 983             // ChunkedInputStream.
 984 
 985             if (pi != null) {
 986                 // Progress monitoring is enabled.
 987 
 988                 pi.setContentType(responses.findValue("content-type"));
 989 
 990                 // Wrap MeteredStream for tracking indeterministic
 991                 // progress, even if the input stream is ChunkedInputStream.
 992                 serverInput = new MeteredStream(serverInput, pi, cl);
 993             }
 994             else    {
 995                 // Progress monitoring is disabled, and there is no
 996                 // need to wrap an unknown length input stream.
 997 
 998                 // ** This is an no-op **
 999             }
1000         }
1001         else    {
1002             if (pi != null)
1003                 pi.finishTracking();
1004         }
1005 
1006         return ret;
1007     }
1008 
1009     public synchronized InputStream getInputStream() {
1010         return serverInput;
1011     }
1012 
1013     public OutputStream getOutputStream() {
1014         return serverOutput;
1015     }
1016 
1017     @Override
1018     public String toString() {
1019         return getClass().getName()+"("+url+")";
1020     }
1021 
1022     public final boolean isKeepingAlive() {
1023         return getHttpKeepAliveSet() && keepingAlive;
1024     }
1025 
1026     public void setCacheRequest(CacheRequest cacheRequest) {
1027         this.cacheRequest = cacheRequest;
1028     }
1029 
1030     CacheRequest getCacheRequest() {
1031         return cacheRequest;
1032     }
1033 
1034     String getRequestMethod() {
1035         if (requests != null) {
1036             String requestLine = requests.getKey(0);
1037             if (requestLine != null) {
1038                return requestLine.split("\\s+")[0];
1039             }
1040         }
1041         return "";
1042     }
1043 
1044     public void setDoNotRetry(boolean value) {
1045         // failedOnce is used to determine if a request should be retried.
1046         failedOnce = value;
1047     }
1048 
1049     public void setIgnoreContinue(boolean value) {
1050         ignoreContinue = value;
1051     }
1052 
1053     /* Use only on connections in error. */
1054     @Override
1055     public void closeServer() {
1056         try {
1057             keepingAlive = false;
1058             serverSocket.close();
1059         } catch (Exception e) {}
1060     }
1061 
1062     /**
1063      * @return the proxy host being used for this client, or null
1064      *          if we're not going through a proxy
1065      */
1066     public String getProxyHostUsed() {
1067         if (!usingProxy) {
1068             return null;
1069         } else {
1070             return ((InetSocketAddress)proxy.address()).getHostString();
1071         }
1072     }
1073 
1074     /**
1075      * @return the proxy port being used for this client.  Meaningless
1076      *          if getProxyHostUsed() gives null.
1077      */
1078     public int getProxyPortUsed() {
1079         if (usingProxy)
1080             return ((InetSocketAddress)proxy.address()).getPort();
1081         return -1;
1082     }
1083 }