diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Http1HeaderParser.java b/src/java.net.http/share/classes/jdk/internal/net/http/Http1HeaderParser.java index 4e4032537bb..6de2bbfcf9f 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/Http1HeaderParser.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1HeaderParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ package jdk.internal.net.http; import java.net.ProtocolException; +import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; @@ -33,6 +34,9 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.net.http.HttpHeaders; + +import jdk.internal.net.http.common.Utils; + import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static jdk.internal.net.http.common.Utils.ACCEPT_ALL; @@ -166,9 +170,30 @@ class Http1HeaderParser { } } + /** + * Returns a character (char) corresponding to the next byte in the + * input, interpreted as an ISO-8859-1 encoded character. + *
+ * The ISO-8859-1 encoding is a 8-bit character coding that
+ * corresponds to the first 256 Unicode characters - from U+0000 to
+ * U+00FF. UTF-16 is backward compatible with ISO-8859-1 - which
+ * means each byte in the input should be interpreted as an unsigned
+ * value from [0, 255] representing the character code.
+ *
+ * @param input a {@code ByteBuffer} containing a partial input
+ * @return the next byte in the input, interpreted as an ISO-8859-1
+ * encoded char
+ * @throws BufferUnderflowException
+ * if the input buffer's current position is not smaller
+ * than its limit
+ */
+ private char get(ByteBuffer input) {
+ return (char)(input.get() & 0xFF);
+ }
+
private void readResumeStatusLine(ByteBuffer input) {
char c = 0;
- while (input.hasRemaining() && (c =(char)input.get()) != CR) {
+ while (input.hasRemaining() && (c = get(input)) != CR) {
if (c == LF) break;
sb.append(c);
}
@@ -180,7 +205,7 @@ class Http1HeaderParser {
}
private void readStatusLineFeed(ByteBuffer input) throws ProtocolException {
- char c = state == State.STATUS_LINE_FOUND_LF ? LF : (char)input.get();
+ char c = state == State.STATUS_LINE_FOUND_LF ? LF : get(input);
if (c != LF) {
throw protocolException("Bad trailing char, \"%s\", when parsing status line, \"%s\"",
c, sb.toString());
@@ -210,7 +235,7 @@ class Http1HeaderParser {
private void maybeStartHeaders(ByteBuffer input) {
assert state == State.STATUS_LINE_END;
assert sb.length() == 0;
- char c = (char)input.get();
+ char c = get(input);
if (c == CR) {
state = State.STATUS_LINE_END_CR;
} else if (c == LF) {
@@ -224,7 +249,7 @@ class Http1HeaderParser {
private void maybeEndHeaders(ByteBuffer input) throws ProtocolException {
assert state == State.STATUS_LINE_END_CR || state == State.STATUS_LINE_END_LF;
assert sb.length() == 0;
- char c = state == State.STATUS_LINE_END_LF ? LF : (char)input.get();
+ char c = state == State.STATUS_LINE_END_LF ? LF : get(input);
if (c == LF) {
headers = HttpHeaders.of(privateMap, ACCEPT_ALL);
privateMap = null;
@@ -238,7 +263,7 @@ class Http1HeaderParser {
assert state == State.HEADER;
assert input.hasRemaining();
while (input.hasRemaining()) {
- char c = (char)input.get();
+ char c = get(input);
if (c == CR) {
state = State.HEADER_FOUND_CR;
break;
@@ -253,15 +278,23 @@ class Http1HeaderParser {
}
}
- private void addHeaderFromString(String headerString) {
+ private void addHeaderFromString(String headerString) throws ProtocolException {
assert sb.length() == 0;
int idx = headerString.indexOf(':');
if (idx == -1)
return;
- String name = headerString.substring(0, idx).trim();
- if (name.isEmpty())
- return;
- String value = headerString.substring(idx + 1, headerString.length()).trim();
+ String name = headerString.substring(0, idx);
+
+ // compatibility with HttpURLConnection;
+ if (name.isEmpty()) return;
+
+ if (!Utils.isValidName(name)) {
+ throw protocolException("Invalid header name \"%s\"", name);
+ }
+ String value = headerString.substring(idx + 1).trim();
+ if (!Utils.isValidValue(value)) {
+ throw protocolException("Invalid header value \"%s: %s\"", name, value);
+ }
privateMap.computeIfAbsent(name.toLowerCase(Locale.US),
k -> new ArrayList<>()).add(value);
@@ -269,7 +302,7 @@ class Http1HeaderParser {
private void resumeOrLF(ByteBuffer input) {
assert state == State.HEADER_FOUND_CR || state == State.HEADER_FOUND_LF;
- char c = state == State.HEADER_FOUND_LF ? LF : (char)input.get();
+ char c = state == State.HEADER_FOUND_LF ? LF : get(input);
if (c == LF) {
// header value will be flushed by
// resumeOrSecondCR if next line does not
@@ -285,9 +318,9 @@ class Http1HeaderParser {
}
}
- private void resumeOrSecondCR(ByteBuffer input) {
+ private void resumeOrSecondCR(ByteBuffer input) throws ProtocolException {
assert state == State.HEADER_FOUND_CR_LF;
- char c = (char)input.get();
+ char c = get(input);
if (c == CR || c == LF) {
if (sb.length() > 0) {
// no continuation line - flush
@@ -322,7 +355,7 @@ class Http1HeaderParser {
private void resumeOrEndHeaders(ByteBuffer input) throws ProtocolException {
assert state == State.HEADER_FOUND_CR_LF_CR;
- char c = (char)input.get();
+ char c = get(input);
if (c == LF) {
state = State.FINISHED;
headers = HttpHeaders.of(privateMap, ACCEPT_ALL);
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java b/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java
index 554e35449b9..9cfc3d3f95a 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java
@@ -801,6 +801,7 @@ class Http2Connection {
try {
decodeHeaders((HeaderFrame) frame, stream.rspHeadersConsumer());
} catch (UncheckedIOException e) {
+ debug.log("Error decoding headers: " + e.getMessage(), e);
protocolError(ResetFrame.PROTOCOL_ERROR, e.getMessage());
return;
}
diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java
index 3236517de40..fb16496edfe 100644
--- a/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java
@@ -400,7 +400,7 @@ public final class Utils {
for (char c : allowedTokenChars) {
tchar[c] = true;
}
- for (char c = 0x21; c < 0xFF; c++) {
+ for (char c = 0x21; c <= 0xFF; c++) {
fieldvchar[c] = true;
}
fieldvchar[0x7F] = false; // a little hole (DEL) in the range
diff --git a/test/jdk/java/net/httpclient/HttpServerAdapters.java b/test/jdk/java/net/httpclient/HttpServerAdapters.java
index 5147afa4f32..07a836b6ec2 100644
--- a/test/jdk/java/net/httpclient/HttpServerAdapters.java
+++ b/test/jdk/java/net/httpclient/HttpServerAdapters.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -196,7 +196,7 @@ public interface HttpServerAdapters {
/**
* A version agnostic adapter class for HTTP Server Exchange.
*/
- public static abstract class HttpTestExchange {
+ public static abstract class HttpTestExchange implements AutoCloseable {
public abstract Version getServerVersion();
public abstract Version getExchangeVersion();
public abstract InputStream getRequestBody();
diff --git a/test/jdk/java/net/httpclient/ISO_8859_1_Test.java b/test/jdk/java/net/httpclient/ISO_8859_1_Test.java
new file mode 100644
index 00000000000..256e42ac30f
--- /dev/null
+++ b/test/jdk/java/net/httpclient/ISO_8859_1_Test.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8252374
+ * @library /test/lib http2/server
+ * @build jdk.test.lib.net.SimpleSSLContext HttpServerAdapters
+ * ReferenceTracker AggregateRequestBodyTest
+ * @modules java.base/sun.net.www.http
+ * java.net.http/jdk.internal.net.http.common
+ * java.net.http/jdk.internal.net.http.frame
+ * java.net.http/jdk.internal.net.http.hpack
+ * @run testng/othervm -Djdk.internal.httpclient.debug=true
+ * -Djdk.httpclient.HttpClient.log=requests,responses,errors
+ * ISO_8859_1_Test
+ * @summary Tests that a client is able to receive ISO-8859-1 encoded header values.
+ */
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URL;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublisher;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Flow.Subscriber;
+import java.util.concurrent.Flow.Subscription;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+import javax.net.ssl.SSLContext;
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import jdk.test.lib.net.SimpleSSLContext;
+import org.testng.Assert;
+import org.testng.ITestContext;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static java.lang.System.out;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.expectThrows;
+
+public class ISO_8859_1_Test implements HttpServerAdapters {
+
+ SSLContext sslContext;
+ DummyServer http1DummyServer;
+ HttpServerAdapters.HttpTestServer http1TestServer; // HTTP/1.1 ( http )
+ HttpServerAdapters.HttpTestServer https1TestServer; // HTTPS/1.1 ( https )
+ HttpServerAdapters.HttpTestServer http2TestServer; // HTTP/2 ( h2c )
+ HttpServerAdapters.HttpTestServer https2TestServer; // HTTP/2 ( h2 )
+ String http1Dummy;
+ String http1URI;
+ String https1URI;
+ String http2URI;
+ String https2URI;
+
+ static final int RESPONSE_CODE = 200;
+ static final int ITERATION_COUNT = 4;
+ static final Class