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