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.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 specifed 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<Void>() {
 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 = url.getFile();
 553         if ((fileName == null) || (fileName.length() == 0))
 554             fileName = "/";
 555 
 556         /**
 557          * proxyDisabled is set by subclass HttpsClient!
 558          */
 559         if (usingProxy && !proxyDisabled) {
 560             // Do not use URLStreamHandler.toExternalForm as the fragment
 561             // should not be part of the RequestURI. It should be an
 562             // absolute URI which does not have a fragment part.
 563             StringBuffer result = new StringBuffer(128);
 564             result.append(url.getProtocol());
 565             result.append(":");
 566             if (url.getAuthority() != null && url.getAuthority().length() > 0) {
 567                 result.append("//");
 568                 result.append(url.getAuthority());
 569             }
 570             if (url.getPath() != null) {
 571                 result.append(url.getPath());
 572             }
 573             if (url.getQuery() != null) {
 574                 result.append('?');
 575                 result.append(url.getQuery());
 576             }
 577 
 578             fileName =  result.toString();
 579         }
 580         if (fileName.indexOf('\n') == -1)
 581             return fileName;
 582         else
 583             throw new java.net.MalformedURLException("Illegal character in URL");
 584     }
 585 
 586     /**
 587      * @deprecated
 588      */
 589     @Deprecated
 590     public void writeRequests(MessageHeader head) {
 591         requests = head;
 592         requests.print(serverOutput);
 593         serverOutput.flush();
 594     }
 595 
 596     public void writeRequests(MessageHeader head,
 597                               PosterOutputStream pos) throws IOException {
 598         requests = head;
 599         requests.print(serverOutput);
 600         poster = pos;
 601         if (poster != null)
 602             poster.writeTo(serverOutput);
 603         serverOutput.flush();
 604     }
 605 
 606     public void writeRequests(MessageHeader head,
 607                               PosterOutputStream pos,
 608                               boolean streaming) throws IOException {
 609         this.streaming = streaming;
 610         writeRequests(head, pos);
 611     }
 612 
 613     /** Parse the first line of the HTTP request.  It usually looks
 614         something like: "HTTP/1.0 <number> comment\r\n". */
 615 
 616     public boolean parseHTTP(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
 617     throws IOException {
 618         /* If "HTTP/*" is found in the beginning, return true.  Let
 619          * HttpURLConnection parse the mime header itself.
 620          *
 621          * If this isn't valid HTTP, then we don't try to parse a header
 622          * out of the beginning of the response into the responses,
 623          * and instead just queue up the output stream to it's very beginning.
 624          * This seems most reasonable, and is what the NN browser does.
 625          */
 626 
 627         try {
 628             serverInput = serverSocket.getInputStream();
 629             if (capture != null) {
 630                 serverInput = new HttpCaptureInputStream(serverInput, capture);
 631             }
 632             serverInput = new BufferedInputStream(serverInput);
 633             return (parseHTTPHeader(responses, pi, httpuc));
 634         } catch (SocketTimeoutException stex) {
 635             // We don't want to retry the request when the app. sets a timeout
 636             // but don't close the server if timeout while waiting for 100-continue
 637             if (ignoreContinue) {
 638                 closeServer();
 639             }
 640             throw stex;
 641         } catch (IOException e) {
 642             closeServer();
 643             cachedHttpClient = false;
 644             if (!failedOnce && requests != null) {
 645                 failedOnce = true;
 646                 if (getRequestMethod().equals("CONNECT") ||
 647                     (httpuc.getRequestMethod().equals("POST") &&
 648                     (!retryPostProp || streaming))) {
 649                     // do not retry the request
 650                 }  else {
 651                     // try once more
 652                     openServer();
 653                     if (needsTunneling()) {
 654                         httpuc.doTunneling();
 655                     }
 656                     afterConnect();
 657                     writeRequests(requests, poster);
 658                     return parseHTTP(responses, pi, httpuc);
 659                 }
 660             }
 661             throw e;
 662         }
 663 
 664     }
 665 
 666     private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc)
 667     throws IOException {
 668         /* If "HTTP/*" is found in the beginning, return true.  Let
 669          * HttpURLConnection parse the mime header itself.
 670          *
 671          * If this isn't valid HTTP, then we don't try to parse a header
 672          * out of the beginning of the response into the responses,
 673          * and instead just queue up the output stream to it's very beginning.
 674          * This seems most reasonable, and is what the NN browser does.
 675          */
 676 
 677         keepAliveConnections = -1;
 678         keepAliveTimeout = 0;
 679 
 680         boolean ret = false;
 681         byte[] b = new byte[8];
 682 
 683         try {
 684             int nread = 0;
 685             serverInput.mark(10);
 686             while (nread < 8) {
 687                 int r = serverInput.read(b, nread, 8 - nread);
 688                 if (r < 0) {
 689                     break;
 690                 }
 691                 nread += r;
 692             }
 693             String keep=null;
 694             ret = b[0] == 'H' && b[1] == 'T'
 695                     && b[2] == 'T' && b[3] == 'P' && b[4] == '/' &&
 696                 b[5] == '1' && b[6] == '.';
 697             serverInput.reset();
 698             if (ret) { // is valid HTTP - response started w/ "HTTP/1."
 699                 responses.parseHeader(serverInput);
 700 
 701                 // we've finished parsing http headers
 702                 // check if there are any applicable cookies to set (in cache)
 703                 CookieHandler cookieHandler = httpuc.getCookieHandler();
 704                 if (cookieHandler != null) {
 705                     URI uri = ParseUtil.toURI(url);
 706                     // NOTE: That cast from Map shouldn't be necessary but
 707                     // a bug in javac is triggered under certain circumstances
 708                     // So we do put the cast in as a workaround until
 709                     // it is resolved.
 710                     if (uri != null)
 711                         cookieHandler.put(uri, responses.getHeaders());
 712                 }
 713 
 714                 /* decide if we're keeping alive:
 715                  * This is a bit tricky.  There's a spec, but most current
 716                  * servers (10/1/96) that support this differ in dialects.
 717                  * If the server/client misunderstand each other, the
 718                  * protocol should fall back onto HTTP/1.0, no keep-alive.
 719                  */
 720                 if (usingProxy) { // not likely a proxy will return this
 721                     keep = responses.findValue("Proxy-Connection");
 722                 }
 723                 if (keep == null) {
 724                     keep = responses.findValue("Connection");
 725                 }
 726                 if (keep != null && keep.toLowerCase(Locale.US).equals("keep-alive")) {
 727                     /* some servers, notably Apache1.1, send something like:
 728                      * "Keep-Alive: timeout=15, max=1" which we should respect.
 729                      */
 730                     HeaderParser p = new HeaderParser(
 731                             responses.findValue("Keep-Alive"));
 732                     if (p != null) {
 733                         /* default should be larger in case of proxy */
 734                         keepAliveConnections = p.findInt("max", usingProxy?50:5);
 735                         keepAliveTimeout = p.findInt("timeout", usingProxy?60:5);
 736                     }
 737                 } else if (b[7] != '0') {
 738                     /*
 739                      * We're talking 1.1 or later. Keep persistent until
 740                      * the server says to close.
 741                      */
 742                     if (keep != null) {
 743                         /*
 744                          * The only Connection token we understand is close.
 745                          * Paranoia: if there is any Connection header then
 746                          * treat as non-persistent.
 747                          */
 748                         keepAliveConnections = 1;
 749                     } else {
 750                         keepAliveConnections = 5;
 751                     }
 752                 }
 753             } else if (nread != 8) {
 754                 if (!failedOnce && requests != null) {
 755                     failedOnce = true;
 756                     if (getRequestMethod().equals("CONNECT") ||
 757                         (httpuc.getRequestMethod().equals("POST") &&
 758                          (!retryPostProp || streaming))) {
 759                         // do not retry the request
 760                     } else {
 761                         closeServer();
 762                         cachedHttpClient = false;
 763                         openServer();
 764                         if (needsTunneling()) {
 765                             httpuc.doTunneling();
 766                         }
 767                         afterConnect();
 768                         writeRequests(requests, poster);
 769                         return parseHTTP(responses, pi, httpuc);
 770                     }
 771                 }
 772                 throw new SocketException("Unexpected end of file from server");
 773             } else {
 774                 // we can't vouche for what this is....
 775                 responses.set("Content-type", "unknown/unknown");
 776             }
 777         } catch (IOException e) {
 778             throw e;
 779         }
 780 
 781         int code = -1;
 782         try {
 783             String resp;
 784             resp = responses.getValue(0);
 785             /* should have no leading/trailing LWS
 786              * expedite the typical case by assuming it has
 787              * form "HTTP/1.x <WS> 2XX <mumble>"
 788              */
 789             int ind;
 790             ind = resp.indexOf(' ');
 791             while(resp.charAt(ind) == ' ')
 792                 ind++;
 793             code = Integer.parseInt(resp.substring(ind, ind + 3));
 794         } catch (Exception e) {}
 795 
 796         if (code == HTTP_CONTINUE && ignoreContinue) {
 797             responses.reset();
 798             return parseHTTPHeader(responses, pi, httpuc);
 799         }
 800 
 801         long cl = -1;
 802 
 803         /*
 804          * Set things up to parse the entity body of the reply.
 805          * We should be smarter about avoid pointless work when
 806          * the HTTP method and response code indicate there will be
 807          * no entity body to parse.
 808          */
 809         String te = responses.findValue("Transfer-Encoding");
 810         if (te != null && te.equalsIgnoreCase("chunked")) {
 811             serverInput = new ChunkedInputStream(serverInput, this, responses);
 812 
 813             /*
 814              * If keep alive not specified then close after the stream
 815              * has completed.
 816              */
 817             if (keepAliveConnections <= 1) {
 818                 keepAliveConnections = 1;
 819                 keepingAlive = false;
 820             } else {
 821                 keepingAlive = true;
 822             }
 823             failedOnce = false;
 824         } else {
 825 
 826             /*
 827              * If it's a keep alive connection then we will keep
 828              * (alive if :-
 829              * 1. content-length is specified, or
 830              * 2. "Not-Modified" or "No-Content" responses - RFC 2616 states that
 831              *    204 or 304 response must not include a message body.
 832              */
 833             String cls = responses.findValue("content-length");
 834             if (cls != null) {
 835                 try {
 836                     cl = Long.parseLong(cls);
 837                 } catch (NumberFormatException e) {
 838                     cl = -1;
 839                 }
 840             }
 841             String requestLine = requests.getKey(0);
 842 
 843             if ((requestLine != null &&
 844                  (requestLine.startsWith("HEAD"))) ||
 845                 code == HttpURLConnection.HTTP_NOT_MODIFIED ||
 846                 code == HttpURLConnection.HTTP_NO_CONTENT) {
 847                 cl = 0;
 848             }
 849 
 850             if (keepAliveConnections > 1 &&
 851                 (cl >= 0 ||
 852                  code == HttpURLConnection.HTTP_NOT_MODIFIED ||
 853                  code == HttpURLConnection.HTTP_NO_CONTENT)) {
 854                 keepingAlive = true;
 855                 failedOnce = false;
 856             } else if (keepingAlive) {
 857                 /* Previously we were keeping alive, and now we're not.  Remove
 858                  * this from the cache (but only here, once) - otherwise we get
 859                  * multiple removes and the cache count gets messed up.
 860                  */
 861                 keepingAlive=false;
 862             }
 863         }
 864 
 865         /* wrap a KeepAliveStream/MeteredStream around it if appropriate */
 866 
 867         if (cl > 0) {
 868             // In this case, content length is well known, so it is okay
 869             // to wrap the input stream with KeepAliveStream/MeteredStream.
 870 
 871             if (pi != null) {
 872                 // Progress monitor is enabled
 873                 pi.setContentType(responses.findValue("content-type"));
 874             }
 875 
 876             if (isKeepingAlive())   {
 877                 // Wrap KeepAliveStream if keep alive is enabled.
 878                 logFinest("KeepAlive stream used: " + url);
 879                 serverInput = new KeepAliveStream(serverInput, pi, cl, this);
 880                 failedOnce = false;
 881             }
 882             else        {
 883                 serverInput = new MeteredStream(serverInput, pi, cl);
 884             }
 885         }
 886         else if (cl == -1)  {
 887             // In this case, content length is unknown - the input
 888             // stream would simply be a regular InputStream or
 889             // ChunkedInputStream.
 890 
 891             if (pi != null) {
 892                 // Progress monitoring is enabled.
 893 
 894                 pi.setContentType(responses.findValue("content-type"));
 895 
 896                 // Wrap MeteredStream for tracking indeterministic
 897                 // progress, even if the input stream is ChunkedInputStream.
 898                 serverInput = new MeteredStream(serverInput, pi, cl);
 899             }
 900             else    {
 901                 // Progress monitoring is disabled, and there is no
 902                 // need to wrap an unknown length input stream.
 903 
 904                 // ** This is an no-op **
 905             }
 906         }
 907         else    {
 908             if (pi != null)
 909                 pi.finishTracking();
 910         }
 911 
 912         return ret;
 913     }
 914 
 915     public synchronized InputStream getInputStream() {
 916         return serverInput;
 917     }
 918 
 919     public OutputStream getOutputStream() {
 920         return serverOutput;
 921     }
 922 
 923     @Override
 924     public String toString() {
 925         return getClass().getName()+"("+url+")";
 926     }
 927 
 928     public final boolean isKeepingAlive() {
 929         return getHttpKeepAliveSet() && keepingAlive;
 930     }
 931 
 932     public void setCacheRequest(CacheRequest cacheRequest) {
 933         this.cacheRequest = cacheRequest;
 934     }
 935 
 936     CacheRequest getCacheRequest() {
 937         return cacheRequest;
 938     }
 939 
 940     String getRequestMethod() {
 941         if (requests != null) {
 942             String requestLine = requests.getKey(0);
 943             if (requestLine != null) {
 944                return requestLine.split("\\s+")[0];
 945             }
 946         }
 947         return "";
 948     }
 949 
 950     @Override
 951     protected void finalize() throws Throwable {
 952         // This should do nothing.  The stream finalizer will
 953         // close the fd.
 954     }
 955 
 956     public void setDoNotRetry(boolean value) {
 957         // failedOnce is used to determine if a request should be retried.
 958         failedOnce = value;
 959     }
 960 
 961     public void setIgnoreContinue(boolean value) {
 962         ignoreContinue = value;
 963     }
 964 
 965     /* Use only on connections in error. */
 966     @Override
 967     public void closeServer() {
 968         try {
 969             keepingAlive = false;
 970             serverSocket.close();
 971         } catch (Exception e) {}
 972     }
 973 
 974     /**
 975      * @return the proxy host being used for this client, or null
 976      *          if we're not going through a proxy
 977      */
 978     public String getProxyHostUsed() {
 979         if (!usingProxy) {
 980             return null;
 981         } else {
 982             return ((InetSocketAddress)proxy.address()).getHostString();
 983         }
 984     }
 985 
 986     /**
 987      * @return the proxy port being used for this client.  Meaningless
 988      *          if getProxyHostUsed() gives null.
 989      */
 990     public int getProxyPortUsed() {
 991         if (usingProxy)
 992             return ((InetSocketAddress)proxy.address()).getPort();
 993         return -1;
 994     }
 995 }