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