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