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