1 /* 2 * Copyright (c) 1994, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.net.www.http; 27 28 import java.io.*; 29 import java.net.*; 30 import java.util.Locale; 31 import java.util.Objects; 32 import java.util.Properties; 33 import sun.net.NetworkClient; 34 import sun.net.ProgressSource; 35 import sun.net.www.MessageHeader; 36 import sun.net.www.HeaderParser; 37 import sun.net.www.MeteredStream; 38 import sun.net.www.ParseUtil; 39 import sun.net.www.protocol.http.AuthenticatorKeys; 40 import sun.net.www.protocol.http.HttpURLConnection; 41 import sun.util.logging.PlatformLogger; 42 import static sun.net.www.protocol.http.HttpURLConnection.TunnelState.*; 43 import sun.security.action.GetPropertyAction; 44 45 /** 46 * @author Herb Jellinek 47 * @author Dave Brown 48 */ 49 public class HttpClient extends NetworkClient { 50 // whether this httpclient comes from the cache 51 protected boolean cachedHttpClient = false; 52 53 protected boolean inCache; 54 55 // Http requests we send 56 MessageHeader requests; 57 58 // Http data we send with the headers 59 PosterOutputStream poster = null; 60 61 // true if we are in streaming mode (fixed length or chunked) 62 boolean streaming; 63 64 // if we've had one io error 65 boolean failedOnce = false; 66 67 /** Response code for CONTINUE */ 68 private boolean ignoreContinue = true; 69 private static final int HTTP_CONTINUE = 100; 70 71 /** Default port number for http daemons. REMIND: make these private */ 72 static final int httpPortNumber = 80; 73 74 /** return default port number (subclasses may override) */ 75 protected int getDefaultPort () { return httpPortNumber; } 76 77 private static int getDefaultPort(String proto) { 78 if ("http".equalsIgnoreCase(proto)) 79 return 80; 80 if ("https".equalsIgnoreCase(proto)) 81 return 443; 82 return -1; 83 } 84 85 /* All proxying (generic as well as instance-specific) may be 86 * disabled through use of this flag 87 */ 88 protected boolean proxyDisabled; 89 90 // are we using proxy in this instance? 91 public boolean usingProxy = false; 92 // target host, port for the URL 93 protected String host; 94 protected int port; 95 96 /* where we cache currently open, persistent connections */ 97 protected static KeepAliveCache kac = new KeepAliveCache(); 98 99 private static boolean keepAliveProp = true; 100 101 // retryPostProp is true by default so as to preserve behavior 102 // from previous releases. 103 private static boolean retryPostProp = true; 104 105 /* Value of the system property jdk.ntlm.cache; 106 if false, then NTLM connections will not be cached. 107 The default value is 'true'. */ 108 private static final boolean cacheNTLMProp; 109 /* Value of the system property jdk.spnego.cache; 110 if false, then connections authentified using the Negotiate/Kerberos 111 scheme will not be cached. 112 The default value is 'true'. */ 113 private static final boolean cacheSPNEGOProp; 114 115 volatile boolean keepingAlive; /* this is a keep-alive connection */ 116 volatile boolean disableKeepAlive;/* keep-alive has been disabled for this 117 connection - this will be used when 118 recomputing the value of keepingAlive */ 119 int keepAliveConnections = -1; /* number of keep-alives left */ 120 121 /**Idle timeout value, in milliseconds. Zero means infinity, 122 * iff keepingAlive=true. 123 * Unfortunately, we can't always believe this one. If I'm connected 124 * through a Netscape proxy to a server that sent me a keep-alive 125 * time of 15 sec, the proxy unilaterally terminates my connection 126 * after 5 sec. So we have to hard code our effective timeout to 127 * 4 sec for the case where we're using a proxy. *SIGH* 128 */ 129 int keepAliveTimeout = 0; 130 131 /** whether the response is to be cached */ 132 private CacheRequest cacheRequest = null; 133 134 /** Url being fetched. */ 135 protected URL url; 136 137 /* if set, the client will be reused and must not be put in cache */ 138 public boolean reuse = false; 139 140 // Traffic capture tool, if configured. See HttpCapture class for info 141 private HttpCapture capture = null; 142 143 private static final PlatformLogger logger = HttpURLConnection.getHttpLogger(); 144 private static void logFinest(String msg) { 145 if (logger.isLoggable(PlatformLogger.Level.FINEST)) { 146 logger.finest(msg); 147 } 148 } 149 150 protected volatile String authenticatorKey; 151 152 /** 153 * A NOP method kept for backwards binary compatibility 154 * @deprecated -- system properties are no longer cached. 155 */ 156 @Deprecated 157 public static synchronized void resetProperties() { 158 } 159 160 int getKeepAliveTimeout() { 161 return keepAliveTimeout; 162 } 163 164 static { 165 Properties props = GetPropertyAction.privilegedGetProperties(); 166 String keepAlive = props.getProperty("http.keepAlive"); 167 String retryPost = props.getProperty("sun.net.http.retryPost"); 168 String cacheNTLM = props.getProperty("jdk.ntlm.cache"); 169 String cacheSPNEGO = props.getProperty("jdk.spnego.cache"); 170 171 if (keepAlive != null) { 172 keepAliveProp = Boolean.parseBoolean(keepAlive); 173 } else { 174 keepAliveProp = true; 175 } 176 177 if (retryPost != null) { 178 retryPostProp = Boolean.parseBoolean(retryPost); 179 } else { 180 retryPostProp = true; 181 } 182 183 if (cacheNTLM != null) { 184 cacheNTLMProp = Boolean.parseBoolean(cacheNTLM); 185 } else { 186 cacheNTLMProp = true; 187 } 188 189 if (cacheSPNEGO != null) { 190 cacheSPNEGOProp = Boolean.parseBoolean(cacheSPNEGO); 191 } else { 192 cacheSPNEGOProp = true; 193 } 194 } 195 196 /** 197 * @return true iff http keep alive is set (i.e. enabled). Defaults 198 * to true if the system property http.keepAlive isn't set. 199 */ 200 public boolean getHttpKeepAliveSet() { 201 return keepAliveProp; 202 } 203 204 205 protected HttpClient() { 206 } 207 208 private HttpClient(URL url) 209 throws IOException { 210 this(url, (String)null, -1, false); 211 } 212 213 protected HttpClient(URL url, 214 boolean proxyDisabled) throws IOException { 215 this(url, null, -1, proxyDisabled); 216 } 217 218 /* This package-only CTOR should only be used for FTP piggy-backed on HTTP 219 * URL's that use this won't take advantage of keep-alive. 220 * Additionally, this constructor may be used as a last resort when the 221 * first HttpClient gotten through New() failed (probably b/c of a 222 * Keep-Alive mismatch). 223 * 224 * XXX That documentation is wrong ... it's not package-private any more 225 */ 226 public HttpClient(URL url, String proxyHost, int proxyPort) 227 throws IOException { 228 this(url, proxyHost, proxyPort, false); 229 } 230 231 protected HttpClient(URL url, Proxy p, int to) throws IOException { 232 proxy = (p == null) ? Proxy.NO_PROXY : p; 233 this.host = url.getHost(); 234 this.url = url; 235 port = url.getPort(); 236 if (port == -1) { 237 port = getDefaultPort(); 238 } 239 setConnectTimeout(to); 240 241 capture = HttpCapture.getCapture(url); 242 openServer(); 243 } 244 245 protected static Proxy newHttpProxy(String proxyHost, int proxyPort, 246 String proto) { 247 if (proxyHost == null || proto == null) 248 return Proxy.NO_PROXY; 249 int pport = proxyPort < 0 ? getDefaultPort(proto) : proxyPort; 250 InetSocketAddress saddr = InetSocketAddress.createUnresolved(proxyHost, pport); 251 return new Proxy(Proxy.Type.HTTP, saddr); 252 } 253 254 /* 255 * This constructor gives "ultimate" flexibility, including the ability 256 * to bypass implicit proxying. Sometimes we need to be using tunneling 257 * (transport or network level) instead of proxying (application level), 258 * for example when we don't want the application level data to become 259 * visible to third parties. 260 * 261 * @param url the URL to which we're connecting 262 * @param proxy proxy to use for this URL (e.g. forwarding) 263 * @param proxyPort proxy port to use for this URL 264 * @param proxyDisabled true to disable default proxying 265 */ 266 private HttpClient(URL url, String proxyHost, int proxyPort, 267 boolean proxyDisabled) 268 throws IOException { 269 this(url, proxyDisabled ? Proxy.NO_PROXY : 270 newHttpProxy(proxyHost, proxyPort, "http"), -1); 271 } 272 273 public HttpClient(URL url, String proxyHost, int proxyPort, 274 boolean proxyDisabled, int to) 275 throws IOException { 276 this(url, proxyDisabled ? Proxy.NO_PROXY : 277 newHttpProxy(proxyHost, proxyPort, "http"), to); 278 } 279 280 /* This class has no public constructor for HTTP. This method is used to 281 * get an HttpClient to the specified URL. If there's currently an 282 * active HttpClient to that server/port, you'll get that one. 283 */ 284 public static HttpClient New(URL url) 285 throws IOException { 286 return HttpClient.New(url, Proxy.NO_PROXY, -1, true, null); 287 } 288 289 public static HttpClient New(URL url, boolean useCache) 290 throws IOException { 291 return HttpClient.New(url, Proxy.NO_PROXY, -1, useCache, null); 292 } 293 294 public static HttpClient New(URL url, Proxy p, int to, boolean useCache, 295 HttpURLConnection httpuc) throws IOException 296 { 297 if (p == null) { 298 p = Proxy.NO_PROXY; 299 } 300 HttpClient ret = null; 301 /* see if one's already around */ 302 if (useCache) { 303 ret = kac.get(url, null); 304 if (ret != null && httpuc != null && 305 httpuc.streaming() && 306 httpuc.getRequestMethod() == "POST") { 307 if (!ret.available()) { 308 ret.inCache = false; 309 ret.closeServer(); 310 ret = null; 311 } 312 } 313 if (ret != null) { 314 String ak = httpuc == null ? AuthenticatorKeys.DEFAULT 315 : httpuc.getAuthenticatorKey(); 316 boolean compatible = Objects.equals(ret.proxy, p) 317 && Objects.equals(ret.getAuthenticatorKey(), ak); 318 if (compatible) { 319 synchronized (ret) { 320 ret.cachedHttpClient = true; 321 assert ret.inCache; 322 ret.inCache = false; 323 if (httpuc != null && ret.needsTunneling()) 324 httpuc.setTunnelState(TUNNELING); 325 logFinest("KeepAlive stream retrieved from the cache, " + ret); 326 } 327 } else { 328 // We cannot return this connection to the cache as it's 329 // KeepAliveTimeout will get reset. We simply close the connection. 330 // This should be fine as it is very rare that a connection 331 // to the same host will not use the same proxy. 332 synchronized(ret) { 333 ret.inCache = false; 334 ret.closeServer(); 335 } 336 ret = null; 337 } 338 } 339 } 340 if (ret == null) { 341 ret = new HttpClient(url, p, to); 342 if (httpuc != null) { 343 ret.authenticatorKey = httpuc.getAuthenticatorKey(); 344 } 345 } else { 346 SecurityManager security = System.getSecurityManager(); 347 if (security != null) { 348 if (ret.proxy == Proxy.NO_PROXY || ret.proxy == null) { 349 security.checkConnect(InetAddress.getByName(url.getHost()).getHostAddress(), url.getPort()); 350 } else { 351 security.checkConnect(url.getHost(), url.getPort()); 352 } 353 } 354 ret.url = url; 355 } 356 return ret; 357 } 358 359 public static HttpClient New(URL url, Proxy p, int to, 360 HttpURLConnection httpuc) throws IOException 361 { 362 return New(url, p, to, true, httpuc); 363 } 364 365 public static HttpClient New(URL url, String proxyHost, int proxyPort, 366 boolean useCache) 367 throws IOException { 368 return New(url, newHttpProxy(proxyHost, proxyPort, "http"), 369 -1, useCache, null); 370 } 371 372 public static HttpClient New(URL url, String proxyHost, int proxyPort, 373 boolean useCache, int to, 374 HttpURLConnection httpuc) 375 throws IOException { 376 return New(url, newHttpProxy(proxyHost, proxyPort, "http"), 377 to, useCache, httpuc); 378 } 379 380 public final String getAuthenticatorKey() { 381 String k = authenticatorKey; 382 if (k == null) return AuthenticatorKeys.DEFAULT; 383 return k; 384 } 385 386 /* return it to the cache as still usable, if: 387 * 1) It's keeping alive, AND 388 * 2) It still has some connections left, AND 389 * 3) It hasn't had a error (PrintStream.checkError()) 390 * 4) It hasn't timed out 391 * 392 * If this client is not keepingAlive, it should have been 393 * removed from the cache in the parseHeaders() method. 394 */ 395 396 public void finished() { 397 if (reuse) /* will be reused */ 398 return; 399 keepAliveConnections--; 400 poster = null; 401 if (keepAliveConnections > 0 && isKeepingAlive() && 402 !(serverOutput.checkError())) { 403 /* This connection is keepingAlive && still valid. 404 * Return it to the cache. 405 */ 406 putInKeepAliveCache(); 407 } else { 408 closeServer(); 409 } 410 } 411 412 protected synchronized boolean available() { 413 boolean available = true; 414 int old = -1; 415 416 try { 417 try { 418 old = serverSocket.getSoTimeout(); 419 serverSocket.setSoTimeout(1); 420 BufferedInputStream tmpbuf = 421 new BufferedInputStream(serverSocket.getInputStream()); 422 int r = tmpbuf.read(); 423 if (r == -1) { 424 logFinest("HttpClient.available(): " + 425 "read returned -1: not available"); 426 available = false; 427 } 428 } catch (SocketTimeoutException e) { 429 logFinest("HttpClient.available(): " + 430 "SocketTimeout: its available"); 431 } finally { 432 if (old != -1) 433 serverSocket.setSoTimeout(old); 434 } 435 } catch (IOException e) { 436 logFinest("HttpClient.available(): " + 437 "SocketException: not available"); 438 available = false; 439 } 440 return available; 441 } 442 443 protected synchronized void putInKeepAliveCache() { 444 if (inCache) { 445 assert false : "Duplicate put to keep alive cache"; 446 return; 447 } 448 inCache = true; 449 kac.put(url, null, this); 450 } 451 452 protected synchronized boolean isInKeepAliveCache() { 453 return inCache; 454 } 455 456 /* 457 * Close an idle connection to this URL (if it exists in the 458 * cache). 459 */ 460 public void closeIdleConnection() { 461 HttpClient http = kac.get(url, null); 462 if (http != null) { 463 http.closeServer(); 464 } 465 } 466 467 /* We're very particular here about what our InputStream to the server 468 * looks like for reasons that are apparent if you can decipher the 469 * method parseHTTP(). That's why this method is overidden from the 470 * superclass. 471 */ 472 @Override 473 public void openServer(String server, int port) throws IOException { 474 serverSocket = doConnect(server, port); 475 try { 476 OutputStream out = serverSocket.getOutputStream(); 477 if (capture != null) { 478 out = new HttpCaptureOutputStream(out, capture); 479 } 480 serverOutput = new PrintStream( 481 new BufferedOutputStream(out), 482 false, encoding); 483 } catch (UnsupportedEncodingException e) { 484 throw new InternalError(encoding+" encoding not found", e); 485 } 486 serverSocket.setTcpNoDelay(true); 487 } 488 489 /* 490 * Returns true if the http request should be tunneled through proxy. 491 * An example where this is the case is Https. 492 */ 493 public boolean needsTunneling() { 494 return false; 495 } 496 497 /* 498 * Returns true if this httpclient is from cache 499 */ 500 public synchronized boolean isCachedConnection() { 501 return cachedHttpClient; 502 } 503 504 /* 505 * Finish any work left after the socket connection is 506 * established. In the normal http case, it's a NO-OP. Subclass 507 * may need to override this. An example is Https, where for 508 * direct connection to the origin server, ssl handshake needs to 509 * be done; for proxy tunneling, the socket needs to be converted 510 * into an SSL socket before ssl handshake can take place. 511 */ 512 public void afterConnect() throws IOException, UnknownHostException { 513 // NO-OP. Needs to be overwritten by HttpsClient 514 } 515 516 /* 517 * call openServer in a privileged block 518 */ 519 private synchronized void privilegedOpenServer(final InetSocketAddress server) 520 throws IOException 521 { 522 try { 523 java.security.AccessController.doPrivileged( 524 new java.security.PrivilegedExceptionAction<>() { 525 public Void run() throws IOException { 526 openServer(server.getHostString(), server.getPort()); 527 return null; 528 } 529 }); 530 } catch (java.security.PrivilegedActionException pae) { 531 throw (IOException) pae.getException(); 532 } 533 } 534 535 /* 536 * call super.openServer 537 */ 538 private void superOpenServer(final String proxyHost, 539 final int proxyPort) 540 throws IOException, UnknownHostException 541 { 542 super.openServer(proxyHost, proxyPort); 543 } 544 545 /* 546 */ 547 protected synchronized void openServer() throws IOException { 548 549 SecurityManager security = System.getSecurityManager(); 550 551 if (security != null) { 552 security.checkConnect(host, port); 553 } 554 555 if (keepingAlive) { // already opened 556 return; 557 } 558 559 if (url.getProtocol().equals("http") || 560 url.getProtocol().equals("https") ) { 561 562 if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) { 563 sun.net.www.URLConnection.setProxiedHost(host); 564 privilegedOpenServer((InetSocketAddress) proxy.address()); 565 usingProxy = true; 566 return; 567 } else { 568 // make direct connection 569 openServer(host, port); 570 usingProxy = false; 571 return; 572 } 573 574 } else { 575 /* we're opening some other kind of url, most likely an 576 * ftp url. 577 */ 578 if ((proxy != null) && (proxy.type() == Proxy.Type.HTTP)) { 579 sun.net.www.URLConnection.setProxiedHost(host); 580 privilegedOpenServer((InetSocketAddress) proxy.address()); 581 usingProxy = true; 582 return; 583 } else { 584 // make direct connection 585 super.openServer(host, port); 586 usingProxy = false; 587 return; 588 } 589 } 590 } 591 592 public String getURLFile() throws IOException { 593 594 String fileName; 595 596 /** 597 * proxyDisabled is set by subclass HttpsClient! 598 */ 599 if (usingProxy && !proxyDisabled) { 600 // Do not use URLStreamHandler.toExternalForm as the fragment 601 // should not be part of the RequestURI. It should be an 602 // absolute URI which does not have a fragment part. 603 StringBuilder result = new StringBuilder(128); 604 result.append(url.getProtocol()); 605 result.append(":"); 606 if (url.getAuthority() != null && !url.getAuthority().isEmpty()) { 607 result.append("//"); 608 result.append(url.getAuthority()); 609 } 610 if (url.getPath() != null) { 611 result.append(url.getPath()); 612 } 613 if (url.getQuery() != null) { 614 result.append('?'); 615 result.append(url.getQuery()); 616 } 617 618 fileName = result.toString(); 619 } else { 620 fileName = url.getFile(); 621 622 if ((fileName == null) || (fileName.isEmpty())) { 623 fileName = "/"; 624 } else if (fileName.charAt(0) == '?') { 625 /* HTTP/1.1 spec says in 5.1.2. about Request-URI: 626 * "Note that the absolute path cannot be empty; if 627 * none is present in the original URI, it MUST be 628 * given as "/" (the server root)." So if the file 629 * name here has only a query string, the path is 630 * empty and we also have to add a "/". 631 */ 632 fileName = "/" + fileName; 633 } 634 } 635 636 if (fileName.indexOf('\n') == -1) 637 return fileName; 638 else 639 throw new java.net.MalformedURLException("Illegal character in URL"); 640 } 641 642 /** 643 * @deprecated 644 */ 645 @Deprecated 646 public void writeRequests(MessageHeader head) { 647 requests = head; 648 requests.print(serverOutput); 649 serverOutput.flush(); 650 } 651 652 public void writeRequests(MessageHeader head, 653 PosterOutputStream pos) throws IOException { 654 requests = head; 655 requests.print(serverOutput); 656 poster = pos; 657 if (poster != null) 658 poster.writeTo(serverOutput); 659 serverOutput.flush(); 660 } 661 662 public void writeRequests(MessageHeader head, 663 PosterOutputStream pos, 664 boolean streaming) throws IOException { 665 this.streaming = streaming; 666 writeRequests(head, pos); 667 } 668 669 /** Parse the first line of the HTTP request. It usually looks 670 something like: {@literal "HTTP/1.0 <number> comment\r\n"}. */ 671 672 public boolean parseHTTP(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc) 673 throws IOException { 674 /* If "HTTP/*" is found in the beginning, return true. Let 675 * HttpURLConnection parse the mime header itself. 676 * 677 * If this isn't valid HTTP, then we don't try to parse a header 678 * out of the beginning of the response into the responses, 679 * and instead just queue up the output stream to it's very beginning. 680 * This seems most reasonable, and is what the NN browser does. 681 */ 682 683 try { 684 serverInput = serverSocket.getInputStream(); 685 if (capture != null) { 686 serverInput = new HttpCaptureInputStream(serverInput, capture); 687 } 688 serverInput = new BufferedInputStream(serverInput); 689 return (parseHTTPHeader(responses, pi, httpuc)); 690 } catch (SocketTimeoutException stex) { 691 // We don't want to retry the request when the app. sets a timeout 692 // but don't close the server if timeout while waiting for 100-continue 693 if (ignoreContinue) { 694 closeServer(); 695 } 696 throw stex; 697 } catch (IOException e) { 698 closeServer(); 699 cachedHttpClient = false; 700 if (!failedOnce && requests != null) { 701 failedOnce = true; 702 if (getRequestMethod().equals("CONNECT") 703 || streaming 704 || (httpuc.getRequestMethod().equals("POST") 705 && !retryPostProp)) { 706 // do not retry the request 707 } else { 708 // try once more 709 openServer(); 710 if (needsTunneling()) { 711 MessageHeader origRequests = requests; 712 httpuc.doTunneling(); 713 requests = origRequests; 714 } 715 afterConnect(); 716 writeRequests(requests, poster); 717 return parseHTTP(responses, pi, httpuc); 718 } 719 } 720 throw e; 721 } 722 723 } 724 725 private boolean parseHTTPHeader(MessageHeader responses, ProgressSource pi, HttpURLConnection httpuc) 726 throws IOException { 727 /* If "HTTP/*" is found in the beginning, return true. Let 728 * HttpURLConnection parse the mime header itself. 729 * 730 * If this isn't valid HTTP, then we don't try to parse a header 731 * out of the beginning of the response into the responses, 732 * and instead just queue up the output stream to it's very beginning. 733 * This seems most reasonable, and is what the NN browser does. 734 */ 735 736 keepAliveConnections = -1; 737 keepAliveTimeout = 0; 738 739 boolean ret = false; 740 byte[] b = new byte[8]; 741 742 try { 743 int nread = 0; 744 serverInput.mark(10); 745 while (nread < 8) { 746 int r = serverInput.read(b, nread, 8 - nread); 747 if (r < 0) { 748 break; 749 } 750 nread += r; 751 } 752 String keep=null; 753 String authenticate=null; 754 ret = b[0] == 'H' && b[1] == 'T' 755 && b[2] == 'T' && b[3] == 'P' && b[4] == '/' && 756 b[5] == '1' && b[6] == '.'; 757 serverInput.reset(); 758 if (ret) { // is valid HTTP - response started w/ "HTTP/1." 759 responses.parseHeader(serverInput); 760 761 // we've finished parsing http headers 762 // check if there are any applicable cookies to set (in cache) 763 CookieHandler cookieHandler = httpuc.getCookieHandler(); 764 if (cookieHandler != null) { 765 URI uri = ParseUtil.toURI(url); 766 // NOTE: That cast from Map shouldn't be necessary but 767 // a bug in javac is triggered under certain circumstances 768 // So we do put the cast in as a workaround until 769 // it is resolved. 770 if (uri != null) 771 cookieHandler.put(uri, responses.getHeaders()); 772 } 773 774 /* decide if we're keeping alive: 775 * This is a bit tricky. There's a spec, but most current 776 * servers (10/1/96) that support this differ in dialects. 777 * If the server/client misunderstand each other, the 778 * protocol should fall back onto HTTP/1.0, no keep-alive. 779 */ 780 if (usingProxy) { // not likely a proxy will return this 781 keep = responses.findValue("Proxy-Connection"); 782 authenticate = responses.findValue("Proxy-Authenticate"); 783 } 784 if (keep == null) { 785 keep = responses.findValue("Connection"); 786 authenticate = responses.findValue("WWW-Authenticate"); 787 } 788 789 // 'disableKeepAlive' starts with the value false. 790 // It can transition from false to true, but once true 791 // it stays true. 792 // If cacheNTLMProp is false, and disableKeepAlive is false, 793 // then we need to examine the response headers to figure out 794 // whether we are doing NTLM authentication. If we do NTLM, 795 // and cacheNTLMProp is false, than we can't keep this connection 796 // alive: we will switch disableKeepAlive to true. 797 boolean canKeepAlive = !disableKeepAlive; 798 if (canKeepAlive && (cacheNTLMProp == false || cacheSPNEGOProp == false) 799 && authenticate != null) { 800 authenticate = authenticate.toLowerCase(Locale.US); 801 if (cacheNTLMProp == false) { 802 canKeepAlive &= !authenticate.startsWith("ntlm "); 803 } 804 if (cacheSPNEGOProp == false) { 805 canKeepAlive &= !authenticate.startsWith("negotiate "); 806 canKeepAlive &= !authenticate.startsWith("kerberos "); 807 } 808 } 809 disableKeepAlive |= !canKeepAlive; 810 811 if (keep != null && keep.toLowerCase(Locale.US).equals("keep-alive")) { 812 /* some servers, notably Apache1.1, send something like: 813 * "Keep-Alive: timeout=15, max=1" which we should respect. 814 */ 815 if (disableKeepAlive) { 816 keepAliveConnections = 1; 817 } else { 818 HeaderParser p = new HeaderParser( 819 responses.findValue("Keep-Alive")); 820 /* default should be larger in case of proxy */ 821 keepAliveConnections = p.findInt("max", usingProxy?50:5); 822 keepAliveTimeout = p.findInt("timeout", usingProxy?60:5); 823 } 824 } else if (b[7] != '0') { 825 /* 826 * We're talking 1.1 or later. Keep persistent until 827 * the server says to close. 828 */ 829 if (keep != null || disableKeepAlive) { 830 /* 831 * The only Connection token we understand is close. 832 * Paranoia: if there is any Connection header then 833 * treat as non-persistent. 834 */ 835 keepAliveConnections = 1; 836 } else { 837 keepAliveConnections = 5; 838 } 839 } 840 } else if (nread != 8) { 841 if (!failedOnce && requests != null) { 842 failedOnce = true; 843 if (getRequestMethod().equals("CONNECT") 844 || streaming 845 || (httpuc.getRequestMethod().equals("POST") 846 && !retryPostProp)) { 847 // do not retry the request 848 } else { 849 closeServer(); 850 cachedHttpClient = false; 851 openServer(); 852 if (needsTunneling()) { 853 MessageHeader origRequests = requests; 854 httpuc.doTunneling(); 855 requests = origRequests; 856 } 857 afterConnect(); 858 writeRequests(requests, poster); 859 return parseHTTP(responses, pi, httpuc); 860 } 861 } 862 throw new SocketException("Unexpected end of file from server"); 863 } else { 864 // we can't vouche for what this is.... 865 responses.set("Content-type", "unknown/unknown"); 866 } 867 } catch (IOException e) { 868 throw e; 869 } 870 871 int code = -1; 872 try { 873 String resp; 874 resp = responses.getValue(0); 875 /* should have no leading/trailing LWS 876 * expedite the typical case by assuming it has 877 * form "HTTP/1.x <WS> 2XX <mumble>" 878 */ 879 int ind; 880 ind = resp.indexOf(' '); 881 while(resp.charAt(ind) == ' ') 882 ind++; 883 code = Integer.parseInt(resp, ind, ind + 3, 10); 884 } catch (Exception e) {} 885 886 if (code == HTTP_CONTINUE && ignoreContinue) { 887 responses.reset(); 888 return parseHTTPHeader(responses, pi, httpuc); 889 } 890 891 long cl = -1; 892 893 /* 894 * Set things up to parse the entity body of the reply. 895 * We should be smarter about avoid pointless work when 896 * the HTTP method and response code indicate there will be 897 * no entity body to parse. 898 */ 899 String te = responses.findValue("Transfer-Encoding"); 900 if (te != null && te.equalsIgnoreCase("chunked")) { 901 serverInput = new ChunkedInputStream(serverInput, this, responses); 902 903 /* 904 * If keep alive not specified then close after the stream 905 * has completed. 906 */ 907 if (keepAliveConnections <= 1) { 908 keepAliveConnections = 1; 909 keepingAlive = false; 910 } else { 911 keepingAlive = !disableKeepAlive; 912 } 913 failedOnce = false; 914 } else { 915 916 /* 917 * If it's a keep alive connection then we will keep 918 * (alive if :- 919 * 1. content-length is specified, or 920 * 2. "Not-Modified" or "No-Content" responses - RFC 2616 states that 921 * 204 or 304 response must not include a message body. 922 */ 923 String cls = responses.findValue("content-length"); 924 if (cls != null) { 925 try { 926 cl = Long.parseLong(cls); 927 } catch (NumberFormatException e) { 928 cl = -1; 929 } 930 } 931 String requestLine = requests.getKey(0); 932 933 if ((requestLine != null && 934 (requestLine.startsWith("HEAD"))) || 935 code == HttpURLConnection.HTTP_NOT_MODIFIED || 936 code == HttpURLConnection.HTTP_NO_CONTENT) { 937 cl = 0; 938 } 939 940 if (keepAliveConnections > 1 && 941 (cl >= 0 || 942 code == HttpURLConnection.HTTP_NOT_MODIFIED || 943 code == HttpURLConnection.HTTP_NO_CONTENT)) { 944 keepingAlive = !disableKeepAlive; 945 failedOnce = false; 946 } else if (keepingAlive) { 947 /* Previously we were keeping alive, and now we're not. Remove 948 * this from the cache (but only here, once) - otherwise we get 949 * multiple removes and the cache count gets messed up. 950 */ 951 keepingAlive=false; 952 } 953 } 954 955 /* wrap a KeepAliveStream/MeteredStream around it if appropriate */ 956 957 if (cl > 0) { 958 // In this case, content length is well known, so it is okay 959 // to wrap the input stream with KeepAliveStream/MeteredStream. 960 961 if (pi != null) { 962 // Progress monitor is enabled 963 pi.setContentType(responses.findValue("content-type")); 964 } 965 966 // If disableKeepAlive == true, the client will not be returned 967 // to the cache. But we still need to use a keepalive stream to 968 // allow the multi-message authentication exchange on the connection 969 boolean useKeepAliveStream = isKeepingAlive() || disableKeepAlive; 970 if (useKeepAliveStream) { 971 // Wrap KeepAliveStream if keep alive is enabled. 972 logFinest("KeepAlive stream used: " + url); 973 serverInput = new KeepAliveStream(serverInput, pi, cl, this); 974 failedOnce = false; 975 } 976 else { 977 serverInput = new MeteredStream(serverInput, pi, cl); 978 } 979 } 980 else if (cl == -1) { 981 // In this case, content length is unknown - the input 982 // stream would simply be a regular InputStream or 983 // ChunkedInputStream. 984 985 if (pi != null) { 986 // Progress monitoring is enabled. 987 988 pi.setContentType(responses.findValue("content-type")); 989 990 // Wrap MeteredStream for tracking indeterministic 991 // progress, even if the input stream is ChunkedInputStream. 992 serverInput = new MeteredStream(serverInput, pi, cl); 993 } 994 else { 995 // Progress monitoring is disabled, and there is no 996 // need to wrap an unknown length input stream. 997 998 // ** This is an no-op ** 999 } 1000 } 1001 else { 1002 if (pi != null) 1003 pi.finishTracking(); 1004 } 1005 1006 return ret; 1007 } 1008 1009 public synchronized InputStream getInputStream() { 1010 return serverInput; 1011 } 1012 1013 public OutputStream getOutputStream() { 1014 return serverOutput; 1015 } 1016 1017 @Override 1018 public String toString() { 1019 return getClass().getName()+"("+url+")"; 1020 } 1021 1022 public final boolean isKeepingAlive() { 1023 return getHttpKeepAliveSet() && keepingAlive; 1024 } 1025 1026 public void setCacheRequest(CacheRequest cacheRequest) { 1027 this.cacheRequest = cacheRequest; 1028 } 1029 1030 CacheRequest getCacheRequest() { 1031 return cacheRequest; 1032 } 1033 1034 String getRequestMethod() { 1035 if (requests != null) { 1036 String requestLine = requests.getKey(0); 1037 if (requestLine != null) { 1038 return requestLine.split("\\s+")[0]; 1039 } 1040 } 1041 return ""; 1042 } 1043 1044 public void setDoNotRetry(boolean value) { 1045 // failedOnce is used to determine if a request should be retried. 1046 failedOnce = value; 1047 } 1048 1049 public void setIgnoreContinue(boolean value) { 1050 ignoreContinue = value; 1051 } 1052 1053 /* Use only on connections in error. */ 1054 @Override 1055 public void closeServer() { 1056 try { 1057 keepingAlive = false; 1058 serverSocket.close(); 1059 } catch (Exception e) {} 1060 } 1061 1062 /** 1063 * @return the proxy host being used for this client, or null 1064 * if we're not going through a proxy 1065 */ 1066 public String getProxyHostUsed() { 1067 if (!usingProxy) { 1068 return null; 1069 } else { 1070 return ((InetSocketAddress)proxy.address()).getHostString(); 1071 } 1072 } 1073 1074 /** 1075 * @return the proxy port being used for this client. Meaningless 1076 * if getProxyHostUsed() gives null. 1077 */ 1078 public int getProxyPortUsed() { 1079 if (usingProxy) 1080 return ((InetSocketAddress)proxy.address()).getPort(); 1081 return -1; 1082 } 1083 }