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/Http1HeaderParser.java
41171 views
1
/*
2
* Copyright (c) 2017, 2020, 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.net.ProtocolException;
29
import java.nio.BufferUnderflowException;
30
import java.nio.ByteBuffer;
31
import java.util.ArrayList;
32
import java.util.HashMap;
33
import java.util.List;
34
import java.util.Locale;
35
import java.util.Map;
36
import java.net.http.HttpHeaders;
37
38
import jdk.internal.net.http.common.Utils;
39
40
import static java.lang.String.format;
41
import static java.util.Objects.requireNonNull;
42
import static jdk.internal.net.http.common.Utils.ACCEPT_ALL;
43
44
class Http1HeaderParser {
45
46
private static final char CR = '\r';
47
private static final char LF = '\n';
48
private static final char HT = '\t';
49
private static final char SP = ' ';
50
51
private StringBuilder sb = new StringBuilder();
52
private String statusLine;
53
private int responseCode;
54
private HttpHeaders headers;
55
private Map<String,List<String>> privateMap = new HashMap<>();
56
57
enum State { INITIAL,
58
STATUS_LINE,
59
STATUS_LINE_FOUND_CR,
60
STATUS_LINE_FOUND_LF,
61
STATUS_LINE_END,
62
STATUS_LINE_END_CR,
63
STATUS_LINE_END_LF,
64
HEADER,
65
HEADER_FOUND_CR,
66
HEADER_FOUND_LF,
67
HEADER_FOUND_CR_LF,
68
HEADER_FOUND_CR_LF_CR,
69
FINISHED }
70
71
private State state = State.INITIAL;
72
73
/** Returns the status-line. */
74
String statusLine() { return statusLine; }
75
76
/** Returns the response code. */
77
int responseCode() { return responseCode; }
78
79
/** Returns the headers, possibly empty. */
80
HttpHeaders headers() {
81
assert state == State.FINISHED : "Unexpected state " + state;
82
return headers;
83
}
84
85
/** A current-state message suitable for inclusion in an exception detail message. */
86
public String currentStateMessage() {
87
String stateName = state.name();
88
String msg;
89
if (stateName.contains("INITIAL")) {
90
return format("HTTP/1.1 header parser received no bytes");
91
} else if (stateName.contains("STATUS")) {
92
msg = format("parsing HTTP/1.1 status line, receiving [%s]", sb.toString());
93
} else if (stateName.contains("HEADER")) {
94
String headerName = sb.toString();
95
if (headerName.indexOf(':') != -1)
96
headerName = headerName.substring(0, headerName.indexOf(':')+1) + "...";
97
msg = format("parsing HTTP/1.1 header, receiving [%s]", headerName);
98
} else {
99
msg = format("HTTP/1.1 parser receiving [%s]", sb.toString());
100
}
101
return format("%s, parser state [%s]", msg, state);
102
}
103
104
/**
105
* Parses HTTP/1.X status-line and headers from the given bytes. Must be
106
* called successive times, with additional data, until returns true.
107
*
108
* All given ByteBuffers will be consumed, until ( possibly ) the last one
109
* ( when true is returned ), which may not be fully consumed.
110
*
111
* @param input the ( partial ) header data
112
* @return true iff the end of the headers block has been reached
113
*/
114
boolean parse(ByteBuffer input) throws ProtocolException {
115
requireNonNull(input, "null input");
116
117
while (canContinueParsing(input)) {
118
switch (state) {
119
case INITIAL -> state = State.STATUS_LINE;
120
case STATUS_LINE -> readResumeStatusLine(input);
121
case STATUS_LINE_FOUND_CR, STATUS_LINE_FOUND_LF -> readStatusLineFeed(input);
122
case STATUS_LINE_END -> maybeStartHeaders(input);
123
case STATUS_LINE_END_CR, STATUS_LINE_END_LF -> maybeEndHeaders(input);
124
case HEADER -> readResumeHeader(input);
125
case HEADER_FOUND_CR, HEADER_FOUND_LF -> resumeOrLF(input);
126
case HEADER_FOUND_CR_LF -> resumeOrSecondCR(input);
127
case HEADER_FOUND_CR_LF_CR -> resumeOrEndHeaders(input);
128
129
default -> throw new InternalError("Unexpected state: " + state);
130
}
131
}
132
133
return state == State.FINISHED;
134
}
135
136
private boolean canContinueParsing(ByteBuffer buffer) {
137
// some states don't require any input to transition
138
// to the next state.
139
return switch (state) {
140
case FINISHED -> false;
141
case STATUS_LINE_FOUND_LF, STATUS_LINE_END_LF, HEADER_FOUND_LF -> true;
142
default -> buffer.hasRemaining();
143
};
144
}
145
146
/**
147
* Returns a character (char) corresponding to the next byte in the
148
* input, interpreted as an ISO-8859-1 encoded character.
149
* <p>
150
* The ISO-8859-1 encoding is a 8-bit character coding that
151
* corresponds to the first 256 Unicode characters - from U+0000 to
152
* U+00FF. UTF-16 is backward compatible with ISO-8859-1 - which
153
* means each byte in the input should be interpreted as an unsigned
154
* value from [0, 255] representing the character code.
155
*
156
* @param input a {@code ByteBuffer} containing a partial input
157
* @return the next byte in the input, interpreted as an ISO-8859-1
158
* encoded char
159
* @throws BufferUnderflowException
160
* if the input buffer's current position is not smaller
161
* than its limit
162
*/
163
private char get(ByteBuffer input) {
164
return (char)(input.get() & 0xFF);
165
}
166
167
private void readResumeStatusLine(ByteBuffer input) {
168
char c = 0;
169
while (input.hasRemaining() && (c = get(input)) != CR) {
170
if (c == LF) break;
171
sb.append(c);
172
}
173
if (c == CR) {
174
state = State.STATUS_LINE_FOUND_CR;
175
} else if (c == LF) {
176
state = State.STATUS_LINE_FOUND_LF;
177
}
178
}
179
180
private void readStatusLineFeed(ByteBuffer input) throws ProtocolException {
181
char c = state == State.STATUS_LINE_FOUND_LF ? LF : get(input);
182
if (c != LF) {
183
throw protocolException("Bad trailing char, \"%s\", when parsing status line, \"%s\"",
184
c, sb.toString());
185
}
186
187
statusLine = sb.toString();
188
sb = new StringBuilder();
189
if (!statusLine.startsWith("HTTP/1.")) {
190
throw protocolException("Invalid status line: \"%s\"", statusLine);
191
}
192
if (statusLine.length() < 12) {
193
throw protocolException("Invalid status line: \"%s\"", statusLine);
194
}
195
try {
196
responseCode = Integer.parseInt(statusLine.substring(9, 12));
197
} catch (NumberFormatException nfe) {
198
throw protocolException("Invalid status line: \"%s\"", statusLine);
199
}
200
// response code expected to be a 3-digit integer (RFC-2616, section 6.1.1)
201
if (responseCode < 100) {
202
throw protocolException("Invalid status line: \"%s\"", statusLine);
203
}
204
205
state = State.STATUS_LINE_END;
206
}
207
208
private void maybeStartHeaders(ByteBuffer input) {
209
assert state == State.STATUS_LINE_END;
210
assert sb.length() == 0;
211
char c = get(input);
212
if (c == CR) {
213
state = State.STATUS_LINE_END_CR;
214
} else if (c == LF) {
215
state = State.STATUS_LINE_END_LF;
216
} else {
217
sb.append(c);
218
state = State.HEADER;
219
}
220
}
221
222
private void maybeEndHeaders(ByteBuffer input) throws ProtocolException {
223
assert state == State.STATUS_LINE_END_CR || state == State.STATUS_LINE_END_LF;
224
assert sb.length() == 0;
225
char c = state == State.STATUS_LINE_END_LF ? LF : get(input);
226
if (c == LF) {
227
headers = HttpHeaders.of(privateMap, ACCEPT_ALL);
228
privateMap = null;
229
state = State.FINISHED; // no headers
230
} else {
231
throw protocolException("Unexpected \"%s\", after status line CR", c);
232
}
233
}
234
235
private void readResumeHeader(ByteBuffer input) {
236
assert state == State.HEADER;
237
assert input.hasRemaining();
238
while (input.hasRemaining()) {
239
char c = get(input);
240
if (c == CR) {
241
state = State.HEADER_FOUND_CR;
242
break;
243
} else if (c == LF) {
244
state = State.HEADER_FOUND_LF;
245
break;
246
}
247
248
if (c == HT)
249
c = SP;
250
sb.append(c);
251
}
252
}
253
254
private void addHeaderFromString(String headerString) throws ProtocolException {
255
assert sb.length() == 0;
256
int idx = headerString.indexOf(':');
257
if (idx == -1)
258
return;
259
String name = headerString.substring(0, idx);
260
261
// compatibility with HttpURLConnection;
262
if (name.isEmpty()) return;
263
264
if (!Utils.isValidName(name)) {
265
throw protocolException("Invalid header name \"%s\"", name);
266
}
267
String value = headerString.substring(idx + 1).trim();
268
if (!Utils.isValidValue(value)) {
269
throw protocolException("Invalid header value \"%s: %s\"", name, value);
270
}
271
272
privateMap.computeIfAbsent(name.toLowerCase(Locale.US),
273
k -> new ArrayList<>()).add(value);
274
}
275
276
private void resumeOrLF(ByteBuffer input) {
277
assert state == State.HEADER_FOUND_CR || state == State.HEADER_FOUND_LF;
278
char c = state == State.HEADER_FOUND_LF ? LF : get(input);
279
if (c == LF) {
280
// header value will be flushed by
281
// resumeOrSecondCR if next line does not
282
// begin by SP or HT
283
state = State.HEADER_FOUND_CR_LF;
284
} else if (c == SP || c == HT) {
285
sb.append(SP); // parity with MessageHeaders
286
state = State.HEADER;
287
} else {
288
sb = new StringBuilder();
289
sb.append(c);
290
state = State.HEADER;
291
}
292
}
293
294
private void resumeOrSecondCR(ByteBuffer input) throws ProtocolException {
295
assert state == State.HEADER_FOUND_CR_LF;
296
char c = get(input);
297
if (c == CR || c == LF) {
298
if (sb.length() > 0) {
299
// no continuation line - flush
300
// previous header value.
301
String headerString = sb.toString();
302
sb = new StringBuilder();
303
addHeaderFromString(headerString);
304
}
305
if (c == CR) {
306
state = State.HEADER_FOUND_CR_LF_CR;
307
} else {
308
state = State.FINISHED;
309
headers = HttpHeaders.of(privateMap, ACCEPT_ALL);
310
privateMap = null;
311
}
312
} else if (c == SP || c == HT) {
313
assert sb.length() != 0;
314
sb.append(SP); // continuation line
315
state = State.HEADER;
316
} else {
317
if (sb.length() > 0) {
318
// no continuation line - flush
319
// previous header value.
320
String headerString = sb.toString();
321
sb = new StringBuilder();
322
addHeaderFromString(headerString);
323
}
324
sb.append(c);
325
state = State.HEADER;
326
}
327
}
328
329
private void resumeOrEndHeaders(ByteBuffer input) throws ProtocolException {
330
assert state == State.HEADER_FOUND_CR_LF_CR;
331
char c = get(input);
332
if (c == LF) {
333
state = State.FINISHED;
334
headers = HttpHeaders.of(privateMap, ACCEPT_ALL);
335
privateMap = null;
336
} else {
337
throw protocolException("Unexpected \"%s\", after CR LF CR", c);
338
}
339
}
340
341
private ProtocolException protocolException(String format, Object... args) {
342
return new ProtocolException(format(format, args));
343
}
344
}
345
346