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                 Thread thread = Thread.currentThread();
 739                 boolean doNotRetry = thread.isVirtual() && thread.isInterrupted();
 740                 failedOnce = true;
 741                 if (getRequestMethod().equals("CONNECT")
 742                     || streaming
 743                     || doNotRetry
 744                     || (httpuc.getRequestMethod().equals("POST")
 745                         && !retryPostProp)) {
 746                     // do not retry the request
 747                 }  else {
 748                     // try once more
 749                     openServer();
 750                     checkTunneling(httpuc);
 751                     afterConnect();
 752                     writeRequests(requests, poster);
 753                     return parseHTTP(responses, pi, httpuc);
 754                 }
 755             }
 756             throw e;
 757         }
 758 
 759     }
 760 
 761     // Check whether tunnel must be open and open it if necessary
 762     // (in the case of HTTPS with proxy)
 763     private void checkTunneling(HttpURLConnection httpuc) throws IOException {
 764         if (needsTunneling()) {
 765             MessageHeader origRequests = requests;
 766             PosterOutputStream origPoster = poster;
 767             httpuc.doTunneling();
 768             requests = origRequests;
 769             poster = origPoster;
 770         }
 771     }
 772 
 773     private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
 774     throws IOException {
 775         /* If "HTTP/*" is found in the beginning, return true.  Let
 776          * HttpURLConnection parse the mime header itself.
 777          *
 778          * If this isn't valid HTTP, then we don't try to parse a header
 779          * out of the beginning of the response into the responses,
 780          * and instead just queue up the output stream to it's very beginning.
 781          * This seems most reasonable, and is what the NN browser does.
 782          */
 783 
 784         keepAliveConnections = -1;
 785         keepAliveTimeout = 0;
 786 
 787         boolean ret = false;
 788         byte[] b = new byte[8];
 789 
 790         try {
 791             int nread = 0;
 792             serverInput.mark(10);
 793             while (nread < 8) {
 794                 int r = serverInput.read(b, nread, 8 - nread);
 795                 if (r < 0) {
 796                     break;
 797                 }
 798                 nread += r;
 799             }
 800             String keep=null;
 801             String authenticate=null;
 802             ret = b[0] == 'H' && b[1] == 'T'
 803                     && b[2] == 'T' && b[3] == 'P' && b[4] == '/' &&
 804                 b[5] == '1' && b[6] == '.';
 805             serverInput.reset();
 806             if (ret) { // is valid HTTP - response started w/ "HTTP/1."
 807                 responses.parseHeader(serverInput);
 808 
 809                 // we've finished parsing http headers
 810                 // check if there are any applicable cookies to set (in cache)
 811                 CookieHandler cookieHandler = httpuc.getCookieHandler();
 812                 if (cookieHandler != null) {
 813                     URI uri = ParseUtil.toURI(url);
 814                     // NOTE: That cast from Map shouldn't be necessary but
 815                     // a bug in javac is triggered under certain circumstances
 816                     // So we do put the cast in as a workaround until
 817                     // it is resolved.
 818                     if (uri != null)
 819                         cookieHandler.put(uri, responses.getHeaders());
 820                 }
 821 
 822                 /* decide if we're keeping alive:
 823                  * This is a bit tricky.  There's a spec, but most current
 824                  * servers (10/1/96) that support this differ in dialects.
 825                  * If the server/client misunderstand each other, the
 826                  * protocol should fall back onto HTTP/1.0, no keep-alive.
 827                  */
 828                 if (usingProxy) { // not likely a proxy will return this
 829                     keep = responses.findValue("Proxy-Connection");
 830                     authenticate = responses.findValue("Proxy-Authenticate");
 831                 }
 832                 if (keep == null) {
 833                     keep = responses.findValue("Connection");
 834                     authenticate = responses.findValue("WWW-Authenticate");
 835                 }
 836 
 837                 // 'disableKeepAlive' starts with the value false.
 838                 // It can transition from false to true, but once true
 839                 // it stays true.
 840                 // If cacheNTLMProp is false, and disableKeepAlive is false,
 841                 // then we need to examine the response headers to figure out
 842                 // whether we are doing NTLM authentication. If we do NTLM,
 843                 // and cacheNTLMProp is false, than we can't keep this connection
 844                 // alive: we will switch disableKeepAlive to true.
 845                 boolean canKeepAlive = !disableKeepAlive;
 846                 if (canKeepAlive && (cacheNTLMProp == false || cacheSPNEGOProp == false)
 847                         && authenticate != null) {
 848                     authenticate = authenticate.toLowerCase(Locale.US);
 849                     if (cacheNTLMProp == false) {
 850                         canKeepAlive &= !authenticate.startsWith("ntlm ");
 851                     }
 852                     if (cacheSPNEGOProp == false) {
 853                         canKeepAlive &= !authenticate.startsWith("negotiate ");
 854                         canKeepAlive &= !authenticate.startsWith("kerberos ");
 855                     }
 856                 }
 857                 disableKeepAlive |= !canKeepAlive;
 858 
 859                 if (keep != null && keep.toLowerCase(Locale.US).equals("keep-alive")) {
 860                     /* some servers, notably Apache1.1, send something like:
 861                      * "Keep-Alive: timeout=15, max=1" which we should respect.
 862                      */
 863                     if (disableKeepAlive) {
 864                         keepAliveConnections = 1;
 865                     } else {
 866                         HeaderParser p = new HeaderParser(
 867                             responses.findValue("Keep-Alive"));
 868                         /* default should be larger in case of proxy */
 869                         keepAliveConnections = p.findInt("max", usingProxy?50:5);
 870                         keepAliveTimeout = p.findInt("timeout", usingProxy?60:5);
 871                     }
 872                 } else if (b[7] != '0') {
 873                     /*
 874                      * We're talking 1.1 or later. Keep persistent until
 875                      * the server says to close.
 876                      */
 877                     if (keep != null || disableKeepAlive) {
 878                         /*
 879                          * The only Connection token we understand is close.
 880                          * Paranoia: if there is any Connection header then
 881                          * treat as non-persistent.
 882                          */
 883                         keepAliveConnections = 1;
 884                     } else {
 885                         keepAliveConnections = 5;
 886                     }
 887                 }
 888             } else if (nread != 8) {
 889                 if (!failedOnce && requests != null) {
 890                     failedOnce = true;
 891                     if (getRequestMethod().equals("CONNECT")
 892                         || streaming
 893                         || (httpuc.getRequestMethod().equals("POST")
 894                             && !retryPostProp)) {
 895                         // do not retry the request
 896                     } else {
 897                         closeServer();
 898                         cachedHttpClient = false;
 899                         openServer();
 900                         checkTunneling(httpuc);
 901                         afterConnect();
 902                         writeRequests(requests, poster);
 903                         return parseHTTP(responses, pi, httpuc);
 904                     }
 905                 }
 906                 throw new SocketException("Unexpected end of file from server");
 907             } else {
 908                 // we can't vouche for what this is....
 909                 responses.set("Content-type", "unknown/unknown");
 910             }
 911         } catch (IOException e) {
 912             throw e;
 913         }
 914 
 915         int code = -1;
 916         try {
 917             String resp;
 918             resp = responses.getValue(0);
 919             /* should have no leading/trailing LWS
 920              * expedite the typical case by assuming it has
 921              * form "HTTP/1.x <WS> 2XX <mumble>"
 922              */
 923             int ind;
 924             ind = resp.indexOf(' ');
 925             while(resp.charAt(ind) == ' ')
 926                 ind++;
 927             code = Integer.parseInt(resp, ind, ind + 3, 10);
 928         } catch (Exception e) {}
 929 
 930         if (code == HTTP_CONTINUE && ignoreContinue) {
 931             responses.reset();
 932             return parseHTTPHeader(responses, pi, httpuc);
 933         }
 934 
 935         long cl = -1;
 936 
 937         /*
 938          * Set things up to parse the entity body of the reply.
 939          * We should be smarter about avoid pointless work when
 940          * the HTTP method and response code indicate there will be
 941          * no entity body to parse.
 942          */
 943         String te = responses.findValue("Transfer-Encoding");
 944         if (te != null && te.equalsIgnoreCase("chunked")) {
 945             serverInput = new ChunkedInputStream(serverInput, this, responses);
 946 
 947             /*
 948              * If keep alive not specified then close after the stream
 949              * has completed.
 950              */
 951             if (keepAliveConnections <= 1) {
 952                 keepAliveConnections = 1;
 953                 keepingAlive = false;
 954             } else {
 955                 keepingAlive = !disableKeepAlive;
 956             }
 957             failedOnce = false;
 958         } else {
 959 
 960             /*
 961              * If it's a keep alive connection then we will keep
 962              * (alive if :-
 963              * 1. content-length is specified, or
 964              * 2. "Not-Modified" or "No-Content" responses - RFC 2616 states that
 965              *    204 or 304 response must not include a message body.
 966              */
 967             String cls = responses.findValue("content-length");
 968             if (cls != null) {
 969                 try {
 970                     cl = Long.parseLong(cls);
 971                 } catch (NumberFormatException e) {
 972                     cl = -1;
 973                 }
 974             }
 975             String requestLine = requests.getKey(0);
 976 
 977             if ((requestLine != null &&
 978                  (requestLine.startsWith("HEAD"))) ||
 979                 code == HttpURLConnection.HTTP_NOT_MODIFIED ||
 980                 code == HttpURLConnection.HTTP_NO_CONTENT) {
 981                 cl = 0;
 982             }
 983 
 984             if (keepAliveConnections > 1 &&
 985                 (cl >= 0 ||
 986                  code == HttpURLConnection.HTTP_NOT_MODIFIED ||
 987                  code == HttpURLConnection.HTTP_NO_CONTENT)) {
 988                 keepingAlive = !disableKeepAlive;
 989                 failedOnce = false;
 990             } else if (keepingAlive) {
 991                 /* Previously we were keeping alive, and now we're not.  Remove
 992                  * this from the cache (but only here, once) - otherwise we get
 993                  * multiple removes and the cache count gets messed up.
 994                  */
 995                 keepingAlive=false;
 996             }
 997         }
 998 
 999         /* wrap a KeepAliveStream/MeteredStream around it if appropriate */
1000 
1001         if (cl > 0) {
1002             // In this case, content length is well known, so it is okay
1003             // to wrap the input stream with KeepAliveStream/MeteredStream.
1004 
1005             if (pi != null) {
1006                 // Progress monitor is enabled
1007                 pi.setContentType(responses.findValue("content-type"));
1008             }
1009 
1010             // If disableKeepAlive == true, the client will not be returned
1011             // to the cache. But we still need to use a keepalive stream to
1012             // allow the multi-message authentication exchange on the connection
1013             boolean useKeepAliveStream = isKeepingAlive() || disableKeepAlive;
1014             if (useKeepAliveStream)   {
1015                 // Wrap KeepAliveStream if keep alive is enabled.
1016                 logFinest("KeepAlive stream used: " + url);
1017                 serverInput = new KeepAliveStream(serverInput, pi, cl, this);
1018                 failedOnce = false;
1019             }
1020             else        {
1021                 serverInput = new MeteredStream(serverInput, pi, cl);
1022             }
1023         }
1024         else if (cl == -1)  {
1025             // In this case, content length is unknown - the input
1026             // stream would simply be a regular InputStream or
1027             // ChunkedInputStream.
1028 
1029             if (pi != null) {
1030                 // Progress monitoring is enabled.
1031 
1032                 pi.setContentType(responses.findValue("content-type"));
1033 
1034                 // Wrap MeteredStream for tracking indeterministic
1035                 // progress, even if the input stream is ChunkedInputStream.
1036                 serverInput = new MeteredStream(serverInput, pi, cl);
1037             }
1038             else    {
1039                 // Progress monitoring is disabled, and there is no
1040                 // need to wrap an unknown length input stream.
1041 
1042                 // ** This is an no-op **
1043             }
1044         }
1045         else    {
1046             if (pi != null)
1047                 pi.finishTracking();
1048         }
1049 
1050         return ret;
1051     }
1052 
1053     public InputStream getInputStream() {
1054         lock();
1055         try {
1056             return serverInput;
1057         } finally {
1058             unlock();
1059         }
1060     }
1061 
1062     public OutputStream getOutputStream() {
1063         return serverOutput;
1064     }
1065 
1066     @Override
1067     public String toString() {
1068         return getClass().getName()+"("+url+")";
1069     }
1070 
1071     public final boolean isKeepingAlive() {
1072         return getHttpKeepAliveSet() && keepingAlive;
1073     }
1074 
1075     public void setCacheRequest(CacheRequest cacheRequest) {
1076         this.cacheRequest = cacheRequest;
1077     }
1078 
1079     CacheRequest getCacheRequest() {
1080         return cacheRequest;
1081     }
1082 
1083     String getRequestMethod() {
1084         if (requests != null) {
1085             String requestLine = requests.getKey(0);
1086             if (requestLine != null) {
1087                return requestLine.split("\\s+")[0];
1088             }
1089         }
1090         return "";
1091     }
1092 
1093     public void setDoNotRetry(boolean value) {
1094         // failedOnce is used to determine if a request should be retried.
1095         failedOnce = value;
1096     }
1097 
1098     public void setIgnoreContinue(boolean value) {
1099         ignoreContinue = value;
1100     }
1101 
1102     /* Use only on connections in error. */
1103     @Override
1104     public void closeServer() {
1105         try {
1106             keepingAlive = false;
1107             serverSocket.close();
1108         } catch (Exception e) {}
1109     }
1110 
1111     /**
1112      * @return the proxy host being used for this client, or null
1113      *          if we're not going through a proxy
1114      */
1115     public String getProxyHostUsed() {
1116         if (!usingProxy) {
1117             return null;
1118         } else {
1119             return ((InetSocketAddress)proxy.address()).getHostString();
1120         }
1121     }
1122 
1123     /**
1124      * @return the proxy port being used for this client.  Meaningless
1125      *          if getProxyHostUsed() gives null.
1126      */
1127     public int getProxyPortUsed() {
1128         if (usingProxy)
1129             return ((InetSocketAddress)proxy.address()).getPort();
1130         return -1;
1131     }
1132 
1133     public final void lock() {
1134         clientLock.lock();
1135     }
1136 
1137     public final void unlock() {
1138         clientLock.unlock();
1139     }
1140 }