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
26package sun.net.www.protocol.http;
27
28import java.net.URL;
29import java.net.URLConnection;
30import java.net.ProtocolException;
31import java.net.HttpRetryException;
32import java.net.PasswordAuthentication;
33import java.net.Authenticator;
34import java.net.InetAddress;
35import java.net.UnknownHostException;
36import java.net.SocketTimeoutException;
37import java.net.Proxy;
38import java.net.ProxySelector;
39import java.net.URI;
40import java.net.InetSocketAddress;
41import java.net.CookieHandler;
42import java.net.ResponseCache;
43import java.net.CacheResponse;
44import java.net.SecureCacheResponse;
45import java.net.CacheRequest;
46import java.net.Authenticator.RequestorType;
47import java.io.*;
48import java.util.Date;
49import java.util.Map;
50import java.util.List;
51import java.util.Locale;
52import java.util.StringTokenizer;
53import java.util.Iterator;
54import java.util.HashSet;
55import java.util.HashMap;
56import java.util.Set;
57import sun.net.*;
58import sun.net.www.*;
59import sun.net.www.http.HttpClient;
60import sun.net.www.http.PosterOutputStream;
61import sun.net.www.http.ChunkedInputStream;
62import sun.net.www.http.ChunkedOutputStream;
63import sun.util.logging.PlatformLogger;
64import java.text.SimpleDateFormat;
65import java.util.TimeZone;
66import java.net.MalformedURLException;
67import java.nio.ByteBuffer;
68import static sun.net.www.protocol.http.AuthScheme.BASIC;
69import static sun.net.www.protocol.http.AuthScheme.DIGEST;
70import static sun.net.www.protocol.http.AuthScheme.NTLM;
71import static sun.net.www.protocol.http.AuthScheme.NEGOTIATE;
72import static sun.net.www.protocol.http.AuthScheme.KERBEROS;
73import 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
80public 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
3290class EmptyInputStream extends InputStream {
3291
3292 @Override
3293 public int available() {
3294 return 0;
3295 }
3296
3297 public int read() {
3298 return -1;
3299 }
3300}