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