1/*
2 * Copyright (c) 1995, 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.protocol.http;
27
28import java.security.PrivilegedAction;
29import java.util.Arrays;
30import java.net.URL;
31import java.net.URLConnection;
32import java.net.ProtocolException;
33import java.net.HttpRetryException;
34import java.net.PasswordAuthentication;
35import java.net.Authenticator;
36import java.net.HttpCookie;
37import java.net.InetAddress;
38import java.net.UnknownHostException;
39import java.net.SocketTimeoutException;
40import java.net.SocketPermission;
41import java.net.Proxy;
42import java.net.ProxySelector;
43import java.net.URI;
44import java.net.InetSocketAddress;
45import java.net.CookieHandler;
46import java.net.ResponseCache;
47import java.net.CacheResponse;
48import java.net.SecureCacheResponse;
49import java.net.CacheRequest;
50import java.net.URLPermission;
51import java.net.Authenticator.RequestorType;
52import java.security.AccessController;
53import java.security.PrivilegedExceptionAction;
54import java.security.PrivilegedActionException;
55import java.io.*;
56import java.util.ArrayList;
57import java.util.Collections;
58import java.util.Date;
59import java.util.Map;
60import java.util.List;
61import java.util.Locale;
62import java.util.StringTokenizer;
63import java.util.Iterator;
64import java.util.HashSet;
65import java.util.HashMap;
66import java.util.Set;
67import java.util.StringJoiner;
68import jdk.internal.misc.JavaNetHttpCookieAccess;
69import jdk.internal.misc.SharedSecrets;
70import sun.net.*;
71import sun.net.www.*;
72import sun.net.www.http.HttpClient;
73import sun.net.www.http.PosterOutputStream;
74import sun.net.www.http.ChunkedInputStream;
75import sun.net.www.http.ChunkedOutputStream;
76import sun.util.logging.PlatformLogger;
77import java.text.SimpleDateFormat;
78import java.util.TimeZone;
79import java.net.MalformedURLException;
80import java.nio.ByteBuffer;
81import java.util.Objects;
82import java.util.Properties;
83import static sun.net.www.protocol.http.AuthScheme.BASIC;
84import static sun.net.www.protocol.http.AuthScheme.DIGEST;
85import static sun.net.www.protocol.http.AuthScheme.NTLM;
86import static sun.net.www.protocol.http.AuthScheme.NEGOTIATE;
87import static sun.net.www.protocol.http.AuthScheme.KERBEROS;
88import static sun.net.www.protocol.http.AuthScheme.UNKNOWN;
89import sun.security.action.GetIntegerAction;
90import sun.security.action.GetPropertyAction;
91
92/**
93 * A class to represent an HTTP connection to a remote object.
94 */
95
96
97public class HttpURLConnection extends java.net.HttpURLConnection {
98
99    static String HTTP_CONNECT = "CONNECT";
100
101    static final String version;
102    public static final String userAgent;
103
104    /* max # of allowed re-directs */
105    static final int defaultmaxRedirects = 20;
106    static final int maxRedirects;
107
108    /* Not all servers support the (Proxy)-Authentication-Info headers.
109     * By default, we don't require them to be sent
110     */
111    static final boolean validateProxy;
112    static final boolean validateServer;
113
114    /** A, possibly empty, set of authentication schemes that are disabled
115     *  when proxying plain HTTP ( not HTTPS ). */
116    static final Set<String> disabledProxyingSchemes;
117
118    /** A, possibly empty, set of authentication schemes that are disabled
119     *  when setting up a tunnel for HTTPS ( HTTP CONNECT ). */
120    static final Set<String> disabledTunnelingSchemes;
121
122    private StreamingOutputStream strOutputStream;
123    private static final String RETRY_MSG1 =
124        "cannot retry due to proxy authentication, in streaming mode";
125    private static final String RETRY_MSG2 =
126        "cannot retry due to server authentication, in streaming mode";
127    private static final String RETRY_MSG3 =
128        "cannot retry due to redirection, in streaming mode";
129
130    /*
131     * System properties related to error stream handling:
132     *
133     * sun.net.http.errorstream.enableBuffering = <boolean>
134     *
135     * With the above system property set to true (default is false),
136     * when the response code is >=400, the HTTP handler will try to
137     * buffer the response body (up to a certain amount and within a
138     * time limit). Thus freeing up the underlying socket connection
139     * for reuse. The rationale behind this is that usually when the
140     * server responds with a >=400 error (client error or server
141     * error, such as 404 file not found), the server will send a
142     * small response body to explain who to contact and what to do to
143     * recover. With this property set to true, even if the
144     * application doesn't call getErrorStream(), read the response
145     * body, and then call close(), the underlying socket connection
146     * can still be kept-alive and reused. The following two system
147     * properties provide further control to the error stream
148     * buffering behaviour.
149     *
150     * sun.net.http.errorstream.timeout = <int>
151     *     the timeout (in millisec) waiting the error stream
152     *     to be buffered; default is 300 ms
153     *
154     * sun.net.http.errorstream.bufferSize = <int>
155     *     the size (in bytes) to use for the buffering the error stream;
156     *     default is 4k
157     */
158
159
160    /* Should we enable buffering of error streams? */
161    private static boolean enableESBuffer = false;
162
163    /* timeout waiting for read for buffered error stream;
164     */
165    private static int timeout4ESBuffer = 0;
166
167    /* buffer size for buffered error stream;
168    */
169    private static int bufSize4ES = 0;
170
171    /*
172     * Restrict setting of request headers through the public api
173     * consistent with JavaScript XMLHttpRequest2 with a few
174     * exceptions. Disallowed headers are silently ignored for
175     * backwards compatibility reasons rather than throwing a
176     * SecurityException. For example, some applets set the
177     * Host header since old JREs did not implement HTTP 1.1.
178     * Additionally, any header starting with Sec- is
179     * disallowed.
180     *
181     * The following headers are allowed for historical reasons:
182     *
183     * Accept-Charset, Accept-Encoding, Cookie, Cookie2, Date,
184     * Referer, TE, User-Agent, headers beginning with Proxy-.
185     *
186     * The following headers are allowed in a limited form:
187     *
188     * Connection: close
189     *
190     * See http://www.w3.org/TR/XMLHttpRequest2.
191     */
192    private static final boolean allowRestrictedHeaders;
193    private static final Set<String> restrictedHeaderSet;
194    private static final String[] restrictedHeaders = {
195        /* Restricted by XMLHttpRequest2 */
196        //"Accept-Charset",
197        //"Accept-Encoding",
198        "Access-Control-Request-Headers",
199        "Access-Control-Request-Method",
200        "Connection", /* close is allowed */
201        "Content-Length",
202        //"Cookie",
203        //"Cookie2",
204        "Content-Transfer-Encoding",
205        //"Date",
206        //"Expect",
207        "Host",
208        "Keep-Alive",
209        "Origin",
210        // "Referer",
211        // "TE",
212        "Trailer",
213        "Transfer-Encoding",
214        "Upgrade",
215        //"User-Agent",
216        "Via"
217    };
218
219    private static String getNetProperty(String name) {
220        PrivilegedAction<String> pa = () -> NetProperties.get(name);
221        return AccessController.doPrivileged(pa);
222    }
223
224    private static Set<String> schemesListToSet(String list) {
225        if (list == null || list.isEmpty())
226            return Collections.emptySet();
227
228        Set<String> s = new HashSet<>();
229        String[] parts = list.split("\\s*,\\s*");
230        for (String part : parts)
231            s.add(part.toLowerCase(Locale.ROOT));
232        return s;
233    }
234
235    static {
236        Properties props = GetPropertyAction.privilegedGetProperties();
237        maxRedirects = GetIntegerAction.privilegedGetProperty(
238                "http.maxRedirects", defaultmaxRedirects);
239        version = props.getProperty("java.version");
240        String agent = props.getProperty("http.agent");
241        if (agent == null) {
242            agent = "Java/"+version;
243        } else {
244            agent = agent + " Java/"+version;
245        }
246        userAgent = agent;
247
248        // A set of net properties to control the use of authentication schemes
249        // when proxing/tunneling.
250        String p = getNetProperty("jdk.http.auth.tunneling.disabledSchemes");
251        disabledTunnelingSchemes = schemesListToSet(p);
252        p = getNetProperty("jdk.http.auth.proxying.disabledSchemes");
253        disabledProxyingSchemes = schemesListToSet(p);
254
255        validateProxy = Boolean.parseBoolean(
256                props.getProperty("http.auth.digest.validateProxy"));
257        validateServer = Boolean.parseBoolean(
258                props.getProperty("http.auth.digest.validateServer"));
259
260        enableESBuffer = Boolean.parseBoolean(
261                props.getProperty("sun.net.http.errorstream.enableBuffering"));
262        timeout4ESBuffer = GetIntegerAction.privilegedGetProperty(
263                "sun.net.http.errorstream.timeout", 300);
264        if (timeout4ESBuffer <= 0) {
265            timeout4ESBuffer = 300; // use the default
266        }
267
268        bufSize4ES = GetIntegerAction.privilegedGetProperty(
269                "sun.net.http.errorstream.bufferSize", 4096);
270        if (bufSize4ES <= 0) {
271            bufSize4ES = 4096; // use the default
272        }
273
274        allowRestrictedHeaders = Boolean.parseBoolean(
275                props.getProperty("sun.net.http.allowRestrictedHeaders"));
276        if (!allowRestrictedHeaders) {
277            restrictedHeaderSet = new HashSet<>(restrictedHeaders.length);
278            for (int i=0; i < restrictedHeaders.length; i++) {
279                restrictedHeaderSet.add(restrictedHeaders[i].toLowerCase());
280            }
281        } else {
282            restrictedHeaderSet = null;
283        }
284    }
285
286    static final String httpVersion = "HTTP/1.1";
287    static final String acceptString =
288        "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2";
289
290    // the following http request headers should NOT have their values
291    // returned for security reasons.
292    private static final String[] EXCLUDE_HEADERS = {
293            "Proxy-Authorization",
294            "Authorization"
295    };
296
297    // also exclude system cookies when any might be set
298    private static final String[] EXCLUDE_HEADERS2= {
299            "Proxy-Authorization",
300            "Authorization",
301            "Cookie",
302            "Cookie2"
303    };
304
305    protected HttpClient http;
306    protected Handler handler;
307    protected Proxy instProxy;
308    protected volatile Authenticator authenticator;
309    protected volatile String authenticatorKey;
310
311    private CookieHandler cookieHandler;
312    private final ResponseCache cacheHandler;
313
314    // the cached response, and cached response headers and body
315    protected CacheResponse cachedResponse;
316    private MessageHeader cachedHeaders;
317    private InputStream cachedInputStream;
318
319    /* output stream to server */
320    protected PrintStream ps = null;
321
322
323    /* buffered error stream */
324    private InputStream errorStream = null;
325
326    /* User set Cookies */
327    private boolean setUserCookies = true;
328    private String userCookies = null;
329    private String userCookies2 = null;
330
331    /* We only have a single static authenticator for now.
332     * REMIND:  backwards compatibility with JDK 1.1.  Should be
333     * eliminated for JDK 2.0.
334     */
335    @Deprecated
336    private static HttpAuthenticator defaultAuth;
337
338    /* all the headers we send
339     * NOTE: do *NOT* dump out the content of 'requests' in the
340     * output or stacktrace since it may contain security-sensitive
341     * headers such as those defined in EXCLUDE_HEADERS.
342     */
343    private MessageHeader requests;
344
345    /* The headers actually set by the user are recorded here also
346     */
347    private MessageHeader userHeaders;
348
349    /* Headers and request method cannot be changed
350     * once this flag is set in :-
351     *     - getOutputStream()
352     *     - getInputStream())
353     *     - connect()
354     * Access synchronized on this.
355     */
356    private boolean connecting = false;
357
358    /* The following two fields are only used with Digest Authentication */
359    String domain;      /* The list of authentication domains */
360    DigestAuthentication.Parameters digestparams;
361
362    /* Current credentials in use */
363    AuthenticationInfo  currentProxyCredentials = null;
364    AuthenticationInfo  currentServerCredentials = null;
365    boolean             needToCheck = true;
366    private boolean doingNTLM2ndStage = false; /* doing the 2nd stage of an NTLM server authentication */
367    private boolean doingNTLMp2ndStage = false; /* doing the 2nd stage of an NTLM proxy authentication */
368
369    /* try auth without calling Authenticator. Used for transparent NTLM authentication */
370    private boolean tryTransparentNTLMServer = true;
371    private boolean tryTransparentNTLMProxy = true;
372    private boolean useProxyResponseCode = false;
373
374    /* Used by Windows specific code */
375    private Object authObj;
376
377    /* Set if the user is manually setting the Authorization or Proxy-Authorization headers */
378    boolean isUserServerAuth;
379    boolean isUserProxyAuth;
380
381    String serverAuthKey, proxyAuthKey;
382
383    /* Progress source */
384    protected ProgressSource pi;
385
386    /* all the response headers we get back */
387    private MessageHeader responses;
388    /* the stream _from_ the server */
389    private InputStream inputStream = null;
390    /* post stream _to_ the server, if any */
391    private PosterOutputStream poster = null;
392
393    /* Indicates if the std. request headers have been set in requests. */
394    private boolean setRequests=false;
395
396    /* Indicates whether a request has already failed or not */
397    private boolean failedOnce=false;
398
399    /* Remembered Exception, we will throw it again if somebody
400       calls getInputStream after disconnect */
401    private Exception rememberedException = null;
402
403    /* If we decide we want to reuse a client, we put it here */
404    private HttpClient reuseClient = null;
405
406    /* Tunnel states */
407    public enum TunnelState {
408        /* No tunnel */
409        NONE,
410
411        /* Setting up a tunnel */
412        SETUP,
413
414        /* Tunnel has been successfully setup */
415        TUNNELING
416    }
417
418    private TunnelState tunnelState = TunnelState.NONE;
419
420    /* Redefine timeouts from java.net.URLConnection as we need -1 to mean
421     * not set. This is to ensure backward compatibility.
422     */
423    private int connectTimeout = NetworkClient.DEFAULT_CONNECT_TIMEOUT;
424    private int readTimeout = NetworkClient.DEFAULT_READ_TIMEOUT;
425
426    /* A permission converted from a URLPermission */
427    private SocketPermission socketPermission;
428
429    /* Logging support */
430    private static final PlatformLogger logger =
431            PlatformLogger.getLogger("sun.net.www.protocol.http.HttpURLConnection");
432
433    /*
434     * privileged request password authentication
435     *
436     */
437    private static PasswordAuthentication
438    privilegedRequestPasswordAuthentication(
439                            final Authenticator authenticator,
440                            final String host,
441                            final InetAddress addr,
442                            final int port,
443                            final String protocol,
444                            final String prompt,
445                            final String scheme,
446                            final URL url,
447                            final RequestorType authType) {
448        return java.security.AccessController.doPrivileged(
449            new java.security.PrivilegedAction<>() {
450                public PasswordAuthentication run() {
451                    if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
452                        logger.finest("Requesting Authentication: host =" + host + " url = " + url);
453                    }
454                    PasswordAuthentication pass = Authenticator.requestPasswordAuthentication(
455                        authenticator, host, addr, port, protocol,
456                        prompt, scheme, url, authType);
457                    if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
458                        logger.finest("Authentication returned: " + (pass != null ? pass.toString() : "null"));
459                    }
460                    return pass;
461                }
462            });
463    }
464
465    private boolean isRestrictedHeader(String key, String value) {
466        if (allowRestrictedHeaders) {
467            return false;
468        }
469
470        key = key.toLowerCase();
471        if (restrictedHeaderSet.contains(key)) {
472            /*
473             * Exceptions to restricted headers:
474             *
475             * Allow "Connection: close".
476             */
477            if (key.equals("connection") && value.equalsIgnoreCase("close")) {
478                return false;
479            }
480            return true;
481        } else if (key.startsWith("sec-")) {
482            return true;
483        }
484        return false;
485    }
486
487    /*
488     * Checks the validity of http message header and whether the header
489     * is restricted and throws IllegalArgumentException if invalid or
490     * restricted.
491     */
492    private boolean isExternalMessageHeaderAllowed(String key, String value) {
493        checkMessageHeader(key, value);
494        if (!isRestrictedHeader(key, value)) {
495            return true;
496        }
497        return false;
498    }
499
500    /* Logging support */
501    public static PlatformLogger getHttpLogger() {
502        return logger;
503    }
504
505    /* Used for Windows NTLM implementation */
506    public Object authObj() {
507        return authObj;
508    }
509
510    public void authObj(Object authObj) {
511        this.authObj = authObj;
512    }
513
514    @Override
515    public synchronized void setAuthenticator(Authenticator auth) {
516        if (connecting || connected) {
517            throw new IllegalStateException(
518                  "Authenticator must be set before connecting");
519        }
520        authenticator = Objects.requireNonNull(auth);
521        authenticatorKey = AuthenticatorKeys.getKey(authenticator);
522    }
523
524    public String getAuthenticatorKey() {
525        String k = authenticatorKey;
526        if (k == null) return AuthenticatorKeys.getKey(authenticator);
527        return k;
528    }
529
530    /*
531     * checks the validity of http message header and throws
532     * IllegalArgumentException if invalid.
533     */
534    private void checkMessageHeader(String key, String value) {
535        char LF = '\n';
536        int index = key.indexOf(LF);
537        int index1 = key.indexOf(':');
538        if (index != -1 || index1 != -1) {
539            throw new IllegalArgumentException(
540                "Illegal character(s) in message header field: " + key);
541        }
542        else {
543            if (value == null) {
544                return;
545            }
546
547            index = value.indexOf(LF);
548            while (index != -1) {
549                index++;
550                if (index < value.length()) {
551                    char c = value.charAt(index);
552                    if ((c==' ') || (c=='\t')) {
553                        // ok, check the next occurrence
554                        index = value.indexOf(LF, index);
555                        continue;
556                    }
557                }
558                throw new IllegalArgumentException(
559                    "Illegal character(s) in message header value: " + value);
560            }
561        }
562    }
563
564    public synchronized void setRequestMethod(String method)
565                        throws ProtocolException {
566        if (connecting) {
567            throw new IllegalStateException("connect in progress");
568        }
569        super.setRequestMethod(method);
570    }
571
572    /* adds the standard key/val pairs to reqests if necessary & write to
573     * given PrintStream
574     */
575    private void writeRequests() throws IOException {
576        /* print all message headers in the MessageHeader
577         * onto the wire - all the ones we've set and any
578         * others that have been set
579         */
580        // send any pre-emptive authentication
581        if (http.usingProxy && tunnelState() != TunnelState.TUNNELING) {
582            setPreemptiveProxyAuthentication(requests);
583        }
584        if (!setRequests) {
585
586            /* We're very particular about the order in which we
587             * set the request headers here.  The order should not
588             * matter, but some careless CGI programs have been
589             * written to expect a very particular order of the
590             * standard headers.  To name names, the order in which
591             * Navigator3.0 sends them.  In particular, we make *sure*
592             * to send Content-type: <> and Content-length:<> second
593             * to last and last, respectively, in the case of a POST
594             * request.
595             */
596            if (!failedOnce) {
597                checkURLFile();
598                requests.prepend(method + " " + getRequestURI()+" "  +
599                                 httpVersion, null);
600            }
601            if (!getUseCaches()) {
602                requests.setIfNotSet ("Cache-Control", "no-cache");
603                requests.setIfNotSet ("Pragma", "no-cache");
604            }
605            requests.setIfNotSet("User-Agent", userAgent);
606            int port = url.getPort();
607            String host = stripIPv6ZoneId(url.getHost());
608            if (port != -1 && port != url.getDefaultPort()) {
609                host += ":" + String.valueOf(port);
610            }
611            String reqHost = requests.findValue("Host");
612            if (reqHost == null ||
613                (!reqHost.equalsIgnoreCase(host) && !checkSetHost()))
614            {
615                requests.set("Host", host);
616            }
617            requests.setIfNotSet("Accept", acceptString);
618
619            /*
620             * For HTTP/1.1 the default behavior is to keep connections alive.
621             * However, we may be talking to a 1.0 server so we should set
622             * keep-alive just in case, except if we have encountered an error
623             * or if keep alive is disabled via a system property
624             */
625
626            // Try keep-alive only on first attempt
627            if (!failedOnce && http.getHttpKeepAliveSet()) {
628                if (http.usingProxy && tunnelState() != TunnelState.TUNNELING) {
629                    requests.setIfNotSet("Proxy-Connection", "keep-alive");
630                } else {
631                    requests.setIfNotSet("Connection", "keep-alive");
632                }
633            } else {
634                /*
635                 * RFC 2616 HTTP/1.1 section 14.10 says:
636                 * HTTP/1.1 applications that do not support persistent
637                 * connections MUST include the "close" connection option
638                 * in every message
639                 */
640                requests.setIfNotSet("Connection", "close");
641            }
642            // Set modified since if necessary
643            long modTime = getIfModifiedSince();
644            if (modTime != 0 ) {
645                Date date = new Date(modTime);
646                //use the preferred date format according to RFC 2068(HTTP1.1),
647                // RFC 822 and RFC 1123
648                SimpleDateFormat fo =
649                  new SimpleDateFormat ("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
650                fo.setTimeZone(TimeZone.getTimeZone("GMT"));
651                requests.setIfNotSet("If-Modified-Since", fo.format(date));
652            }
653            // check for preemptive authorization
654            AuthenticationInfo sauth = AuthenticationInfo.getServerAuth(url,
655                                             getAuthenticatorKey());
656            if (sauth != null && sauth.supportsPreemptiveAuthorization() ) {
657                // Sets "Authorization"
658                requests.setIfNotSet(sauth.getHeaderName(), sauth.getHeaderValue(url,method));
659                currentServerCredentials = sauth;
660            }
661
662            if (!method.equals("PUT") && (poster != null || streaming())) {
663                requests.setIfNotSet ("Content-type",
664                        "application/x-www-form-urlencoded");
665            }
666
667            boolean chunked = false;
668
669            if (streaming()) {
670                if (chunkLength != -1) {
671                    requests.set ("Transfer-Encoding", "chunked");
672                    chunked = true;
673                } else { /* fixed content length */
674                    if (fixedContentLengthLong != -1) {
675                        requests.set ("Content-Length",
676                                      String.valueOf(fixedContentLengthLong));
677                    } else if (fixedContentLength != -1) {
678                        requests.set ("Content-Length",
679                                      String.valueOf(fixedContentLength));
680                    }
681                }
682            } else if (poster != null) {
683                /* add Content-Length & POST/PUT data */
684                synchronized (poster) {
685                    /* close it, so no more data can be added */
686                    poster.close();
687                    requests.set("Content-Length",
688                                 String.valueOf(poster.size()));
689                }
690            }
691
692            if (!chunked) {
693                if (requests.findValue("Transfer-Encoding") != null) {
694                    requests.remove("Transfer-Encoding");
695                    if (logger.isLoggable(PlatformLogger.Level.WARNING)) {
696                        logger.warning(
697                            "use streaming mode for chunked encoding");
698                    }
699                }
700            }
701
702            // get applicable cookies based on the uri and request headers
703            // add them to the existing request headers
704            setCookieHeader();
705
706            setRequests=true;
707        }
708        if (logger.isLoggable(PlatformLogger.Level.FINE)) {
709            logger.fine(requests.toString());
710        }
711        http.writeRequests(requests, poster, streaming());
712        if (ps.checkError()) {
713            String proxyHost = http.getProxyHostUsed();
714            int proxyPort = http.getProxyPortUsed();
715            disconnectInternal();
716            if (failedOnce) {
717                throw new IOException("Error writing to server");
718            } else { // try once more
719                failedOnce=true;
720                if (proxyHost != null) {
721                    setProxiedClient(url, proxyHost, proxyPort);
722                } else {
723                    setNewClient (url);
724                }
725                ps = (PrintStream) http.getOutputStream();
726                connected=true;
727                responses = new MessageHeader();
728                setRequests=false;
729                writeRequests();
730            }
731        }
732    }
733
734    private boolean checkSetHost() {
735        SecurityManager s = System.getSecurityManager();
736        if (s != null) {
737            String name = s.getClass().getName();
738            if (name.equals("sun.plugin2.applet.AWTAppletSecurityManager") ||
739                name.equals("sun.plugin2.applet.FXAppletSecurityManager") ||
740                name.equals("com.sun.javaws.security.JavaWebStartSecurity") ||
741                name.equals("sun.plugin.security.ActivatorSecurityManager"))
742            {
743                int CHECK_SET_HOST = -2;
744                try {
745                    s.checkConnect(url.toExternalForm(), CHECK_SET_HOST);
746                } catch (SecurityException ex) {
747                    return false;
748                }
749            }
750        }
751        return true;
752    }
753
754    private void checkURLFile() {
755        SecurityManager s = System.getSecurityManager();
756        if (s != null) {
757            String name = s.getClass().getName();
758            if (name.equals("sun.plugin2.applet.AWTAppletSecurityManager") ||
759                name.equals("sun.plugin2.applet.FXAppletSecurityManager") ||
760                name.equals("com.sun.javaws.security.JavaWebStartSecurity") ||
761                name.equals("sun.plugin.security.ActivatorSecurityManager"))
762            {
763                int CHECK_SUBPATH = -3;
764                try {
765                    s.checkConnect(url.toExternalForm(), CHECK_SUBPATH);
766                } catch (SecurityException ex) {
767                    throw new SecurityException("denied access outside a permitted URL subpath", ex);
768                }
769            }
770        }
771    }
772
773    /**
774     * Create a new HttpClient object, bypassing the cache of
775     * HTTP client objects/connections.
776     *
777     * @param url       the URL being accessed
778     */
779    protected void setNewClient (URL url)
780    throws IOException {
781        setNewClient(url, false);
782    }
783
784    /**
785     * Obtain a HttpsClient object. Use the cached copy if specified.
786     *
787     * @param url       the URL being accessed
788     * @param useCache  whether the cached connection should be used
789     *        if present
790     */
791    protected void setNewClient (URL url, boolean useCache)
792        throws IOException {
793        http = HttpClient.New(url, null, -1, useCache, connectTimeout, this);
794        http.setReadTimeout(readTimeout);
795    }
796
797
798    /**
799     * Create a new HttpClient object, set up so that it uses
800     * per-instance proxying to the given HTTP proxy.  This
801     * bypasses the cache of HTTP client objects/connections.
802     *
803     * @param url       the URL being accessed
804     * @param proxyHost the proxy host to use
805     * @param proxyPort the proxy port to use
806     */
807    protected void setProxiedClient (URL url, String proxyHost, int proxyPort)
808    throws IOException {
809        setProxiedClient(url, proxyHost, proxyPort, false);
810    }
811
812    /**
813     * Obtain a HttpClient object, set up so that it uses per-instance
814     * proxying to the given HTTP proxy. Use the cached copy of HTTP
815     * client objects/connections if specified.
816     *
817     * @param url       the URL being accessed
818     * @param proxyHost the proxy host to use
819     * @param proxyPort the proxy port to use
820     * @param useCache  whether the cached connection should be used
821     *        if present
822     */
823    protected void setProxiedClient (URL url,
824                                     String proxyHost, int proxyPort,
825                                     boolean useCache)
826        throws IOException {
827        proxiedConnect(url, proxyHost, proxyPort, useCache);
828    }
829
830    protected void proxiedConnect(URL url,
831                                  String proxyHost, int proxyPort,
832                                  boolean useCache)
833        throws IOException {
834        http = HttpClient.New (url, proxyHost, proxyPort, useCache,
835            connectTimeout, this);
836        http.setReadTimeout(readTimeout);
837    }
838
839    protected HttpURLConnection(URL u, Handler handler)
840    throws IOException {
841        // we set proxy == null to distinguish this case with the case
842        // when per connection proxy is set
843        this(u, null, handler);
844    }
845
846    public HttpURLConnection(URL u, String host, int port) {
847        this(u, new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(host, port)));
848    }
849
850    /** this constructor is used by other protocol handlers such as ftp
851        that want to use http to fetch urls on their behalf.*/
852    public HttpURLConnection(URL u, Proxy p) {
853        this(u, p, new Handler());
854    }
855
856    protected HttpURLConnection(URL u, Proxy p, Handler handler) {
857        super(u);
858        requests = new MessageHeader();
859        responses = new MessageHeader();
860        userHeaders = new MessageHeader();
861        this.handler = handler;
862        instProxy = p;
863        if (instProxy instanceof sun.net.ApplicationProxy) {
864            /* Application set Proxies should not have access to cookies
865             * in a secure environment unless explicitly allowed. */
866            try {
867                cookieHandler = CookieHandler.getDefault();
868            } catch (SecurityException se) { /* swallow exception */ }
869        } else {
870            cookieHandler = java.security.AccessController.doPrivileged(
871                new java.security.PrivilegedAction<>() {
872                public CookieHandler run() {
873                    return CookieHandler.getDefault();
874                }
875            });
876        }
877        cacheHandler = java.security.AccessController.doPrivileged(
878            new java.security.PrivilegedAction<>() {
879                public ResponseCache run() {
880                return ResponseCache.getDefault();
881            }
882        });
883    }
884
885    /**
886     * @deprecated.  Use java.net.Authenticator.setDefault() instead.
887     */
888    @Deprecated
889    public static void setDefaultAuthenticator(HttpAuthenticator a) {
890        defaultAuth = a;
891    }
892
893    /**
894     * opens a stream allowing redirects only to the same host.
895     */
896    public static InputStream openConnectionCheckRedirects(URLConnection c)
897        throws IOException
898    {
899        boolean redir;
900        int redirects = 0;
901        InputStream in;
902        Authenticator a = null;
903
904        do {
905            if (c instanceof HttpURLConnection) {
906                ((HttpURLConnection) c).setInstanceFollowRedirects(false);
907                if (a == null) {
908                    a = ((HttpURLConnection) c).authenticator;
909                }
910            }
911
912            // We want to open the input stream before
913            // getting headers, because getHeaderField()
914            // et al swallow IOExceptions.
915            in = c.getInputStream();
916            redir = false;
917
918            if (c instanceof HttpURLConnection) {
919                HttpURLConnection http = (HttpURLConnection) c;
920                int stat = http.getResponseCode();
921                if (stat >= 300 && stat <= 307 && stat != 306 &&
922                        stat != HttpURLConnection.HTTP_NOT_MODIFIED) {
923                    URL base = http.getURL();
924                    String loc = http.getHeaderField("Location");
925                    URL target = null;
926                    if (loc != null) {
927                        target = new URL(base, loc);
928                    }
929                    http.disconnect();
930                    if (target == null
931                        || !base.getProtocol().equals(target.getProtocol())
932                        || base.getPort() != target.getPort()
933                        || !hostsEqual(base, target)
934                        || redirects >= 5)
935                    {
936                        throw new SecurityException("illegal URL redirect");
937                    }
938                    redir = true;
939                    c = target.openConnection();
940                    if (a != null && c instanceof HttpURLConnection) {
941                        ((HttpURLConnection)c).setAuthenticator(a);
942                    }
943                    redirects++;
944                }
945            }
946        } while (redir);
947        return in;
948    }
949
950
951    //
952    // Same as java.net.URL.hostsEqual
953    //
954    private static boolean hostsEqual(URL u1, URL u2) {
955        final String h1 = u1.getHost();
956        final String h2 = u2.getHost();
957
958        if (h1 == null) {
959            return h2 == null;
960        } else if (h2 == null) {
961            return false;
962        } else if (h1.equalsIgnoreCase(h2)) {
963            return true;
964        }
965        // Have to resolve addresses before comparing, otherwise
966        // names like tachyon and tachyon.eng would compare different
967        final boolean result[] = {false};
968
969        java.security.AccessController.doPrivileged(
970            new java.security.PrivilegedAction<>() {
971                public Void run() {
972                try {
973                    InetAddress a1 = InetAddress.getByName(h1);
974                    InetAddress a2 = InetAddress.getByName(h2);
975                    result[0] = a1.equals(a2);
976                } catch(UnknownHostException | SecurityException e) {
977                }
978                return null;
979            }
980        });
981
982        return result[0];
983    }
984
985    // overridden in HTTPS subclass
986
987    public void connect() throws IOException {
988        synchronized (this) {
989            connecting = true;
990        }
991        plainConnect();
992    }
993
994    private boolean checkReuseConnection () {
995        if (connected) {
996            return true;
997        }
998        if (reuseClient != null) {
999            http = reuseClient;
1000            http.setReadTimeout(getReadTimeout());
1001            http.reuse = false;
1002            reuseClient = null;
1003            connected = true;
1004            return true;
1005        }
1006        return false;
1007    }
1008
1009    private String getHostAndPort(URL url) {
1010        String host = url.getHost();
1011        final String hostarg = host;
1012        try {
1013            // lookup hostname and use IP address if available
1014            host = AccessController.doPrivileged(
1015                new PrivilegedExceptionAction<>() {
1016                    public String run() throws IOException {
1017                            InetAddress addr = InetAddress.getByName(hostarg);
1018                            return addr.getHostAddress();
1019                    }
1020                }
1021            );
1022        } catch (PrivilegedActionException e) {}
1023        int port = url.getPort();
1024        if (port == -1) {
1025            String scheme = url.getProtocol();
1026            if ("http".equals(scheme)) {
1027                return host + ":80";
1028            } else { // scheme must be https
1029                return host + ":443";
1030            }
1031        }
1032        return host + ":" + Integer.toString(port);
1033    }
1034
1035    protected void plainConnect()  throws IOException {
1036        synchronized (this) {
1037            if (connected) {
1038                return;
1039            }
1040        }
1041        SocketPermission p = URLtoSocketPermission(this.url);
1042        if (p != null) {
1043            try {
1044                AccessController.doPrivilegedWithCombiner(
1045                    new PrivilegedExceptionAction<>() {
1046                        public Void run() throws IOException {
1047                            plainConnect0();
1048                            return null;
1049                        }
1050                    }, null, p
1051                );
1052            } catch (PrivilegedActionException e) {
1053                    throw (IOException) e.getException();
1054            }
1055        } else {
1056            // run without additional permission
1057            plainConnect0();
1058        }
1059    }
1060
1061    /**
1062     *  if the caller has a URLPermission for connecting to the
1063     *  given URL, then return a SocketPermission which permits
1064     *  access to that destination. Return null otherwise. The permission
1065     *  is cached in a field (which can only be changed by redirects)
1066     */
1067    SocketPermission URLtoSocketPermission(URL url) throws IOException {
1068
1069        if (socketPermission != null) {
1070            return socketPermission;
1071        }
1072
1073        SecurityManager sm = System.getSecurityManager();
1074
1075        if (sm == null) {
1076            return null;
1077        }
1078
1079        // the permission, which we might grant
1080
1081        SocketPermission newPerm = new SocketPermission(
1082            getHostAndPort(url), "connect"
1083        );
1084
1085        String actions = getRequestMethod()+":" +
1086                getUserSetHeaders().getHeaderNamesInList();
1087
1088        String urlstring = url.getProtocol() + "://" + url.getAuthority()
1089                + url.getPath();
1090
1091        URLPermission p = new URLPermission(urlstring, actions);
1092        try {
1093            sm.checkPermission(p);
1094            socketPermission = newPerm;
1095            return socketPermission;
1096        } catch (SecurityException e) {
1097            // fall thru
1098        }
1099        return null;
1100    }
1101
1102    protected void plainConnect0()  throws IOException {
1103        // try to see if request can be served from local cache
1104        if (cacheHandler != null && getUseCaches()) {
1105            try {
1106                URI uri = ParseUtil.toURI(url);
1107                if (uri != null) {
1108                    cachedResponse = cacheHandler.get(uri, getRequestMethod(), getUserSetHeaders().getHeaders());
1109                    if ("https".equalsIgnoreCase(uri.getScheme())
1110                        && !(cachedResponse instanceof SecureCacheResponse)) {
1111                        cachedResponse = null;
1112                    }
1113                    if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
1114                        logger.finest("Cache Request for " + uri + " / " + getRequestMethod());
1115                        logger.finest("From cache: " + (cachedResponse != null ? cachedResponse.toString() : "null"));
1116                    }
1117                    if (cachedResponse != null) {
1118                        cachedHeaders = mapToMessageHeader(cachedResponse.getHeaders());
1119                        cachedInputStream = cachedResponse.getBody();
1120                    }
1121                }
1122            } catch (IOException ioex) {
1123                // ignore and commence normal connection
1124            }
1125            if (cachedHeaders != null && cachedInputStream != null) {
1126                connected = true;
1127                return;
1128            } else {
1129                cachedResponse = null;
1130            }
1131        }
1132        try {
1133            /* Try to open connections using the following scheme,
1134             * return on the first one that's successful:
1135             * 1) if (instProxy != null)
1136             *        connect to instProxy; raise exception if failed
1137             * 2) else use system default ProxySelector
1138             * 3) else make a direct connection if ProxySelector is not present
1139             */
1140
1141            if (instProxy == null) { // no instance Proxy is set
1142                /**
1143                 * Do we have to use a proxy?
1144                 */
1145                ProxySelector sel =
1146                    java.security.AccessController.doPrivileged(
1147                        new java.security.PrivilegedAction<>() {
1148                            public ProxySelector run() {
1149                                     return ProxySelector.getDefault();
1150                                 }
1151                             });
1152                if (sel != null) {
1153                    URI uri = sun.net.www.ParseUtil.toURI(url);
1154                    if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
1155                        logger.finest("ProxySelector Request for " + uri);
1156                    }
1157                    Iterator<Proxy> it = sel.select(uri).iterator();
1158                    Proxy p;
1159                    while (it.hasNext()) {
1160                        p = it.next();
1161                        try {
1162                            if (!failedOnce) {
1163                                http = getNewHttpClient(url, p, connectTimeout);
1164                                http.setReadTimeout(readTimeout);
1165                            } else {
1166                                // make sure to construct new connection if first
1167                                // attempt failed
1168                                http = getNewHttpClient(url, p, connectTimeout, false);
1169                                http.setReadTimeout(readTimeout);
1170                            }
1171                            if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
1172                                if (p != null) {
1173                                    logger.finest("Proxy used: " + p.toString());
1174                                }
1175                            }
1176                            break;
1177                        } catch (IOException ioex) {
1178                            if (p != Proxy.NO_PROXY) {
1179                                sel.connectFailed(uri, p.address(), ioex);
1180                                if (!it.hasNext()) {
1181                                    throw ioex;
1182                                }
1183                            } else {
1184                                throw ioex;
1185                            }
1186                            continue;
1187                        }
1188                    }
1189                } else {
1190                    // No proxy selector, create http client with no proxy
1191                    if (!failedOnce) {
1192                        http = getNewHttpClient(url, null, connectTimeout);
1193                        http.setReadTimeout(readTimeout);
1194                    } else {
1195                        // make sure to construct new connection if first
1196                        // attempt failed
1197                        http = getNewHttpClient(url, null, connectTimeout, false);
1198                        http.setReadTimeout(readTimeout);
1199                    }
1200                }
1201            } else {
1202                if (!failedOnce) {
1203                    http = getNewHttpClient(url, instProxy, connectTimeout);
1204                    http.setReadTimeout(readTimeout);
1205                } else {
1206                    // make sure to construct new connection if first
1207                    // attempt failed
1208                    http = getNewHttpClient(url, instProxy, connectTimeout, false);
1209                    http.setReadTimeout(readTimeout);
1210                }
1211            }
1212
1213            ps = (PrintStream)http.getOutputStream();
1214        } catch (IOException e) {
1215            throw e;
1216        }
1217        // constructor to HTTP client calls openserver
1218        connected = true;
1219    }
1220
1221    // subclass HttpsClient will overwrite & return an instance of HttpsClient
1222    protected HttpClient getNewHttpClient(URL url, Proxy p, int connectTimeout)
1223        throws IOException {
1224        return HttpClient.New(url, p, connectTimeout, this);
1225    }
1226
1227    // subclass HttpsClient will overwrite & return an instance of HttpsClient
1228    protected HttpClient getNewHttpClient(URL url, Proxy p,
1229                                          int connectTimeout, boolean useCache)
1230        throws IOException {
1231        return HttpClient.New(url, p, connectTimeout, useCache, this);
1232    }
1233
1234    private void expect100Continue() throws IOException {
1235            // Expect: 100-Continue was set, so check the return code for
1236            // Acceptance
1237            int oldTimeout = http.getReadTimeout();
1238            boolean enforceTimeOut = false;
1239            boolean timedOut = false;
1240            if (oldTimeout <= 0) {
1241                // 5s read timeout in case the server doesn't understand
1242                // Expect: 100-Continue
1243                http.setReadTimeout(5000);
1244                enforceTimeOut = true;
1245            }
1246
1247            try {
1248                http.parseHTTP(responses, pi, this);
1249            } catch (SocketTimeoutException se) {
1250                if (!enforceTimeOut) {
1251                    throw se;
1252                }
1253                timedOut = true;
1254                http.setIgnoreContinue(true);
1255            }
1256            if (!timedOut) {
1257                // Can't use getResponseCode() yet
1258                String resp = responses.getValue(0);
1259                // Parse the response which is of the form:
1260                // HTTP/1.1 417 Expectation Failed
1261                // HTTP/1.1 100 Continue
1262                if (resp != null && resp.startsWith("HTTP/")) {
1263                    String[] sa = resp.split("\\s+");
1264                    responseCode = -1;
1265                    try {
1266                        // Response code is 2nd token on the line
1267                        if (sa.length > 1)
1268                            responseCode = Integer.parseInt(sa[1]);
1269                    } catch (NumberFormatException numberFormatException) {
1270                    }
1271                }
1272                if (responseCode != 100) {
1273                    throw new ProtocolException("Server rejected operation");
1274                }
1275            }
1276
1277            http.setReadTimeout(oldTimeout);
1278
1279            responseCode = -1;
1280            responses.reset();
1281            // Proceed
1282    }
1283
1284    /*
1285     * Allowable input/output sequences:
1286     * [interpreted as request entity]
1287     * - get output, [write output,] get input, [read input]
1288     * - get output, [write output]
1289     * [interpreted as GET]
1290     * - get input, [read input]
1291     * Disallowed:
1292     * - get input, [read input,] get output, [write output]
1293     */
1294
1295    @Override
1296    public synchronized OutputStream getOutputStream() throws IOException {
1297        connecting = true;
1298        SocketPermission p = URLtoSocketPermission(this.url);
1299
1300        if (p != null) {
1301            try {
1302                return AccessController.doPrivilegedWithCombiner(
1303                    new PrivilegedExceptionAction<>() {
1304                        public OutputStream run() throws IOException {
1305                            return getOutputStream0();
1306                        }
1307                    }, null, p
1308                );
1309            } catch (PrivilegedActionException e) {
1310                throw (IOException) e.getException();
1311            }
1312        } else {
1313            return getOutputStream0();
1314        }
1315    }
1316
1317    private synchronized OutputStream getOutputStream0() throws IOException {
1318        try {
1319            if (!doOutput) {
1320                throw new ProtocolException("cannot write to a URLConnection"
1321                               + " if doOutput=false - call setDoOutput(true)");
1322            }
1323
1324            if (method.equals("GET")) {
1325                method = "POST"; // Backward compatibility
1326            }
1327            if ("TRACE".equals(method) && "http".equals(url.getProtocol())) {
1328                throw new ProtocolException("HTTP method TRACE" +
1329                                            " doesn't support output");
1330            }
1331
1332            // if there's already an input stream open, throw an exception
1333            if (inputStream != null) {
1334                throw new ProtocolException("Cannot write output after reading input.");
1335            }
1336
1337            if (!checkReuseConnection())
1338                connect();
1339
1340            boolean expectContinue = false;
1341            String expects = requests.findValue("Expect");
1342            if ("100-Continue".equalsIgnoreCase(expects) && streaming()) {
1343                http.setIgnoreContinue(false);
1344                expectContinue = true;
1345            }
1346
1347            if (streaming() && strOutputStream == null) {
1348                writeRequests();
1349            }
1350
1351            if (expectContinue) {
1352                expect100Continue();
1353            }
1354            ps = (PrintStream)http.getOutputStream();
1355            if (streaming()) {
1356                if (strOutputStream == null) {
1357                    if (chunkLength != -1) { /* chunked */
1358                         strOutputStream = new StreamingOutputStream(
1359                               new ChunkedOutputStream(ps, chunkLength), -1L);
1360                    } else { /* must be fixed content length */
1361                        long length = 0L;
1362                        if (fixedContentLengthLong != -1) {
1363                            length = fixedContentLengthLong;
1364                        } else if (fixedContentLength != -1) {
1365                            length = fixedContentLength;
1366                        }
1367                        strOutputStream = new StreamingOutputStream(ps, length);
1368                    }
1369                }
1370                return strOutputStream;
1371            } else {
1372                if (poster == null) {
1373                    poster = new PosterOutputStream();
1374                }
1375                return poster;
1376            }
1377        } catch (RuntimeException e) {
1378            disconnectInternal();
1379            throw e;
1380        } catch (ProtocolException e) {
1381            // Save the response code which may have been set while enforcing
1382            // the 100-continue. disconnectInternal() forces it to -1
1383            int i = responseCode;
1384            disconnectInternal();
1385            responseCode = i;
1386            throw e;
1387        } catch (IOException e) {
1388            disconnectInternal();
1389            throw e;
1390        }
1391    }
1392
1393    public boolean streaming () {
1394        return (fixedContentLength != -1) || (fixedContentLengthLong != -1) ||
1395               (chunkLength != -1);
1396    }
1397
1398    /*
1399     * get applicable cookies based on the uri and request headers
1400     * add them to the existing request headers
1401     */
1402    private void setCookieHeader() throws IOException {
1403        if (cookieHandler != null) {
1404            // we only want to capture the user defined Cookies once, as
1405            // they cannot be changed by user code after we are connected,
1406            // only internally.
1407            synchronized (this) {
1408                if (setUserCookies) {
1409                    int k = requests.getKey("Cookie");
1410                    if (k != -1)
1411                        userCookies = requests.getValue(k);
1412                    k = requests.getKey("Cookie2");
1413                    if (k != -1)
1414                        userCookies2 = requests.getValue(k);
1415                    setUserCookies = false;
1416                }
1417            }
1418
1419            // remove old Cookie header before setting new one.
1420            requests.remove("Cookie");
1421            requests.remove("Cookie2");
1422
1423            URI uri = ParseUtil.toURI(url);
1424            if (uri != null) {
1425                if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
1426                    logger.finest("CookieHandler request for " + uri);
1427                }
1428                Map<String, List<String>> cookies
1429                    = cookieHandler.get(
1430                        uri, requests.getHeaders(EXCLUDE_HEADERS));
1431                if (!cookies.isEmpty()) {
1432                    if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
1433                        logger.finest("Cookies retrieved: " + cookies.toString());
1434                    }
1435                    for (Map.Entry<String, List<String>> entry :
1436                             cookies.entrySet()) {
1437                        String key = entry.getKey();
1438                        // ignore all entries that don't have "Cookie"
1439                        // or "Cookie2" as keys
1440                        if (!"Cookie".equalsIgnoreCase(key) &&
1441                            !"Cookie2".equalsIgnoreCase(key)) {
1442                            continue;
1443                        }
1444                        List<String> l = entry.getValue();
1445                        if (l != null && !l.isEmpty()) {
1446                            StringJoiner cookieValue = new StringJoiner("; ");
1447                            for (String value : l) {
1448                                cookieValue.add(value);
1449                            }
1450                            requests.add(key, cookieValue.toString());
1451                        }
1452                    }
1453                }
1454            }
1455            if (userCookies != null) {
1456                int k;
1457                if ((k = requests.getKey("Cookie")) != -1)
1458                    requests.set("Cookie", requests.getValue(k) + ";" + userCookies);
1459                else
1460                    requests.set("Cookie", userCookies);
1461            }
1462            if (userCookies2 != null) {
1463                int k;
1464                if ((k = requests.getKey("Cookie2")) != -1)
1465                    requests.set("Cookie2", requests.getValue(k) + ";" + userCookies2);
1466                else
1467                    requests.set("Cookie2", userCookies2);
1468            }
1469
1470        } // end of getting cookies
1471    }
1472
1473    @Override
1474    public synchronized InputStream getInputStream() throws IOException {
1475        connecting = true;
1476        SocketPermission p = URLtoSocketPermission(this.url);
1477
1478        if (p != null) {
1479            try {
1480                return AccessController.doPrivilegedWithCombiner(
1481                    new PrivilegedExceptionAction<>() {
1482                        public InputStream run() throws IOException {
1483                            return getInputStream0();
1484                        }
1485                    }, null, p
1486                );
1487            } catch (PrivilegedActionException e) {
1488                throw (IOException) e.getException();
1489            }
1490        } else {
1491            return getInputStream0();
1492        }
1493    }
1494
1495    @SuppressWarnings("empty-statement")
1496    private synchronized InputStream getInputStream0() throws IOException {
1497
1498        if (!doInput) {
1499            throw new ProtocolException("Cannot read from URLConnection"
1500                   + " if doInput=false (call setDoInput(true))");
1501        }
1502
1503        if (rememberedException != null) {
1504            if (rememberedException instanceof RuntimeException)
1505                throw new RuntimeException(rememberedException);
1506            else {
1507                throw getChainedException((IOException)rememberedException);
1508            }
1509        }
1510
1511        if (inputStream != null) {
1512            return inputStream;
1513        }
1514
1515        if (streaming() ) {
1516            if (strOutputStream == null) {
1517                getOutputStream();
1518            }
1519            /* make sure stream is closed */
1520            strOutputStream.close ();
1521            if (!strOutputStream.writtenOK()) {
1522                throw new IOException ("Incomplete output stream");
1523            }
1524        }
1525
1526        int redirects = 0;
1527        int respCode = 0;
1528        long cl = -1;
1529        AuthenticationInfo serverAuthentication = null;
1530        AuthenticationInfo proxyAuthentication = null;
1531        AuthenticationHeader srvHdr = null;
1532
1533        /**
1534         * Failed Negotiate
1535         *
1536         * In some cases, the Negotiate auth is supported for the
1537         * remote host but the negotiate process still fails (For
1538         * example, if the web page is located on a backend server
1539         * and delegation is needed but fails). The authentication
1540         * process will start again, and we need to detect this
1541         * kind of failure and do proper fallback (say, to NTLM).
1542         *
1543         * In order to achieve this, the inNegotiate flag is set
1544         * when the first negotiate challenge is met (and reset
1545         * if authentication is finished). If a fresh new negotiate
1546         * challenge (no parameter) is found while inNegotiate is
1547         * set, we know there's a failed auth attempt recently.
1548         * Here we'll ignore the header line so that fallback
1549         * can be practiced.
1550         *
1551         * inNegotiateProxy is for proxy authentication.
1552         */
1553        boolean inNegotiate = false;
1554        boolean inNegotiateProxy = false;
1555
1556        // If the user has set either of these headers then do not remove them
1557        isUserServerAuth = requests.getKey("Authorization") != -1;
1558        isUserProxyAuth = requests.getKey("Proxy-Authorization") != -1;
1559
1560        try {
1561            do {
1562                if (!checkReuseConnection())
1563                    connect();
1564
1565                if (cachedInputStream != null) {
1566                    return cachedInputStream;
1567                }
1568
1569                // Check if URL should be metered
1570                boolean meteredInput = ProgressMonitor.getDefault().shouldMeterInput(url, method);
1571
1572                if (meteredInput)   {
1573                    pi = new ProgressSource(url, method);
1574                    pi.beginTracking();
1575                }
1576
1577                /* REMIND: This exists to fix the HttpsURLConnection subclass.
1578                 * Hotjava needs to run on JDK1.1FCS.  Do proper fix once a
1579                 * proper solution for SSL can be found.
1580                 */
1581                ps = (PrintStream)http.getOutputStream();
1582
1583                if (!streaming()) {
1584                    writeRequests();
1585                }
1586                http.parseHTTP(responses, pi, this);
1587                if (logger.isLoggable(PlatformLogger.Level.FINE)) {
1588                    logger.fine(responses.toString());
1589                }
1590
1591                boolean b1 = responses.filterNTLMResponses("WWW-Authenticate");
1592                boolean b2 = responses.filterNTLMResponses("Proxy-Authenticate");
1593                if (b1 || b2) {
1594                    if (logger.isLoggable(PlatformLogger.Level.FINE)) {
1595                        logger.fine(">>>> Headers are filtered");
1596                        logger.fine(responses.toString());
1597                    }
1598                }
1599
1600                inputStream = http.getInputStream();
1601
1602                respCode = getResponseCode();
1603                if (respCode == -1) {
1604                    disconnectInternal();
1605                    throw new IOException ("Invalid Http response");
1606                }
1607                if (respCode == HTTP_PROXY_AUTH) {
1608                    if (streaming()) {
1609                        disconnectInternal();
1610                        throw new HttpRetryException (
1611                            RETRY_MSG1, HTTP_PROXY_AUTH);
1612                    }
1613
1614                    // Read comments labeled "Failed Negotiate" for details.
1615                    boolean dontUseNegotiate = false;
1616                    Iterator<String> iter = responses.multiValueIterator("Proxy-Authenticate");
1617                    while (iter.hasNext()) {
1618                        String value = iter.next().trim();
1619                        if (value.equalsIgnoreCase("Negotiate") ||
1620                                value.equalsIgnoreCase("Kerberos")) {
1621                            if (!inNegotiateProxy) {
1622                                inNegotiateProxy = true;
1623                            } else {
1624                                dontUseNegotiate = true;
1625                                doingNTLMp2ndStage = false;
1626                                proxyAuthentication = null;
1627                            }
1628                            break;
1629                        }
1630                    }
1631
1632                    // changes: add a 3rd parameter to the constructor of
1633                    // AuthenticationHeader, so that NegotiateAuthentication.
1634                    // isSupported can be tested.
1635                    // The other 2 appearances of "new AuthenticationHeader" is
1636                    // altered in similar ways.
1637
1638                    AuthenticationHeader authhdr = new AuthenticationHeader (
1639                            "Proxy-Authenticate",
1640                            responses,
1641                            new HttpCallerInfo(url,
1642                                               http.getProxyHostUsed(),
1643                                               http.getProxyPortUsed(),
1644                                               authenticator),
1645                            dontUseNegotiate,
1646                            disabledProxyingSchemes
1647                    );
1648
1649                    if (!doingNTLMp2ndStage) {
1650                        proxyAuthentication =
1651                            resetProxyAuthentication(proxyAuthentication, authhdr);
1652                        if (proxyAuthentication != null) {
1653                            redirects++;
1654                            disconnectInternal();
1655                            continue;
1656                        }
1657                    } else {
1658                        /* in this case, only one header field will be present */
1659                        String raw = responses.findValue ("Proxy-Authenticate");
1660                        reset ();
1661                        if (!proxyAuthentication.setHeaders(this,
1662                                                        authhdr.headerParser(), raw)) {
1663                            disconnectInternal();
1664                            throw new IOException ("Authentication failure");
1665                        }
1666                        if (serverAuthentication != null && srvHdr != null &&
1667                                !serverAuthentication.setHeaders(this,
1668                                                        srvHdr.headerParser(), raw)) {
1669                            disconnectInternal ();
1670                            throw new IOException ("Authentication failure");
1671                        }
1672                        authObj = null;
1673                        doingNTLMp2ndStage = false;
1674                        continue;
1675                    }
1676                } else {
1677                    inNegotiateProxy = false;
1678                    doingNTLMp2ndStage = false;
1679                    if (!isUserProxyAuth)
1680                        requests.remove("Proxy-Authorization");
1681                }
1682
1683                // cache proxy authentication info
1684                if (proxyAuthentication != null) {
1685                    // cache auth info on success, domain header not relevant.
1686                    proxyAuthentication.addToCache();
1687                }
1688
1689                if (respCode == HTTP_UNAUTHORIZED) {
1690                    if (streaming()) {
1691                        disconnectInternal();
1692                        throw new HttpRetryException (
1693                            RETRY_MSG2, HTTP_UNAUTHORIZED);
1694                    }
1695
1696                    // Read comments labeled "Failed Negotiate" for details.
1697                    boolean dontUseNegotiate = false;
1698                    Iterator<String> iter = responses.multiValueIterator("WWW-Authenticate");
1699                    while (iter.hasNext()) {
1700                        String value = iter.next().trim();
1701                        if (value.equalsIgnoreCase("Negotiate") ||
1702                                value.equalsIgnoreCase("Kerberos")) {
1703                            if (!inNegotiate) {
1704                                inNegotiate = true;
1705                            } else {
1706                                dontUseNegotiate = true;
1707                                doingNTLM2ndStage = false;
1708                                serverAuthentication = null;
1709                            }
1710                            break;
1711                        }
1712                    }
1713
1714                    srvHdr = new AuthenticationHeader (
1715                            "WWW-Authenticate", responses,
1716                            new HttpCallerInfo(url, authenticator),
1717                            dontUseNegotiate
1718                    );
1719
1720                    String raw = srvHdr.raw();
1721                    if (!doingNTLM2ndStage) {
1722                        if ((serverAuthentication != null)&&
1723                            serverAuthentication.getAuthScheme() != NTLM) {
1724                            if (serverAuthentication.isAuthorizationStale (raw)) {
1725                                /* we can retry with the current credentials */
1726                                disconnectWeb();
1727                                redirects++;
1728                                requests.set(serverAuthentication.getHeaderName(),
1729                                            serverAuthentication.getHeaderValue(url, method));
1730                                currentServerCredentials = serverAuthentication;
1731                                setCookieHeader();
1732                                continue;
1733                            } else {
1734                                serverAuthentication.removeFromCache();
1735                            }
1736                        }
1737                        serverAuthentication = getServerAuthentication(srvHdr);
1738                        currentServerCredentials = serverAuthentication;
1739
1740                        if (serverAuthentication != null) {
1741                            disconnectWeb();
1742                            redirects++; // don't let things loop ad nauseum
1743                            setCookieHeader();
1744                            continue;
1745                        }
1746                    } else {
1747                        reset ();
1748                        /* header not used for ntlm */
1749                        if (!serverAuthentication.setHeaders(this, null, raw)) {
1750                            disconnectWeb();
1751                            throw new IOException ("Authentication failure");
1752                        }
1753                        doingNTLM2ndStage = false;
1754                        authObj = null;
1755                        setCookieHeader();
1756                        continue;
1757                    }
1758                }
1759                // cache server authentication info
1760                if (serverAuthentication != null) {
1761                    // cache auth info on success
1762                    if (!(serverAuthentication instanceof DigestAuthentication) ||
1763                        (domain == null)) {
1764                        if (serverAuthentication instanceof BasicAuthentication) {
1765                            // check if the path is shorter than the existing entry
1766                            String npath = AuthenticationInfo.reducePath (url.getPath());
1767                            String opath = serverAuthentication.path;
1768                            if (!opath.startsWith (npath) || npath.length() >= opath.length()) {
1769                                /* npath is longer, there must be a common root */
1770                                npath = BasicAuthentication.getRootPath (opath, npath);
1771                            }
1772                            // remove the entry and create a new one
1773                            BasicAuthentication a =
1774                                (BasicAuthentication) serverAuthentication.clone();
1775                            serverAuthentication.removeFromCache();
1776                            a.path = npath;
1777                            serverAuthentication = a;
1778                        }
1779                        serverAuthentication.addToCache();
1780                    } else {
1781                        // what we cache is based on the domain list in the request
1782                        DigestAuthentication srv = (DigestAuthentication)
1783                            serverAuthentication;
1784                        StringTokenizer tok = new StringTokenizer (domain," ");
1785                        String realm = srv.realm;
1786                        PasswordAuthentication pw = srv.pw;
1787                        digestparams = srv.params;
1788                        while (tok.hasMoreTokens()) {
1789                            String path = tok.nextToken();
1790                            try {
1791                                /* path could be an abs_path or a complete URI */
1792                                URL u = new URL (url, path);
1793                                DigestAuthentication d = new DigestAuthentication (
1794                                                   false, u, realm, "Digest", pw,
1795                                                   digestparams, srv.authenticatorKey);
1796                                d.addToCache ();
1797                            } catch (Exception e) {}
1798                        }
1799                    }
1800                }
1801
1802                // some flags should be reset to its initialized form so that
1803                // even after a redirect the necessary checks can still be
1804                // preformed.
1805                inNegotiate = false;
1806                inNegotiateProxy = false;
1807
1808                //serverAuthentication = null;
1809                doingNTLMp2ndStage = false;
1810                doingNTLM2ndStage = false;
1811                if (!isUserServerAuth)
1812                    requests.remove("Authorization");
1813                if (!isUserProxyAuth)
1814                    requests.remove("Proxy-Authorization");
1815
1816                if (respCode == HTTP_OK) {
1817                    checkResponseCredentials (false);
1818                } else {
1819                    needToCheck = false;
1820                }
1821
1822                // a flag need to clean
1823                needToCheck = true;
1824
1825                if (followRedirect()) {
1826                    /* if we should follow a redirect, then the followRedirects()
1827                     * method will disconnect() and re-connect us to the new
1828                     * location
1829                     */
1830                    redirects++;
1831
1832                    // redirecting HTTP response may have set cookie, so
1833                    // need to re-generate request header
1834                    setCookieHeader();
1835
1836                    continue;
1837                }
1838
1839                try {
1840                    cl = Long.parseLong(responses.findValue("content-length"));
1841                } catch (Exception exc) { };
1842
1843                if (method.equals("HEAD") || cl == 0 ||
1844                    respCode == HTTP_NOT_MODIFIED ||
1845                    respCode == HTTP_NO_CONTENT) {
1846
1847                    if (pi != null) {
1848                        pi.finishTracking();
1849                        pi = null;
1850                    }
1851                    http.finished();
1852                    http = null;
1853                    inputStream = new EmptyInputStream();
1854                    connected = false;
1855                }
1856
1857                if (respCode == 200 || respCode == 203 || respCode == 206 ||
1858                    respCode == 300 || respCode == 301 || respCode == 410) {
1859                    if (cacheHandler != null && getUseCaches()) {
1860                        // give cache a chance to save response in cache
1861                        URI uri = ParseUtil.toURI(url);
1862                        if (uri != null) {
1863                            URLConnection uconn = this;
1864                            if ("https".equalsIgnoreCase(uri.getScheme())) {
1865                                try {
1866                                // use reflection to get to the public
1867                                // HttpsURLConnection instance saved in
1868                                // DelegateHttpsURLConnection
1869                                uconn = (URLConnection)this.getClass().getField("httpsURLConnection").get(this);
1870                                } catch (IllegalAccessException |
1871                                         NoSuchFieldException e) {
1872                                    // ignored; use 'this'
1873                                }
1874                            }
1875                            CacheRequest cacheRequest =
1876                                cacheHandler.put(uri, uconn);
1877                            if (cacheRequest != null && http != null) {
1878                                http.setCacheRequest(cacheRequest);
1879                                inputStream = new HttpInputStream(inputStream, cacheRequest);
1880                            }
1881                        }
1882                    }
1883                }
1884
1885                if (!(inputStream instanceof HttpInputStream)) {
1886                    inputStream = new HttpInputStream(inputStream);
1887                }
1888
1889                if (respCode >= 400) {
1890                    if (respCode == 404 || respCode == 410) {
1891                        throw new FileNotFoundException(url.toString());
1892                    } else {
1893                        throw new java.io.IOException("Server returned HTTP" +
1894                              " response code: " + respCode + " for URL: " +
1895                              url.toString());
1896                    }
1897                }
1898                poster = null;
1899                strOutputStream = null;
1900                return inputStream;
1901            } while (redirects < maxRedirects);
1902
1903            throw new ProtocolException("Server redirected too many " +
1904                                        " times ("+ redirects + ")");
1905        } catch (RuntimeException e) {
1906            disconnectInternal();
1907            rememberedException = e;
1908            throw e;
1909        } catch (IOException e) {
1910            rememberedException = e;
1911
1912            // buffer the error stream if bytes < 4k
1913            // and it can be buffered within 1 second
1914            String te = responses.findValue("Transfer-Encoding");
1915            if (http != null && http.isKeepingAlive() && enableESBuffer &&
1916                (cl > 0 || (te != null && te.equalsIgnoreCase("chunked")))) {
1917                errorStream = ErrorStream.getErrorStream(inputStream, cl, http);
1918            }
1919            throw e;
1920        } finally {
1921            if (proxyAuthKey != null) {
1922                AuthenticationInfo.endAuthRequest(proxyAuthKey);
1923            }
1924            if (serverAuthKey != null) {
1925                AuthenticationInfo.endAuthRequest(serverAuthKey);
1926            }
1927        }
1928    }
1929
1930    /*
1931     * Creates a chained exception that has the same type as
1932     * original exception and with the same message. Right now,
1933     * there is no convenient APIs for doing so.
1934     */
1935    private IOException getChainedException(final IOException rememberedException) {
1936        try {
1937            final Object[] args = { rememberedException.getMessage() };
1938            IOException chainedException =
1939                java.security.AccessController.doPrivileged(
1940                    new java.security.PrivilegedExceptionAction<>() {
1941                        public IOException run() throws Exception {
1942                            return (IOException)
1943                                rememberedException.getClass()
1944                                .getConstructor(new Class<?>[] { String.class })
1945                                .newInstance(args);
1946                        }
1947                    });
1948            chainedException.initCause(rememberedException);
1949            return chainedException;
1950        } catch (Exception ignored) {
1951            return rememberedException;
1952        }
1953    }
1954
1955    @Override
1956    public InputStream getErrorStream() {
1957        if (connected && responseCode >= 400) {
1958            // Client Error 4xx and Server Error 5xx
1959            if (errorStream != null) {
1960                return errorStream;
1961            } else if (inputStream != null) {
1962                return inputStream;
1963            }
1964        }
1965        return null;
1966    }
1967
1968    /**
1969     * set or reset proxy authentication info in request headers
1970     * after receiving a 407 error. In the case of NTLM however,
1971     * receiving a 407 is normal and we just skip the stale check
1972     * because ntlm does not support this feature.
1973     */
1974    private AuthenticationInfo
1975        resetProxyAuthentication(AuthenticationInfo proxyAuthentication, AuthenticationHeader auth) throws IOException {
1976        if ((proxyAuthentication != null )&&
1977             proxyAuthentication.getAuthScheme() != NTLM) {
1978            String raw = auth.raw();
1979            if (proxyAuthentication.isAuthorizationStale (raw)) {
1980                /* we can retry with the current credentials */
1981                String value;
1982                if (proxyAuthentication instanceof DigestAuthentication) {
1983                    DigestAuthentication digestProxy = (DigestAuthentication)
1984                        proxyAuthentication;
1985                    if (tunnelState() == TunnelState.SETUP) {
1986                        value = digestProxy.getHeaderValue(connectRequestURI(url), HTTP_CONNECT);
1987                    } else {
1988                        value = digestProxy.getHeaderValue(getRequestURI(), method);
1989                    }
1990                } else {
1991                    value = proxyAuthentication.getHeaderValue(url, method);
1992                }
1993                requests.set(proxyAuthentication.getHeaderName(), value);
1994                currentProxyCredentials = proxyAuthentication;
1995                return proxyAuthentication;
1996            } else {
1997                proxyAuthentication.removeFromCache();
1998            }
1999        }
2000        proxyAuthentication = getHttpProxyAuthentication(auth);
2001        currentProxyCredentials = proxyAuthentication;
2002        return  proxyAuthentication;
2003    }
2004
2005    /**
2006     * Returns the tunnel state.
2007     *
2008     * @return  the state
2009     */
2010    TunnelState tunnelState() {
2011        return tunnelState;
2012    }
2013
2014    /**
2015     * Set the tunneling status.
2016     *
2017     * @param tunnelState the state
2018     */
2019    public void setTunnelState(TunnelState tunnelState) {
2020        this.tunnelState = tunnelState;
2021    }
2022
2023    /**
2024     * establish a tunnel through proxy server
2025     */
2026    public synchronized void doTunneling() throws IOException {
2027        int retryTunnel = 0;
2028        String statusLine = "";
2029        int respCode = 0;
2030        AuthenticationInfo proxyAuthentication = null;
2031        String proxyHost = null;
2032        int proxyPort = -1;
2033
2034        // save current requests so that they can be restored after tunnel is setup.
2035        MessageHeader savedRequests = requests;
2036        requests = new MessageHeader();
2037
2038        // Read comments labeled "Failed Negotiate" for details.
2039        boolean inNegotiateProxy = false;
2040
2041        try {
2042            /* Actively setting up a tunnel */
2043            setTunnelState(TunnelState.SETUP);
2044
2045            do {
2046                if (!checkReuseConnection()) {
2047                    proxiedConnect(url, proxyHost, proxyPort, false);
2048                }
2049                // send the "CONNECT" request to establish a tunnel
2050                // through proxy server
2051                sendCONNECTRequest();
2052                responses.reset();
2053
2054                // There is no need to track progress in HTTP Tunneling,
2055                // so ProgressSource is null.
2056                http.parseHTTP(responses, null, this);
2057
2058                /* Log the response to the CONNECT */
2059                if (logger.isLoggable(PlatformLogger.Level.FINE)) {
2060                    logger.fine(responses.toString());
2061                }
2062
2063                if (responses.filterNTLMResponses("Proxy-Authenticate")) {
2064                    if (logger.isLoggable(PlatformLogger.Level.FINE)) {
2065                        logger.fine(">>>> Headers are filtered");
2066                        logger.fine(responses.toString());
2067                    }
2068                }
2069
2070                statusLine = responses.getValue(0);
2071                StringTokenizer st = new StringTokenizer(statusLine);
2072                st.nextToken();
2073                respCode = Integer.parseInt(st.nextToken().trim());
2074                if (respCode == HTTP_PROXY_AUTH) {
2075                    // Read comments labeled "Failed Negotiate" for details.
2076                    boolean dontUseNegotiate = false;
2077                    Iterator<String> iter = responses.multiValueIterator("Proxy-Authenticate");
2078                    while (iter.hasNext()) {
2079                        String value = iter.next().trim();
2080                        if (value.equalsIgnoreCase("Negotiate") ||
2081                                value.equalsIgnoreCase("Kerberos")) {
2082                            if (!inNegotiateProxy) {
2083                                inNegotiateProxy = true;
2084                            } else {
2085                                dontUseNegotiate = true;
2086                                doingNTLMp2ndStage = false;
2087                                proxyAuthentication = null;
2088                            }
2089                            break;
2090                        }
2091                    }
2092
2093                    AuthenticationHeader authhdr = new AuthenticationHeader(
2094                            "Proxy-Authenticate",
2095                            responses,
2096                            new HttpCallerInfo(url,
2097                                               http.getProxyHostUsed(),
2098                                               http.getProxyPortUsed(),
2099                                               authenticator),
2100                            dontUseNegotiate,
2101                            disabledTunnelingSchemes
2102                    );
2103                    if (!doingNTLMp2ndStage) {
2104                        proxyAuthentication =
2105                            resetProxyAuthentication(proxyAuthentication, authhdr);
2106                        if (proxyAuthentication != null) {
2107                            proxyHost = http.getProxyHostUsed();
2108                            proxyPort = http.getProxyPortUsed();
2109                            disconnectInternal();
2110                            retryTunnel++;
2111                            continue;
2112                        }
2113                    } else {
2114                        String raw = responses.findValue ("Proxy-Authenticate");
2115                        reset ();
2116                        if (!proxyAuthentication.setHeaders(this,
2117                                                authhdr.headerParser(), raw)) {
2118                            disconnectInternal();
2119                            throw new IOException ("Authentication failure");
2120                        }
2121                        authObj = null;
2122                        doingNTLMp2ndStage = false;
2123                        continue;
2124                    }
2125                }
2126                // cache proxy authentication info
2127                if (proxyAuthentication != null) {
2128                    // cache auth info on success, domain header not relevant.
2129                    proxyAuthentication.addToCache();
2130                }
2131
2132                if (respCode == HTTP_OK) {
2133                    setTunnelState(TunnelState.TUNNELING);
2134                    break;
2135                }
2136                // we don't know how to deal with other response code
2137                // so disconnect and report error
2138                disconnectInternal();
2139                setTunnelState(TunnelState.NONE);
2140                break;
2141            } while (retryTunnel < maxRedirects);
2142
2143            if (retryTunnel >= maxRedirects || (respCode != HTTP_OK)) {
2144                throw new IOException("Unable to tunnel through proxy."+
2145                                      " Proxy returns \"" +
2146                                      statusLine + "\"");
2147            }
2148        } finally  {
2149            if (proxyAuthKey != null) {
2150                AuthenticationInfo.endAuthRequest(proxyAuthKey);
2151            }
2152        }
2153
2154        // restore original request headers
2155        requests = savedRequests;
2156
2157        // reset responses
2158        responses.reset();
2159    }
2160
2161    static String connectRequestURI(URL url) {
2162        String host = url.getHost();
2163        int port = url.getPort();
2164        port = port != -1 ? port : url.getDefaultPort();
2165
2166        return host + ":" + port;
2167    }
2168
2169    /**
2170     * send a CONNECT request for establishing a tunnel to proxy server
2171     */
2172    private void sendCONNECTRequest() throws IOException {
2173        int port = url.getPort();
2174
2175        requests.set(0, HTTP_CONNECT + " " + connectRequestURI(url)
2176                         + " " + httpVersion, null);
2177        requests.setIfNotSet("User-Agent", userAgent);
2178
2179        String host = url.getHost();
2180        if (port != -1 && port != url.getDefaultPort()) {
2181            host += ":" + String.valueOf(port);
2182        }
2183        requests.setIfNotSet("Host", host);
2184
2185        // Not really necessary for a tunnel, but can't hurt
2186        requests.setIfNotSet("Accept", acceptString);
2187
2188        if (http.getHttpKeepAliveSet()) {
2189            requests.setIfNotSet("Proxy-Connection", "keep-alive");
2190        }
2191
2192        setPreemptiveProxyAuthentication(requests);
2193
2194         /* Log the CONNECT request */
2195        if (logger.isLoggable(PlatformLogger.Level.FINE)) {
2196            logger.fine(requests.toString());
2197        }
2198
2199        http.writeRequests(requests, null);
2200    }
2201
2202    /**
2203     * Sets pre-emptive proxy authentication in header
2204     */
2205    private void setPreemptiveProxyAuthentication(MessageHeader requests) throws IOException {
2206        AuthenticationInfo pauth
2207            = AuthenticationInfo.getProxyAuth(http.getProxyHostUsed(),
2208                                              http.getProxyPortUsed(),
2209                                              getAuthenticatorKey());
2210        if (pauth != null && pauth.supportsPreemptiveAuthorization()) {
2211            String value;
2212            if (pauth instanceof DigestAuthentication) {
2213                DigestAuthentication digestProxy = (DigestAuthentication) pauth;
2214                if (tunnelState() == TunnelState.SETUP) {
2215                    value = digestProxy
2216                        .getHeaderValue(connectRequestURI(url), HTTP_CONNECT);
2217                } else {
2218                    value = digestProxy.getHeaderValue(getRequestURI(), method);
2219                }
2220            } else {
2221                value = pauth.getHeaderValue(url, method);
2222            }
2223
2224            // Sets "Proxy-authorization"
2225            requests.set(pauth.getHeaderName(), value);
2226            currentProxyCredentials = pauth;
2227        }
2228    }
2229
2230    /**
2231     * Gets the authentication for an HTTP proxy, and applies it to
2232     * the connection.
2233     */
2234    @SuppressWarnings("fallthrough")
2235    private AuthenticationInfo getHttpProxyAuthentication (AuthenticationHeader authhdr) {
2236        /* get authorization from authenticator */
2237        AuthenticationInfo ret = null;
2238        String raw = authhdr.raw();
2239        String host = http.getProxyHostUsed();
2240        int port = http.getProxyPortUsed();
2241        if (host != null && authhdr.isPresent()) {
2242            HeaderParser p = authhdr.headerParser();
2243            String realm = p.findValue("realm");
2244            String scheme = authhdr.scheme();
2245            AuthScheme authScheme = UNKNOWN;
2246            if ("basic".equalsIgnoreCase(scheme)) {
2247                authScheme = BASIC;
2248            } else if ("digest".equalsIgnoreCase(scheme)) {
2249                authScheme = DIGEST;
2250            } else if ("ntlm".equalsIgnoreCase(scheme)) {
2251                authScheme = NTLM;
2252                doingNTLMp2ndStage = true;
2253            } else if ("Kerberos".equalsIgnoreCase(scheme)) {
2254                authScheme = KERBEROS;
2255                doingNTLMp2ndStage = true;
2256            } else if ("Negotiate".equalsIgnoreCase(scheme)) {
2257                authScheme = NEGOTIATE;
2258                doingNTLMp2ndStage = true;
2259            }
2260
2261            if (realm == null)
2262                realm = "";
2263            proxyAuthKey = AuthenticationInfo.getProxyAuthKey(host, port, realm,
2264                                authScheme, getAuthenticatorKey());
2265            ret = AuthenticationInfo.getProxyAuth(proxyAuthKey);
2266            if (ret == null) {
2267                switch (authScheme) {
2268                case BASIC:
2269                    InetAddress addr = null;
2270                    try {
2271                        final String finalHost = host;
2272                        addr = java.security.AccessController.doPrivileged(
2273                            new java.security.PrivilegedExceptionAction<>() {
2274                                public InetAddress run()
2275                                    throws java.net.UnknownHostException {
2276                                    return InetAddress.getByName(finalHost);
2277                                }
2278                            });
2279                    } catch (java.security.PrivilegedActionException ignored) {
2280                        // User will have an unknown host.
2281                    }
2282                    PasswordAuthentication a =
2283                        privilegedRequestPasswordAuthentication(
2284                                    authenticator,
2285                                    host, addr, port, "http",
2286                                    realm, scheme, url, RequestorType.PROXY);
2287                    if (a != null) {
2288                        ret = new BasicAuthentication(true, host, port, realm, a,
2289                                             getAuthenticatorKey());
2290                    }
2291                    break;
2292                case DIGEST:
2293                    a = privilegedRequestPasswordAuthentication(
2294                                    authenticator,
2295                                    host, null, port, url.getProtocol(),
2296                                    realm, scheme, url, RequestorType.PROXY);
2297                    if (a != null) {
2298                        DigestAuthentication.Parameters params =
2299                            new DigestAuthentication.Parameters();
2300                        ret = new DigestAuthentication(true, host, port, realm,
2301                                             scheme, a, params,
2302                                             getAuthenticatorKey());
2303                    }
2304                    break;
2305                case NTLM:
2306                    if (NTLMAuthenticationProxy.supported) {
2307                        /* tryTransparentNTLMProxy will always be true the first
2308                         * time around, but verify that the platform supports it
2309                         * otherwise don't try. */
2310                        if (tryTransparentNTLMProxy) {
2311                            tryTransparentNTLMProxy =
2312                                    NTLMAuthenticationProxy.supportsTransparentAuth;
2313                            /* If the platform supports transparent authentication
2314                             * then normally it's ok to do transparent auth to a proxy
2315                                         * because we generally trust proxies (chosen by the user)
2316                                         * But not in the case of 305 response where the server
2317                             * chose it. */
2318                            if (tryTransparentNTLMProxy && useProxyResponseCode) {
2319                                tryTransparentNTLMProxy = false;
2320                            }
2321
2322                        }
2323                        a = null;
2324                        if (tryTransparentNTLMProxy) {
2325                            logger.finest("Trying Transparent NTLM authentication");
2326                        } else {
2327                            a = privilegedRequestPasswordAuthentication(
2328                                                authenticator,
2329                                                host, null, port, url.getProtocol(),
2330                                                "", scheme, url, RequestorType.PROXY);
2331                        }
2332                        /* If we are not trying transparent authentication then
2333                         * we need to have a PasswordAuthentication instance. For
2334                         * transparent authentication (Windows only) the username
2335                         * and password will be picked up from the current logged
2336                         * on users credentials.
2337                        */
2338                        if (tryTransparentNTLMProxy ||
2339                              (!tryTransparentNTLMProxy && a != null)) {
2340                            ret = NTLMAuthenticationProxy.proxy.create(true, host,
2341                                    port, a, getAuthenticatorKey());
2342                        }
2343
2344                        /* set to false so that we do not try again */
2345                        tryTransparentNTLMProxy = false;
2346                    }
2347                    break;
2348                case NEGOTIATE:
2349                    ret = new NegotiateAuthentication(new HttpCallerInfo(authhdr.getHttpCallerInfo(), "Negotiate"));
2350                    break;
2351                case KERBEROS:
2352                    ret = new NegotiateAuthentication(new HttpCallerInfo(authhdr.getHttpCallerInfo(), "Kerberos"));
2353                    break;
2354                case UNKNOWN:
2355                    if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
2356                        logger.finest("Unknown/Unsupported authentication scheme: " + scheme);
2357                    }
2358                /*fall through*/
2359                default:
2360                    throw new AssertionError("should not reach here");
2361                }
2362            }
2363            // For backwards compatibility, we also try defaultAuth
2364            // REMIND:  Get rid of this for JDK2.0.
2365
2366            if (ret == null && defaultAuth != null
2367                && defaultAuth.schemeSupported(scheme)) {
2368                try {
2369                    URL u = new URL("http", host, port, "/");
2370                    String a = defaultAuth.authString(u, scheme, realm);
2371                    if (a != null) {
2372                        ret = new BasicAuthentication (true, host, port, realm, a,
2373                                  getAuthenticatorKey());
2374                        // not in cache by default - cache on success
2375                    }
2376                } catch (java.net.MalformedURLException ignored) {
2377                }
2378            }
2379            if (ret != null) {
2380                if (!ret.setHeaders(this, p, raw)) {
2381                    ret = null;
2382                }
2383            }
2384        }
2385        if (logger.isLoggable(PlatformLogger.Level.FINER)) {
2386            logger.finer("Proxy Authentication for " + authhdr.toString() +" returned " + (ret != null ? ret.toString() : "null"));
2387        }
2388        return ret;
2389    }
2390
2391    /**
2392     * Gets the authentication for an HTTP server, and applies it to
2393     * the connection.
2394     * @param authHdr the AuthenticationHeader which tells what auth scheme is
2395     * preferred.
2396     */
2397    @SuppressWarnings("fallthrough")
2398    private AuthenticationInfo getServerAuthentication (AuthenticationHeader authhdr) {
2399        /* get authorization from authenticator */
2400        AuthenticationInfo ret = null;
2401        String raw = authhdr.raw();
2402        /* When we get an NTLM auth from cache, don't set any special headers */
2403        if (authhdr.isPresent()) {
2404            HeaderParser p = authhdr.headerParser();
2405            String realm = p.findValue("realm");
2406            String scheme = authhdr.scheme();
2407            AuthScheme authScheme = UNKNOWN;
2408            if ("basic".equalsIgnoreCase(scheme)) {
2409                authScheme = BASIC;
2410            } else if ("digest".equalsIgnoreCase(scheme)) {
2411                authScheme = DIGEST;
2412            } else if ("ntlm".equalsIgnoreCase(scheme)) {
2413                authScheme = NTLM;
2414                doingNTLM2ndStage = true;
2415            } else if ("Kerberos".equalsIgnoreCase(scheme)) {
2416                authScheme = KERBEROS;
2417                doingNTLM2ndStage = true;
2418            } else if ("Negotiate".equalsIgnoreCase(scheme)) {
2419                authScheme = NEGOTIATE;
2420                doingNTLM2ndStage = true;
2421            }
2422
2423            domain = p.findValue ("domain");
2424            if (realm == null)
2425                realm = "";
2426            serverAuthKey = AuthenticationInfo.getServerAuthKey(url, realm, authScheme,
2427                                               getAuthenticatorKey());
2428            ret = AuthenticationInfo.getServerAuth(serverAuthKey);
2429            InetAddress addr = null;
2430            if (ret == null) {
2431                try {
2432                    addr = InetAddress.getByName(url.getHost());
2433                } catch (java.net.UnknownHostException ignored) {
2434                    // User will have addr = null
2435                }
2436            }
2437            // replacing -1 with default port for a protocol
2438            int port = url.getPort();
2439            if (port == -1) {
2440                port = url.getDefaultPort();
2441            }
2442            if (ret == null) {
2443                switch(authScheme) {
2444                case KERBEROS:
2445                    ret = new NegotiateAuthentication(new HttpCallerInfo(authhdr.getHttpCallerInfo(), "Kerberos"));
2446                    break;
2447                case NEGOTIATE:
2448                    ret = new NegotiateAuthentication(new HttpCallerInfo(authhdr.getHttpCallerInfo(), "Negotiate"));
2449                    break;
2450                case BASIC:
2451                    PasswordAuthentication a =
2452                        privilegedRequestPasswordAuthentication(
2453                            authenticator,
2454                            url.getHost(), addr, port, url.getProtocol(),
2455                            realm, scheme, url, RequestorType.SERVER);
2456                    if (a != null) {
2457                        ret = new BasicAuthentication(false, url, realm, a,
2458                                    getAuthenticatorKey());
2459                    }
2460                    break;
2461                case DIGEST:
2462                    a = privilegedRequestPasswordAuthentication(
2463                            authenticator,
2464                            url.getHost(), addr, port, url.getProtocol(),
2465                            realm, scheme, url, RequestorType.SERVER);
2466                    if (a != null) {
2467                        digestparams = new DigestAuthentication.Parameters();
2468                        ret = new DigestAuthentication(false, url, realm, scheme,
2469                                    a, digestparams,
2470                                    getAuthenticatorKey());
2471                    }
2472                    break;
2473                case NTLM:
2474                    if (NTLMAuthenticationProxy.supported) {
2475                        URL url1;
2476                        try {
2477                            url1 = new URL (url, "/"); /* truncate the path */
2478                        } catch (Exception e) {
2479                            url1 = url;
2480                        }
2481
2482                        /* tryTransparentNTLMServer will always be true the first
2483                         * time around, but verify that the platform supports it
2484                         * otherwise don't try. */
2485                        if (tryTransparentNTLMServer) {
2486                            tryTransparentNTLMServer =
2487                                    NTLMAuthenticationProxy.supportsTransparentAuth;
2488                            /* If the platform supports transparent authentication
2489                             * then check if we are in a secure environment
2490                             * whether, or not, we should try transparent authentication.*/
2491                            if (tryTransparentNTLMServer) {
2492                                tryTransparentNTLMServer =
2493                                        NTLMAuthenticationProxy.isTrustedSite(url);
2494                            }
2495                        }
2496                        a = null;
2497                        if (tryTransparentNTLMServer) {
2498                            logger.finest("Trying Transparent NTLM authentication");
2499                        } else {
2500                            a = privilegedRequestPasswordAuthentication(
2501                                authenticator,
2502                                url.getHost(), addr, port, url.getProtocol(),
2503                                "", scheme, url, RequestorType.SERVER);
2504                        }
2505
2506                        /* If we are not trying transparent authentication then
2507                         * we need to have a PasswordAuthentication instance. For
2508                         * transparent authentication (Windows only) the username
2509                         * and password will be picked up from the current logged
2510                         * on users credentials.
2511                         */
2512                        if (tryTransparentNTLMServer ||
2513                              (!tryTransparentNTLMServer && a != null)) {
2514                            ret = NTLMAuthenticationProxy.proxy.create(false,
2515                                     url1, a, getAuthenticatorKey());
2516                        }
2517
2518                        /* set to false so that we do not try again */
2519                        tryTransparentNTLMServer = false;
2520                    }
2521                    break;
2522                case UNKNOWN:
2523                    if (logger.isLoggable(PlatformLogger.Level.FINEST)) {
2524                        logger.finest("Unknown/Unsupported authentication scheme: " + scheme);
2525                    }
2526                /*fall through*/
2527                default:
2528                    throw new AssertionError("should not reach here");
2529                }
2530            }
2531
2532            // For backwards compatibility, we also try defaultAuth
2533            // REMIND:  Get rid of this for JDK2.0.
2534
2535            if (ret == null && defaultAuth != null
2536                && defaultAuth.schemeSupported(scheme)) {
2537                String a = defaultAuth.authString(url, scheme, realm);
2538                if (a != null) {
2539                    ret = new BasicAuthentication (false, url, realm, a,
2540                                    getAuthenticatorKey());
2541                    // not in cache by default - cache on success
2542                }
2543            }
2544
2545            if (ret != null ) {
2546                if (!ret.setHeaders(this, p, raw)) {
2547                    ret = null;
2548                }
2549            }
2550        }
2551        if (logger.isLoggable(PlatformLogger.Level.FINER)) {
2552            logger.finer("Server Authentication for " + authhdr.toString() +" returned " + (ret != null ? ret.toString() : "null"));
2553        }
2554        return ret;
2555    }
2556
2557    /* inclose will be true if called from close(), in which case we
2558     * force the call to check because this is the last chance to do so.
2559     * If not in close(), then the authentication info could arrive in a trailer
2560     * field, which we have not read yet.
2561     */
2562    private void checkResponseCredentials (boolean inClose) throws IOException {
2563        try {
2564            if (!needToCheck)
2565                return;
2566            if ((validateProxy && currentProxyCredentials != null) &&
2567                (currentProxyCredentials instanceof DigestAuthentication)) {
2568                String raw = responses.findValue ("Proxy-Authentication-Info");
2569                if (inClose || (raw != null)) {
2570                    DigestAuthentication da = (DigestAuthentication)
2571                        currentProxyCredentials;
2572                    da.checkResponse (raw, method, getRequestURI());
2573                    currentProxyCredentials = null;
2574                }
2575            }
2576            if ((validateServer && currentServerCredentials != null) &&
2577                (currentServerCredentials instanceof DigestAuthentication)) {
2578                String raw = responses.findValue ("Authentication-Info");
2579                if (inClose || (raw != null)) {
2580                    DigestAuthentication da = (DigestAuthentication)
2581                        currentServerCredentials;
2582                    da.checkResponse (raw, method, url);
2583                    currentServerCredentials = null;
2584                }
2585            }
2586            if ((currentServerCredentials==null) && (currentProxyCredentials == null)) {
2587                needToCheck = false;
2588            }
2589        } catch (IOException e) {
2590            disconnectInternal();
2591            connected = false;
2592            throw e;
2593        }
2594    }
2595
2596   /* The request URI used in the request line for this request.
2597    * Also, needed for digest authentication
2598    */
2599
2600    String requestURI = null;
2601
2602    String getRequestURI() throws IOException {
2603        if (requestURI == null) {
2604            requestURI = http.getURLFile();
2605        }
2606        return requestURI;
2607    }
2608
2609    /* Tells us whether to follow a redirect.  If so, it
2610     * closes the connection (break any keep-alive) and
2611     * resets the url, re-connects, and resets the request
2612     * property.
2613     */
2614    private boolean followRedirect() throws IOException {
2615        if (!getInstanceFollowRedirects()) {
2616            return false;
2617        }
2618
2619        final int stat = getResponseCode();
2620        if (stat < 300 || stat > 307 || stat == 306
2621                                || stat == HTTP_NOT_MODIFIED) {
2622            return false;
2623        }
2624        final String loc = getHeaderField("Location");
2625        if (loc == null) {
2626            /* this should be present - if not, we have no choice
2627             * but to go forward w/ the response we got
2628             */
2629            return false;
2630        }
2631
2632        URL locUrl;
2633        try {
2634            locUrl = new URL(loc);
2635            if (!url.getProtocol().equalsIgnoreCase(locUrl.getProtocol())) {
2636                return false;
2637            }
2638
2639        } catch (MalformedURLException mue) {
2640          // treat loc as a relative URI to conform to popular browsers
2641          locUrl = new URL(url, loc);
2642        }
2643
2644        final URL locUrl0 = locUrl;
2645        socketPermission = null; // force recalculation
2646        SocketPermission p = URLtoSocketPermission(locUrl);
2647
2648        if (p != null) {
2649            try {
2650                return AccessController.doPrivilegedWithCombiner(
2651                    new PrivilegedExceptionAction<>() {
2652                        public Boolean run() throws IOException {
2653                            return followRedirect0(loc, stat, locUrl0);
2654                        }
2655                    }, null, p
2656                );
2657            } catch (PrivilegedActionException e) {
2658                throw (IOException) e.getException();
2659            }
2660        } else {
2661            // run without additional permission
2662            return followRedirect0(loc, stat, locUrl);
2663        }
2664    }
2665
2666    /* Tells us whether to follow a redirect.  If so, it
2667     * closes the connection (break any keep-alive) and
2668     * resets the url, re-connects, and resets the request
2669     * property.
2670     */
2671    private boolean followRedirect0(String loc, int stat, URL locUrl)
2672        throws IOException
2673    {
2674        disconnectInternal();
2675        if (streaming()) {
2676            throw new HttpRetryException (RETRY_MSG3, stat, loc);
2677        }
2678        if (logger.isLoggable(PlatformLogger.Level.FINE)) {
2679            logger.fine("Redirected from " + url + " to " + locUrl);
2680        }
2681
2682        // clear out old response headers!!!!
2683        responses = new MessageHeader();
2684        if (stat == HTTP_USE_PROXY) {
2685            /* This means we must re-request the resource through the
2686             * proxy denoted in the "Location:" field of the response.
2687             * Judging by the spec, the string in the Location header
2688             * _should_ denote a URL - let's hope for "http://my.proxy.org"
2689             * Make a new HttpClient to the proxy, using HttpClient's
2690             * Instance-specific proxy fields, but note we're still fetching
2691             * the same URL.
2692             */
2693            String proxyHost = locUrl.getHost();
2694            int proxyPort = locUrl.getPort();
2695
2696            SecurityManager security = System.getSecurityManager();
2697            if (security != null) {
2698                security.checkConnect(proxyHost, proxyPort);
2699            }
2700
2701            setProxiedClient (url, proxyHost, proxyPort);
2702            requests.set(0, method + " " + getRequestURI()+" "  +
2703                             httpVersion, null);
2704            connected = true;
2705            // need to remember this in case NTLM proxy authentication gets
2706            // used. We can't use transparent authentication when user
2707            // doesn't know about proxy.
2708            useProxyResponseCode = true;
2709        } else {
2710            // maintain previous headers, just change the name
2711            // of the file we're getting
2712            url = locUrl;
2713            requestURI = null; // force it to be recalculated
2714            if (method.equals("POST") && !Boolean.getBoolean("http.strictPostRedirect") && (stat!=307)) {
2715                /* The HTTP/1.1 spec says that a redirect from a POST
2716                 * *should not* be immediately turned into a GET, and
2717                 * that some HTTP/1.0 clients incorrectly did this.
2718                 * Correct behavior redirects a POST to another POST.
2719                 * Unfortunately, since most browsers have this incorrect
2720                 * behavior, the web works this way now.  Typical usage
2721                 * seems to be:
2722                 *   POST a login code or passwd to a web page.
2723                 *   after validation, the server redirects to another
2724                 *     (welcome) page
2725                 *   The second request is (erroneously) expected to be GET
2726                 *
2727                 * We will do the incorrect thing (POST-->GET) by default.
2728                 * We will provide the capability to do the "right" thing
2729                 * (POST-->POST) by a system property, "http.strictPostRedirect=true"
2730                 */
2731
2732                requests = new MessageHeader();
2733                setRequests = false;
2734                super.setRequestMethod("GET"); // avoid the connecting check
2735                poster = null;
2736                if (!checkReuseConnection())
2737                    connect();
2738            } else {
2739                if (!checkReuseConnection())
2740                    connect();
2741                /* Even after a connect() call, http variable still can be
2742                 * null, if a ResponseCache has been installed and it returns
2743                 * a non-null CacheResponse instance. So check nullity before using it.
2744                 *
2745                 * And further, if http is null, there's no need to do anything
2746                 * about request headers because successive http session will use
2747                 * cachedInputStream/cachedHeaders anyway, which is returned by
2748                 * CacheResponse.
2749                 */
2750                if (http != null) {
2751                    requests.set(0, method + " " + getRequestURI()+" "  +
2752                                 httpVersion, null);
2753                    int port = url.getPort();
2754                    String host = stripIPv6ZoneId(url.getHost());
2755                    if (port != -1 && port != url.getDefaultPort()) {
2756                        host += ":" + String.valueOf(port);
2757                    }
2758                    requests.set("Host", host);
2759                }
2760            }
2761        }
2762        return true;
2763    }
2764
2765    /* dummy byte buffer for reading off socket prior to closing */
2766    byte[] cdata = new byte [128];
2767
2768    /**
2769     * Reset (without disconnecting the TCP conn) in order to do another transaction with this instance
2770     */
2771    private void reset() throws IOException {
2772        http.reuse = true;
2773        /* must save before calling close */
2774        reuseClient = http;
2775        InputStream is = http.getInputStream();
2776        if (!method.equals("HEAD")) {
2777            try {
2778                /* we want to read the rest of the response without using the
2779                 * hurry mechanism, because that would close the connection
2780                 * if everything is not available immediately
2781                 */
2782                if ((is instanceof ChunkedInputStream) ||
2783                    (is instanceof MeteredStream)) {
2784                    /* reading until eof will not block */
2785                    while (is.read (cdata) > 0) {}
2786                } else {
2787                    /* raw stream, which will block on read, so only read
2788                     * the expected number of bytes, probably 0
2789                     */
2790                    long cl = 0;
2791                    int n = 0;
2792                    String cls = responses.findValue ("Content-Length");
2793                    if (cls != null) {
2794                        try {
2795                            cl = Long.parseLong (cls);
2796                        } catch (NumberFormatException e) {
2797                            cl = 0;
2798                        }
2799                    }
2800                    for (long i=0; i<cl; ) {
2801                        if ((n = is.read (cdata)) == -1) {
2802                            break;
2803                        } else {
2804                            i+= n;
2805                        }
2806                    }
2807                }
2808            } catch (IOException e) {
2809                http.reuse = false;
2810                reuseClient = null;
2811                disconnectInternal();
2812                return;
2813            }
2814            try {
2815                if (is instanceof MeteredStream) {
2816                    is.close();
2817                }
2818            } catch (IOException e) { }
2819        }
2820        responseCode = -1;
2821        responses = new MessageHeader();
2822        connected = false;
2823    }
2824
2825    /**
2826     * Disconnect from the web server at the first 401 error. Do not
2827     * disconnect when using a proxy, a good proxy should have already
2828     * closed the connection to the web server.
2829     */
2830    private void disconnectWeb() throws IOException {
2831        if (usingProxy() && http.isKeepingAlive()) {
2832            responseCode = -1;
2833            // clean up, particularly, skip the content part
2834            // of a 401 error response
2835            reset();
2836        } else {
2837            disconnectInternal();
2838        }
2839    }
2840
2841    /**
2842     * Disconnect from the server (for internal use)
2843     */
2844    private void disconnectInternal() {
2845        responseCode = -1;
2846        inputStream = null;
2847        if (pi != null) {
2848            pi.finishTracking();
2849            pi = null;
2850        }
2851        if (http != null) {
2852            http.closeServer();
2853            http = null;
2854            connected = false;
2855        }
2856    }
2857
2858    /**
2859     * Disconnect from the server (public API)
2860     */
2861    public void disconnect() {
2862
2863        responseCode = -1;
2864        if (pi != null) {
2865            pi.finishTracking();
2866            pi = null;
2867        }
2868
2869        if (http != null) {
2870            /*
2871             * If we have an input stream this means we received a response
2872             * from the server. That stream may have been read to EOF and
2873             * dependening on the stream type may already be closed or the
2874             * the http client may be returned to the keep-alive cache.
2875             * If the http client has been returned to the keep-alive cache
2876             * it may be closed (idle timeout) or may be allocated to
2877             * another request.
2878             *
2879             * In other to avoid timing issues we close the input stream
2880             * which will either close the underlying connection or return
2881             * the client to the cache. If there's a possibility that the
2882             * client has been returned to the cache (ie: stream is a keep
2883             * alive stream or a chunked input stream) then we remove an
2884             * idle connection to the server. Note that this approach
2885             * can be considered an approximation in that we may close a
2886             * different idle connection to that used by the request.
2887             * Additionally it's possible that we close two connections
2888             * - the first becuase it wasn't an EOF (and couldn't be
2889             * hurried) - the second, another idle connection to the
2890             * same server. The is okay because "disconnect" is an
2891             * indication that the application doesn't intend to access
2892             * this http server for a while.
2893             */
2894
2895            if (inputStream != null) {
2896                HttpClient hc = http;
2897
2898                // un-synchronized
2899                boolean ka = hc.isKeepingAlive();
2900
2901                try {
2902                    inputStream.close();
2903                } catch (IOException ioe) { }
2904
2905                // if the connection is persistent it may have been closed
2906                // or returned to the keep-alive cache. If it's been returned
2907                // to the keep-alive cache then we would like to close it
2908                // but it may have been allocated
2909
2910                if (ka) {
2911                    hc.closeIdleConnection();
2912                }
2913
2914
2915            } else {
2916                // We are deliberatly being disconnected so HttpClient
2917                // should not try to resend the request no matter what stage
2918                // of the connection we are in.
2919                http.setDoNotRetry(true);
2920
2921                http.closeServer();
2922            }
2923
2924            //      poster = null;
2925            http = null;
2926            connected = false;
2927        }
2928        cachedInputStream = null;
2929        if (cachedHeaders != null) {
2930            cachedHeaders.reset();
2931        }
2932    }
2933
2934    public boolean usingProxy() {
2935        if (http != null) {
2936            return (http.getProxyHostUsed() != null);
2937        }
2938        return false;
2939    }
2940
2941    // constant strings represent set-cookie header names
2942    private static final String SET_COOKIE = "set-cookie";
2943    private static final String SET_COOKIE2 = "set-cookie2";
2944
2945    /**
2946     * Returns a filtered version of the given headers value.
2947     *
2948     * Note: The implementation currently only filters out HttpOnly cookies
2949     *       from Set-Cookie and Set-Cookie2 headers.
2950     */
2951    private String filterHeaderField(String name, String value) {
2952        if (value == null)
2953            return null;
2954
2955        if (SET_COOKIE.equalsIgnoreCase(name) ||
2956            SET_COOKIE2.equalsIgnoreCase(name)) {
2957
2958            // Filtering only if there is a cookie handler. [Assumption: the
2959            // cookie handler will store/retrieve the HttpOnly cookies]
2960            if (cookieHandler == null || value.length() == 0)
2961                return value;
2962
2963            JavaNetHttpCookieAccess access =
2964                    SharedSecrets.getJavaNetHttpCookieAccess();
2965            StringJoiner retValue = new StringJoiner(",");  // RFC 2965, comma separated
2966            List<HttpCookie> cookies = access.parse(value);
2967            for (HttpCookie cookie : cookies) {
2968                // skip HttpOnly cookies
2969                if (!cookie.isHttpOnly())
2970                    retValue.add(access.header(cookie));
2971            }
2972            return retValue.toString();
2973        }
2974
2975        return value;
2976    }
2977
2978    // Cache the filtered response headers so that they don't need
2979    // to be generated for every getHeaderFields() call.
2980    private Map<String, List<String>> filteredHeaders;  // null
2981
2982    private Map<String, List<String>> getFilteredHeaderFields() {
2983        if (filteredHeaders != null)
2984            return filteredHeaders;
2985
2986        Map<String, List<String>> headers, tmpMap = new HashMap<>();
2987
2988        if (cachedHeaders != null)
2989            headers = cachedHeaders.getHeaders();
2990        else
2991            headers = responses.getHeaders();
2992
2993        for (Map.Entry<String, List<String>> e: headers.entrySet()) {
2994            String key = e.getKey();
2995            List<String> values = e.getValue(), filteredVals = new ArrayList<>();
2996            for (String value : values) {
2997                String fVal = filterHeaderField(key, value);
2998                if (fVal != null)
2999                    filteredVals.add(fVal);
3000            }
3001            if (!filteredVals.isEmpty())
3002                tmpMap.put(key, Collections.unmodifiableList(filteredVals));
3003        }
3004
3005        return filteredHeaders = Collections.unmodifiableMap(tmpMap);
3006    }
3007
3008    /**
3009     * Gets a header field by name. Returns null if not known.
3010     * @param name the name of the header field
3011     */
3012    @Override
3013    public String getHeaderField(String name) {
3014        try {
3015            getInputStream();
3016        } catch (IOException e) {}
3017
3018        if (cachedHeaders != null) {
3019            return filterHeaderField(name, cachedHeaders.findValue(name));
3020        }
3021
3022        return filterHeaderField(name, responses.findValue(name));
3023    }
3024
3025    /**
3026     * Returns an unmodifiable Map of the header fields.
3027     * The Map keys are Strings that represent the
3028     * response-header field names. Each Map value is an
3029     * unmodifiable List of Strings that represents
3030     * the corresponding field values.
3031     *
3032     * @return a Map of header fields
3033     * @since 1.4
3034     */
3035    @Override
3036    public Map<String, List<String>> getHeaderFields() {
3037        try {
3038            getInputStream();
3039        } catch (IOException e) {}
3040
3041        return getFilteredHeaderFields();
3042    }
3043
3044    /**
3045     * Gets a header field by index. Returns null if not known.
3046     * @param n the index of the header field
3047     */
3048    @Override
3049    public String getHeaderField(int n) {
3050        try {
3051            getInputStream();
3052        } catch (IOException e) {}
3053
3054        if (cachedHeaders != null) {
3055           return filterHeaderField(cachedHeaders.getKey(n),
3056                                    cachedHeaders.getValue(n));
3057        }
3058        return filterHeaderField(responses.getKey(n), responses.getValue(n));
3059    }
3060
3061    /**
3062     * Gets a header field by index. Returns null if not known.
3063     * @param n the index of the header field
3064     */
3065    @Override
3066    public String getHeaderFieldKey(int n) {
3067        try {
3068            getInputStream();
3069        } catch (IOException e) {}
3070
3071        if (cachedHeaders != null) {
3072            return cachedHeaders.getKey(n);
3073        }
3074
3075        return responses.getKey(n);
3076    }
3077
3078    /**
3079     * Sets request property. If a property with the key already
3080     * exists, overwrite its value with the new value.
3081     * @param value the value to be set
3082     */
3083    @Override
3084    public synchronized void setRequestProperty(String key, String value) {
3085        if (connected || connecting)
3086            throw new IllegalStateException("Already connected");
3087        if (key == null)
3088            throw new NullPointerException ("key is null");
3089
3090        if (isExternalMessageHeaderAllowed(key, value)) {
3091            requests.set(key, value);
3092            if (!key.equalsIgnoreCase("Content-Type")) {
3093                userHeaders.set(key, value);
3094            }
3095        }
3096    }
3097
3098    MessageHeader getUserSetHeaders() {
3099        return userHeaders;
3100    }
3101
3102    /**
3103     * Adds a general request property specified by a
3104     * key-value pair.  This method will not overwrite
3105     * existing values associated with the same key.
3106     *
3107     * @param   key     the keyword by which the request is known
3108     *                  (e.g., "<code>accept</code>").
3109     * @param   value  the value associated with it.
3110     * @see #getRequestProperties(java.lang.String)
3111     * @since 1.4
3112     */
3113    @Override
3114    public synchronized void addRequestProperty(String key, String value) {
3115        if (connected || connecting)
3116            throw new IllegalStateException("Already connected");
3117        if (key == null)
3118            throw new NullPointerException ("key is null");
3119
3120        if (isExternalMessageHeaderAllowed(key, value)) {
3121            requests.add(key, value);
3122            if (!key.equalsIgnoreCase("Content-Type")) {
3123                    userHeaders.add(key, value);
3124            }
3125        }
3126    }
3127
3128    //
3129    // Set a property for authentication.  This can safely disregard
3130    // the connected test.
3131    //
3132    public void setAuthenticationProperty(String key, String value) {
3133        checkMessageHeader(key, value);
3134        requests.set(key, value);
3135    }
3136
3137    @Override
3138    public synchronized String getRequestProperty (String key) {
3139        if (key == null) {
3140            return null;
3141        }
3142
3143        // don't return headers containing security sensitive information
3144        for (int i=0; i < EXCLUDE_HEADERS.length; i++) {
3145            if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) {
3146                return null;
3147            }
3148        }
3149        if (!setUserCookies) {
3150            if (key.equalsIgnoreCase("Cookie")) {
3151                return userCookies;
3152            }
3153            if (key.equalsIgnoreCase("Cookie2")) {
3154                return userCookies2;
3155            }
3156        }
3157        return requests.findValue(key);
3158    }
3159
3160    /**
3161     * Returns an unmodifiable Map of general request
3162     * properties for this connection. The Map keys
3163     * are Strings that represent the request-header
3164     * field names. Each Map value is a unmodifiable List
3165     * of Strings that represents the corresponding
3166     * field values.
3167     *
3168     * @return  a Map of the general request properties for this connection.
3169     * @throws IllegalStateException if already connected
3170     * @since 1.4
3171     */
3172    @Override
3173    public synchronized Map<String, List<String>> getRequestProperties() {
3174        if (connected)
3175            throw new IllegalStateException("Already connected");
3176
3177        // exclude headers containing security-sensitive info
3178        if (setUserCookies) {
3179            return requests.getHeaders(EXCLUDE_HEADERS);
3180        }
3181        /*
3182         * The cookies in the requests message headers may have
3183         * been modified. Use the saved user cookies instead.
3184         */
3185        Map<String, List<String>> userCookiesMap = null;
3186        if (userCookies != null || userCookies2 != null) {
3187            userCookiesMap = new HashMap<>();
3188            if (userCookies != null) {
3189                userCookiesMap.put("Cookie", Arrays.asList(userCookies));
3190            }
3191            if (userCookies2 != null) {
3192                userCookiesMap.put("Cookie2", Arrays.asList(userCookies2));
3193            }
3194        }
3195        return requests.filterAndAddHeaders(EXCLUDE_HEADERS2, userCookiesMap);
3196    }
3197
3198    @Override
3199    public void setConnectTimeout(int timeout) {
3200        if (timeout < 0)
3201            throw new IllegalArgumentException("timeouts can't be negative");
3202        connectTimeout = timeout;
3203    }
3204
3205
3206    /**
3207     * Returns setting for connect timeout.
3208     * <p>
3209     * 0 return implies that the option is disabled
3210     * (i.e., timeout of infinity).
3211     *
3212     * @return an <code>int</code> that indicates the connect timeout
3213     *         value in milliseconds
3214     * @see java.net.URLConnection#setConnectTimeout(int)
3215     * @see java.net.URLConnection#connect()
3216     * @since 1.5
3217     */
3218    @Override
3219    public int getConnectTimeout() {
3220        return (connectTimeout < 0 ? 0 : connectTimeout);
3221    }
3222
3223    /**
3224     * Sets the read timeout to a specified timeout, in
3225     * milliseconds. A non-zero value specifies the timeout when
3226     * reading from Input stream when a connection is established to a
3227     * resource. If the timeout expires before there is data available
3228     * for read, a java.net.SocketTimeoutException is raised. A
3229     * timeout of zero is interpreted as an infinite timeout.
3230     *
3231     * <p> Some non-standard implementation of this method ignores the
3232     * specified timeout. To see the read timeout set, please call
3233     * getReadTimeout().
3234     *
3235     * @param timeout an <code>int</code> that specifies the timeout
3236     * value to be used in milliseconds
3237     * @throws IllegalArgumentException if the timeout parameter is negative
3238     *
3239     * @see java.net.URLConnectiongetReadTimeout()
3240     * @see java.io.InputStream#read()
3241     * @since 1.5
3242     */
3243    @Override
3244    public void setReadTimeout(int timeout) {
3245        if (timeout < 0)
3246            throw new IllegalArgumentException("timeouts can't be negative");
3247        readTimeout = timeout;
3248    }
3249
3250    /**
3251     * Returns setting for read timeout. 0 return implies that the
3252     * option is disabled (i.e., timeout of infinity).
3253     *
3254     * @return an <code>int</code> that indicates the read timeout
3255     *         value in milliseconds
3256     *
3257     * @see java.net.URLConnection#setReadTimeout(int)
3258     * @see java.io.InputStream#read()
3259     * @since 1.5
3260     */
3261    @Override
3262    public int getReadTimeout() {
3263        return readTimeout < 0 ? 0 : readTimeout;
3264    }
3265
3266    public CookieHandler getCookieHandler() {
3267        return cookieHandler;
3268    }
3269
3270    String getMethod() {
3271        return method;
3272    }
3273
3274    private MessageHeader mapToMessageHeader(Map<String, List<String>> map) {
3275        MessageHeader headers = new MessageHeader();
3276        if (map == null || map.isEmpty()) {
3277            return headers;
3278        }
3279        for (Map.Entry<String, List<String>> entry : map.entrySet()) {
3280            String key = entry.getKey();
3281            List<String> values = entry.getValue();
3282            for (String value : values) {
3283                if (key == null) {
3284                    headers.prepend(key, value);
3285                } else {
3286                    headers.add(key, value);
3287                }
3288            }
3289        }
3290        return headers;
3291    }
3292
3293    /**
3294     * Returns the given host, without the IPv6 Zone Id, if present.
3295     * (e.g. [fe80::a00:27ff:aaaa:aaaa%eth0] -> [fe80::a00:27ff:aaaa:aaaa])
3296     *
3297     * @param host host address (not null, not empty)
3298     * @return host address without Zone Id
3299     */
3300    static String stripIPv6ZoneId(String host) {
3301        if (host.charAt(0) != '[') { // not an IPv6-literal
3302            return host;
3303        }
3304        int i = host.lastIndexOf('%');
3305        if (i == -1) { // doesn't contain zone_id
3306            return host;
3307        }
3308        return host.substring(0, i) + "]";
3309    }
3310
3311    /* The purpose of this wrapper is just to capture the close() call
3312     * so we can check authentication information that may have
3313     * arrived in a Trailer field
3314     */
3315    class HttpInputStream extends FilterInputStream {
3316        private CacheRequest cacheRequest;
3317        private OutputStream outputStream;
3318        private boolean marked = false;
3319        private int inCache = 0;
3320        private int markCount = 0;
3321        private boolean closed;  // false
3322
3323        public HttpInputStream (InputStream is) {
3324            super (is);
3325            this.cacheRequest = null;
3326            this.outputStream = null;
3327        }
3328
3329        public HttpInputStream (InputStream is, CacheRequest cacheRequest) {
3330            super (is);
3331            this.cacheRequest = cacheRequest;
3332            try {
3333                this.outputStream = cacheRequest.getBody();
3334            } catch (IOException ioex) {
3335                this.cacheRequest.abort();
3336                this.cacheRequest = null;
3337                this.outputStream = null;
3338            }
3339        }
3340
3341        /**
3342         * Marks the current position in this input stream. A subsequent
3343         * call to the <code>reset</code> method repositions this stream at
3344         * the last marked position so that subsequent reads re-read the same
3345         * bytes.
3346         * <p>
3347         * The <code>readlimit</code> argument tells this input stream to
3348         * allow that many bytes to be read before the mark position gets
3349         * invalidated.
3350         * <p>
3351         * This method simply performs <code>in.mark(readlimit)</code>.
3352         *
3353         * @param   readlimit   the maximum limit of bytes that can be read before
3354         *                      the mark position becomes invalid.
3355         * @see     java.io.FilterInputStream#in
3356         * @see     java.io.FilterInputStream#reset()
3357         */
3358        @Override
3359        public synchronized void mark(int readlimit) {
3360            super.mark(readlimit);
3361            if (cacheRequest != null) {
3362                marked = true;
3363                markCount = 0;
3364            }
3365        }
3366
3367        /**
3368         * Repositions this stream to the position at the time the
3369         * <code>mark</code> method was last called on this input stream.
3370         * <p>
3371         * This method
3372         * simply performs <code>in.reset()</code>.
3373         * <p>
3374         * Stream marks are intended to be used in
3375         * situations where you need to read ahead a little to see what's in
3376         * the stream. Often this is most easily done by invoking some
3377         * general parser. If the stream is of the type handled by the
3378         * parse, it just chugs along happily. If the stream is not of
3379         * that type, the parser should toss an exception when it fails.
3380         * If this happens within readlimit bytes, it allows the outer
3381         * code to reset the stream and try another parser.
3382         *
3383         * @exception  IOException  if the stream has not been marked or if the
3384         *               mark has been invalidated.
3385         * @see        java.io.FilterInputStream#in
3386         * @see        java.io.FilterInputStream#mark(int)
3387         */
3388        @Override
3389        public synchronized void reset() throws IOException {
3390            super.reset();
3391            if (cacheRequest != null) {
3392                marked = false;
3393                inCache += markCount;
3394            }
3395        }
3396
3397        private void ensureOpen() throws IOException {
3398            if (closed)
3399                throw new IOException("stream is closed");
3400        }
3401
3402        @Override
3403        public int read() throws IOException {
3404            ensureOpen();
3405            try {
3406                byte[] b = new byte[1];
3407                int ret = read(b);
3408                return (ret == -1? ret : (b[0] & 0x00FF));
3409            } catch (IOException ioex) {
3410                if (cacheRequest != null) {
3411                    cacheRequest.abort();
3412                }
3413                throw ioex;
3414            }
3415        }
3416
3417        @Override
3418        public int read(byte[] b) throws IOException {
3419            return read(b, 0, b.length);
3420        }
3421
3422        @Override
3423        public int read(byte[] b, int off, int len) throws IOException {
3424            ensureOpen();
3425            try {
3426                int newLen = super.read(b, off, len);
3427                int nWrite;
3428                // write to cache
3429                if (inCache > 0) {
3430                    if (inCache >= newLen) {
3431                        inCache -= newLen;
3432                        nWrite = 0;
3433                    } else {
3434                        nWrite = newLen - inCache;
3435                        inCache = 0;
3436                    }
3437                } else {
3438                    nWrite = newLen;
3439                }
3440                if (nWrite > 0 && outputStream != null)
3441                    outputStream.write(b, off + (newLen-nWrite), nWrite);
3442                if (marked) {
3443                    markCount += newLen;
3444                }
3445                return newLen;
3446            } catch (IOException ioex) {
3447                if (cacheRequest != null) {
3448                    cacheRequest.abort();
3449                }
3450                throw ioex;
3451            }
3452        }
3453
3454        /* skip() calls read() in order to ensure that entire response gets
3455         * cached. same implementation as InputStream.skip */
3456
3457        private byte[] skipBuffer;
3458        private static final int SKIP_BUFFER_SIZE = 8096;
3459
3460        @Override
3461        public long skip (long n) throws IOException {
3462            ensureOpen();
3463            long remaining = n;
3464            int nr;
3465            if (skipBuffer == null)
3466                skipBuffer = new byte[SKIP_BUFFER_SIZE];
3467
3468            byte[] localSkipBuffer = skipBuffer;
3469
3470            if (n <= 0) {
3471                return 0;
3472            }
3473
3474            while (remaining > 0) {
3475                nr = read(localSkipBuffer, 0,
3476                          (int) Math.min(SKIP_BUFFER_SIZE, remaining));
3477                if (nr < 0) {
3478                    break;
3479                }
3480                remaining -= nr;
3481            }
3482
3483            return n - remaining;
3484        }
3485
3486        @Override
3487        public void close () throws IOException {
3488            if (closed)
3489                return;
3490
3491            try {
3492                if (outputStream != null) {
3493                    if (read() != -1) {
3494                        cacheRequest.abort();
3495                    } else {
3496                        outputStream.close();
3497                    }
3498                }
3499                super.close ();
3500            } catch (IOException ioex) {
3501                if (cacheRequest != null) {
3502                    cacheRequest.abort();
3503                }
3504                throw ioex;
3505            } finally {
3506                closed = true;
3507                HttpURLConnection.this.http = null;
3508                checkResponseCredentials (true);
3509            }
3510        }
3511    }
3512
3513    class StreamingOutputStream extends FilterOutputStream {
3514
3515        long expected;
3516        long written;
3517        boolean closed;
3518        boolean error;
3519        IOException errorExcp;
3520
3521        /**
3522         * expectedLength == -1 if the stream is chunked
3523         * expectedLength > 0 if the stream is fixed content-length
3524         *    In the 2nd case, we make sure the expected number of
3525         *    of bytes are actually written
3526         */
3527        StreamingOutputStream (OutputStream os, long expectedLength) {
3528            super (os);
3529            expected = expectedLength;
3530            written = 0L;
3531            closed = false;
3532            error = false;
3533        }
3534
3535        @Override
3536        public void write (int b) throws IOException {
3537            checkError();
3538            written ++;
3539            if (expected != -1L && written > expected) {
3540                throw new IOException ("too many bytes written");
3541            }
3542            out.write (b);
3543        }
3544
3545        @Override
3546        public void write (byte[] b) throws IOException {
3547            write (b, 0, b.length);
3548        }
3549
3550        @Override
3551        public void write (byte[] b, int off, int len) throws IOException {
3552            checkError();
3553            written += len;
3554            if (expected != -1L && written > expected) {
3555                out.close ();
3556                throw new IOException ("too many bytes written");
3557            }
3558            out.write (b, off, len);
3559        }
3560
3561        void checkError () throws IOException {
3562            if (closed) {
3563                throw new IOException ("Stream is closed");
3564            }
3565            if (error) {
3566                throw errorExcp;
3567            }
3568            if (((PrintStream)out).checkError()) {
3569                throw new IOException("Error writing request body to server");
3570            }
3571        }
3572
3573        /* this is called to check that all the bytes
3574         * that were supposed to be written were written
3575         * and that the stream is now closed().
3576         */
3577        boolean writtenOK () {
3578            return closed && ! error;
3579        }
3580
3581        @Override
3582        public void close () throws IOException {
3583            if (closed) {
3584                return;
3585            }
3586            closed = true;
3587            if (expected != -1L) {
3588                /* not chunked */
3589                if (written != expected) {
3590                    error = true;
3591                    errorExcp = new IOException ("insufficient data written");
3592                    out.close ();
3593                    throw errorExcp;
3594                }
3595                super.flush(); /* can't close the socket */
3596            } else {
3597                /* chunked */
3598                super.close (); /* force final chunk to be written */
3599                /* trailing \r\n */
3600                OutputStream o = http.getOutputStream();
3601                o.write ('\r');
3602                o.write ('\n');
3603                o.flush();
3604            }
3605        }
3606    }
3607
3608
3609    static class ErrorStream extends InputStream {
3610        ByteBuffer buffer;
3611        InputStream is;
3612
3613        private ErrorStream(ByteBuffer buf) {
3614            buffer = buf;
3615            is = null;
3616        }
3617
3618        private ErrorStream(ByteBuffer buf, InputStream is) {
3619            buffer = buf;
3620            this.is = is;
3621        }
3622
3623        // when this method is called, it's either the case that cl > 0, or
3624        // if chunk-encoded, cl = -1; in other words, cl can't be 0
3625        public static InputStream getErrorStream(InputStream is, long cl, HttpClient http) {
3626
3627            // cl can't be 0; this following is here for extra precaution
3628            if (cl == 0) {
3629                return null;
3630            }
3631
3632            try {
3633                // set SO_TIMEOUT to 1/5th of the total timeout
3634                // remember the old timeout value so that we can restore it
3635                int oldTimeout = http.getReadTimeout();
3636                http.setReadTimeout(timeout4ESBuffer/5);
3637
3638                long expected = 0;
3639                boolean isChunked = false;
3640                // the chunked case
3641                if (cl < 0) {
3642                    expected = bufSize4ES;
3643                    isChunked = true;
3644                } else {
3645                    expected = cl;
3646                }
3647                if (expected <= bufSize4ES) {
3648                    int exp = (int) expected;
3649                    byte[] buffer = new byte[exp];
3650                    int count = 0, time = 0, len = 0;
3651                    do {
3652                        try {
3653                            len = is.read(buffer, count,
3654                                             buffer.length - count);
3655                            if (len < 0) {
3656                                if (isChunked) {
3657                                    // chunked ended
3658                                    // if chunked ended prematurely,
3659                                    // an IOException would be thrown
3660                                    break;
3661                                }
3662                                // the server sends less than cl bytes of data
3663                                throw new IOException("the server closes"+
3664                                                      " before sending "+cl+
3665                                                      " bytes of data");
3666                            }
3667                            count += len;
3668                        } catch (SocketTimeoutException ex) {
3669                            time += timeout4ESBuffer/5;
3670                        }
3671                    } while (count < exp && time < timeout4ESBuffer);
3672
3673                    // reset SO_TIMEOUT to old value
3674                    http.setReadTimeout(oldTimeout);
3675
3676                    // if count < cl at this point, we will not try to reuse
3677                    // the connection
3678                    if (count == 0) {
3679                        // since we haven't read anything,
3680                        // we will return the underlying
3681                        // inputstream back to the application
3682                        return null;
3683                    }  else if ((count == expected && !(isChunked)) || (isChunked && len <0)) {
3684                        // put the connection into keep-alive cache
3685                        // the inputstream will try to do the right thing
3686                        is.close();
3687                        return new ErrorStream(ByteBuffer.wrap(buffer, 0, count));
3688                    } else {
3689                        // we read part of the response body
3690                        return new ErrorStream(
3691                                      ByteBuffer.wrap(buffer, 0, count), is);
3692                    }
3693                }
3694                return null;
3695            } catch (IOException ioex) {
3696                // ioex.printStackTrace();
3697                return null;
3698            }
3699        }
3700
3701        @Override
3702        public int available() throws IOException {
3703            if (is == null) {
3704                return buffer.remaining();
3705            } else {
3706                return buffer.remaining()+is.available();
3707            }
3708        }
3709
3710        public int read() throws IOException {
3711            byte[] b = new byte[1];
3712            int ret = read(b);
3713            return (ret == -1? ret : (b[0] & 0x00FF));
3714        }
3715
3716        @Override
3717        public int read(byte[] b) throws IOException {
3718            return read(b, 0, b.length);
3719        }
3720
3721        @Override
3722        public int read(byte[] b, int off, int len) throws IOException {
3723            int rem = buffer.remaining();
3724            if (rem > 0) {
3725                int ret = rem < len? rem : len;
3726                buffer.get(b, off, ret);
3727                return ret;
3728            } else {
3729                if (is == null) {
3730                    return -1;
3731                } else {
3732                    return is.read(b, off, len);
3733                }
3734            }
3735        }
3736
3737        @Override
3738        public void close() throws IOException {
3739            buffer = null;
3740            if (is != null) {
3741                is.close();
3742            }
3743        }
3744    }
3745}
3746
3747/** An input stream that just returns EOF.  This is for
3748 * HTTP URLConnections that are KeepAlive && use the
3749 * HEAD method - i.e., stream not dead, but nothing to be read.
3750 */
3751
3752class EmptyInputStream extends InputStream {
3753
3754    @Override
3755    public int available() {
3756        return 0;
3757    }
3758
3759    public int read() {
3760        return -1;
3761    }
3762}
3763