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