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