From 8c5bc182b686a8bd92e7f56536d1b145d72ec36d Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Tue, 17 Jun 2025 11:19:44 +0530 Subject: [PATCH 1/5] 8359709: introduce a test --- .../net/HttpURLConnection/HostHeaderTest.java | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 test/jdk/java/net/HttpURLConnection/HostHeaderTest.java diff --git a/test/jdk/java/net/HttpURLConnection/HostHeaderTest.java b/test/jdk/java/net/HttpURLConnection/HostHeaderTest.java new file mode 100644 index 0000000000000..99f435b1806b6 --- /dev/null +++ b/test/jdk/java/net/HttpURLConnection/HostHeaderTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2025, 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. + */ + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.util.List; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import jdk.test.lib.net.URIBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; + +/* + * @test + * @bug 8359709 + * @summary verify that if the Host header is allowed to be set by the application + * then the correct value gets set in a HTTP request issued through + * java.net.HttpURLConnection + * @library /test/lib + * @run junit HostHeaderTest + * @run junit/othervm -Dsun.net.http.allowRestrictedHeaders=true HostHeaderTest + * @run junit/othervm -Dsun.net.http.allowRestrictedHeaders=false HostHeaderTest + */ +class HostHeaderTest { + + private static final boolean allowsHostHeader = Boolean.getBoolean("sun.net.http.allowRestrictedHeaders"); + + private static HttpServer server; + + @BeforeAll + static void beforeAll() throws Exception { + final InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); + server = HttpServer.create(addr, 0); + server.createContext("/", new Handler()); + server.start(); + System.err.println("started server at " + server.getAddress()); + } + + @AfterAll + static void afterAll() throws Exception { + if (server != null) { + System.err.println("stopping server " + server.getAddress()); + server.stop(0); + } + } + + @Test + void testHostHeader() throws Exception { + final InetSocketAddress serverAddr = server.getAddress(); + final URL reqURL = URIBuilder.newBuilder() + .scheme("http") + .loopback() + .port(serverAddr.getPort()) + .path("/?testHostHeader") + .build().toURL(); + final URLConnection conn = reqURL.openConnection(Proxy.NO_PROXY); + + conn.setRequestProperty("Host", "foobar"); + if (!allowsHostHeader) { + // if restricted headers aren't allowed to be set by the user, then + // we expect the previous call to setRequestProperty to not set the Host + // header + assertNull(conn.getRequestProperty("Host"), "Host header unexpectedly set"); + } + + assertInstanceOf(HttpURLConnection.class, conn); + final HttpURLConnection httpURLConn = (HttpURLConnection) conn; + + // send the HTTP request + System.err.println("sending request " + reqURL); + final int respCode = httpURLConn.getResponseCode(); + assertEquals(200, respCode, "unexpected response code"); + // verify that the server side handler received the expected + // Host header value in the request + try (final InputStream is = httpURLConn.getInputStream()) { + final byte[] resp = is.readAllBytes(); + // if Host header wasn't explicitly set, then we expect it to be + // derived from the request URL + final String expected = allowsHostHeader + ? "foobar" + : reqURL.getHost() + ":" + reqURL.getPort(); + final String actual = new String(resp, US_ASCII); + assertEquals(expected, actual, "unexpected Host header received on server side"); + } + } + + private static final class Handler implements HttpHandler { + private static final int NO_RESPONSE_BODY = -1; + + @Override + public void handle(final HttpExchange exchange) throws IOException { + final List headerVals = exchange.getRequestHeaders().get("Host"); + System.err.println("Host header has value(s): " + headerVals); + // unexpected Host header value, respond with 400 status code + if (headerVals == null || headerVals.size() != 1) { + System.err.println("Unexpected header value(s) for Host header: " + headerVals); + exchange.sendResponseHeaders(400, NO_RESPONSE_BODY); + return; + } + // respond back with the Host header value that we found in the request + final byte[] response = headerVals.getFirst().getBytes(US_ASCII); + exchange.sendResponseHeaders(200, response.length); + try (final OutputStream os = exchange.getResponseBody()) { + os.write(response); + } + } + } +} From 6dc368589130fd0099e22122aaafc28deb77e3cc Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Tue, 17 Jun 2025 11:36:23 +0530 Subject: [PATCH 2/5] 8359709: java.net.HttpURLConnection sends unexpected "Host" request header in some cases after JDK-8344190 --- .../classes/sun/net/www/protocol/http/HttpURLConnection.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java index 5d8e6f467aa72..26609b4863c67 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java @@ -621,8 +621,9 @@ private void writeRequests() throws IOException { if (port != -1 && port != url.getDefaultPort()) { host += ":" + String.valueOf(port); } - String reqHost = requests.findValue("Host"); - if (reqHost == null || !reqHost.equalsIgnoreCase(host)) { + if (requests.findValue("Host") == null) { + // if the "Host" header hasn't been explicitly set, then set its + // value to the one determined through the request URL requests.set("Host", host); } requests.setIfNotSet("Accept", acceptString); From 5c65cc717077137f08fb4301b260825e0affa202 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Tue, 17 Jun 2025 19:14:28 +0530 Subject: [PATCH 3/5] Daniel's suggestion - use setIfNotSet --- .../sun/net/www/protocol/http/HttpURLConnection.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java index 26609b4863c67..b66d9ddb8cba2 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java @@ -621,11 +621,10 @@ private void writeRequests() throws IOException { if (port != -1 && port != url.getDefaultPort()) { host += ":" + String.valueOf(port); } - if (requests.findValue("Host") == null) { - // if the "Host" header hasn't been explicitly set, then set its - // value to the one determined through the request URL - requests.set("Host", host); - } + // if the "Host" header hasn't been explicitly set, then set its + // value to the one determined through the request URL + requests.setIfNotSet("Host", host); + requests.setIfNotSet("Accept", acceptString); /* From 666f29b518411569004536905b985e0dd18240fc Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Tue, 17 Jun 2025 19:16:16 +0530 Subject: [PATCH 4/5] Volkan's suggestion - use test specific context root for the handler --- test/jdk/java/net/HttpURLConnection/HostHeaderTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/jdk/java/net/HttpURLConnection/HostHeaderTest.java b/test/jdk/java/net/HttpURLConnection/HostHeaderTest.java index 99f435b1806b6..96966105b368c 100644 --- a/test/jdk/java/net/HttpURLConnection/HostHeaderTest.java +++ b/test/jdk/java/net/HttpURLConnection/HostHeaderTest.java @@ -57,6 +57,7 @@ */ class HostHeaderTest { + private static final String SERVER_CTX_ROOT = "/8359709"; private static final boolean allowsHostHeader = Boolean.getBoolean("sun.net.http.allowRestrictedHeaders"); private static HttpServer server; @@ -65,7 +66,7 @@ class HostHeaderTest { static void beforeAll() throws Exception { final InetSocketAddress addr = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); server = HttpServer.create(addr, 0); - server.createContext("/", new Handler()); + server.createContext(SERVER_CTX_ROOT, new Handler()); server.start(); System.err.println("started server at " + server.getAddress()); } @@ -85,7 +86,7 @@ void testHostHeader() throws Exception { .scheme("http") .loopback() .port(serverAddr.getPort()) - .path("/?testHostHeader") + .path(SERVER_CTX_ROOT) .build().toURL(); final URLConnection conn = reqURL.openConnection(Proxy.NO_PROXY); From 419e86bed9aa7517a03dd2e05d7e3e0036883f66 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Wed, 18 Jun 2025 07:09:30 +0530 Subject: [PATCH 5/5] Daniel's suggestion - use trailing slash for path registered for the handler --- test/jdk/java/net/HttpURLConnection/HostHeaderTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/net/HttpURLConnection/HostHeaderTest.java b/test/jdk/java/net/HttpURLConnection/HostHeaderTest.java index 96966105b368c..329f0eedaf950 100644 --- a/test/jdk/java/net/HttpURLConnection/HostHeaderTest.java +++ b/test/jdk/java/net/HttpURLConnection/HostHeaderTest.java @@ -57,7 +57,7 @@ */ class HostHeaderTest { - private static final String SERVER_CTX_ROOT = "/8359709"; + private static final String SERVER_CTX_ROOT = "/8359709/"; private static final boolean allowsHostHeader = Boolean.getBoolean("sun.net.http.allowRestrictedHeaders"); private static HttpServer server;