Skip to content

Commit 1c47244

Browse files
committedNov 13, 2020
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

+48-15
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

+1
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

+1-1
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

+2-2
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();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,442 @@
1+
/*
2+
* Copyright (c) 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.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @bug 8252374
27+
* @library /test/lib http2/server
28+
* @build jdk.test.lib.net.SimpleSSLContext HttpServerAdapters
29+
* ReferenceTracker AggregateRequestBodyTest
30+
* @modules java.base/sun.net.www.http
31+
* java.net.http/jdk.internal.net.http.common
32+
* java.net.http/jdk.internal.net.http.frame
33+
* java.net.http/jdk.internal.net.http.hpack
34+
* @run testng/othervm -Djdk.internal.httpclient.debug=true
35+
* -Djdk.httpclient.HttpClient.log=requests,responses,errors
36+
* ISO_8859_1_Test
37+
* @summary Tests that a client is able to receive ISO-8859-1 encoded header values.
38+
*/
39+
40+
import java.io.BufferedReader;
41+
import java.io.IOException;
42+
import java.io.InputStreamReader;
43+
import java.net.InetAddress;
44+
import java.net.InetSocketAddress;
45+
import java.net.ServerSocket;
46+
import java.net.Socket;
47+
import java.net.URI;
48+
import java.net.URL;
49+
import java.net.http.HttpClient;
50+
import java.net.http.HttpRequest;
51+
import java.net.http.HttpRequest.BodyPublisher;
52+
import java.net.http.HttpRequest.BodyPublishers;
53+
import java.net.http.HttpResponse;
54+
import java.net.http.HttpResponse.BodyHandlers;
55+
import java.nio.ByteBuffer;
56+
import java.nio.charset.StandardCharsets;
57+
import java.util.ArrayList;
58+
import java.util.LinkedHashMap;
59+
import java.util.List;
60+
import java.util.Map;
61+
import java.util.concurrent.CompletableFuture;
62+
import java.util.concurrent.CompletionException;
63+
import java.util.concurrent.ConcurrentHashMap;
64+
import java.util.concurrent.ConcurrentLinkedDeque;
65+
import java.util.concurrent.ConcurrentMap;
66+
import java.util.concurrent.CopyOnWriteArrayList;
67+
import java.util.concurrent.ExecutionException;
68+
import java.util.concurrent.Executor;
69+
import java.util.concurrent.Executors;
70+
import java.util.concurrent.Flow.Subscriber;
71+
import java.util.concurrent.Flow.Subscription;
72+
import java.util.concurrent.TimeUnit;
73+
import java.util.concurrent.TimeoutException;
74+
import java.util.concurrent.atomic.AtomicLong;
75+
import java.util.function.Consumer;
76+
import java.util.function.Supplier;
77+
import java.util.stream.Collectors;
78+
import java.util.stream.LongStream;
79+
import java.util.stream.Stream;
80+
import javax.net.ssl.SSLContext;
81+
82+
import com.sun.net.httpserver.HttpServer;
83+
import com.sun.net.httpserver.HttpsConfigurator;
84+
import com.sun.net.httpserver.HttpsServer;
85+
import jdk.test.lib.net.SimpleSSLContext;
86+
import org.testng.Assert;
87+
import org.testng.ITestContext;
88+
import org.testng.annotations.AfterClass;
89+
import org.testng.annotations.AfterTest;
90+
import org.testng.annotations.BeforeMethod;
91+
import org.testng.annotations.BeforeTest;
92+
import org.testng.annotations.DataProvider;
93+
import org.testng.annotations.Test;
94+
95+
import static java.lang.System.out;
96+
import static org.testng.Assert.assertEquals;
97+
import static org.testng.Assert.assertFalse;
98+
import static org.testng.Assert.assertTrue;
99+
import static org.testng.Assert.expectThrows;
100+
101+
public class ISO_8859_1_Test implements HttpServerAdapters {
102+
103+
SSLContext sslContext;
104+
DummyServer http1DummyServer;
105+
HttpServerAdapters.HttpTestServer http1TestServer; // HTTP/1.1 ( http )
106+
HttpServerAdapters.HttpTestServer https1TestServer; // HTTPS/1.1 ( https )
107+
HttpServerAdapters.HttpTestServer http2TestServer; // HTTP/2 ( h2c )
108+
HttpServerAdapters.HttpTestServer https2TestServer; // HTTP/2 ( h2 )
109+
String http1Dummy;
110+
String http1URI;
111+
String https1URI;
112+
String http2URI;
113+
String https2URI;
114+
115+
static final int RESPONSE_CODE = 200;
116+
static final int ITERATION_COUNT = 4;
117+
static final Class<IllegalArgumentException> IAE = IllegalArgumentException.class;
118+
static final Class<CompletionException> CE = CompletionException.class;
119+
// a shared executor helps reduce the amount of threads created by the test
120+
static final Executor executor = new TestExecutor(Executors.newCachedThreadPool());
121+
static final ConcurrentMap<String, Throwable> FAILURES = new ConcurrentHashMap<>();
122+
static volatile boolean tasksFailed;
123+
static final AtomicLong serverCount = new AtomicLong();
124+
static final AtomicLong clientCount = new AtomicLong();
125+
static final long start = System.nanoTime();
126+
public static String now() {
127+
long now = System.nanoTime() - start;
128+
long secs = now / 1000_000_000;
129+
long mill = (now % 1000_000_000) / 1000_000;
130+
long nan = now % 1000_000;
131+
return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);
132+
}
133+
134+
final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE;
135+
private volatile HttpClient sharedClient;
136+
137+
static class TestExecutor implements Executor {
138+
final AtomicLong tasks = new AtomicLong();
139+
Executor executor;
140+
TestExecutor(Executor executor) {
141+
this.executor = executor;
142+
}
143+
144+
@Override
145+
public void execute(Runnable command) {
146+
long id = tasks.incrementAndGet();
147+
executor.execute(() -> {
148+
try {
149+
command.run();
150+
} catch (Throwable t) {
151+
tasksFailed = true;
152+
System.out.printf(now() + "Task %s failed: %s%n", id, t);
153+
System.err.printf(now() + "Task %s failed: %s%n", id, t);
154+
FAILURES.putIfAbsent("Task " + id, t);
155+
throw t;
156+
}
157+
});
158+
}
159+
}
160+
161+
protected boolean stopAfterFirstFailure() {
162+
return Boolean.getBoolean("jdk.internal.httpclient.debug");
163+
}
164+
165+
@BeforeMethod
166+
void beforeMethod(ITestContext context) {
167+
if (stopAfterFirstFailure() && context.getFailedTests().size() > 0) {
168+
throw new RuntimeException("some tests failed");
169+
}
170+
}
171+
172+
@AfterClass
173+
static final void printFailedTests() {
174+
out.println("\n=========================");
175+
try {
176+
out.printf("%n%sCreated %d servers and %d clients%n",
177+
now(), serverCount.get(), clientCount.get());
178+
if (FAILURES.isEmpty()) return;
179+
out.println("Failed tests: ");
180+
FAILURES.entrySet().forEach((e) -> {
181+
out.printf("\t%s: %s%n", e.getKey(), e.getValue());
182+
e.getValue().printStackTrace(out);
183+
e.getValue().printStackTrace();
184+
});
185+
if (tasksFailed) {
186+
System.out.println("WARNING: Some tasks failed");
187+
}
188+
} finally {
189+
out.println("\n=========================\n");
190+
}
191+
}
192+
193+
private String[] uris() {
194+
return new String[] {
195+
http1Dummy,
196+
http1URI,
197+
https1URI,
198+
http2URI,
199+
https2URI,
200+
};
201+
}
202+
203+
static AtomicLong URICOUNT = new AtomicLong();
204+
205+
@DataProvider(name = "variants")
206+
public Object[][] variants(ITestContext context) {
207+
if (stopAfterFirstFailure() && context.getFailedTests().size() > 0) {
208+
return new Object[0][];
209+
}
210+
String[] uris = uris();
211+
Object[][] result = new Object[uris.length * 2][];
212+
int i = 0;
213+
for (boolean sameClient : List.of(false, true)) {
214+
for (String uri : uris()) {
215+
result[i++] = new Object[]{uri, sameClient};
216+
}
217+
}
218+
assert i == uris.length * 2;
219+
return result;
220+
}
221+
222+
private HttpClient makeNewClient() {
223+
clientCount.incrementAndGet();
224+
HttpClient client = HttpClient.newBuilder()
225+
.proxy(HttpClient.Builder.NO_PROXY)
226+
.executor(executor)
227+
.sslContext(sslContext)
228+
.build();
229+
return TRACKER.track(client);
230+
}
231+
232+
HttpClient newHttpClient(boolean share) {
233+
if (!share) return makeNewClient();
234+
HttpClient shared = sharedClient;
235+
if (shared != null) return shared;
236+
synchronized (this) {
237+
shared = sharedClient;
238+
if (shared == null) {
239+
shared = sharedClient = makeNewClient();
240+
}
241+
return shared;
242+
}
243+
}
244+
245+
private static final Exception completionCause(CompletionException x) {
246+
Throwable c = x;
247+
while (c instanceof CompletionException
248+
|| c instanceof ExecutionException) {
249+
if (c.getCause() == null) break;
250+
c = c.getCause();
251+
}
252+
if (c instanceof Error) throw (Error)c;
253+
return (Exception)c;
254+
}
255+
256+
@Test(dataProvider = "variants")
257+
public void test(String uri, boolean sameClient) throws Exception {
258+
System.out.println("Request to " + uri);
259+
260+
HttpClient client = newHttpClient(sameClient);
261+
262+
List<CompletableFuture<HttpResponse<String>>> cfs = new ArrayList<>();
263+
for (int i = 0; i < ITERATION_COUNT; i++) {
264+
HttpRequest request = HttpRequest.newBuilder(URI.create(uri + "/" + i))
265+
.build();
266+
cfs.add(client.sendAsync(request, BodyHandlers.ofString()));
267+
}
268+
try {
269+
CompletableFuture.allOf(cfs.toArray(CompletableFuture[]::new)).join();
270+
} catch (CompletionException x) {
271+
throw completionCause(x);
272+
}
273+
for (CompletableFuture<HttpResponse<String>> cf : cfs) {
274+
var response = cf.get();
275+
System.out.println("Got: " + response);
276+
var value = response.headers().firstValue("Header8859").orElse(null);
277+
assertEquals(value, "U\u00ffU");
278+
}
279+
System.out.println("HttpClient: PASSED");
280+
if (uri.contains("http1")) {
281+
System.out.println("Testing with URLConnection");
282+
var url = URI.create(uri).toURL();
283+
var conn = url.openConnection();
284+
conn.connect();
285+
conn.getInputStream().readAllBytes();
286+
var value = conn.getHeaderField("Header8859");
287+
assertEquals(value, "U\u00ffU", "legacy stack failed");
288+
System.out.println("URLConnection: PASSED");
289+
}
290+
System.out.println("test: DONE");
291+
}
292+
293+
static final class DummyServer extends Thread implements AutoCloseable {
294+
String RESP = """
295+
HTTP/1.1 200 OK\r
296+
Content-length: 0\r
297+
Header8859: U\u00ffU\r
298+
Connection: close\r
299+
\r
300+
""";
301+
302+
static final InetSocketAddress LOOPBACK =
303+
new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
304+
final ServerSocket socket;
305+
final CopyOnWriteArrayList<Socket> accepted = new CopyOnWriteArrayList<Socket>();
306+
final CompletableFuture<Void> done = new CompletableFuture();
307+
volatile boolean closed;
308+
DummyServer() throws IOException {
309+
socket = new ServerSocket();
310+
socket.bind(LOOPBACK);
311+
}
312+
313+
public String serverAuthority() {
314+
String address = socket.getInetAddress().getHostAddress();
315+
if (address.indexOf(':') >= 0) {
316+
address = "[" + address + "]";
317+
}
318+
return address + ":" + socket.getLocalPort();
319+
}
320+
321+
public void run() {
322+
try {
323+
while (!socket.isClosed()) {
324+
try (Socket client = socket.accept()) {
325+
accepted.add(client);
326+
try {
327+
System.out.println("Accepted: " + client);
328+
String req = "";
329+
BufferedReader reader = new BufferedReader(
330+
new InputStreamReader(client.getInputStream(),
331+
StandardCharsets.ISO_8859_1));
332+
String line = null;
333+
while (!(line = reader.readLine()).isEmpty()) {
334+
System.out.println("Got line: " + line);
335+
req = req + line + "\r\n";
336+
}
337+
System.out.println(req);
338+
System.out.println("Sending back " + RESP);
339+
client.getOutputStream().write(RESP.getBytes(StandardCharsets.ISO_8859_1));
340+
client.getOutputStream().flush();
341+
} finally {
342+
accepted.remove(client);
343+
}
344+
}
345+
}
346+
} catch (Throwable t) {
347+
if (closed) {
348+
done.complete(null);
349+
} else {
350+
done.completeExceptionally(t);
351+
}
352+
} finally {
353+
done.complete(null);
354+
}
355+
}
356+
357+
final void close(AutoCloseable toclose) {
358+
try { toclose.close(); } catch (Exception x) {};
359+
}
360+
361+
final public void close() {
362+
closed = true;
363+
close(socket);
364+
accepted.forEach(this::close);
365+
}
366+
}
367+
368+
final static class ISO88591Handler implements HttpServerAdapters.HttpTestHandler {
369+
@Override
370+
public void handle(HttpTestExchange t) throws IOException {
371+
try (HttpTestExchange e = t) {
372+
t.getRequestBody().readAllBytes();
373+
t.getResponseHeaders().addHeader("Header8859", "U\u00ffU");
374+
t.sendResponseHeaders(200, 0);
375+
}
376+
377+
}
378+
}
379+
380+
@BeforeTest
381+
public void setup() throws Exception {
382+
sslContext = new SimpleSSLContext().get();
383+
if (sslContext == null)
384+
throw new AssertionError("Unexpected null sslContext");
385+
386+
HttpServerAdapters.HttpTestHandler handler = new ISO88591Handler();
387+
InetSocketAddress loopback = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
388+
389+
http1DummyServer = new DummyServer();
390+
http1Dummy = "http://" + http1DummyServer.serverAuthority() +"/http1/dummy/x";
391+
392+
HttpServer http1 = HttpServer.create(loopback, 0);
393+
http1TestServer = HttpServerAdapters.HttpTestServer.of(http1);
394+
http1TestServer.addHandler(handler, "/http1/server/");
395+
http1URI = "http://" + http1TestServer.serverAuthority() + "/http1/server/x";
396+
397+
HttpsServer https1 = HttpsServer.create(loopback, 0);
398+
https1.setHttpsConfigurator(new HttpsConfigurator(sslContext));
399+
https1TestServer = HttpServerAdapters.HttpTestServer.of(https1);
400+
https1TestServer.addHandler(handler, "/https1/server/");
401+
https1URI = "https://" + https1TestServer.serverAuthority() + "/https1/server/x";
402+
403+
// HTTP/2
404+
http2TestServer = HttpServerAdapters.HttpTestServer.of(new Http2TestServer("localhost", false, 0));
405+
http2TestServer.addHandler(handler, "/http2/server/");
406+
http2URI = "http://" + http2TestServer.serverAuthority() + "/http2/server/x";
407+
408+
https2TestServer = HttpServerAdapters.HttpTestServer.of(new Http2TestServer("localhost", true, sslContext));
409+
https2TestServer.addHandler(handler, "/https2/server/");
410+
https2URI = "https://" + https2TestServer.serverAuthority() + "/https2/server/x";
411+
412+
serverCount.addAndGet(5);
413+
http1TestServer.start();
414+
https1TestServer.start();
415+
http2TestServer.start();
416+
https2TestServer.start();
417+
http1DummyServer.start();
418+
}
419+
420+
@AfterTest
421+
public void teardown() throws Exception {
422+
String sharedClientName =
423+
sharedClient == null ? null : sharedClient.toString();
424+
sharedClient = null;
425+
Thread.sleep(100);
426+
AssertionError fail = TRACKER.check(500);
427+
try {
428+
http1TestServer.stop();
429+
https1TestServer.stop();
430+
http2TestServer.stop();
431+
https2TestServer.stop();
432+
http1DummyServer.close();
433+
} finally {
434+
if (fail != null) {
435+
if (sharedClientName != null) {
436+
System.err.println("Shared client name is: " + sharedClientName);
437+
}
438+
throw fail;
439+
}
440+
}
441+
}
442+
}

‎test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/Http1HeaderParserTest.java

+67-8
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
@@ -40,6 +40,7 @@
4040
import org.testng.annotations.DataProvider;
4141
import static java.lang.System.out;
4242
import static java.lang.String.format;
43+
import static java.nio.charset.StandardCharsets.ISO_8859_1;
4344
import static java.nio.charset.StandardCharsets.US_ASCII;
4445
import static java.util.stream.Collectors.toList;
4546
import static org.testng.Assert.*;
@@ -69,6 +70,12 @@ public Object[][] responses() {
6970
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
7071
"XXXXX",
7172

73+
"HTTP/1.1 200 OK\r\n" +
74+
"X-Header: U\u00ffU\r\n" + // value with U+00FF - Extended Latin-1
75+
"Content-Length: 9\r\n" +
76+
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
77+
"XXXXX",
78+
7279
"HTTP/1.1 200 OK\r\n" +
7380
"Content-Length: 9\r\n" +
7481
"Content-Type: text/html; charset=UTF-8\r\n\r\n" + // more than one SP after ':'
@@ -222,13 +229,19 @@ public Object[][] responses() {
222229
"XXXXX",
223230

224231
"HTTP/1.1 200 OK\r\n" +
225-
": no header\r\n\r\n" + // no/empty header-name, followed by header
232+
"X-foo: bar\r\n" +
233+
" : no header\r\n" + // fold, not a blank header-name
234+
"Content-Length: 65\r\n\r\n" +
226235
"XXXXX",
227236

228237
"HTTP/1.1 200 OK\r\n" +
229-
"Conte\r" +
230-
" nt-Length: 9\r\n" + // fold/bad header name ???
231-
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
238+
"X-foo: bar\r\n" +
239+
" \t : no header\r\n" + // fold, not a blank header-name
240+
"Content-Length: 65\r\n\r\n" +
241+
"XXXXX",
242+
243+
"HTTP/1.1 200 OK\r\n" +
244+
": no header\r\n\r\n" + // no/empty header-name, followed by header
232245
"XXXXX",
233246

234247
"HTTP/1.1 200 OK\r\n" +
@@ -310,7 +323,7 @@ public void verifyHeaders(String respString) throws Exception {
310323
.replace("\r", "<CR>")
311324
.replace("\n","<LF>")
312325
.replace("LF>", "LF>\n\t"));
313-
byte[] bytes = respString.getBytes(US_ASCII);
326+
byte[] bytes = respString.getBytes(ISO_8859_1);
314327
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
315328
MessageHeader m = new MessageHeader(bais);
316329
Map<String,List<String>> messageHeaderMap = m.getHeaders();
@@ -326,7 +339,7 @@ public void verifyHeaders(String respString) throws Exception {
326339
String statusLine1 = messageHeaderMap.get(null).get(0);
327340
String statusLine2 = decoder.statusLine();
328341
if (statusLine1.startsWith("HTTP")) {// skip the case where MH's messes up the status-line
329-
assertEquals(statusLine1, statusLine2, "Status-line not equal");
342+
assertEquals(statusLine2, statusLine1, "Status-line not equal");
330343
} else {
331344
assertTrue(statusLine2.startsWith("HTTP/1."), "Status-line not HTTP/1.");
332345
}
@@ -384,7 +397,53 @@ public Object[][] errors() {
384397

385398
"HTTP/1.1 -22\r\n",
386399

387-
"HTTP/1.1 -20 \r\n"
400+
"HTTP/1.1 -20 \r\n",
401+
402+
"HTTP/1.1 200 OK\r\n" +
403+
"X-fo\u00ffo: foo\r\n" + // invalid char in name
404+
"Content-Length: 5\r\n" +
405+
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
406+
"XXXXX",
407+
408+
"HTTP/1.1 200 OK\r\n" +
409+
"HTTP/1.1 200 OK\r\n" +
410+
"X-foo : bar\r\n" + // trim space after name
411+
"Content-Length: 5\r\n" +
412+
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
413+
"XXXXX",
414+
415+
"HTTP/1.1 200 OK\r\n" +
416+
" X-foo: bar\r\n" + // trim space before name
417+
"Content-Length: 5\r\n" +
418+
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
419+
"XXXXX",
420+
421+
"HTTP/1.1 200 OK\r\n" +
422+
"X foo: bar\r\n" + // invalid space in name
423+
"Content-Length: 5\r\n" +
424+
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
425+
"XXXXX",
426+
427+
"HTTP/1.1 200 OK\r\n" +
428+
"Content-Length: 5\r\n" +
429+
"Content Type: text/html; charset=UTF-8\r\n\r\n" + // invalid space in name
430+
"XXXXX",
431+
432+
"HTTP/1.1 200 OK\r\n" +
433+
"Conte\r" +
434+
" nt-Length: 9\r\n" + // fold results in space in header name
435+
"Content-Type: text/html; charset=UTF-8\r\n\r\n" +
436+
"XXXXX",
437+
438+
"HTTP/1.1 200 OK\r\n" +
439+
" : no header\r\n" + // all blank header-name (not fold)
440+
"Content-Length: 65\r\n\r\n" +
441+
"XXXXX",
442+
443+
"HTTP/1.1 200 OK\r\n" +
444+
" \t : no header\r\n" + // all blank header-name (not fold)
445+
"Content-Length: 65\r\n\r\n" +
446+
"XXXXX",
388447

389448
};
390449
Arrays.stream(bad).forEach(responses::add);

0 commit comments

Comments
 (0)
Please sign in to comment.