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