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