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