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