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