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