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