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