diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index 86bdbe7d2d82..387c8db7feca 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -135,6 +135,7 @@ public class HttpClient extends ContainerLifeCycle implements AutoCloseable private String defaultRequestContentType = "application/octet-stream"; private boolean useInputDirectByteBuffers = true; private boolean useOutputDirectByteBuffers = true; + private int maxRequestHeadersSize = 8192; private int maxResponseHeadersSize = -1; private Sweeper destinationSweeper; @@ -1071,6 +1072,24 @@ public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers) this.useInputDirectByteBuffers = useInputDirectByteBuffers; } + /** + * @return the max size in bytes of the request headers + */ + @ManagedAttribute("The max size in bytes of the request headers") + public int getMaxRequestHeadersSize() + { + return maxRequestHeadersSize; + } + + /** + * Set the max size in bytes of the request headers. + * @param maxRequestHeadersSize the max size in bytes of the request headers + */ + public void setMaxRequestHeadersSize(int maxRequestHeadersSize) + { + this.maxRequestHeadersSize = maxRequestHeadersSize; + } + /** * @return whether to use direct ByteBuffers for writing */ diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/internal/HttpSenderOverHTTP.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/internal/HttpSenderOverHTTP.java index 39a7787ace58..8edfea405d76 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/internal/HttpSenderOverHTTP.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/internal/HttpSenderOverHTTP.java @@ -50,6 +50,7 @@ public class HttpSenderOverHTTP extends HttpSender public HttpSenderOverHTTP(HttpChannelOverHTTP channel) { super(channel); + generator.setMaxHeaderBytes(channel.getHttpDestination().getHttpClient().getMaxRequestHeadersSize()); } @Override @@ -158,6 +159,7 @@ protected Action process() throws Exception HttpClient httpClient = getHttpChannel().getHttpDestination().getHttpClient(); HttpExchange exchange = getHttpExchange(); ByteBufferPool bufferPool = httpClient.getByteBufferPool(); + int requestHeadersSize = httpClient.getRequestBufferSize(); boolean useDirectByteBuffers = httpClient.isUseOutputDirectByteBuffers(); while (true) { @@ -174,14 +176,24 @@ protected Action process() throws Exception { case NEED_HEADER: { - headerBuffer = bufferPool.acquire(httpClient.getRequestBufferSize(), useDirectByteBuffers); + headerBuffer = bufferPool.acquire(requestHeadersSize, useDirectByteBuffers); break; } case HEADER_OVERFLOW: { - headerBuffer.release(); - headerBuffer = null; - throw new IllegalArgumentException("Request header too large"); + int maxRequestHeadersSize = httpClient.getMaxRequestHeadersSize(); + if (maxRequestHeadersSize > requestHeadersSize) + { + generator.reset(); + headerBuffer.release(); + headerBuffer = bufferPool.acquire(maxRequestHeadersSize, useDirectByteBuffers); + requestHeadersSize = maxRequestHeadersSize; + break; + } + else + { + throw new IllegalArgumentException("Request headers too large"); + } } case NEED_CHUNK: { diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index f76edcfd7e4b..785115587631 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -59,8 +59,10 @@ import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.Content; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.RetainableByteBuffer; import org.eclipse.jetty.logging.StacklessLogging; import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.internal.HttpChannelState; import org.eclipse.jetty.toolchain.test.Net; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; @@ -1936,6 +1938,48 @@ protected void service(org.eclipse.jetty.server.Request request, org.eclipse.jet assertEquals(HttpStatus.OK_200, response.getStatus()); } + @ParameterizedTest + @ArgumentsSource(ScenarioProvider.class) + public void testRequestHeadersSizeOverflow(Scenario scenario) throws Exception + { + start(scenario, new EmptyServerHandler()); + + RetainableByteBuffer.Mutable buffer = client.getByteBufferPool().acquire(client.getRequestBufferSize(), false); + int capacity = buffer.capacity(); + buffer.release(); + client.setMaxRequestHeadersSize(3 * capacity); + connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setRequestHeaderSize(3 * capacity); + + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scenario.getScheme()) + // Overflow the request headers size, but don't exceed the max. + .agent("A".repeat(2 * capacity)) + .timeout(5, TimeUnit.SECONDS) + .send(); + + assertEquals(HttpStatus.OK_200, response.getStatus()); + } + + @ParameterizedTest + @ArgumentsSource(ScenarioProvider.class) + public void testMaxRequestHeadersSize(Scenario scenario) throws Exception + { + start(scenario, new EmptyServerHandler()); + + RetainableByteBuffer.Mutable buffer = client.getByteBufferPool().acquire(client.getRequestBufferSize(), false); + int capacity = buffer.capacity(); + buffer.release(); + client.setMaxRequestHeadersSize(2 * capacity); + connector.getBean(HttpConnectionFactory.class).getHttpConfiguration().setRequestHeaderSize(4 * capacity); + + assertThrows(ExecutionException.class, () -> client.newRequest("localhost", connector.getLocalPort()) + .scheme(scenario.getScheme()) + // Overflow the max request headers size. + .agent("A".repeat(3 * capacity)) + .timeout(5, TimeUnit.SECONDS) + .send()); + } + private void assertCopyRequest(Request original) { Request copy = client.copyRequest(original, original.getURI()); diff --git a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java index d87442ab436c..72b2a4367cf9 100644 --- a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java +++ b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java @@ -103,7 +103,7 @@ static void configure(HttpClient httpClient, HTTP2Client http2Client) http2Client.setUseOutputDirectByteBuffers(httpClient.isUseOutputDirectByteBuffers()); http2Client.setConnectBlocking(httpClient.isConnectBlocking()); http2Client.setBindAddress(httpClient.getBindAddress()); - http2Client.setMaxRequestHeadersSize(httpClient.getRequestBufferSize()); + http2Client.setMaxRequestHeadersSize(httpClient.getMaxRequestHeadersSize()); http2Client.setMaxResponseHeadersSize(httpClient.getMaxResponseHeadersSize()); } diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java index 0bd1df1c8c25..f6b936ece250 100644 --- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java +++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/HttpClientTransportOverHTTP2Test.java @@ -145,7 +145,7 @@ private void testPropertiesAreForwarded(HTTP2Client http2Client, HttpClientTrans assertEquals(httpClient.getIdleTimeout(), http2Client.getIdleTimeout()); assertEquals(httpClient.isUseInputDirectByteBuffers(), http2Client.isUseInputDirectByteBuffers()); assertEquals(httpClient.isUseOutputDirectByteBuffers(), http2Client.isUseOutputDirectByteBuffers()); - assertEquals(httpClient.getRequestBufferSize(), http2Client.getMaxRequestHeadersSize()); + assertEquals(httpClient.getMaxRequestHeadersSize(), http2Client.getMaxRequestHeadersSize()); assertEquals(httpClient.getMaxResponseHeadersSize(), http2Client.getMaxResponseHeadersSize()); } assertTrue(http2Client.isStopped()); diff --git a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/HttpClientTransportOverHTTP3.java b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/HttpClientTransportOverHTTP3.java index c828c3028957..58eee23d0016 100644 --- a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/HttpClientTransportOverHTTP3.java +++ b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/HttpClientTransportOverHTTP3.java @@ -83,6 +83,7 @@ static void configure(HttpClient httpClient, HTTP3Client http3Client) configuration.setInputBufferSize(httpClient.getResponseBufferSize()); configuration.setUseInputDirectByteBuffers(httpClient.isUseInputDirectByteBuffers()); configuration.setUseOutputDirectByteBuffers(httpClient.isUseOutputDirectByteBuffers()); + configuration.setMaxRequestHeadersSize(httpClient.getMaxRequestHeadersSize()); configuration.setMaxResponseHeadersSize(httpClient.getMaxResponseHeadersSize()); } diff --git a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HttpClientTransportOverHTTP3Test.java b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HttpClientTransportOverHTTP3Test.java index efb7c3442470..9a457a8c6393 100644 --- a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HttpClientTransportOverHTTP3Test.java +++ b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/HttpClientTransportOverHTTP3Test.java @@ -92,6 +92,7 @@ private void testPropertiesAreForwarded(HTTP3Client http3Client, HttpClientTrans HTTP3Configuration http3Configuration = http3Client.getHTTP3Configuration(); assertEquals(httpClient.isUseInputDirectByteBuffers(), http3Configuration.isUseInputDirectByteBuffers()); assertEquals(httpClient.isUseOutputDirectByteBuffers(), http3Configuration.isUseOutputDirectByteBuffers()); + assertEquals(httpClient.getMaxRequestHeadersSize(), http3Configuration.getMaxRequestHeadersSize()); assertEquals(httpClient.getMaxResponseHeadersSize(), http3Configuration.getMaxResponseHeadersSize()); } assertTrue(http3Client.isStopped());