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