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 /* If the platform supports transparent authentication 2177 * then check if we are in a secure environment 2178 * whether, or not, we should try transparent authentication.*/ 2179 if (tryTransparentNTLMServer) { 2180 tryTransparentNTLMServer = 2181 NTLMAuthenticationProxy.proxy.isTrustedSite(url); 2182 } 2183 } 2184 a = null; 2185 if (tryTransparentNTLMServer) { 2186 logger.finest("Trying Transparent NTLM authentication"); 2187 } else { 2188 a = privilegedRequestPasswordAuthentication( 2189 url.getHost(), addr, port, url.getProtocol(), 2190 "", scheme, url, RequestorType.SERVER); 2191 } 2192 2193 /* If we are not trying transparent authentication then 2194 * we need to have a PasswordAuthentication instance. For 2195 * transparent authentication (Windows only) the username 2196 * and password will be picked up from the current logged 2197 * on users credentials. 2198 */ 2199 if (tryTransparentNTLMServer || 2200 (!tryTransparentNTLMServer && a != null)) { 2201 ret = NTLMAuthenticationProxy.proxy.create(false, url1, a); 2202 } 2203 2204 /* set to false so that we do not try again */ 2205 tryTransparentNTLMServer = false; 2206 } 2207 break; 2208 case UNKNOWN: 2209 logger.finest("Unknown/Unsupported authentication scheme: " + scheme); 2210 default: 2211 throw new AssertionError("should not reach here"); 2212 } 2213 } 2214 2215 // For backwards compatibility, we also try defaultAuth 2216 // REMIND: Get rid of this for JDK2.0. 2217 2218 if (ret == null && defaultAuth != null 2219 && defaultAuth.schemeSupported(scheme)) { 2220 String a = defaultAuth.authString(url, scheme, realm); 2221 if (a != null) { 2222 ret = new BasicAuthentication (false, url, realm, a); 2223 // not in cache by default - cache on success 2224 } 2225 } 2226 2227 if (ret != null ) { 2228 if (!ret.setHeaders(this, p, raw)) { 2229 ret = null; 2230 } 2231 } 2232 } 2233 if (logger.isLoggable(PlatformLogger.FINER)) { 2234 logger.finer("Server Authentication for " + authhdr.toString() +" returned " + (ret != null ? ret.toString() : "null")); 2235 } 2236 return ret; 2237 } 2238 2239 /* inclose will be true if called from close(), in which case we 2240 * force the call to check because this is the last chance to do so. 2241 * If not in close(), then the authentication info could arrive in a trailer 2242 * field, which we have not read yet. 2243 */ 2244 private void checkResponseCredentials (boolean inClose) throws IOException { 2245 try { 2246 if (!needToCheck) 2247 return; 2248 if ((validateProxy && currentProxyCredentials != null) && 2249 (currentProxyCredentials instanceof DigestAuthentication)) { 2250 String raw = responses.findValue ("Proxy-Authentication-Info"); 2251 if (inClose || (raw != null)) { 2252 DigestAuthentication da = (DigestAuthentication) 2253 currentProxyCredentials; 2254 da.checkResponse (raw, method, getRequestURI()); 2255 currentProxyCredentials = null; 2256 } 2257 } 2258 if ((validateServer && currentServerCredentials != null) && 2259 (currentServerCredentials instanceof DigestAuthentication)) { 2260 String raw = responses.findValue ("Authentication-Info"); 2261 if (inClose || (raw != null)) { 2262 DigestAuthentication da = (DigestAuthentication) 2263 currentServerCredentials; 2264 da.checkResponse (raw, method, url); 2265 currentServerCredentials = null; 2266 } 2267 } 2268 if ((currentServerCredentials==null) && (currentProxyCredentials == null)) { 2269 needToCheck = false; 2270 } 2271 } catch (IOException e) { 2272 disconnectInternal(); 2273 connected = false; 2274 throw e; 2275 } 2276 } 2277 2278 /* The request URI used in the request line for this request. 2279 * Also, needed for digest authentication 2280 */ 2281 2282 String requestURI = null; 2283 2284 String getRequestURI() throws IOException { 2285 if (requestURI == null) { 2286 requestURI = http.getURLFile(); 2287 } 2288 return requestURI; 2289 } 2290 2291 /* Tells us whether to follow a redirect. If so, it 2292 * closes the connection (break any keep-alive) and 2293 * resets the url, re-connects, and resets the request 2294 * property. 2295 */ 2296 private boolean followRedirect() throws IOException { 2297 if (!getInstanceFollowRedirects()) { 2298 return false; 2299 } 2300 2301 int stat = getResponseCode(); 2302 if (stat < 300 || stat > 307 || stat == 306 2303 || stat == HTTP_NOT_MODIFIED) { 2304 return false; 2305 } 2306 String loc = getHeaderField("Location"); 2307 if (loc == null) { 2308 /* this should be present - if not, we have no choice 2309 * but to go forward w/ the response we got 2310 */ 2311 return false; 2312 } 2313 URL locUrl; 2314 try { 2315 locUrl = new URL(loc); 2316 if (!url.getProtocol().equalsIgnoreCase(locUrl.getProtocol())) { 2317 return false; 2318 } 2319 2320 } catch (MalformedURLException mue) { 2321 // treat loc as a relative URI to conform to popular browsers 2322 locUrl = new URL(url, loc); 2323 } 2324 disconnectInternal(); 2325 if (streaming()) { 2326 throw new HttpRetryException (RETRY_MSG3, stat, loc); 2327 } 2328 if (logger.isLoggable(PlatformLogger.FINE)) { 2329 logger.fine("Redirected from " + url + " to " + locUrl); 2330 } 2331 2332 // clear out old response headers!!!! 2333 responses = new MessageHeader(); 2334 if (stat == HTTP_USE_PROXY) { 2335 /* This means we must re-request the resource through the 2336 * proxy denoted in the "Location:" field of the response. 2337 * Judging by the spec, the string in the Location header 2338 * _should_ denote a URL - let's hope for "http://my.proxy.org" 2339 * Make a new HttpClient to the proxy, using HttpClient's 2340 * Instance-specific proxy fields, but note we're still fetching 2341 * the same URL. 2342 */ 2343 String proxyHost = locUrl.getHost(); 2344 int proxyPort = locUrl.getPort(); 2345 2346 SecurityManager security = System.getSecurityManager(); 2347 if (security != null) { 2348 security.checkConnect(proxyHost, proxyPort); 2349 } 2350 2351 setProxiedClient (url, proxyHost, proxyPort); 2352 requests.set(0, method + " " + getRequestURI()+" " + 2353 httpVersion, null); 2354 connected = true; 2355 } else { 2356 // maintain previous headers, just change the name 2357 // of the file we're getting 2358 url = locUrl; 2359 requestURI = null; // force it to be recalculated 2360 if (method.equals("POST") && !Boolean.getBoolean("http.strictPostRedirect") && (stat!=307)) { 2361 /* The HTTP/1.1 spec says that a redirect from a POST 2362 * *should not* be immediately turned into a GET, and 2363 * that some HTTP/1.0 clients incorrectly did this. 2364 * Correct behavior redirects a POST to another POST. 2365 * Unfortunately, since most browsers have this incorrect 2366 * behavior, the web works this way now. Typical usage 2367 * seems to be: 2368 * POST a login code or passwd to a web page. 2369 * after validation, the server redirects to another 2370 * (welcome) page 2371 * The second request is (erroneously) expected to be GET 2372 * 2373 * We will do the incorrect thing (POST-->GET) by default. 2374 * We will provide the capability to do the "right" thing 2375 * (POST-->POST) by a system property, "http.strictPostRedirect=true" 2376 */ 2377 2378 requests = new MessageHeader(); 2379 setRequests = false; 2380 setRequestMethod("GET"); 2381 poster = null; 2382 if (!checkReuseConnection()) 2383 connect(); 2384 } else { 2385 if (!checkReuseConnection()) 2386 connect(); 2387 /* Even after a connect() call, http variable still can be 2388 * null, if a ResponseCache has been installed and it returns 2389 * a non-null CacheResponse instance. So check nullity before using it. 2390 * 2391 * And further, if http is null, there's no need to do anything 2392 * about request headers because successive http session will use 2393 * cachedInputStream/cachedHeaders anyway, which is returned by 2394 * CacheResponse. 2395 */ 2396 if (http != null) { 2397 requests.set(0, method + " " + getRequestURI()+" " + 2398 httpVersion, null); 2399 int port = url.getPort(); 2400 String host = url.getHost(); 2401 if (port != -1 && port != url.getDefaultPort()) { 2402 host += ":" + String.valueOf(port); 2403 } 2404 requests.set("Host", host); 2405 } 2406 } 2407 } 2408 return true; 2409 } 2410 2411 /* dummy byte buffer for reading off socket prior to closing */ 2412 byte[] cdata = new byte [128]; 2413 2414 /** 2415 * Reset (without disconnecting the TCP conn) in order to do another transaction with this instance 2416 */ 2417 private void reset() throws IOException { 2418 http.reuse = true; 2419 /* must save before calling close */ 2420 reuseClient = http; 2421 InputStream is = http.getInputStream(); 2422 if (!method.equals("HEAD")) { 2423 try { 2424 /* we want to read the rest of the response without using the 2425 * hurry mechanism, because that would close the connection 2426 * if everything is not available immediately 2427 */ 2428 if ((is instanceof ChunkedInputStream) || 2429 (is instanceof MeteredStream)) { 2430 /* reading until eof will not block */ 2431 while (is.read (cdata) > 0) {} 2432 } else { 2433 /* raw stream, which will block on read, so only read 2434 * the expected number of bytes, probably 0 2435 */ 2436 long cl = 0; 2437 int n = 0; 2438 String cls = responses.findValue ("Content-Length"); 2439 if (cls != null) { 2440 try { 2441 cl = Long.parseLong (cls); 2442 } catch (NumberFormatException e) { 2443 cl = 0; 2444 } 2445 } 2446 for (long i=0; i<cl; ) { 2447 if ((n = is.read (cdata)) == -1) { 2448 break; 2449 } else { 2450 i+= n; 2451 } 2452 } 2453 } 2454 } catch (IOException e) { 2455 http.reuse = false; 2456 reuseClient = null; 2457 disconnectInternal(); 2458 return; 2459 } 2460 try { 2461 if (is instanceof MeteredStream) { 2462 is.close(); 2463 } 2464 } catch (IOException e) { } 2465 } 2466 responseCode = -1; 2467 responses = new MessageHeader(); 2468 connected = false; 2469 } 2470 2471 /** 2472 * Disconnect from the web server at the first 401 error. Do not 2473 * disconnect when using a proxy, a good proxy should have already 2474 * closed the connection to the web server. 2475 */ 2476 private void disconnectWeb() throws IOException { 2477 if (usingProxy() && http.isKeepingAlive()) { 2478 responseCode = -1; 2479 // clean up, particularly, skip the content part 2480 // of a 401 error response 2481 reset(); 2482 } else { 2483 disconnectInternal(); 2484 } 2485 } 2486 2487 /** 2488 * Disconnect from the server (for internal use) 2489 */ 2490 private void disconnectInternal() { 2491 responseCode = -1; 2492 inputStream = null; 2493 if (pi != null) { 2494 pi.finishTracking(); 2495 pi = null; 2496 } 2497 if (http != null) { 2498 http.closeServer(); 2499 http = null; 2500 connected = false; 2501 } 2502 } 2503 2504 /** 2505 * Disconnect from the server (public API) 2506 */ 2507 public void disconnect() { 2508 2509 responseCode = -1; 2510 if (pi != null) { 2511 pi.finishTracking(); 2512 pi = null; 2513 } 2514 2515 if (http != null) { 2516 /* 2517 * If we have an input stream this means we received a response 2518 * from the server. That stream may have been read to EOF and 2519 * dependening on the stream type may already be closed or the 2520 * the http client may be returned to the keep-alive cache. 2521 * If the http client has been returned to the keep-alive cache 2522 * it may be closed (idle timeout) or may be allocated to 2523 * another request. 2524 * 2525 * In other to avoid timing issues we close the input stream 2526 * which will either close the underlying connection or return 2527 * the client to the cache. If there's a possibility that the 2528 * client has been returned to the cache (ie: stream is a keep 2529 * alive stream or a chunked input stream) then we remove an 2530 * idle connection to the server. Note that this approach 2531 * can be considered an approximation in that we may close a 2532 * different idle connection to that used by the request. 2533 * Additionally it's possible that we close two connections 2534 * - the first becuase it wasn't an EOF (and couldn't be 2535 * hurried) - the second, another idle connection to the 2536 * same server. The is okay because "disconnect" is an 2537 * indication that the application doesn't intend to access 2538 * this http server for a while. 2539 */ 2540 2541 if (inputStream != null) { 2542 HttpClient hc = http; 2543 2544 // un-synchronized 2545 boolean ka = hc.isKeepingAlive(); 2546 2547 try { 2548 inputStream.close(); 2549 } catch (IOException ioe) { } 2550 2551 // if the connection is persistent it may have been closed 2552 // or returned to the keep-alive cache. If it's been returned 2553 // to the keep-alive cache then we would like to close it 2554 // but it may have been allocated 2555 2556 if (ka) { 2557 hc.closeIdleConnection(); 2558 } 2559 2560 2561 } else { 2562 // We are deliberatly being disconnected so HttpClient 2563 // should not try to resend the request no matter what stage 2564 // of the connection we are in. 2565 http.setDoNotRetry(true); 2566 2567 http.closeServer(); 2568 } 2569 2570 // poster = null; 2571 http = null; 2572 connected = false; 2573 } 2574 cachedInputStream = null; 2575 if (cachedHeaders != null) { 2576 cachedHeaders.reset(); 2577 } 2578 } 2579 2580 public boolean usingProxy() { 2581 if (http != null) { 2582 return (http.getProxyHostUsed() != null); 2583 } 2584 return false; 2585 } 2586 2587 /** 2588 * Gets a header field by name. Returns null if not known. 2589 * @param name the name of the header field 2590 */ 2591 @Override 2592 public String getHeaderField(String name) { 2593 try { 2594 getInputStream(); 2595 } catch (IOException e) {} 2596 2597 if (cachedHeaders != null) { 2598 return cachedHeaders.findValue(name); 2599 } 2600 2601 return responses.findValue(name); 2602 } 2603 2604 /** 2605 * Returns an unmodifiable Map of the header fields. 2606 * The Map keys are Strings that represent the 2607 * response-header field names. Each Map value is an 2608 * unmodifiable List of Strings that represents 2609 * the corresponding field values. 2610 * 2611 * @return a Map of header fields 2612 * @since 1.4 2613 */ 2614 @Override 2615 public Map<String, List<String>> getHeaderFields() { 2616 try { 2617 getInputStream(); 2618 } catch (IOException e) {} 2619 2620 if (cachedHeaders != null) { 2621 return cachedHeaders.getHeaders(); 2622 } 2623 2624 return responses.getHeaders(); 2625 } 2626 2627 /** 2628 * Gets a header field by index. Returns null if not known. 2629 * @param n the index of the header field 2630 */ 2631 @Override 2632 public String getHeaderField(int n) { 2633 try { 2634 getInputStream(); 2635 } catch (IOException e) {} 2636 2637 if (cachedHeaders != null) { 2638 return cachedHeaders.getValue(n); 2639 } 2640 return responses.getValue(n); 2641 } 2642 2643 /** 2644 * Gets a header field by index. Returns null if not known. 2645 * @param n the index of the header field 2646 */ 2647 @Override 2648 public String getHeaderFieldKey(int n) { 2649 try { 2650 getInputStream(); 2651 } catch (IOException e) {} 2652 2653 if (cachedHeaders != null) { 2654 return cachedHeaders.getKey(n); 2655 } 2656 2657 return responses.getKey(n); 2658 } 2659 2660 /** 2661 * Sets request property. If a property with the key already 2662 * exists, overwrite its value with the new value. 2663 * @param value the value to be set 2664 */ 2665 @Override 2666 public void setRequestProperty(String key, String value) { 2667 if (connected) 2668 throw new IllegalStateException("Already connected"); 2669 if (key == null) 2670 throw new NullPointerException ("key is null"); 2671 2672 if (isExternalMessageHeaderAllowed(key, value)) { 2673 requests.set(key, value); 2674 } 2675 } 2676 2677 /** 2678 * Adds a general request property specified by a 2679 * key-value pair. This method will not overwrite 2680 * existing values associated with the same key. 2681 * 2682 * @param key the keyword by which the request is known 2683 * (e.g., "<code>accept</code>"). 2684 * @param value the value associated with it. 2685 * @see #getRequestProperties(java.lang.String) 2686 * @since 1.4 2687 */ 2688 @Override 2689 public void addRequestProperty(String key, String value) { 2690 if (connected) 2691 throw new IllegalStateException("Already connected"); 2692 if (key == null) 2693 throw new NullPointerException ("key is null"); 2694 2695 if (isExternalMessageHeaderAllowed(key, value)) { 2696 requests.add(key, value); 2697 } 2698 } 2699 2700 // 2701 // Set a property for authentication. This can safely disregard 2702 // the connected test. 2703 // 2704 public void setAuthenticationProperty(String key, String value) { 2705 checkMessageHeader(key, value); 2706 requests.set(key, value); 2707 } 2708 2709 @Override 2710 public synchronized String getRequestProperty (String key) { 2711 if (key == null) { 2712 return null; 2713 } 2714 2715 // don't return headers containing security sensitive information 2716 for (int i=0; i < EXCLUDE_HEADERS.length; i++) { 2717 if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) { 2718 return null; 2719 } 2720 } 2721 if (!setUserCookies) { 2722 if (key.equalsIgnoreCase("Cookie")) { 2723 return userCookies; 2724 } 2725 if (key.equalsIgnoreCase("Cookie2")) { 2726 return userCookies2; 2727 } 2728 } 2729 return requests.findValue(key); 2730 } 2731 2732 /** 2733 * Returns an unmodifiable Map of general request 2734 * properties for this connection. The Map keys 2735 * are Strings that represent the request-header 2736 * field names. Each Map value is a unmodifiable List 2737 * of Strings that represents the corresponding 2738 * field values. 2739 * 2740 * @return a Map of the general request properties for this connection. 2741 * @throws IllegalStateException if already connected 2742 * @since 1.4 2743 */ 2744 @Override 2745 public synchronized Map<String, List<String>> getRequestProperties() { 2746 if (connected) 2747 throw new IllegalStateException("Already connected"); 2748 2749 // exclude headers containing security-sensitive info 2750 if (setUserCookies) { 2751 return requests.getHeaders(EXCLUDE_HEADERS); 2752 } 2753 /* 2754 * The cookies in the requests message headers may have 2755 * been modified. Use the saved user cookies instead. 2756 */ 2757 Map userCookiesMap = null; 2758 if (userCookies != null || userCookies2 != null) { 2759 userCookiesMap = new HashMap(); 2760 if (userCookies != null) { 2761 userCookiesMap.put("Cookie", userCookies); 2762 } 2763 if (userCookies2 != null) { 2764 userCookiesMap.put("Cookie2", userCookies2); 2765 } 2766 } 2767 return requests.filterAndAddHeaders(EXCLUDE_HEADERS2, userCookiesMap); 2768 } 2769 2770 @Override 2771 public void setConnectTimeout(int timeout) { 2772 if (timeout < 0) 2773 throw new IllegalArgumentException("timeouts can't be negative"); 2774 connectTimeout = timeout; 2775 } 2776 2777 2778 /** 2779 * Returns setting for connect timeout. 2780 * <p> 2781 * 0 return implies that the option is disabled 2782 * (i.e., timeout of infinity). 2783 * 2784 * @return an <code>int</code> that indicates the connect timeout 2785 * value in milliseconds 2786 * @see java.net.URLConnection#setConnectTimeout(int) 2787 * @see java.net.URLConnection#connect() 2788 * @since 1.5 2789 */ 2790 @Override 2791 public int getConnectTimeout() { 2792 return (connectTimeout < 0 ? 0 : connectTimeout); 2793 } 2794 2795 /** 2796 * Sets the read timeout to a specified timeout, in 2797 * milliseconds. A non-zero value specifies the timeout when 2798 * reading from Input stream when a connection is established to a 2799 * resource. If the timeout expires before there is data available 2800 * for read, a java.net.SocketTimeoutException is raised. A 2801 * timeout of zero is interpreted as an infinite timeout. 2802 * 2803 * <p> Some non-standard implementation of this method ignores the 2804 * specified timeout. To see the read timeout set, please call 2805 * getReadTimeout(). 2806 * 2807 * @param timeout an <code>int</code> that specifies the timeout 2808 * value to be used in milliseconds 2809 * @throws IllegalArgumentException if the timeout parameter is negative 2810 * 2811 * @see java.net.URLConnectiongetReadTimeout() 2812 * @see java.io.InputStream#read() 2813 * @since 1.5 2814 */ 2815 @Override 2816 public void setReadTimeout(int timeout) { 2817 if (timeout < 0) 2818 throw new IllegalArgumentException("timeouts can't be negative"); 2819 readTimeout = timeout; 2820 } 2821 2822 /** 2823 * Returns setting for read timeout. 0 return implies that the 2824 * option is disabled (i.e., timeout of infinity). 2825 * 2826 * @return an <code>int</code> that indicates the read timeout 2827 * value in milliseconds 2828 * 2829 * @see java.net.URLConnection#setReadTimeout(int) 2830 * @see java.io.InputStream#read() 2831 * @since 1.5 2832 */ 2833 @Override 2834 public int getReadTimeout() { 2835 return readTimeout < 0 ? 0 : readTimeout; 2836 } 2837 2838 String getMethod() { 2839 return method; 2840 } 2841 2842 private MessageHeader mapToMessageHeader(Map<String, List<String>> map) { 2843 MessageHeader headers = new MessageHeader(); 2844 if (map == null || map.isEmpty()) { 2845 return headers; 2846 } 2847 for (Map.Entry<String, List<String>> entry : map.entrySet()) { 2848 String key = entry.getKey(); 2849 List<String> values = entry.getValue(); 2850 for (String value : values) { 2851 if (key == null) { 2852 headers.prepend(key, value); 2853 } else { 2854 headers.add(key, value); 2855 } 2856 } 2857 } 2858 return headers; 2859 } 2860 2861 /* The purpose of this wrapper is just to capture the close() call 2862 * so we can check authentication information that may have 2863 * arrived in a Trailer field 2864 */ 2865 class HttpInputStream extends FilterInputStream { 2866 private CacheRequest cacheRequest; 2867 private OutputStream outputStream; 2868 private boolean marked = false; 2869 private int inCache = 0; 2870 private int markCount = 0; 2871 2872 public HttpInputStream (InputStream is) { 2873 super (is); 2874 this.cacheRequest = null; 2875 this.outputStream = null; 2876 } 2877 2878 public HttpInputStream (InputStream is, CacheRequest cacheRequest) { 2879 super (is); 2880 this.cacheRequest = cacheRequest; 2881 try { 2882 this.outputStream = cacheRequest.getBody(); 2883 } catch (IOException ioex) { 2884 this.cacheRequest.abort(); 2885 this.cacheRequest = null; 2886 this.outputStream = null; 2887 } 2888 } 2889 2890 /** 2891 * Marks the current position in this input stream. A subsequent 2892 * call to the <code>reset</code> method repositions this stream at 2893 * the last marked position so that subsequent reads re-read the same 2894 * bytes. 2895 * <p> 2896 * The <code>readlimit</code> argument tells this input stream to 2897 * allow that many bytes to be read before the mark position gets 2898 * invalidated. 2899 * <p> 2900 * This method simply performs <code>in.mark(readlimit)</code>. 2901 * 2902 * @param readlimit the maximum limit of bytes that can be read before 2903 * the mark position becomes invalid. 2904 * @see java.io.FilterInputStream#in 2905 * @see java.io.FilterInputStream#reset() 2906 */ 2907 @Override 2908 public synchronized void mark(int readlimit) { 2909 super.mark(readlimit); 2910 if (cacheRequest != null) { 2911 marked = true; 2912 markCount = 0; 2913 } 2914 } 2915 2916 /** 2917 * Repositions this stream to the position at the time the 2918 * <code>mark</code> method was last called on this input stream. 2919 * <p> 2920 * This method 2921 * simply performs <code>in.reset()</code>. 2922 * <p> 2923 * Stream marks are intended to be used in 2924 * situations where you need to read ahead a little to see what's in 2925 * the stream. Often this is most easily done by invoking some 2926 * general parser. If the stream is of the type handled by the 2927 * parse, it just chugs along happily. If the stream is not of 2928 * that type, the parser should toss an exception when it fails. 2929 * If this happens within readlimit bytes, it allows the outer 2930 * code to reset the stream and try another parser. 2931 * 2932 * @exception IOException if the stream has not been marked or if the 2933 * mark has been invalidated. 2934 * @see java.io.FilterInputStream#in 2935 * @see java.io.FilterInputStream#mark(int) 2936 */ 2937 @Override 2938 public synchronized void reset() throws IOException { 2939 super.reset(); 2940 if (cacheRequest != null) { 2941 marked = false; 2942 inCache += markCount; 2943 } 2944 } 2945 2946 @Override 2947 public int read() throws IOException { 2948 try { 2949 byte[] b = new byte[1]; 2950 int ret = read(b); 2951 return (ret == -1? ret : (b[0] & 0x00FF)); 2952 } catch (IOException ioex) { 2953 if (cacheRequest != null) { 2954 cacheRequest.abort(); 2955 } 2956 throw ioex; 2957 } 2958 } 2959 2960 @Override 2961 public int read(byte[] b) throws IOException { 2962 return read(b, 0, b.length); 2963 } 2964 2965 @Override 2966 public int read(byte[] b, int off, int len) throws IOException { 2967 try { 2968 int newLen = super.read(b, off, len); 2969 int nWrite; 2970 // write to cache 2971 if (inCache > 0) { 2972 if (inCache >= newLen) { 2973 inCache -= newLen; 2974 nWrite = 0; 2975 } else { 2976 nWrite = newLen - inCache; 2977 inCache = 0; 2978 } 2979 } else { 2980 nWrite = newLen; 2981 } 2982 if (nWrite > 0 && outputStream != null) 2983 outputStream.write(b, off + (newLen-nWrite), nWrite); 2984 if (marked) { 2985 markCount += newLen; 2986 } 2987 return newLen; 2988 } catch (IOException ioex) { 2989 if (cacheRequest != null) { 2990 cacheRequest.abort(); 2991 } 2992 throw ioex; 2993 } 2994 } 2995 2996 /* skip() calls read() in order to ensure that entire response gets 2997 * cached. same implementation as InputStream.skip */ 2998 2999 private byte[] skipBuffer; 3000 private static final int SKIP_BUFFER_SIZE = 8096; 3001 3002 @Override 3003 public long skip (long n) throws IOException { 3004 3005 long remaining = n; 3006 int nr; 3007 if (skipBuffer == null) 3008 skipBuffer = new byte[SKIP_BUFFER_SIZE]; 3009 3010 byte[] localSkipBuffer = skipBuffer; 3011 3012 if (n <= 0) { 3013 return 0; 3014 } 3015 3016 while (remaining > 0) { 3017 nr = read(localSkipBuffer, 0, 3018 (int) Math.min(SKIP_BUFFER_SIZE, remaining)); 3019 if (nr < 0) { 3020 break; 3021 } 3022 remaining -= nr; 3023 } 3024 3025 return n - remaining; 3026 } 3027 3028 @Override 3029 public void close () throws IOException { 3030 try { 3031 if (outputStream != null) { 3032 if (read() != -1) { 3033 cacheRequest.abort(); 3034 } else { 3035 outputStream.close(); 3036 } 3037 } 3038 super.close (); 3039 } catch (IOException ioex) { 3040 if (cacheRequest != null) { 3041 cacheRequest.abort(); 3042 } 3043 throw ioex; 3044 } finally { 3045 HttpURLConnection.this.http = null; 3046 checkResponseCredentials (true); 3047 } 3048 } 3049 } 3050 3051 class StreamingOutputStream extends FilterOutputStream { 3052 3053 long expected; 3054 long written; 3055 boolean closed; 3056 boolean error; 3057 IOException errorExcp; 3058 3059 /** 3060 * expectedLength == -1 if the stream is chunked 3061 * expectedLength > 0 if the stream is fixed content-length 3062 * In the 2nd case, we make sure the expected number of 3063 * of bytes are actually written 3064 */ 3065 StreamingOutputStream (OutputStream os, long expectedLength) { 3066 super (os); 3067 expected = expectedLength; 3068 written = 0L; 3069 closed = false; 3070 error = false; 3071 } 3072 3073 @Override 3074 public void write (int b) throws IOException { 3075 checkError(); 3076 written ++; 3077 if (expected != -1L && written > expected) { 3078 throw new IOException ("too many bytes written"); 3079 } 3080 out.write (b); 3081 } 3082 3083 @Override 3084 public void write (byte[] b) throws IOException { 3085 write (b, 0, b.length); 3086 } 3087 3088 @Override 3089 public void write (byte[] b, int off, int len) throws IOException { 3090 checkError(); 3091 written += len; 3092 if (expected != -1L && written > expected) { 3093 out.close (); 3094 throw new IOException ("too many bytes written"); 3095 } 3096 out.write (b, off, len); 3097 } 3098 3099 void checkError () throws IOException { 3100 if (closed) { 3101 throw new IOException ("Stream is closed"); 3102 } 3103 if (error) { 3104 throw errorExcp; 3105 } 3106 if (((PrintStream)out).checkError()) { 3107 throw new IOException("Error writing request body to server"); 3108 } 3109 } 3110 3111 /* this is called to check that all the bytes 3112 * that were supposed to be written were written 3113 * and that the stream is now closed(). 3114 */ 3115 boolean writtenOK () { 3116 return closed && ! error; 3117 } 3118 3119 @Override 3120 public void close () throws IOException { 3121 if (closed) { 3122 return; 3123 } 3124 closed = true; 3125 if (expected != -1L) { 3126 /* not chunked */ 3127 if (written != expected) { 3128 error = true; 3129 errorExcp = new IOException ("insufficient data written"); 3130 out.close (); 3131 throw errorExcp; 3132 } 3133 super.flush(); /* can't close the socket */ 3134 } else { 3135 /* chunked */ 3136 super.close (); /* force final chunk to be written */ 3137 /* trailing \r\n */ 3138 OutputStream o = http.getOutputStream(); 3139 o.write ('\r'); 3140 o.write ('\n'); 3141 o.flush(); 3142 } 3143 } 3144 } 3145 3146 3147 static class ErrorStream extends InputStream { 3148 ByteBuffer buffer; 3149 InputStream is; 3150 3151 private ErrorStream(ByteBuffer buf) { 3152 buffer = buf; 3153 is = null; 3154 } 3155 3156 private ErrorStream(ByteBuffer buf, InputStream is) { 3157 buffer = buf; 3158 this.is = is; 3159 } 3160 3161 // when this method is called, it's either the case that cl > 0, or 3162 // if chunk-encoded, cl = -1; in other words, cl can't be 0 3163 public static InputStream getErrorStream(InputStream is, long cl, HttpClient http) { 3164 3165 // cl can't be 0; this following is here for extra precaution 3166 if (cl == 0) { 3167 return null; 3168 } 3169 3170 try { 3171 // set SO_TIMEOUT to 1/5th of the total timeout 3172 // remember the old timeout value so that we can restore it 3173 int oldTimeout = http.getReadTimeout(); 3174 http.setReadTimeout(timeout4ESBuffer/5); 3175 3176 long expected = 0; 3177 boolean isChunked = false; 3178 // the chunked case 3179 if (cl < 0) { 3180 expected = bufSize4ES; 3181 isChunked = true; 3182 } else { 3183 expected = cl; 3184 } 3185 if (expected <= bufSize4ES) { 3186 int exp = (int) expected; 3187 byte[] buffer = new byte[exp]; 3188 int count = 0, time = 0, len = 0; 3189 do { 3190 try { 3191 len = is.read(buffer, count, 3192 buffer.length - count); 3193 if (len < 0) { 3194 if (isChunked) { 3195 // chunked ended 3196 // if chunked ended prematurely, 3197 // an IOException would be thrown 3198 break; 3199 } 3200 // the server sends less than cl bytes of data 3201 throw new IOException("the server closes"+ 3202 " before sending "+cl+ 3203 " bytes of data"); 3204 } 3205 count += len; 3206 } catch (SocketTimeoutException ex) { 3207 time += timeout4ESBuffer/5; 3208 } 3209 } while (count < exp && time < timeout4ESBuffer); 3210 3211 // reset SO_TIMEOUT to old value 3212 http.setReadTimeout(oldTimeout); 3213 3214 // if count < cl at this point, we will not try to reuse 3215 // the connection 3216 if (count == 0) { 3217 // since we haven't read anything, 3218 // we will return the underlying 3219 // inputstream back to the application 3220 return null; 3221 } else if ((count == expected && !(isChunked)) || (isChunked && len <0)) { 3222 // put the connection into keep-alive cache 3223 // the inputstream will try to do the right thing 3224 is.close(); 3225 return new ErrorStream(ByteBuffer.wrap(buffer, 0, count)); 3226 } else { 3227 // we read part of the response body 3228 return new ErrorStream( 3229 ByteBuffer.wrap(buffer, 0, count), is); 3230 } 3231 } 3232 return null; 3233 } catch (IOException ioex) { 3234 // ioex.printStackTrace(); 3235 return null; 3236 } 3237 } 3238 3239 @Override 3240 public int available() throws IOException { 3241 if (is == null) { 3242 return buffer.remaining(); 3243 } else { 3244 return buffer.remaining()+is.available(); 3245 } 3246 } 3247 3248 public int read() throws IOException { 3249 byte[] b = new byte[1]; 3250 int ret = read(b); 3251 return (ret == -1? ret : (b[0] & 0x00FF)); 3252 } 3253 3254 @Override 3255 public int read(byte[] b) throws IOException { 3256 return read(b, 0, b.length); 3257 } 3258 3259 @Override 3260 public int read(byte[] b, int off, int len) throws IOException { 3261 int rem = buffer.remaining(); 3262 if (rem > 0) { 3263 int ret = rem < len? rem : len; 3264 buffer.get(b, off, ret); 3265 return ret; 3266 } else { 3267 if (is == null) { 3268 return -1; 3269 } else { 3270 return is.read(b, off, len); 3271 } 3272 } 3273 } 3274 3275 @Override 3276 public void close() throws IOException { 3277 buffer = null; 3278 if (is != null) { 3279 is.close(); 3280 } 3281 } 3282 } 3283 } 3284 3285 /** An input stream that just returns EOF. This is for 3286 * HTTP URLConnections that are KeepAlive && use the 3287 * HEAD method - i.e., stream not dead, but nothing to be read. 3288 */ 3289 3290 class EmptyInputStream extends InputStream { 3291 3292 @Override 3293 public int available() { 3294 return 0; 3295 } 3296 3297 public int read() { 3298 return -1; 3299 } 3300 }