Você está na página 1de 53

Old src/share/classes/sun/net/www/protocol/http/HttpURLConnection.

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

class EmptyInputStream extends InputStream {


@Override
public int available() {
return 0;
}
public int read() {
return -1;
}
}

Você também pode gostar