History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/sun/net/www/protocol/http/HttpURLConnection.java (1 of 18352 files). Show all

Unified · Split

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