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