Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java
41171 views
1
/*
2
* Copyright (c) 2015, 2019, 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 jdk.internal.net.http;
27
28
import java.io.IOException;
29
import java.net.URI;
30
import java.net.http.HttpClient;
31
import java.nio.ByteBuffer;
32
import java.util.ArrayList;
33
import java.util.List;
34
import java.util.Map;
35
import java.net.InetSocketAddress;
36
import java.util.Objects;
37
import java.util.concurrent.Flow;
38
import java.util.function.BiPredicate;
39
import java.net.http.HttpHeaders;
40
import java.net.http.HttpRequest;
41
import jdk.internal.net.http.Http1Exchange.Http1BodySubscriber;
42
import jdk.internal.net.http.common.HttpHeadersBuilder;
43
import jdk.internal.net.http.common.Log;
44
import jdk.internal.net.http.common.Logger;
45
import jdk.internal.net.http.common.Utils;
46
47
import static java.lang.String.format;
48
import static java.nio.charset.StandardCharsets.US_ASCII;
49
50
/**
51
* An HTTP/1.1 request.
52
*/
53
class Http1Request {
54
55
private static final String COOKIE_HEADER = "Cookie";
56
private static final BiPredicate<String,String> NOCOOKIES =
57
(k,v) -> !COOKIE_HEADER.equalsIgnoreCase(k);
58
59
private final HttpRequestImpl request;
60
private final Http1Exchange<?> http1Exchange;
61
private final HttpConnection connection;
62
private final HttpRequest.BodyPublisher requestPublisher;
63
private volatile HttpHeaders userHeaders;
64
private final HttpHeadersBuilder systemHeadersBuilder;
65
private volatile boolean streaming;
66
private volatile long contentLength;
67
68
Http1Request(HttpRequestImpl request,
69
Http1Exchange<?> http1Exchange)
70
throws IOException
71
{
72
this.request = request;
73
this.http1Exchange = http1Exchange;
74
this.connection = http1Exchange.connection();
75
this.requestPublisher = request.requestPublisher; // may be null
76
this.userHeaders = request.getUserHeaders();
77
this.systemHeadersBuilder = request.getSystemHeadersBuilder();
78
}
79
80
private void logHeaders(String completeHeaders) {
81
if (Log.headers()) {
82
//StringBuilder sb = new StringBuilder(256);
83
//sb.append("REQUEST HEADERS:\n");
84
//Log.dumpHeaders(sb, " ", systemHeaders);
85
//Log.dumpHeaders(sb, " ", userHeaders);
86
//Log.logHeaders(sb.toString());
87
88
String s = completeHeaders.replaceAll("\r\n", "\n");
89
if (s.endsWith("\n\n")) s = s.substring(0, s.length() - 2);
90
Log.logHeaders("REQUEST HEADERS:\n{0}\n", s);
91
}
92
}
93
94
95
public void collectHeaders0(StringBuilder sb) {
96
BiPredicate<String,String> filter =
97
connection.headerFilter(request);
98
99
// Filter out 'Cookie:' headers, we will collect them at the end.
100
BiPredicate<String,String> nocookies = NOCOOKIES.and(filter);
101
102
HttpHeaders systemHeaders = systemHeadersBuilder.build();
103
HttpClient client = http1Exchange.client();
104
105
// Filter overridable headers from userHeaders
106
userHeaders = HttpHeaders.of(userHeaders.map(),
107
connection.contextRestricted(request, client));
108
109
final HttpHeaders uh = userHeaders;
110
111
// Filter any headers from systemHeaders that are set in userHeaders
112
systemHeaders = HttpHeaders.of(systemHeaders.map(), (k,v) -> uh.firstValue(k).isEmpty());
113
114
// If we're sending this request through a tunnel,
115
// then don't send any preemptive proxy-* headers that
116
// the authentication filter may have saved in its
117
// cache.
118
collectHeaders1(sb, systemHeaders, nocookies);
119
120
// If we're sending this request through a tunnel,
121
// don't send any user-supplied proxy-* headers
122
// to the target server.
123
collectHeaders1(sb, userHeaders, nocookies);
124
125
// Gather all 'Cookie:' headers and concatenate their
126
// values in a single line.
127
collectCookies(sb, systemHeaders, userHeaders);
128
129
// terminate headers
130
sb.append('\r').append('\n');
131
}
132
133
// Concatenate any 'Cookie:' header in a single line, as mandated
134
// by RFC 6265, section 5.4:
135
//
136
// <<When the user agent generates an HTTP request, the user agent MUST
137
// NOT attach more than one Cookie header field.>>
138
//
139
// This constraint is relaxed for the HTTP/2 protocol, which
140
// explicitly allows sending multiple Cookie header fields.
141
// RFC 7540 section 8.1.2.5:
142
//
143
// <<To allow for better compression efficiency, the Cookie header
144
// field MAY be split into separate header fields, each with one or
145
// more cookie-pairs.>>
146
//
147
// This method will therefore concatenate multiple Cookie header field
148
// values into a single field, in a similar way than was implemented in
149
// the legacy HttpURLConnection.
150
//
151
// Note that at this point this method performs no further validation
152
// on the actual field-values, except to check that they do not contain
153
// any illegal character for header field values.
154
//
155
private void collectCookies(StringBuilder sb,
156
HttpHeaders system,
157
HttpHeaders user) {
158
List<String> systemList = system.allValues(COOKIE_HEADER);
159
List<String> userList = user.allValues(COOKIE_HEADER);
160
boolean found = false;
161
if (systemList != null) {
162
for (String cookie : systemList) {
163
if (!found) {
164
found = true;
165
sb.append(COOKIE_HEADER).append(':').append(' ');
166
} else {
167
sb.append(';').append(' ');
168
}
169
sb.append(cookie);
170
}
171
}
172
if (userList != null) {
173
for (String cookie : userList) {
174
if (!found) {
175
found = true;
176
sb.append(COOKIE_HEADER).append(':').append(' ');
177
} else {
178
sb.append(';').append(' ');
179
}
180
sb.append(cookie);
181
}
182
}
183
if (found) sb.append('\r').append('\n');
184
}
185
186
private void collectHeaders1(StringBuilder sb,
187
HttpHeaders headers,
188
BiPredicate<String,String> filter) {
189
for (Map.Entry<String,List<String>> entry : headers.map().entrySet()) {
190
String key = entry.getKey();
191
List<String> values = entry.getValue();
192
for (String value : values) {
193
if (!filter.test(key, value))
194
continue;
195
sb.append(key).append(':').append(' ')
196
.append(value)
197
.append('\r').append('\n');
198
}
199
}
200
}
201
202
private String getPathAndQuery(URI uri) {
203
String path = uri.getRawPath();
204
String query = uri.getRawQuery();
205
if (path == null || path.isEmpty()) {
206
path = "/";
207
}
208
if (query == null) {
209
query = "";
210
}
211
if (query.isEmpty()) {
212
return Utils.encode(path);
213
} else {
214
return Utils.encode(path + "?" + query);
215
}
216
}
217
218
private String authorityString(InetSocketAddress addr) {
219
return addr.getHostString() + ":" + addr.getPort();
220
}
221
222
private String hostString() {
223
URI uri = request.uri();
224
int port = uri.getPort();
225
String host = uri.getHost();
226
227
boolean defaultPort;
228
if (port == -1) {
229
defaultPort = true;
230
} else if (request.secure()) {
231
defaultPort = port == 443;
232
} else {
233
defaultPort = port == 80;
234
}
235
236
if (defaultPort) {
237
return host;
238
} else {
239
return host + ":" + Integer.toString(port);
240
}
241
}
242
243
private String requestURI() {
244
URI uri = request.uri();
245
String method = request.method();
246
247
if ((request.proxy() == null && !method.equals("CONNECT"))
248
|| request.isWebSocket()) {
249
return getPathAndQuery(uri);
250
}
251
if (request.secure()) {
252
if (request.method().equals("CONNECT")) {
253
// use authority for connect itself
254
return authorityString(request.authority());
255
} else {
256
// requests over tunnel do not require full URL
257
return getPathAndQuery(uri);
258
}
259
}
260
if (request.method().equals("CONNECT")) {
261
// use authority for connect itself
262
return authorityString(request.authority());
263
}
264
265
return uri == null? authorityString(request.authority()) : uri.toString();
266
}
267
268
private boolean finished;
269
270
synchronized boolean finished() {
271
return finished;
272
}
273
274
synchronized void setFinished() {
275
finished = true;
276
}
277
278
List<ByteBuffer> headers() {
279
if (Log.requests() && request != null) {
280
Log.logRequest(request.toString());
281
}
282
String uriString = requestURI();
283
StringBuilder sb = new StringBuilder(64);
284
sb.append(request.method())
285
.append(' ')
286
.append(uriString)
287
.append(" HTTP/1.1\r\n");
288
289
URI uri = request.uri();
290
if (uri != null) {
291
systemHeadersBuilder.setHeader("Host", hostString());
292
}
293
if (requestPublisher == null) {
294
// Not a user request, or maybe a method, e.g. GET, with no body.
295
contentLength = 0;
296
} else {
297
contentLength = requestPublisher.contentLength();
298
}
299
300
if (contentLength == 0) {
301
systemHeadersBuilder.setHeader("Content-Length", "0");
302
} else if (contentLength > 0) {
303
systemHeadersBuilder.setHeader("Content-Length", Long.toString(contentLength));
304
streaming = false;
305
} else {
306
streaming = true;
307
systemHeadersBuilder.setHeader("Transfer-encoding", "chunked");
308
}
309
collectHeaders0(sb);
310
String hs = sb.toString();
311
logHeaders(hs);
312
ByteBuffer b = ByteBuffer.wrap(hs.getBytes(US_ASCII));
313
return List.of(b);
314
}
315
316
Http1BodySubscriber continueRequest() {
317
Http1BodySubscriber subscriber;
318
if (streaming) {
319
subscriber = new StreamSubscriber();
320
requestPublisher.subscribe(subscriber);
321
} else {
322
if (contentLength == 0)
323
return null;
324
325
subscriber = new FixedContentSubscriber();
326
requestPublisher.subscribe(subscriber);
327
}
328
return subscriber;
329
}
330
331
final class StreamSubscriber extends Http1BodySubscriber {
332
333
StreamSubscriber() { super(debug); }
334
335
@Override
336
public void onSubscribe(Flow.Subscription subscription) {
337
if (isSubscribed()) {
338
Throwable t = new IllegalStateException("already subscribed");
339
http1Exchange.appendToOutgoing(t);
340
} else {
341
setSubscription(subscription);
342
}
343
}
344
345
@Override
346
public void onNext(ByteBuffer item) {
347
Objects.requireNonNull(item);
348
if (complete) {
349
Throwable t = new IllegalStateException("subscription already completed");
350
http1Exchange.appendToOutgoing(t);
351
} else {
352
int chunklen = item.remaining();
353
ArrayList<ByteBuffer> l = new ArrayList<>(3);
354
l.add(getHeader(chunklen));
355
l.add(item);
356
l.add(ByteBuffer.wrap(CRLF));
357
http1Exchange.appendToOutgoing(l);
358
}
359
}
360
361
@Override
362
public String currentStateMessage() {
363
return "streaming request body " + (complete ? "complete" : "incomplete");
364
}
365
366
@Override
367
public void onError(Throwable throwable) {
368
if (complete)
369
return;
370
371
cancelSubscription();
372
http1Exchange.appendToOutgoing(throwable);
373
}
374
375
@Override
376
public void onComplete() {
377
if (complete) {
378
Throwable t = new IllegalStateException("subscription already completed");
379
http1Exchange.appendToOutgoing(t);
380
} else {
381
ArrayList<ByteBuffer> l = new ArrayList<>(2);
382
l.add(ByteBuffer.wrap(EMPTY_CHUNK_BYTES));
383
l.add(ByteBuffer.wrap(CRLF));
384
complete = true;
385
//setFinished();
386
http1Exchange.appendToOutgoing(l);
387
http1Exchange.appendToOutgoing(COMPLETED);
388
setFinished(); // TODO: before or after,? does it matter?
389
390
}
391
}
392
}
393
394
final class FixedContentSubscriber extends Http1BodySubscriber {
395
396
private volatile long contentWritten;
397
FixedContentSubscriber() { super(debug); }
398
399
@Override
400
public void onSubscribe(Flow.Subscription subscription) {
401
if (isSubscribed()) {
402
Throwable t = new IllegalStateException("already subscribed");
403
http1Exchange.appendToOutgoing(t);
404
} else {
405
setSubscription(subscription);
406
}
407
}
408
409
@Override
410
public void onNext(ByteBuffer item) {
411
if (debug.on()) debug.log("onNext");
412
Objects.requireNonNull(item);
413
if (complete) {
414
Throwable t = new IllegalStateException("subscription already completed");
415
http1Exchange.appendToOutgoing(t);
416
} else {
417
long writing = item.remaining();
418
long written = (contentWritten += writing);
419
420
if (written > contentLength) {
421
cancelSubscription();
422
String msg = connection.getConnectionFlow()
423
+ " [" + Thread.currentThread().getName() +"] "
424
+ "Too many bytes in request body. Expected: "
425
+ contentLength + ", got: " + written;
426
http1Exchange.appendToOutgoing(new IOException(msg));
427
} else {
428
http1Exchange.appendToOutgoing(List.of(item));
429
}
430
}
431
}
432
433
@Override
434
public String currentStateMessage() {
435
return format("fixed content-length: %d, bytes sent: %d",
436
contentLength, contentWritten);
437
}
438
439
@Override
440
public void onError(Throwable throwable) {
441
if (debug.on()) debug.log("onError");
442
if (complete) // TODO: error?
443
return;
444
445
cancelSubscription();
446
http1Exchange.appendToOutgoing(throwable);
447
}
448
449
@Override
450
public void onComplete() {
451
if (debug.on()) debug.log("onComplete");
452
if (complete) {
453
Throwable t = new IllegalStateException("subscription already completed");
454
http1Exchange.appendToOutgoing(t);
455
} else {
456
complete = true;
457
long written = contentWritten;
458
if (contentLength > written) {
459
cancelSubscription();
460
Throwable t = new IOException(connection.getConnectionFlow()
461
+ " [" + Thread.currentThread().getName() +"] "
462
+ "Too few bytes returned by the publisher ("
463
+ written + "/"
464
+ contentLength + ")");
465
http1Exchange.appendToOutgoing(t);
466
} else {
467
http1Exchange.appendToOutgoing(COMPLETED);
468
}
469
}
470
}
471
}
472
473
private static final byte[] CRLF = {'\r', '\n'};
474
private static final byte[] EMPTY_CHUNK_BYTES = {'0', '\r', '\n'};
475
476
/** Returns a header for a particular chunk size */
477
private static ByteBuffer getHeader(int size) {
478
String hexStr = Integer.toHexString(size);
479
byte[] hexBytes = hexStr.getBytes(US_ASCII);
480
byte[] header = new byte[hexStr.length()+2];
481
System.arraycopy(hexBytes, 0, header, 0, hexBytes.length);
482
header[hexBytes.length] = CRLF[0];
483
header[hexBytes.length+1] = CRLF[1];
484
return ByteBuffer.wrap(header);
485
}
486
487
final Logger debug = Utils.getDebugLogger(this::toString, Utils.DEBUG);
488
489
}
490
491