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