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