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