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