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