Skip to content

Commit 1c47244

Browse files
committed
8255244: HttpClient: Response headers contain incorrectly encoded Unicode characters
Reviewed-by: chegar, michaelm
1 parent 56ea786 commit 1c47244

File tree

6 files changed

+561
-26
lines changed

6 files changed

+561
-26
lines changed

src/java.net.http/share/classes/jdk/internal/net/http/Http1HeaderParser.java

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -26,13 +26,17 @@
2626
package jdk.internal.net.http;
2727

2828
import java.net.ProtocolException;
29+
import java.nio.BufferUnderflowException;
2930
import java.nio.ByteBuffer;
3031
import java.util.ArrayList;
3132
import java.util.HashMap;
3233
import java.util.List;
3334
import java.util.Locale;
3435
import java.util.Map;
3536
import java.net.http.HttpHeaders;
37+
38+
import jdk.internal.net.http.common.Utils;
39+
3640
import static java.lang.String.format;
3741
import static java.util.Objects.requireNonNull;
3842
import static jdk.internal.net.http.common.Utils.ACCEPT_ALL;
@@ -166,9 +170,30 @@ private boolean canContinueParsing(ByteBuffer buffer) {
166170
}
167171
}
168172

173+
/**
174+
* Returns a character (char) corresponding to the next byte in the
175+
* input, interpreted as an ISO-8859-1 encoded character.
176+
* <p>
177+
* The ISO-8859-1 encoding is a 8-bit character coding that
178+
* corresponds to the first 256 Unicode characters - from U+0000 to
179+
* U+00FF. UTF-16 is backward compatible with ISO-8859-1 - which
180+
* means each byte in the input should be interpreted as an unsigned
181+
* value from [0, 255] representing the character code.
182+
*
183+
* @param input a {@code ByteBuffer} containing a partial input
184+
* @return the next byte in the input, interpreted as an ISO-8859-1
185+
* encoded char
186+
* @throws BufferUnderflowException
187+
* if the input buffer's current position is not smaller
188+
* than its limit
189+
*/
190+
private char get(ByteBuffer input) {
191+
return (char)(input.get() & 0xFF);
192+
}
193+
169194
private void readResumeStatusLine(ByteBuffer input) {
170195
char c = 0;
171-
while (input.hasRemaining() && (c =(char)input.get()) != CR) {
196+
while (input.hasRemaining() && (c = get(input)) != CR) {
172197
if (c == LF) break;
173198
sb.append(c);
174199
}
@@ -180,7 +205,7 @@ private void readResumeStatusLine(ByteBuffer input) {
180205
}
181206

182207
private void readStatusLineFeed(ByteBuffer input) throws ProtocolException {
183-
char c = state == State.STATUS_LINE_FOUND_LF ? LF : (char)input.get();
208+
char c = state == State.STATUS_LINE_FOUND_LF ? LF : get(input);
184209
if (c != LF) {
185210
throw protocolException("Bad trailing char, \"%s\", when parsing status line, \"%s\"",
186211
c, sb.toString());
@@ -210,7 +235,7 @@ private void readStatusLineFeed(ByteBuffer input) throws ProtocolException {
210235
private void maybeStartHeaders(ByteBuffer input) {
211236
assert state == State.STATUS_LINE_END;
212237
assert sb.length() == 0;
213-
char c = (char)input.get();
238+
char c = get(input);
214239
if (c == CR) {
215240
state = State.STATUS_LINE_END_CR;
216241
} else if (c == LF) {
@@ -224,7 +249,7 @@ private void maybeStartHeaders(ByteBuffer input) {
224249
private void maybeEndHeaders(ByteBuffer input) throws ProtocolException {
225250
assert state == State.STATUS_LINE_END_CR || state == State.STATUS_LINE_END_LF;
226251
assert sb.length() == 0;
227-
char c = state == State.STATUS_LINE_END_LF ? LF : (char)input.get();
252+
char c = state == State.STATUS_LINE_END_LF ? LF : get(input);
228253
if (c == LF) {
229254
headers = HttpHeaders.of(privateMap, ACCEPT_ALL);
230255
privateMap = null;
@@ -238,7 +263,7 @@ private void readResumeHeader(ByteBuffer input) {
238263
assert state == State.HEADER;
239264
assert input.hasRemaining();
240265
while (input.hasRemaining()) {
241-
char c = (char)input.get();
266+
char c = get(input);
242267
if (c == CR) {
243268
state = State.HEADER_FOUND_CR;
244269
break;
@@ -253,23 +278,31 @@ private void readResumeHeader(ByteBuffer input) {
253278
}
254279
}
255280

256-
private void addHeaderFromString(String headerString) {
281+
private void addHeaderFromString(String headerString) throws ProtocolException {
257282
assert sb.length() == 0;
258283
int idx = headerString.indexOf(':');
259284
if (idx == -1)
260285
return;
261-
String name = headerString.substring(0, idx).trim();
262-
if (name.isEmpty())
263-
return;
264-
String value = headerString.substring(idx + 1, headerString.length()).trim();
286+
String name = headerString.substring(0, idx);
287+
288+
// compatibility with HttpURLConnection;
289+
if (name.isEmpty()) return;
290+
291+
if (!Utils.isValidName(name)) {
292+
throw protocolException("Invalid header name \"%s\"", name);
293+
}
294+
String value = headerString.substring(idx + 1).trim();
295+
if (!Utils.isValidValue(value)) {
296+
throw protocolException("Invalid header value \"%s: %s\"", name, value);
297+
}
265298

266299
privateMap.computeIfAbsent(name.toLowerCase(Locale.US),
267300
k -> new ArrayList<>()).add(value);
268301
}
269302

270303
private void resumeOrLF(ByteBuffer input) {
271304
assert state == State.HEADER_FOUND_CR || state == State.HEADER_FOUND_LF;
272-
char c = state == State.HEADER_FOUND_LF ? LF : (char)input.get();
305+
char c = state == State.HEADER_FOUND_LF ? LF : get(input);
273306
if (c == LF) {
274307
// header value will be flushed by
275308
// resumeOrSecondCR if next line does not
@@ -285,9 +318,9 @@ private void resumeOrLF(ByteBuffer input) {
285318
}
286319
}
287320

288-
private void resumeOrSecondCR(ByteBuffer input) {
321+
private void resumeOrSecondCR(ByteBuffer input) throws ProtocolException {
289322
assert state == State.HEADER_FOUND_CR_LF;
290-
char c = (char)input.get();
323+
char c = get(input);
291324
if (c == CR || c == LF) {
292325
if (sb.length() > 0) {
293326
// no continuation line - flush
@@ -322,7 +355,7 @@ private void resumeOrSecondCR(ByteBuffer input) {
322355

323356
private void resumeOrEndHeaders(ByteBuffer input) throws ProtocolException {
324357
assert state == State.HEADER_FOUND_CR_LF_CR;
325-
char c = (char)input.get();
358+
char c = get(input);
326359
if (c == LF) {
327360
state = State.FINISHED;
328361
headers = HttpHeaders.of(privateMap, ACCEPT_ALL);

src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,7 @@ void processFrame(Http2Frame frame) throws IOException {
801801
try {
802802
decodeHeaders((HeaderFrame) frame, stream.rspHeadersConsumer());
803803
} catch (UncheckedIOException e) {
804+
debug.log("Error decoding headers: " + e.getMessage(), e);
804805
protocolError(ResetFrame.PROTOCOL_ERROR, e.getMessage());
805806
return;
806807
}

src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ public static URLPermission permissionForServer(URI uri,
400400
for (char c : allowedTokenChars) {
401401
tchar[c] = true;
402402
}
403-
for (char c = 0x21; c < 0xFF; c++) {
403+
for (char c = 0x21; c <= 0xFF; c++) {
404404
fieldvchar[c] = true;
405405
}
406406
fieldvchar[0x7F] = false; // a little hole (DEL) in the range

test/jdk/java/net/httpclient/HttpServerAdapters.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -196,7 +196,7 @@ public void addHeader(String name, String value) {
196196
/**
197197
* A version agnostic adapter class for HTTP Server Exchange.
198198
*/
199-
public static abstract class HttpTestExchange {
199+
public static abstract class HttpTestExchange implements AutoCloseable {
200200
public abstract Version getServerVersion();
201201
public abstract Version getExchangeVersion();
202202
public abstract InputStream getRequestBody();

0 commit comments

Comments
 (0)