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