undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
312
test/jdk/java/net/httpclient/http3/H3HeadersEncoding.java
Normal file
312
test/jdk/java/net/httpclient/http3/H3HeadersEncoding.java
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
* Copyright (c) 2024, 2026, 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
|
||||
* @library /test/lib /test/jdk/java/net/httpclient/lib
|
||||
* @build jdk.httpclient.test.lib.common.HttpServerAdapters
|
||||
* jdk.test.lib.net.SimpleSSLContext
|
||||
* @compile ../ReferenceTracker.java
|
||||
* @run junit/othervm -Djdk.httpclient.qpack.encoderTableCapacityLimit=4096
|
||||
* -Djdk.httpclient.qpack.decoderMaxTableCapacity=4096
|
||||
* -Dhttp3.test.server.encoderAllowedHeaders=*
|
||||
* -Dhttp3.test.server.decoderMaxTableCapacity=4096
|
||||
* -Dhttp3.test.server.encoderTableCapacityLimit=4096
|
||||
* -Djdk.internal.httpclient.qpack.log.level=NORMAL
|
||||
* H3HeadersEncoding
|
||||
* @summary this test verifies that when QPACK dynamic table is enabled multiple
|
||||
* random headers can be encoded/decoded correctly
|
||||
*/
|
||||
|
||||
import jdk.test.lib.RandomFactory;
|
||||
import jdk.test.lib.net.SimpleSSLContext;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpHeaders;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpOption.Http3DiscoveryMode;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.net.http.HttpClient.Version.HTTP_3;
|
||||
import static java.net.http.HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY;
|
||||
import static java.net.http.HttpOption.H3_DISCOVERY;
|
||||
import static jdk.httpclient.test.lib.common.HttpServerAdapters.*;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class H3HeadersEncoding {
|
||||
|
||||
private static final int REQUESTS_COUNT = 500;
|
||||
private static final int HEADERS_PER_REQUEST = 20;
|
||||
private static final SSLContext sslContext = SimpleSSLContext.findSSLContext();
|
||||
private static HttpTestServer http3TestServer;
|
||||
private static HeadersHandler serverHeadersHandler;
|
||||
private static String http3URI;
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() throws Exception {
|
||||
System.out.println("Creating servers");
|
||||
http3TestServer = HttpTestServer.create(Http3DiscoveryMode.HTTP_3_URI_ONLY, sslContext);
|
||||
serverHeadersHandler = new HeadersHandler();
|
||||
http3TestServer.addHandler(serverHeadersHandler, "/http3/headers");
|
||||
http3URI = "https://" + http3TestServer.serverAuthority() + "/http3/headers";
|
||||
|
||||
http3TestServer.start();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void tearDown() {
|
||||
http3TestServer.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serialRequests() throws Exception {
|
||||
try (HttpClient client = newClient()) {
|
||||
for (int i = 0; i < REQUESTS_COUNT; i++) {
|
||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(URI.create(http3URI))
|
||||
.version(HTTP_3)
|
||||
.setOption(H3_DISCOVERY, HTTP_3_URI_ONLY);
|
||||
List<TestHeader> rndHeaders = TestHeader.randomHeaders(HEADERS_PER_REQUEST);
|
||||
String[] requestHeaders = rndHeaders.stream()
|
||||
.flatMap(th -> Stream.of(th.name(), th.value()))
|
||||
.toArray(String[]::new);
|
||||
requestBuilder.headers(requestHeaders);
|
||||
|
||||
// Send client request headers in request body for further check
|
||||
// on the server handler side
|
||||
requestBuilder.POST(HttpRequest.BodyPublishers.ofString(
|
||||
TestHeader.headersToBodyContent(rndHeaders)));
|
||||
|
||||
HttpResponse<String> response =
|
||||
client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
|
||||
// Headers received by the client
|
||||
var serverHeadersClientSide = TestHeader.fromHttpHeaders(response.headers());
|
||||
// Headers sent by the server handler
|
||||
var serverHeadersServerSide = TestHeader.bodyContentToHeaders(response.body());
|
||||
|
||||
// Check that all headers that server sent are received on client side
|
||||
checkHeaders(serverHeadersServerSide, serverHeadersClientSide, "client");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asyncRequests() throws Exception {
|
||||
try (HttpClient client = newClient()) {
|
||||
ArrayList<List<TestHeader>> requestsHeaders = new ArrayList<>(REQUESTS_COUNT);
|
||||
ArrayList<HttpRequest> requests = new ArrayList<>(REQUESTS_COUNT);
|
||||
CopyOnWriteArrayList<CompletableFuture<HttpResponse<String>>> futureReplies
|
||||
= new CopyOnWriteArrayList<>();
|
||||
|
||||
// Prepare all requests first
|
||||
for (int i = 0; i < REQUESTS_COUNT; i++) {
|
||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(URI.create(http3URI))
|
||||
.version(HTTP_3)
|
||||
.setOption(H3_DISCOVERY, HTTP_3_URI_ONLY);
|
||||
List<TestHeader> rndHeaders = TestHeader.randomHeaders(HEADERS_PER_REQUEST);
|
||||
String[] requestHeaders = rndHeaders.stream()
|
||||
.flatMap(th -> Stream.of(th.name(), th.value()))
|
||||
.toArray(String[]::new);
|
||||
requestBuilder.headers(requestHeaders);
|
||||
requestsHeaders.add(i, rndHeaders);
|
||||
|
||||
// Send client request headers in request body for further check
|
||||
// on the server handler side
|
||||
requestBuilder.POST(HttpRequest.BodyPublishers.ofString(
|
||||
TestHeader.headersToBodyContent(rndHeaders)));
|
||||
requests.add(i, requestBuilder.build());
|
||||
}
|
||||
|
||||
// Send async request
|
||||
for (int i = 0; i < REQUESTS_COUNT; i++) {
|
||||
CompletableFuture<HttpResponse<String>> cf =
|
||||
client.sendAsync(requests.get(i),
|
||||
HttpResponse.BodyHandlers.ofString());
|
||||
futureReplies.add(i, cf);
|
||||
}
|
||||
|
||||
// Join on all CF responses
|
||||
for (int i = 0; i < REQUESTS_COUNT; i++) {
|
||||
futureReplies.get(i).join();
|
||||
}
|
||||
|
||||
// Check the responses
|
||||
for (int i = 0; i < REQUESTS_COUNT; i++) {
|
||||
HttpResponse<String> reply = futureReplies.get(i).get();
|
||||
// Headers received by the client
|
||||
var serverHeadersClientSide = TestHeader.fromHttpHeaders(reply.headers());
|
||||
// Headers sent by the server handler
|
||||
var serverHeadersServerSide = TestHeader.bodyContentToHeaders(reply.body());
|
||||
// Check that all headers that server sent are received on client side
|
||||
checkHeaders(serverHeadersServerSide, serverHeadersClientSide, "client");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void checkHeaders(List<TestHeader> mustPresent,
|
||||
List<TestHeader> allHeaders,
|
||||
String sideDescription) {
|
||||
List<TestHeader> notFound = new ArrayList<>();
|
||||
for (var header : mustPresent) {
|
||||
if (!allHeaders.contains(header)) {
|
||||
notFound.add(header);
|
||||
}
|
||||
}
|
||||
if (!notFound.isEmpty()) {
|
||||
System.err.println("The following headers was not found on "
|
||||
+ sideDescription + " side: " + notFound);
|
||||
throw new RuntimeException("Headers not found: " + notFound);
|
||||
}
|
||||
}
|
||||
|
||||
HttpClient newClient() {
|
||||
var builder = createClientBuilderForH3()
|
||||
.sslContext(sslContext)
|
||||
.version(HTTP_3)
|
||||
.proxy(HttpClient.Builder.NO_PROXY);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static final Random RND = RandomFactory.getRandom();
|
||||
|
||||
record TestHeader(String name, String value) {
|
||||
static TestHeader randomHeader() {
|
||||
// It is better to have same id generated two or more
|
||||
// times during the test run, therefore the range below
|
||||
int headerId = RND.nextInt(10, 10 + HEADERS_PER_REQUEST * 3);
|
||||
return new TestHeader("test_header" + headerId, "TestValue");
|
||||
}
|
||||
|
||||
static List<TestHeader> randomHeaders(int count) {
|
||||
return IntStream
|
||||
.range(0, count)
|
||||
.boxed()
|
||||
.map(ign -> TestHeader.randomHeader())
|
||||
.toList();
|
||||
}
|
||||
|
||||
static List<TestHeader> fromHttpHeaders(HttpHeaders httpHeaders) {
|
||||
var headersMap = httpHeaders.map();
|
||||
return fromHeadersEntrySet(headersMap.entrySet());
|
||||
}
|
||||
|
||||
static List<TestHeader> fromTestHttpHeaders(HttpTestRequestHeaders requestHeaders) {
|
||||
var headersSet = requestHeaders.entrySet();
|
||||
return fromHeadersEntrySet(headersSet);
|
||||
}
|
||||
|
||||
private static List<TestHeader> fromHeadersEntrySet(Set<Map.Entry<String, List<String>>> entrySet) {
|
||||
return entrySet.stream()
|
||||
.flatMap(entry -> {
|
||||
var name = entry.getKey();
|
||||
return entry.getValue()
|
||||
.stream()
|
||||
.map(value -> new TestHeader(name, value));
|
||||
}).toList();
|
||||
}
|
||||
|
||||
public static String headersToBodyContent(List<TestHeader> rndHeaders) {
|
||||
return rndHeaders.stream()
|
||||
.map(TestHeader::toString)
|
||||
.collect(Collectors.joining(System.lineSeparator()));
|
||||
}
|
||||
|
||||
public static List<TestHeader> bodyContentToHeaders(String bodyContent) {
|
||||
return Arrays.stream(bodyContent.split(System.lineSeparator()))
|
||||
.filter(Predicate.not(String::isBlank))
|
||||
.map(String::strip)
|
||||
.map(TestHeader::fromBodyHeaderLine)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public static TestHeader fromBodyHeaderLine(String headerLine) {
|
||||
String[] parts = headerLine.split(":");
|
||||
if (parts.length != 2) {
|
||||
throw new RuntimeException("Internal test error");
|
||||
}
|
||||
return new TestHeader(parts[0], parts[1]);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return name + ":" + value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class HeadersHandler implements HttpTestHandler {
|
||||
@Override
|
||||
public void handle(HttpTestExchange t) throws IOException {
|
||||
|
||||
var clientHeadersServerSide = TestHeader.fromTestHttpHeaders(t.getRequestHeaders());
|
||||
|
||||
String requestBody;
|
||||
try (InputStream is = t.getRequestBody()) {
|
||||
byte[] body = is.readAllBytes();
|
||||
requestBody = new String(body);
|
||||
}
|
||||
var clientHeadersClientSide = TestHeader.bodyContentToHeaders(requestBody);
|
||||
|
||||
// Check that all headers that client sent are received on tne server side
|
||||
checkHeaders(clientHeadersClientSide, clientHeadersServerSide,
|
||||
"server handler");
|
||||
|
||||
// Response back with a set of random headers
|
||||
var responseHeaders = t.getResponseHeaders();
|
||||
List<TestHeader> serverResp = new ArrayList<>();
|
||||
for (TestHeader h : TestHeader.randomHeaders(HEADERS_PER_REQUEST)) {
|
||||
serverResp.add(h);
|
||||
responseHeaders.addHeader(h.name(), h.value());
|
||||
}
|
||||
|
||||
String responseBody = TestHeader.headersToBodyContent(serverResp);
|
||||
try (OutputStream os = t.getResponseBody()) {
|
||||
byte[] responseBodyBytes = responseBody.getBytes();
|
||||
t.sendResponseHeaders(200, responseBodyBytes.length);
|
||||
if (!t.getRequestMethod().equals("HEAD")) {
|
||||
os.write(responseBodyBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue