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