1 /* 2 * Copyright 1995-2010 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 && tunnelState() != TunnelState.TUNNELING) { 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 } else { 1262 inNegotiateProxy = false; 1263 doingNTLMp2ndStage = false; 1264 if (!isUserProxyAuth) 1265 requests.remove("Proxy-Authorization"); 1266 } 1267 1268 // cache proxy authentication info 1269 if (proxyAuthentication != null) { 1270 // cache auth info on success, domain header not relevant. 1271 proxyAuthentication.addToCache(); 1272 } 1273 1274 if (respCode == HTTP_UNAUTHORIZED) { 1275 if (streaming()) { 1276 disconnectInternal(); 1277 throw new HttpRetryException ( 1278 RETRY_MSG2, HTTP_UNAUTHORIZED); 1279 } 1280 1281 // Read comments labeled "Failed Negotiate" for details. 1282 boolean dontUseNegotiate = false; 1283 Iterator iter = responses.multiValueIterator("WWW-Authenticate"); 1284 while (iter.hasNext()) { 1285 String value = ((String)iter.next()).trim(); 1286 if (value.equalsIgnoreCase("Negotiate") || 1287 value.equalsIgnoreCase("Kerberos")) { 1288 if (!inNegotiate) { 1289 inNegotiate = true; 1290 } else { 1291 dontUseNegotiate = true; 1292 doingNTLM2ndStage = false; 1293 serverAuthentication = null; 1294 } 1295 break; 1296 } 1297 } 1298 1299 srvHdr = new AuthenticationHeader ( 1300 "WWW-Authenticate", responses, 1301 new HttpCallerInfo(url), 1302 dontUseNegotiate 1303 ); 1304 1305 String raw = srvHdr.raw(); 1306 if (!doingNTLM2ndStage) { 1307 if ((serverAuthentication != null)&& 1308 serverAuthentication.getAuthScheme() != NTLM) { 1309 if (serverAuthentication.isAuthorizationStale (raw)) { 1310 /* we can retry with the current credentials */ 1311 disconnectWeb(); 1312 redirects++; 1313 requests.set(serverAuthentication.getHeaderName(), 1314 serverAuthentication.getHeaderValue(url, method)); 1315 currentServerCredentials = serverAuthentication; 1316 setCookieHeader(); 1317 continue; 1318 } else { 1319 serverAuthentication.removeFromCache(); 1320 } 1321 } 1322 serverAuthentication = getServerAuthentication(srvHdr); 1323 currentServerCredentials = serverAuthentication; 1324 1325 if (serverAuthentication != null) { 1326 disconnectWeb(); 1327 redirects++; // don't let things loop ad nauseum 1328 setCookieHeader(); 1329 continue; 1330 } 1331 } else { 1332 reset (); 1333 /* header not used for ntlm */ 1334 if (!serverAuthentication.setHeaders(this, null, raw)) { 1335 disconnectWeb(); 1336 throw new IOException ("Authentication failure"); 1337 } 1338 doingNTLM2ndStage = false; 1339 authObj = null; 1340 setCookieHeader(); 1341 continue; 1342 } 1343 } 1344 // cache server authentication info 1345 if (serverAuthentication != null) { 1346 // cache auth info on success 1347 if (!(serverAuthentication instanceof DigestAuthentication) || 1348 (domain == null)) { 1349 if (serverAuthentication instanceof BasicAuthentication) { 1350 // check if the path is shorter than the existing entry 1351 String npath = AuthenticationInfo.reducePath (url.getPath()); 1352 String opath = serverAuthentication.path; 1353 if (!opath.startsWith (npath) || npath.length() >= opath.length()) { 1354 /* npath is longer, there must be a common root */ 1355 npath = BasicAuthentication.getRootPath (opath, npath); 1356 } 1357 // remove the entry and create a new one 1358 BasicAuthentication a = 1359 (BasicAuthentication) serverAuthentication.clone(); 1360 serverAuthentication.removeFromCache(); 1361 a.path = npath; 1362 serverAuthentication = a; 1363 } 1364 serverAuthentication.addToCache(); 1365 } else { 1366 // what we cache is based on the domain list in the request 1367 DigestAuthentication srv = (DigestAuthentication) 1368 serverAuthentication; 1369 StringTokenizer tok = new StringTokenizer (domain," "); 1370 String realm = srv.realm; 1371 PasswordAuthentication pw = srv.pw; 1372 digestparams = srv.params; 1373 while (tok.hasMoreTokens()) { 1374 String path = tok.nextToken(); 1375 try { 1376 /* path could be an abs_path or a complete URI */ 1377 URL u = new URL (url, path); 1378 DigestAuthentication d = new DigestAuthentication ( 1379 false, u, realm, "Digest", pw, digestparams); 1380 d.addToCache (); 1381 } catch (Exception e) {} 1382 } 1383 } 1384 } 1385 1386 // some flags should be reset to its initialized form so that 1387 // even after a redirect the necessary checks can still be 1388 // preformed. 1389 inNegotiate = false; 1390 inNegotiateProxy = false; 1391 1392 //serverAuthentication = null; 1393 doingNTLMp2ndStage = false; 1394 doingNTLM2ndStage = false; 1395 if (!isUserServerAuth) 1396 requests.remove("Authorization"); 1397 if (!isUserProxyAuth) 1398 requests.remove("Proxy-Authorization"); 1399 1400 if (respCode == HTTP_OK) { 1401 checkResponseCredentials (false); 1402 } else { 1403 needToCheck = false; 1404 } 1405 1406 // a flag need to clean 1407 needToCheck = true; 1408 1409 if (followRedirect()) { 1410 /* if we should follow a redirect, then the followRedirects() 1411 * method will disconnect() and re-connect us to the new 1412 * location 1413 */ 1414 redirects++; 1415 1416 // redirecting HTTP response may have set cookie, so 1417 // need to re-generate request header 1418 setCookieHeader(); 1419 1420 continue; 1421 } 1422 1423 try { 1424 cl = Long.parseLong(responses.findValue("content-length")); 1425 } catch (Exception exc) { }; 1426 1427 if (method.equals("HEAD") || cl == 0 || 1428 respCode == HTTP_NOT_MODIFIED || 1429 respCode == HTTP_NO_CONTENT) { 1430 1431 if (pi != null) { 1432 pi.finishTracking(); 1433 pi = null; 1434 } 1435 http.finished(); 1436 http = null; 1437 inputStream = new EmptyInputStream(); 1438 connected = false; 1439 } 1440 1441 if (respCode == 200 || respCode == 203 || respCode == 206 || 1442 respCode == 300 || respCode == 301 || respCode == 410) { 1443 if (cacheHandler != null) { 1444 // give cache a chance to save response in cache 1445 URI uri = ParseUtil.toURI(url); 1446 if (uri != null) { 1447 URLConnection uconn = this; 1448 if ("https".equalsIgnoreCase(uri.getScheme())) { 1449 try { 1450 // use reflection to get to the public 1451 // HttpsURLConnection instance saved in 1452 // DelegateHttpsURLConnection 1453 uconn = (URLConnection)this.getClass().getField("httpsURLConnection").get(this); 1454 } catch (IllegalAccessException iae) { 1455 // ignored; use 'this' 1456 } catch (NoSuchFieldException nsfe) { 1457 // ignored; use 'this' 1458 } 1459 } 1460 CacheRequest cacheRequest = 1461 cacheHandler.put(uri, uconn); 1462 if (cacheRequest != null && http != null) { 1463 http.setCacheRequest(cacheRequest); 1464 inputStream = new HttpInputStream(inputStream, cacheRequest); 1465 } 1466 } 1467 } 1468 } 1469 1470 if (!(inputStream instanceof HttpInputStream)) { 1471 inputStream = new HttpInputStream(inputStream); 1472 } 1473 1474 if (respCode >= 400) { 1475 if (respCode == 404 || respCode == 410) { 1476 throw new FileNotFoundException(url.toString()); 1477 } else { 1478 throw new java.io.IOException("Server returned HTTP" + 1479 " response code: " + respCode + " for URL: " + 1480 url.toString()); 1481 } 1482 } 1483 poster = null; 1484 strOutputStream = null; 1485 return inputStream; 1486 } while (redirects < maxRedirects); 1487 1488 throw new ProtocolException("Server redirected too many " + 1489 " times ("+ redirects + ")"); 1490 } catch (RuntimeException e) { 1491 disconnectInternal(); 1492 rememberedException = e; 1493 throw e; 1494 } catch (IOException e) { 1495 rememberedException = e; 1496 1497 // buffer the error stream if bytes < 4k 1498 // and it can be buffered within 1 second 1499 String te = responses.findValue("Transfer-Encoding"); 1500 if (http != null && http.isKeepingAlive() && enableESBuffer && 1501 (cl > 0 || (te != null && te.equalsIgnoreCase("chunked")))) { 1502 errorStream = ErrorStream.getErrorStream(inputStream, cl, http); 1503 } 1504 throw e; 1505 } finally { 1506 if (respCode == HTTP_PROXY_AUTH && proxyAuthentication != null) { 1507 proxyAuthentication.endAuthRequest(); 1508 } 1509 else if (respCode == HTTP_UNAUTHORIZED && serverAuthentication != null) { 1510 serverAuthentication.endAuthRequest(); 1511 } 1512 } 1513 } 1514 1515 /* 1516 * Creates a chained exception that has the same type as 1517 * original exception and with the same message. Right now, 1518 * there is no convenient APIs for doing so. 1519 */ 1520 private IOException getChainedException(final IOException rememberedException) { 1521 try { 1522 final Object[] args = { rememberedException.getMessage() }; 1523 IOException chainedException = 1524 java.security.AccessController.doPrivileged( 1525 new java.security.PrivilegedExceptionAction<IOException>() { 1526 public IOException run() throws Exception { 1527 return (IOException) 1528 rememberedException.getClass() 1529 .getConstructor(new Class[] { String.class }) 1530 .newInstance(args); 1531 } 1532 }); 1533 chainedException.initCause(rememberedException); 1534 return chainedException; 1535 } catch (Exception ignored) { 1536 return rememberedException; 1537 } 1538 } 1539 1540 @Override 1541 public InputStream getErrorStream() { 1542 if (connected && responseCode >= 400) { 1543 // Client Error 4xx and Server Error 5xx 1544 if (errorStream != null) { 1545 return errorStream; 1546 } else if (inputStream != null) { 1547 return inputStream; 1548 } 1549 } 1550 return null; 1551 } 1552 1553 /** 1554 * set or reset proxy authentication info in request headers 1555 * after receiving a 407 error. In the case of NTLM however, 1556 * receiving a 407 is normal and we just skip the stale check 1557 * because ntlm does not support this feature. 1558 */ 1559 private AuthenticationInfo 1560 resetProxyAuthentication(AuthenticationInfo proxyAuthentication, AuthenticationHeader auth) throws IOException { 1561 if ((proxyAuthentication != null )&& 1562 proxyAuthentication.getAuthScheme() != NTLM) { 1563 String raw = auth.raw(); 1564 if (proxyAuthentication.isAuthorizationStale (raw)) { 1565 /* we can retry with the current credentials */ 1566 String value; 1567 if (proxyAuthentication instanceof DigestAuthentication) { 1568 DigestAuthentication digestProxy = (DigestAuthentication) 1569 proxyAuthentication; 1570 if (tunnelState() == TunnelState.SETUP) { 1571 value = digestProxy.getHeaderValue(connectRequestURI(url), HTTP_CONNECT); 1572 } else { 1573 value = digestProxy.getHeaderValue(getRequestURI(), method); 1574 } 1575 } else { 1576 value = proxyAuthentication.getHeaderValue(url, method); 1577 } 1578 requests.set(proxyAuthentication.getHeaderName(), value); 1579 currentProxyCredentials = proxyAuthentication; 1580 return proxyAuthentication; 1581 } else { 1582 proxyAuthentication.removeFromCache(); 1583 } 1584 } 1585 proxyAuthentication = getHttpProxyAuthentication(auth); 1586 currentProxyCredentials = proxyAuthentication; 1587 return proxyAuthentication; 1588 } 1589 1590 /** 1591 * Returns the tunnel state. 1592 * 1593 * @return the state 1594 */ 1595 TunnelState tunnelState() { 1596 return tunnelState; 1597 } 1598 1599 /** 1600 * Set the tunneling status. 1601 * 1602 * @param the state 1603 */ 1604 void setTunnelState(TunnelState tunnelState) { 1605 this.tunnelState = tunnelState; 1606 } 1607 1608 /** 1609 * establish a tunnel through proxy server 1610 */ 1611 public synchronized void doTunneling() throws IOException { 1612 int retryTunnel = 0; 1613 String statusLine = ""; 1614 int respCode = 0; 1615 AuthenticationInfo proxyAuthentication = null; 1616 String proxyHost = null; 1617 int proxyPort = -1; 1618 1619 // save current requests so that they can be restored after tunnel is setup. 1620 MessageHeader savedRequests = requests; 1621 requests = new MessageHeader(); 1622 1623 // Read comments labeled "Failed Negotiate" for details. 1624 boolean inNegotiateProxy = false; 1625 1626 try { 1627 /* Actively setting up a tunnel */ 1628 setTunnelState(TunnelState.SETUP); 1629 1630 do { 1631 if (!checkReuseConnection()) { 1632 proxiedConnect(url, proxyHost, proxyPort, false); 1633 } 1634 // send the "CONNECT" request to establish a tunnel 1635 // through proxy server 1636 sendCONNECTRequest(); 1637 responses.reset(); 1638 1639 // There is no need to track progress in HTTP Tunneling, 1640 // so ProgressSource is null. 1641 http.parseHTTP(responses, null, this); 1642 1643 /* Log the response to the CONNECT */ 1644 if (logger.isLoggable(PlatformLogger.FINE)) { 1645 logger.fine(responses.toString()); 1646 } 1647 1648 statusLine = responses.getValue(0); 1649 StringTokenizer st = new StringTokenizer(statusLine); 1650 st.nextToken(); 1651 respCode = Integer.parseInt(st.nextToken().trim()); 1652 if (respCode == HTTP_PROXY_AUTH) { 1653 // Read comments labeled "Failed Negotiate" for details. 1654 boolean dontUseNegotiate = false; 1655 Iterator iter = responses.multiValueIterator("Proxy-Authenticate"); 1656 while (iter.hasNext()) { 1657 String value = ((String)iter.next()).trim(); 1658 if (value.equalsIgnoreCase("Negotiate") || 1659 value.equalsIgnoreCase("Kerberos")) { 1660 if (!inNegotiateProxy) { 1661 inNegotiateProxy = true; 1662 } else { 1663 dontUseNegotiate = true; 1664 doingNTLMp2ndStage = false; 1665 proxyAuthentication = null; 1666 } 1667 break; 1668 } 1669 } 1670 1671 AuthenticationHeader authhdr = new AuthenticationHeader ( 1672 "Proxy-Authenticate", responses, 1673 new HttpCallerInfo(url, http.getProxyHostUsed(), 1674 http.getProxyPortUsed()), 1675 dontUseNegotiate 1676 ); 1677 if (!doingNTLMp2ndStage) { 1678 proxyAuthentication = 1679 resetProxyAuthentication(proxyAuthentication, authhdr); 1680 if (proxyAuthentication != null) { 1681 proxyHost = http.getProxyHostUsed(); 1682 proxyPort = http.getProxyPortUsed(); 1683 disconnectInternal(); 1684 retryTunnel++; 1685 continue; 1686 } 1687 } else { 1688 String raw = responses.findValue ("Proxy-Authenticate"); 1689 reset (); 1690 if (!proxyAuthentication.setHeaders(this, 1691 authhdr.headerParser(), raw)) { 1692 disconnectInternal(); 1693 throw new IOException ("Authentication failure"); 1694 } 1695 authObj = null; 1696 doingNTLMp2ndStage = false; 1697 continue; 1698 } 1699 } 1700 // cache proxy authentication info 1701 if (proxyAuthentication != null) { 1702 // cache auth info on success, domain header not relevant. 1703 proxyAuthentication.addToCache(); 1704 } 1705 1706 if (respCode == HTTP_OK) { 1707 setTunnelState(TunnelState.TUNNELING); 1708 break; 1709 } 1710 // we don't know how to deal with other response code 1711 // so disconnect and report error 1712 disconnectInternal(); 1713 setTunnelState(TunnelState.NONE); 1714 break; 1715 } while (retryTunnel < maxRedirects); 1716 1717 if (retryTunnel >= maxRedirects || (respCode != HTTP_OK)) { 1718 throw new IOException("Unable to tunnel through proxy."+ 1719 " Proxy returns \"" + 1720 statusLine + "\""); 1721 } 1722 } finally { 1723 if (respCode == HTTP_PROXY_AUTH && proxyAuthentication != null) { 1724 proxyAuthentication.endAuthRequest(); 1725 } 1726 } 1727 1728 // restore original request headers 1729 requests = savedRequests; 1730 1731 // reset responses 1732 responses.reset(); 1733 } 1734 1735 static String connectRequestURI(URL url) { 1736 String host = url.getHost(); 1737 int port = url.getPort(); 1738 port = port != -1 ? port : url.getDefaultPort(); 1739 1740 return host + ":" + port; 1741 } 1742 1743 /** 1744 * send a CONNECT request for establishing a tunnel to proxy server 1745 */ 1746 private void sendCONNECTRequest() throws IOException { 1747 int port = url.getPort(); 1748 1749 // setRequests == true indicates the std. request headers 1750 // have been set in (previous) requests. 1751 // so the first one must be the http method (GET, etc.). 1752 // we need to set it to CONNECT soon, remove this one first. 1753 // otherwise, there may have 2 http methods in headers 1754 if (setRequests) requests.set(0, null, null); 1755 1756 requests.prepend(HTTP_CONNECT + " " + connectRequestURI(url) 1757 + " " + httpVersion, null); 1758 requests.setIfNotSet("User-Agent", userAgent); 1759 1760 String host = url.getHost(); 1761 if (port != -1 && port != url.getDefaultPort()) { 1762 host += ":" + String.valueOf(port); 1763 } 1764 requests.setIfNotSet("Host", host); 1765 1766 // Not really necessary for a tunnel, but can't hurt 1767 requests.setIfNotSet("Accept", acceptString); 1768 1769 setPreemptiveProxyAuthentication(requests); 1770 1771 /* Log the CONNECT request */ 1772 if (logger.isLoggable(PlatformLogger.FINE)) { 1773 logger.fine(requests.toString()); 1774 } 1775 1776 http.writeRequests(requests, null); 1777 // remove CONNECT header 1778 requests.set(0, null, null); 1779 } 1780 1781 /** 1782 * Sets pre-emptive proxy authentication in header 1783 */ 1784 private void setPreemptiveProxyAuthentication(MessageHeader requests) throws IOException { 1785 AuthenticationInfo pauth 1786 = AuthenticationInfo.getProxyAuth(http.getProxyHostUsed(), 1787 http.getProxyPortUsed()); 1788 if (pauth != null && pauth.supportsPreemptiveAuthorization()) { 1789 String value; 1790 if (pauth instanceof DigestAuthentication) { 1791 DigestAuthentication digestProxy = (DigestAuthentication) pauth; 1792 if (tunnelState() == TunnelState.SETUP) { 1793 value = digestProxy 1794 .getHeaderValue(connectRequestURI(url), HTTP_CONNECT); 1795 } else { 1796 value = digestProxy.getHeaderValue(getRequestURI(), method); 1797 } 1798 } else { 1799 value = pauth.getHeaderValue(url, method); 1800 } 1801 1802 // Sets "Proxy-authorization" 1803 requests.set(pauth.getHeaderName(), value); 1804 currentProxyCredentials = pauth; 1805 } 1806 } 1807 1808 /** 1809 * Gets the authentication for an HTTP proxy, and applies it to 1810 * the connection. 1811 */ 1812 private AuthenticationInfo getHttpProxyAuthentication (AuthenticationHeader authhdr) { 1813 /* get authorization from authenticator */ 1814 AuthenticationInfo ret = null; 1815 String raw = authhdr.raw(); 1816 String host = http.getProxyHostUsed(); 1817 int port = http.getProxyPortUsed(); 1818 if (host != null && authhdr.isPresent()) { 1819 HeaderParser p = authhdr.headerParser(); 1820 String realm = p.findValue("realm"); 1821 String scheme = authhdr.scheme(); 1822 AuthScheme authScheme = UNKNOWN; 1823 if ("basic".equalsIgnoreCase(scheme)) { 1824 authScheme = BASIC; 1825 } else if ("digest".equalsIgnoreCase(scheme)) { 1826 authScheme = DIGEST; 1827 } else if ("ntlm".equalsIgnoreCase(scheme)) { 1828 authScheme = NTLM; 1829 doingNTLMp2ndStage = true; 1830 } else if ("Kerberos".equalsIgnoreCase(scheme)) { 1831 authScheme = KERBEROS; 1832 doingNTLMp2ndStage = true; 1833 } else if ("Negotiate".equalsIgnoreCase(scheme)) { 1834 authScheme = NEGOTIATE; 1835 doingNTLMp2ndStage = true; 1836 } 1837 1838 if (realm == null) 1839 realm = ""; 1840 ret = AuthenticationInfo.getProxyAuth(host, 1841 port, 1842 realm, 1843 authScheme); 1844 if (ret == null) { 1845 switch (authScheme) { 1846 case BASIC: 1847 InetAddress addr = null; 1848 try { 1849 final String finalHost = host; 1850 addr = java.security.AccessController.doPrivileged( 1851 new java.security.PrivilegedExceptionAction<InetAddress>() { 1852 public InetAddress run() 1853 throws java.net.UnknownHostException { 1854 return InetAddress.getByName(finalHost); 1855 } 1856 }); 1857 } catch (java.security.PrivilegedActionException ignored) { 1858 // User will have an unknown host. 1859 } 1860 PasswordAuthentication a = 1861 privilegedRequestPasswordAuthentication( 1862 host, addr, port, "http", 1863 realm, scheme, url, RequestorType.PROXY); 1864 if (a != null) { 1865 ret = new BasicAuthentication(true, host, port, realm, a); 1866 } 1867 break; 1868 case DIGEST: 1869 a = privilegedRequestPasswordAuthentication( 1870 host, null, port, url.getProtocol(), 1871 realm, scheme, url, RequestorType.PROXY); 1872 if (a != null) { 1873 DigestAuthentication.Parameters params = 1874 new DigestAuthentication.Parameters(); 1875 ret = new DigestAuthentication(true, host, port, realm, 1876 scheme, a, params); 1877 } 1878 break; 1879 case NTLM: 1880 if (NTLMAuthenticationProxy.proxy.supported) { 1881 /* tryTransparentNTLMProxy will always be true the first 1882 * time around, but verify that the platform supports it 1883 * otherwise don't try. */ 1884 if (tryTransparentNTLMProxy) { 1885 tryTransparentNTLMProxy = 1886 NTLMAuthenticationProxy.proxy.supportsTransparentAuth; 1887 } 1888 a = null; 1889 if (tryTransparentNTLMProxy) { 1890 logger.finest("Trying Transparent NTLM authentication"); 1891 } else { 1892 a = privilegedRequestPasswordAuthentication( 1893 host, null, port, url.getProtocol(), 1894 "", scheme, url, RequestorType.PROXY); 1895 } 1896 /* If we are not trying transparent authentication then 1897 * we need to have a PasswordAuthentication instance. For 1898 * transparent authentication (Windows only) the username 1899 * and password will be picked up from the current logged 1900 * on users credentials. 1901 */ 1902 if (tryTransparentNTLMProxy || 1903 (!tryTransparentNTLMProxy && a != null)) { 1904 ret = NTLMAuthenticationProxy.proxy.create(true, host, port, a); 1905 } 1906 1907 /* set to false so that we do not try again */ 1908 tryTransparentNTLMProxy = false; 1909 } 1910 break; 1911 case NEGOTIATE: 1912 ret = new NegotiateAuthentication(new HttpCallerInfo(authhdr.getHttpCallerInfo(), "Negotiate")); 1913 break; 1914 case KERBEROS: 1915 ret = new NegotiateAuthentication(new HttpCallerInfo(authhdr.getHttpCallerInfo(), "Kerberos")); 1916 break; 1917 case UNKNOWN: 1918 logger.finest("Unknown/Unsupported authentication scheme: " + scheme); 1919 default: 1920 throw new AssertionError("should not reach here"); 1921 } 1922 } 1923 // For backwards compatibility, we also try defaultAuth 1924 // REMIND: Get rid of this for JDK2.0. 1925 1926 if (ret == null && defaultAuth != null 1927 && defaultAuth.schemeSupported(scheme)) { 1928 try { 1929 URL u = new URL("http", host, port, "/"); 1930 String a = defaultAuth.authString(u, scheme, realm); 1931 if (a != null) { 1932 ret = new BasicAuthentication (true, host, port, realm, a); 1933 // not in cache by default - cache on success 1934 } 1935 } catch (java.net.MalformedURLException ignored) { 1936 } 1937 } 1938 if (ret != null) { 1939 if (!ret.setHeaders(this, p, raw)) { 1940 ret = null; 1941 } 1942 } 1943 } 1944 if (logger.isLoggable(PlatformLogger.FINER)) { 1945 logger.finer("Proxy Authentication for " + authhdr.toString() +" returned " + (ret != null ? ret.toString() : "null")); 1946 } 1947 return ret; 1948 } 1949 1950 /** 1951 * Gets the authentication for an HTTP server, and applies it to 1952 * the connection. 1953 * @param authHdr the AuthenticationHeader which tells what auth scheme is 1954 * prefered. 1955 */ 1956 private AuthenticationInfo getServerAuthentication (AuthenticationHeader authhdr) { 1957 /* get authorization from authenticator */ 1958 AuthenticationInfo ret = null; 1959 String raw = authhdr.raw(); 1960 /* When we get an NTLM auth from cache, don't set any special headers */ 1961 if (authhdr.isPresent()) { 1962 HeaderParser p = authhdr.headerParser(); 1963 String realm = p.findValue("realm"); 1964 String scheme = authhdr.scheme(); 1965 AuthScheme authScheme = UNKNOWN; 1966 if ("basic".equalsIgnoreCase(scheme)) { 1967 authScheme = BASIC; 1968 } else if ("digest".equalsIgnoreCase(scheme)) { 1969 authScheme = DIGEST; 1970 } else if ("ntlm".equalsIgnoreCase(scheme)) { 1971 authScheme = NTLM; 1972 doingNTLM2ndStage = true; 1973 } else if ("Kerberos".equalsIgnoreCase(scheme)) { 1974 authScheme = KERBEROS; 1975 doingNTLM2ndStage = true; 1976 } else if ("Negotiate".equalsIgnoreCase(scheme)) { 1977 authScheme = NEGOTIATE; 1978 doingNTLM2ndStage = true; 1979 } 1980 1981 domain = p.findValue ("domain"); 1982 if (realm == null) 1983 realm = ""; 1984 ret = AuthenticationInfo.getServerAuth(url, realm, authScheme); 1985 InetAddress addr = null; 1986 if (ret == null) { 1987 try { 1988 addr = InetAddress.getByName(url.getHost()); 1989 } catch (java.net.UnknownHostException ignored) { 1990 // User will have addr = null 1991 } 1992 } 1993 // replacing -1 with default port for a protocol 1994 int port = url.getPort(); 1995 if (port == -1) { 1996 port = url.getDefaultPort(); 1997 } 1998 if (ret == null) { 1999 switch(authScheme) { 2000 case KERBEROS: 2001 ret = new NegotiateAuthentication(new HttpCallerInfo(authhdr.getHttpCallerInfo(), "Kerberos")); 2002 break; 2003 case NEGOTIATE: 2004 ret = new NegotiateAuthentication(new HttpCallerInfo(authhdr.getHttpCallerInfo(), "Negotiate")); 2005 break; 2006 case BASIC: 2007 PasswordAuthentication a = 2008 privilegedRequestPasswordAuthentication( 2009 url.getHost(), addr, port, url.getProtocol(), 2010 realm, scheme, url, RequestorType.SERVER); 2011 if (a != null) { 2012 ret = new BasicAuthentication(false, url, realm, a); 2013 } 2014 break; 2015 case DIGEST: 2016 a = privilegedRequestPasswordAuthentication( 2017 url.getHost(), addr, port, url.getProtocol(), 2018 realm, scheme, url, RequestorType.SERVER); 2019 if (a != null) { 2020 digestparams = new DigestAuthentication.Parameters(); 2021 ret = new DigestAuthentication(false, url, realm, scheme, a, digestparams); 2022 } 2023 break; 2024 case NTLM: 2025 if (NTLMAuthenticationProxy.proxy.supported) { 2026 URL url1; 2027 try { 2028 url1 = new URL (url, "/"); /* truncate the path */ 2029 } catch (Exception e) { 2030 url1 = url; 2031 } 2032 2033 /* tryTransparentNTLMServer will always be true the first 2034 * time around, but verify that the platform supports it 2035 * otherwise don't try. */ 2036 if (tryTransparentNTLMServer) { 2037 tryTransparentNTLMServer = 2038 NTLMAuthenticationProxy.proxy.supportsTransparentAuth; 2039 } 2040 a = null; 2041 if (tryTransparentNTLMServer) { 2042 logger.finest("Trying Transparent NTLM authentication"); 2043 } else { 2044 a = privilegedRequestPasswordAuthentication( 2045 url.getHost(), addr, port, url.getProtocol(), 2046 "", scheme, url, RequestorType.SERVER); 2047 } 2048 2049 /* If we are not trying transparent authentication then 2050 * we need to have a PasswordAuthentication instance. For 2051 * transparent authentication (Windows only) the username 2052 * and password will be picked up from the current logged 2053 * on users credentials. 2054 */ 2055 if (tryTransparentNTLMServer || 2056 (!tryTransparentNTLMServer && a != null)) { 2057 ret = NTLMAuthenticationProxy.proxy.create(false, url1, a); 2058 } 2059 2060 /* set to false so that we do not try again */ 2061 tryTransparentNTLMServer = false; 2062 } 2063 break; 2064 case UNKNOWN: 2065 logger.finest("Unknown/Unsupported authentication scheme: " + scheme); 2066 default: 2067 throw new AssertionError("should not reach here"); 2068 } 2069 } 2070 2071 // For backwards compatibility, we also try defaultAuth 2072 // REMIND: Get rid of this for JDK2.0. 2073 2074 if (ret == null && defaultAuth != null 2075 && defaultAuth.schemeSupported(scheme)) { 2076 String a = defaultAuth.authString(url, scheme, realm); 2077 if (a != null) { 2078 ret = new BasicAuthentication (false, url, realm, a); 2079 // not in cache by default - cache on success 2080 } 2081 } 2082 2083 if (ret != null ) { 2084 if (!ret.setHeaders(this, p, raw)) { 2085 ret = null; 2086 } 2087 } 2088 } 2089 if (logger.isLoggable(PlatformLogger.FINER)) { 2090 logger.finer("Server Authentication for " + authhdr.toString() +" returned " + (ret != null ? ret.toString() : "null")); 2091 } 2092 return ret; 2093 } 2094 2095 /* inclose will be true if called from close(), in which case we 2096 * force the call to check because this is the last chance to do so. 2097 * If not in close(), then the authentication info could arrive in a trailer 2098 * field, which we have not read yet. 2099 */ 2100 private void checkResponseCredentials (boolean inClose) throws IOException { 2101 try { 2102 if (!needToCheck) 2103 return; 2104 if ((validateProxy && currentProxyCredentials != null) && 2105 (currentProxyCredentials instanceof DigestAuthentication)) { 2106 String raw = responses.findValue ("Proxy-Authentication-Info"); 2107 if (inClose || (raw != null)) { 2108 DigestAuthentication da = (DigestAuthentication) 2109 currentProxyCredentials; 2110 da.checkResponse (raw, method, getRequestURI()); 2111 currentProxyCredentials = null; 2112 } 2113 } 2114 if ((validateServer && currentServerCredentials != null) && 2115 (currentServerCredentials instanceof DigestAuthentication)) { 2116 String raw = responses.findValue ("Authentication-Info"); 2117 if (inClose || (raw != null)) { 2118 DigestAuthentication da = (DigestAuthentication) 2119 currentServerCredentials; 2120 da.checkResponse (raw, method, url); 2121 currentServerCredentials = null; 2122 } 2123 } 2124 if ((currentServerCredentials==null) && (currentProxyCredentials == null)) { 2125 needToCheck = false; 2126 } 2127 } catch (IOException e) { 2128 disconnectInternal(); 2129 connected = false; 2130 throw e; 2131 } 2132 } 2133 2134 /* The request URI used in the request line for this request. 2135 * Also, needed for digest authentication 2136 */ 2137 2138 String requestURI = null; 2139 2140 String getRequestURI() throws IOException { 2141 if (requestURI == null) { 2142 requestURI = http.getURLFile(); 2143 } 2144 return requestURI; 2145 } 2146 2147 /* Tells us whether to follow a redirect. If so, it 2148 * closes the connection (break any keep-alive) and 2149 * resets the url, re-connects, and resets the request 2150 * property. 2151 */ 2152 private boolean followRedirect() throws IOException { 2153 if (!getInstanceFollowRedirects()) { 2154 return false; 2155 } 2156 2157 int stat = getResponseCode(); 2158 if (stat < 300 || stat > 307 || stat == 306 2159 || stat == HTTP_NOT_MODIFIED) { 2160 return false; 2161 } 2162 String loc = getHeaderField("Location"); 2163 if (loc == null) { 2164 /* this should be present - if not, we have no choice 2165 * but to go forward w/ the response we got 2166 */ 2167 return false; 2168 } 2169 URL locUrl; 2170 try { 2171 locUrl = new URL(loc); 2172 if (!url.getProtocol().equalsIgnoreCase(locUrl.getProtocol())) { 2173 return false; 2174 } 2175 2176 } catch (MalformedURLException mue) { 2177 // treat loc as a relative URI to conform to popular browsers 2178 locUrl = new URL(url, loc); 2179 } 2180 disconnectInternal(); 2181 if (streaming()) { 2182 throw new HttpRetryException (RETRY_MSG3, stat, loc); 2183 } 2184 if (logger.isLoggable(PlatformLogger.FINE)) { 2185 logger.fine("Redirected from " + url + " to " + locUrl); 2186 } 2187 2188 // clear out old response headers!!!! 2189 responses = new MessageHeader(); 2190 if (stat == HTTP_USE_PROXY) { 2191 /* This means we must re-request the resource through the 2192 * proxy denoted in the "Location:" field of the response. 2193 * Judging by the spec, the string in the Location header 2194 * _should_ denote a URL - let's hope for "http://my.proxy.org" 2195 * Make a new HttpClient to the proxy, using HttpClient's 2196 * Instance-specific proxy fields, but note we're still fetching 2197 * the same URL. 2198 */ 2199 String proxyHost = locUrl.getHost(); 2200 int proxyPort = locUrl.getPort(); 2201 2202 SecurityManager security = System.getSecurityManager(); 2203 if (security != null) { 2204 security.checkConnect(proxyHost, proxyPort); 2205 } 2206 2207 setProxiedClient (url, proxyHost, proxyPort); 2208 requests.set(0, method + " " + getRequestURI()+" " + 2209 httpVersion, null); 2210 connected = true; 2211 } else { 2212 // maintain previous headers, just change the name 2213 // of the file we're getting 2214 url = locUrl; 2215 requestURI = null; // force it to be recalculated 2216 if (method.equals("POST") && !Boolean.getBoolean("http.strictPostRedirect") && (stat!=307)) { 2217 /* The HTTP/1.1 spec says that a redirect from a POST 2218 * *should not* be immediately turned into a GET, and 2219 * that some HTTP/1.0 clients incorrectly did this. 2220 * Correct behavior redirects a POST to another POST. 2221 * Unfortunately, since most browsers have this incorrect 2222 * behavior, the web works this way now. Typical usage 2223 * seems to be: 2224 * POST a login code or passwd to a web page. 2225 * after validation, the server redirects to another 2226 * (welcome) page 2227 * The second request is (erroneously) expected to be GET 2228 * 2229 * We will do the incorrect thing (POST-->GET) by default. 2230 * We will provide the capability to do the "right" thing 2231 * (POST-->POST) by a system property, "http.strictPostRedirect=true" 2232 */ 2233 2234 requests = new MessageHeader(); 2235 setRequests = false; 2236 setRequestMethod("GET"); 2237 poster = null; 2238 if (!checkReuseConnection()) 2239 connect(); 2240 } else { 2241 if (!checkReuseConnection()) 2242 connect(); 2243 /* Even after a connect() call, http variable still can be 2244 * null, if a ResponseCache has been installed and it returns 2245 * a non-null CacheResponse instance. So check nullity before using it. 2246 * 2247 * And further, if http is null, there's no need to do anything 2248 * about request headers because successive http session will use 2249 * cachedInputStream/cachedHeaders anyway, which is returned by 2250 * CacheResponse. 2251 */ 2252 if (http != null) { 2253 requests.set(0, method + " " + getRequestURI()+" " + 2254 httpVersion, null); 2255 int port = url.getPort(); 2256 String host = url.getHost(); 2257 if (port != -1 && port != url.getDefaultPort()) { 2258 host += ":" + String.valueOf(port); 2259 } 2260 requests.set("Host", host); 2261 } 2262 } 2263 } 2264 return true; 2265 } 2266 2267 /* dummy byte buffer for reading off socket prior to closing */ 2268 byte[] cdata = new byte [128]; 2269 2270 /** 2271 * Reset (without disconnecting the TCP conn) in order to do another transaction with this instance 2272 */ 2273 private void reset() throws IOException { 2274 http.reuse = true; 2275 /* must save before calling close */ 2276 reuseClient = http; 2277 InputStream is = http.getInputStream(); 2278 if (!method.equals("HEAD")) { 2279 try { 2280 /* we want to read the rest of the response without using the 2281 * hurry mechanism, because that would close the connection 2282 * if everything is not available immediately 2283 */ 2284 if ((is instanceof ChunkedInputStream) || 2285 (is instanceof MeteredStream)) { 2286 /* reading until eof will not block */ 2287 while (is.read (cdata) > 0) {} 2288 } else { 2289 /* raw stream, which will block on read, so only read 2290 * the expected number of bytes, probably 0 2291 */ 2292 long cl = 0; 2293 int n = 0; 2294 String cls = responses.findValue ("Content-Length"); 2295 if (cls != null) { 2296 try { 2297 cl = Long.parseLong (cls); 2298 } catch (NumberFormatException e) { 2299 cl = 0; 2300 } 2301 } 2302 for (long i=0; i<cl; ) { 2303 if ((n = is.read (cdata)) == -1) { 2304 break; 2305 } else { 2306 i+= n; 2307 } 2308 } 2309 } 2310 } catch (IOException e) { 2311 http.reuse = false; 2312 reuseClient = null; 2313 disconnectInternal(); 2314 return; 2315 } 2316 try { 2317 if (is instanceof MeteredStream) { 2318 is.close(); 2319 } 2320 } catch (IOException e) { } 2321 } 2322 responseCode = -1; 2323 responses = new MessageHeader(); 2324 connected = false; 2325 } 2326 2327 /** 2328 * Disconnect from the web server at the first 401 error. Do not 2329 * disconnect when using a proxy, a good proxy should have already 2330 * closed the connection to the web server. 2331 */ 2332 private void disconnectWeb() throws IOException { 2333 if (usingProxy()) { 2334 responseCode = -1; 2335 // clean up, particularly, skip the content part 2336 // of a 401 error response 2337 reset(); 2338 } else { 2339 disconnectInternal(); 2340 } 2341 } 2342 2343 /** 2344 * Disconnect from the server (for internal use) 2345 */ 2346 private void disconnectInternal() { 2347 responseCode = -1; 2348 inputStream = null; 2349 if (pi != null) { 2350 pi.finishTracking(); 2351 pi = null; 2352 } 2353 if (http != null) { 2354 http.closeServer(); 2355 http = null; 2356 connected = false; 2357 } 2358 } 2359 2360 /** 2361 * Disconnect from the server (public API) 2362 */ 2363 public void disconnect() { 2364 2365 responseCode = -1; 2366 if (pi != null) { 2367 pi.finishTracking(); 2368 pi = null; 2369 } 2370 2371 if (http != null) { 2372 /* 2373 * If we have an input stream this means we received a response 2374 * from the server. That stream may have been read to EOF and 2375 * dependening on the stream type may already be closed or the 2376 * the http client may be returned to the keep-alive cache. 2377 * If the http client has been returned to the keep-alive cache 2378 * it may be closed (idle timeout) or may be allocated to 2379 * another request. 2380 * 2381 * In other to avoid timing issues we close the input stream 2382 * which will either close the underlying connection or return 2383 * the client to the cache. If there's a possibility that the 2384 * client has been returned to the cache (ie: stream is a keep 2385 * alive stream or a chunked input stream) then we remove an 2386 * idle connection to the server. Note that this approach 2387 * can be considered an approximation in that we may close a 2388 * different idle connection to that used by the request. 2389 * Additionally it's possible that we close two connections 2390 * - the first becuase it wasn't an EOF (and couldn't be 2391 * hurried) - the second, another idle connection to the 2392 * same server. The is okay because "disconnect" is an 2393 * indication that the application doesn't intend to access 2394 * this http server for a while. 2395 */ 2396 2397 if (inputStream != null) { 2398 HttpClient hc = http; 2399 2400 // un-synchronized 2401 boolean ka = hc.isKeepingAlive(); 2402 2403 try { 2404 inputStream.close(); 2405 } catch (IOException ioe) { } 2406 2407 // if the connection is persistent it may have been closed 2408 // or returned to the keep-alive cache. If it's been returned 2409 // to the keep-alive cache then we would like to close it 2410 // but it may have been allocated 2411 2412 if (ka) { 2413 hc.closeIdleConnection(); 2414 } 2415 2416 2417 } else { 2418 // We are deliberatly being disconnected so HttpClient 2419 // should not try to resend the request no matter what stage 2420 // of the connection we are in. 2421 http.setDoNotRetry(true); 2422 2423 http.closeServer(); 2424 } 2425 2426 // poster = null; 2427 http = null; 2428 connected = false; 2429 } 2430 cachedInputStream = null; 2431 if (cachedHeaders != null) { 2432 cachedHeaders.reset(); 2433 } 2434 } 2435 2436 public boolean usingProxy() { 2437 if (http != null) { 2438 return (http.getProxyHostUsed() != null); 2439 } 2440 return false; 2441 } 2442 2443 /** 2444 * Gets a header field by name. Returns null if not known. 2445 * @param name the name of the header field 2446 */ 2447 @Override 2448 public String getHeaderField(String name) { 2449 try { 2450 getInputStream(); 2451 } catch (IOException e) {} 2452 2453 if (cachedHeaders != null) { 2454 return cachedHeaders.findValue(name); 2455 } 2456 2457 return responses.findValue(name); 2458 } 2459 2460 /** 2461 * Returns an unmodifiable Map of the header fields. 2462 * The Map keys are Strings that represent the 2463 * response-header field names. Each Map value is an 2464 * unmodifiable List of Strings that represents 2465 * the corresponding field values. 2466 * 2467 * @return a Map of header fields 2468 * @since 1.4 2469 */ 2470 @Override 2471 public Map<String, List<String>> getHeaderFields() { 2472 try { 2473 getInputStream(); 2474 } catch (IOException e) {} 2475 2476 if (cachedHeaders != null) { 2477 return cachedHeaders.getHeaders(); 2478 } 2479 2480 return responses.getHeaders(); 2481 } 2482 2483 /** 2484 * Gets a header field by index. Returns null if not known. 2485 * @param n the index of the header field 2486 */ 2487 @Override 2488 public String getHeaderField(int n) { 2489 try { 2490 getInputStream(); 2491 } catch (IOException e) {} 2492 2493 if (cachedHeaders != null) { 2494 return cachedHeaders.getValue(n); 2495 } 2496 return responses.getValue(n); 2497 } 2498 2499 /** 2500 * Gets a header field by index. Returns null if not known. 2501 * @param n the index of the header field 2502 */ 2503 @Override 2504 public String getHeaderFieldKey(int n) { 2505 try { 2506 getInputStream(); 2507 } catch (IOException e) {} 2508 2509 if (cachedHeaders != null) { 2510 return cachedHeaders.getKey(n); 2511 } 2512 2513 return responses.getKey(n); 2514 } 2515 2516 /** 2517 * Sets request property. If a property with the key already 2518 * exists, overwrite its value with the new value. 2519 * @param value the value to be set 2520 */ 2521 @Override 2522 public void setRequestProperty(String key, String value) { 2523 if (connected) 2524 throw new IllegalStateException("Already connected"); 2525 if (key == null) 2526 throw new NullPointerException ("key is null"); 2527 2528 checkMessageHeader(key, value); 2529 requests.set(key, value); 2530 } 2531 2532 /** 2533 * Adds a general request property specified by a 2534 * key-value pair. This method will not overwrite 2535 * existing values associated with the same key. 2536 * 2537 * @param key the keyword by which the request is known 2538 * (e.g., "<code>accept</code>"). 2539 * @param value the value associated with it. 2540 * @see #getRequestProperties(java.lang.String) 2541 * @since 1.4 2542 */ 2543 @Override 2544 public void addRequestProperty(String key, String value) { 2545 if (connected) 2546 throw new IllegalStateException("Already connected"); 2547 if (key == null) 2548 throw new NullPointerException ("key is null"); 2549 2550 checkMessageHeader(key, value); 2551 requests.add(key, value); 2552 } 2553 2554 // 2555 // Set a property for authentication. This can safely disregard 2556 // the connected test. 2557 // 2558 public void setAuthenticationProperty(String key, String value) { 2559 checkMessageHeader(key, value); 2560 requests.set(key, value); 2561 } 2562 2563 @Override 2564 public String getRequestProperty (String key) { 2565 // don't return headers containing security sensitive information 2566 if (key != null) { 2567 for (int i=0; i < EXCLUDE_HEADERS.length; i++) { 2568 if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) { 2569 return null; 2570 } 2571 } 2572 } 2573 return requests.findValue(key); 2574 } 2575 2576 /** 2577 * Returns an unmodifiable Map of general request 2578 * properties for this connection. The Map keys 2579 * are Strings that represent the request-header 2580 * field names. Each Map value is a unmodifiable List 2581 * of Strings that represents the corresponding 2582 * field values. 2583 * 2584 * @return a Map of the general request properties for this connection. 2585 * @throws IllegalStateException if already connected 2586 * @since 1.4 2587 */ 2588 @Override 2589 public Map<String, List<String>> getRequestProperties() { 2590 if (connected) 2591 throw new IllegalStateException("Already connected"); 2592 2593 // exclude headers containing security-sensitive info 2594 return requests.getHeaders(EXCLUDE_HEADERS); 2595 } 2596 2597 @Override 2598 public void setConnectTimeout(int timeout) { 2599 if (timeout < 0) 2600 throw new IllegalArgumentException("timeouts can't be negative"); 2601 connectTimeout = timeout; 2602 } 2603 2604 2605 /** 2606 * Returns setting for connect timeout. 2607 * <p> 2608 * 0 return implies that the option is disabled 2609 * (i.e., timeout of infinity). 2610 * 2611 * @return an <code>int</code> that indicates the connect timeout 2612 * value in milliseconds 2613 * @see java.net.URLConnection#setConnectTimeout(int) 2614 * @see java.net.URLConnection#connect() 2615 * @since 1.5 2616 */ 2617 @Override 2618 public int getConnectTimeout() { 2619 return (connectTimeout < 0 ? 0 : connectTimeout); 2620 } 2621 2622 /** 2623 * Sets the read timeout to a specified timeout, in 2624 * milliseconds. A non-zero value specifies the timeout when 2625 * reading from Input stream when a connection is established to a 2626 * resource. If the timeout expires before there is data available 2627 * for read, a java.net.SocketTimeoutException is raised. A 2628 * timeout of zero is interpreted as an infinite timeout. 2629 * 2630 * <p> Some non-standard implementation of this method ignores the 2631 * specified timeout. To see the read timeout set, please call 2632 * getReadTimeout(). 2633 * 2634 * @param timeout an <code>int</code> that specifies the timeout 2635 * value to be used in milliseconds 2636 * @throws IllegalArgumentException if the timeout parameter is negative 2637 * 2638 * @see java.net.URLConnectiongetReadTimeout() 2639 * @see java.io.InputStream#read() 2640 * @since 1.5 2641 */ 2642 @Override 2643 public void setReadTimeout(int timeout) { 2644 if (timeout < 0) 2645 throw new IllegalArgumentException("timeouts can't be negative"); 2646 readTimeout = timeout; 2647 } 2648 2649 /** 2650 * Returns setting for read timeout. 0 return implies that the 2651 * option is disabled (i.e., timeout of infinity). 2652 * 2653 * @return an <code>int</code> that indicates the read timeout 2654 * value in milliseconds 2655 * 2656 * @see java.net.URLConnection#setReadTimeout(int) 2657 * @see java.io.InputStream#read() 2658 * @since 1.5 2659 */ 2660 @Override 2661 public int getReadTimeout() { 2662 return readTimeout < 0 ? 0 : readTimeout; 2663 } 2664 2665 String getMethod() { 2666 return method; 2667 } 2668 2669 private MessageHeader mapToMessageHeader(Map<String, List<String>> map) { 2670 MessageHeader headers = new MessageHeader(); 2671 if (map == null || map.isEmpty()) { 2672 return headers; 2673 } 2674 for (Map.Entry<String, List<String>> entry : map.entrySet()) { 2675 String key = entry.getKey(); 2676 List<String> values = entry.getValue(); 2677 for (String value : values) { 2678 if (key == null) { 2679 headers.prepend(key, value); 2680 } else { 2681 headers.add(key, value); 2682 } 2683 } 2684 } 2685 return headers; 2686 } 2687 2688 /* The purpose of this wrapper is just to capture the close() call 2689 * so we can check authentication information that may have 2690 * arrived in a Trailer field 2691 */ 2692 class HttpInputStream extends FilterInputStream { 2693 private CacheRequest cacheRequest; 2694 private OutputStream outputStream; 2695 private boolean marked = false; 2696 private int inCache = 0; 2697 private int markCount = 0; 2698 2699 public HttpInputStream (InputStream is) { 2700 super (is); 2701 this.cacheRequest = null; 2702 this.outputStream = null; 2703 } 2704 2705 public HttpInputStream (InputStream is, CacheRequest cacheRequest) { 2706 super (is); 2707 this.cacheRequest = cacheRequest; 2708 try { 2709 this.outputStream = cacheRequest.getBody(); 2710 } catch (IOException ioex) { 2711 this.cacheRequest.abort(); 2712 this.cacheRequest = null; 2713 this.outputStream = null; 2714 } 2715 } 2716 2717 /** 2718 * Marks the current position in this input stream. A subsequent 2719 * call to the <code>reset</code> method repositions this stream at 2720 * the last marked position so that subsequent reads re-read the same 2721 * bytes. 2722 * <p> 2723 * The <code>readlimit</code> argument tells this input stream to 2724 * allow that many bytes to be read before the mark position gets 2725 * invalidated. 2726 * <p> 2727 * This method simply performs <code>in.mark(readlimit)</code>. 2728 * 2729 * @param readlimit the maximum limit of bytes that can be read before 2730 * the mark position becomes invalid. 2731 * @see java.io.FilterInputStream#in 2732 * @see java.io.FilterInputStream#reset() 2733 */ 2734 @Override 2735 public synchronized void mark(int readlimit) { 2736 super.mark(readlimit); 2737 if (cacheRequest != null) { 2738 marked = true; 2739 markCount = 0; 2740 } 2741 } 2742 2743 /** 2744 * Repositions this stream to the position at the time the 2745 * <code>mark</code> method was last called on this input stream. 2746 * <p> 2747 * This method 2748 * simply performs <code>in.reset()</code>. 2749 * <p> 2750 * Stream marks are intended to be used in 2751 * situations where you need to read ahead a little to see what's in 2752 * the stream. Often this is most easily done by invoking some 2753 * general parser. If the stream is of the type handled by the 2754 * parse, it just chugs along happily. If the stream is not of 2755 * that type, the parser should toss an exception when it fails. 2756 * If this happens within readlimit bytes, it allows the outer 2757 * code to reset the stream and try another parser. 2758 * 2759 * @exception IOException if the stream has not been marked or if the 2760 * mark has been invalidated. 2761 * @see java.io.FilterInputStream#in 2762 * @see java.io.FilterInputStream#mark(int) 2763 */ 2764 @Override 2765 public synchronized void reset() throws IOException { 2766 super.reset(); 2767 if (cacheRequest != null) { 2768 marked = false; 2769 inCache += markCount; 2770 } 2771 } 2772 2773 @Override 2774 public int read() throws IOException { 2775 try { 2776 byte[] b = new byte[1]; 2777 int ret = read(b); 2778 return (ret == -1? ret : (b[0] & 0x00FF)); 2779 } catch (IOException ioex) { 2780 if (cacheRequest != null) { 2781 cacheRequest.abort(); 2782 } 2783 throw ioex; 2784 } 2785 } 2786 2787 @Override 2788 public int read(byte[] b) throws IOException { 2789 return read(b, 0, b.length); 2790 } 2791 2792 @Override 2793 public int read(byte[] b, int off, int len) throws IOException { 2794 try { 2795 int newLen = super.read(b, off, len); 2796 int nWrite; 2797 // write to cache 2798 if (inCache > 0) { 2799 if (inCache >= newLen) { 2800 inCache -= newLen; 2801 nWrite = 0; 2802 } else { 2803 nWrite = newLen - inCache; 2804 inCache = 0; 2805 } 2806 } else { 2807 nWrite = newLen; 2808 } 2809 if (nWrite > 0 && outputStream != null) 2810 outputStream.write(b, off + (newLen-nWrite), nWrite); 2811 if (marked) { 2812 markCount += newLen; 2813 } 2814 return newLen; 2815 } catch (IOException ioex) { 2816 if (cacheRequest != null) { 2817 cacheRequest.abort(); 2818 } 2819 throw ioex; 2820 } 2821 } 2822 2823 @Override 2824 public void close () throws IOException { 2825 try { 2826 if (outputStream != null) { 2827 if (read() != -1) { 2828 cacheRequest.abort(); 2829 } else { 2830 outputStream.close(); 2831 } 2832 } 2833 super.close (); 2834 } catch (IOException ioex) { 2835 if (cacheRequest != null) { 2836 cacheRequest.abort(); 2837 } 2838 throw ioex; 2839 } finally { 2840 HttpURLConnection.this.http = null; 2841 checkResponseCredentials (true); 2842 } 2843 } 2844 } 2845 2846 class StreamingOutputStream extends FilterOutputStream { 2847 2848 long expected; 2849 long written; 2850 boolean closed; 2851 boolean error; 2852 IOException errorExcp; 2853 2854 /** 2855 * expectedLength == -1 if the stream is chunked 2856 * expectedLength > 0 if the stream is fixed content-length 2857 * In the 2nd case, we make sure the expected number of 2858 * of bytes are actually written 2859 */ 2860 StreamingOutputStream (OutputStream os, long expectedLength) { 2861 super (os); 2862 expected = expectedLength; 2863 written = 0L; 2864 closed = false; 2865 error = false; 2866 } 2867 2868 @Override 2869 public void write (int b) throws IOException { 2870 checkError(); 2871 written ++; 2872 if (expected != -1L && written > expected) { 2873 throw new IOException ("too many bytes written"); 2874 } 2875 out.write (b); 2876 } 2877 2878 @Override 2879 public void write (byte[] b) throws IOException { 2880 write (b, 0, b.length); 2881 } 2882 2883 @Override 2884 public void write (byte[] b, int off, int len) throws IOException { 2885 checkError(); 2886 written += len; 2887 if (expected != -1L && written > expected) { 2888 out.close (); 2889 throw new IOException ("too many bytes written"); 2890 } 2891 out.write (b, off, len); 2892 } 2893 2894 void checkError () throws IOException { 2895 if (closed) { 2896 throw new IOException ("Stream is closed"); 2897 } 2898 if (error) { 2899 throw errorExcp; 2900 } 2901 if (((PrintStream)out).checkError()) { 2902 throw new IOException("Error writing request body to server"); 2903 } 2904 } 2905 2906 /* this is called to check that all the bytes 2907 * that were supposed to be written were written 2908 * and that the stream is now closed(). 2909 */ 2910 boolean writtenOK () { 2911 return closed && ! error; 2912 } 2913 2914 @Override 2915 public void close () throws IOException { 2916 if (closed) { 2917 return; 2918 } 2919 closed = true; 2920 if (expected != -1L) { 2921 /* not chunked */ 2922 if (written != expected) { 2923 error = true; 2924 errorExcp = new IOException ("insufficient data written"); 2925 out.close (); 2926 throw errorExcp; 2927 } 2928 super.flush(); /* can't close the socket */ 2929 } else { 2930 /* chunked */ 2931 super.close (); /* force final chunk to be written */ 2932 /* trailing \r\n */ 2933 OutputStream o = http.getOutputStream(); 2934 o.write ('\r'); 2935 o.write ('\n'); 2936 o.flush(); 2937 } 2938 } 2939 } 2940 2941 2942 static class ErrorStream extends InputStream { 2943 ByteBuffer buffer; 2944 InputStream is; 2945 2946 private ErrorStream(ByteBuffer buf) { 2947 buffer = buf; 2948 is = null; 2949 } 2950 2951 private ErrorStream(ByteBuffer buf, InputStream is) { 2952 buffer = buf; 2953 this.is = is; 2954 } 2955 2956 // when this method is called, it's either the case that cl > 0, or 2957 // if chunk-encoded, cl = -1; in other words, cl can't be 0 2958 public static InputStream getErrorStream(InputStream is, long cl, HttpClient http) { 2959 2960 // cl can't be 0; this following is here for extra precaution 2961 if (cl == 0) { 2962 return null; 2963 } 2964 2965 try { 2966 // set SO_TIMEOUT to 1/5th of the total timeout 2967 // remember the old timeout value so that we can restore it 2968 int oldTimeout = http.getReadTimeout(); 2969 http.setReadTimeout(timeout4ESBuffer/5); 2970 2971 long expected = 0; 2972 boolean isChunked = false; 2973 // the chunked case 2974 if (cl < 0) { 2975 expected = bufSize4ES; 2976 isChunked = true; 2977 } else { 2978 expected = cl; 2979 } 2980 if (expected <= bufSize4ES) { 2981 int exp = (int) expected; 2982 byte[] buffer = new byte[exp]; 2983 int count = 0, time = 0, len = 0; 2984 do { 2985 try { 2986 len = is.read(buffer, count, 2987 buffer.length - count); 2988 if (len < 0) { 2989 if (isChunked) { 2990 // chunked ended 2991 // if chunked ended prematurely, 2992 // an IOException would be thrown 2993 break; 2994 } 2995 // the server sends less than cl bytes of data 2996 throw new IOException("the server closes"+ 2997 " before sending "+cl+ 2998 " bytes of data"); 2999 } 3000 count += len; 3001 } catch (SocketTimeoutException ex) { 3002 time += timeout4ESBuffer/5; 3003 } 3004 } while (count < exp && time < timeout4ESBuffer); 3005 3006 // reset SO_TIMEOUT to old value 3007 http.setReadTimeout(oldTimeout); 3008 3009 // if count < cl at this point, we will not try to reuse 3010 // the connection 3011 if (count == 0) { 3012 // since we haven't read anything, 3013 // we will return the underlying 3014 // inputstream back to the application 3015 return null; 3016 } else if ((count == expected && !(isChunked)) || (isChunked && len <0)) { 3017 // put the connection into keep-alive cache 3018 // the inputstream will try to do the right thing 3019 is.close(); 3020 return new ErrorStream(ByteBuffer.wrap(buffer, 0, count)); 3021 } else { 3022 // we read part of the response body 3023 return new ErrorStream( 3024 ByteBuffer.wrap(buffer, 0, count), is); 3025 } 3026 } 3027 return null; 3028 } catch (IOException ioex) { 3029 // ioex.printStackTrace(); 3030 return null; 3031 } 3032 } 3033 3034 @Override 3035 public int available() throws IOException { 3036 if (is == null) { 3037 return buffer.remaining(); 3038 } else { 3039 return buffer.remaining()+is.available(); 3040 } 3041 } 3042 3043 public int read() throws IOException { 3044 byte[] b = new byte[1]; 3045 int ret = read(b); 3046 return (ret == -1? ret : (b[0] & 0x00FF)); 3047 } 3048 3049 @Override 3050 public int read(byte[] b) throws IOException { 3051 return read(b, 0, b.length); 3052 } 3053 3054 @Override 3055 public int read(byte[] b, int off, int len) throws IOException { 3056 int rem = buffer.remaining(); 3057 if (rem > 0) { 3058 int ret = rem < len? rem : len; 3059 buffer.get(b, off, ret); 3060 return ret; 3061 } else { 3062 if (is == null) { 3063 return -1; 3064 } else { 3065 return is.read(b, off, len); 3066 } 3067 } 3068 } 3069 3070 @Override 3071 public void close() throws IOException { 3072 buffer = null; 3073 if (is != null) { 3074 is.close(); 3075 } 3076 } 3077 } 3078 } 3079 3080 /** An input stream that just returns EOF. This is for 3081 * HTTP URLConnections that are KeepAlive && use the 3082 * HEAD method - i.e., stream not dead, but nothing to be read. 3083 */ 3084 3085 class EmptyInputStream extends InputStream { 3086 3087 @Override 3088 public int available() { 3089 return 0; 3090 } 3091 3092 public int read() { 3093 return -1; 3094 } 3095 } --- EOF ---