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