diff --git a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WebServerDTO.java b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WebServerDTO.java index 8f8bbeaeef..b118c708ad 100644 --- a/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WebServerDTO.java +++ b/artemis-dto/src/main/java/org/apache/activemq/artemis/dto/WebServerDTO.java @@ -107,6 +107,12 @@ public class WebServerDTO extends ComponentDTO { @XmlAttribute public Integer scanPeriod; + @XmlAttribute + public Integer maxRequestHeaderSize; + + @XmlAttribute + public Integer maxResponseHeaderSize; + public String getPath() { return path; } diff --git a/artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponent.java b/artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponent.java index 4b77d51641..53470e7678 100644 --- a/artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponent.java +++ b/artemis-web/src/main/java/org/apache/activemq/artemis/component/WebServerComponent.java @@ -136,6 +136,14 @@ public class WebServerComponent implements ExternalComponent, WebServerComponent HttpConfiguration httpConfiguration = new HttpConfiguration(); + if (webServerConfig.maxRequestHeaderSize != null) { + httpConfiguration.setRequestHeaderSize(webServerConfig.maxRequestHeaderSize); + } + + if (webServerConfig.maxResponseHeaderSize != null) { + httpConfiguration.setResponseHeaderSize(webServerConfig.maxResponseHeaderSize); + } + if (this.webServerConfig.customizer != null) { try { httpConfiguration.addCustomizer((HttpConfiguration.Customizer) ClassloadingUtil.getInstanceWithTypeCheck(this.webServerConfig.customizer, HttpConfiguration.Customizer.class, this.getClass().getClassLoader())); diff --git a/docs/user-manual/web-server.adoc b/docs/user-manual/web-server.adoc index 0a17a8d67b..672bf208c1 100644 --- a/docs/user-manual/web-server.adoc +++ b/docs/user-manual/web-server.adoc @@ -45,6 +45,14 @@ idleThreadTimeout:: The time to wait before terminating an idle thread from the embedded web server. Measured in milliseconds. Default is `60000`. scanPeriod:: How often to scan for changes of the key and trust store files related to a binding when the `sslAutoReload` attribute value of the `binding` element is `true`, for further details see <>. Measured in seconds. Default is `5`. +maxRequestHeaderSize:: +The maximum allowed size for the HTTP request line and HTTP request headers. +Measured in bytes. +Default is `8192`. +maxResponseHeaderSize:: +The maximum allowed size for the HTTP response headers. +Measured in bytes. +Default is `8192`. === Binding diff --git a/tests/integration-tests-isolated/src/test/java/org/apache/activemq/artemis/tests/integration/isolated/web/WebServerComponentTest.java b/tests/integration-tests-isolated/src/test/java/org/apache/activemq/artemis/tests/integration/isolated/web/WebServerComponentTest.java index cd0dea2824..9e6345df5c 100644 --- a/tests/integration-tests-isolated/src/test/java/org/apache/activemq/artemis/tests/integration/isolated/web/WebServerComponentTest.java +++ b/tests/integration-tests-isolated/src/test/java/org/apache/activemq/artemis/tests/integration/isolated/web/WebServerComponentTest.java @@ -56,6 +56,8 @@ import org.apache.activemq.artemis.core.server.ActiveMQComponent; import org.apache.activemq.artemis.dto.BindingDTO; import org.apache.activemq.artemis.dto.RequestLogDTO; import org.apache.activemq.artemis.dto.WebServerDTO; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -143,6 +145,75 @@ public class WebServerComponentTest { assertTrue(logEntryFound); } + @Test + public void testLargeRequestHeader() throws Exception { + testLargeRequestHeader(false); + } + + @Test + public void testLargeRequestHeaderNegative() throws Exception { + testLargeRequestHeader(true); + } + + private void testLargeRequestHeader(boolean fail) throws Exception { + final int defaultRequestHeaderSize = new HttpConfiguration().getRequestHeaderSize(); + BindingDTO bindingDTO = new BindingDTO(); + bindingDTO.uri = "http://localhost:0"; + WebServerDTO webServerDTO = new WebServerDTO(); + webServerDTO.setBindings(Collections.singletonList(bindingDTO)); + webServerDTO.path = "webapps"; + webServerDTO.webContentEnabled = true; + if (!fail) { + webServerDTO.maxRequestHeaderSize = defaultRequestHeaderSize * 2; + } + WebServerComponent webServerComponent = new WebServerComponent(); + assertFalse(webServerComponent.isStarted()); + webServerComponent.configure(webServerDTO, "./src/test/resources/", "./src/test/resources/"); + testedComponents.add(webServerComponent); + webServerComponent.start(); + + final int port = webServerComponent.getPort(); + // Make the connection attempt. + CountDownLatch latch = new CountDownLatch(1); + final ClientHandler clientHandler = new ClientHandler(latch); + Channel ch = getChannel(port, clientHandler); + + URI uri = new URI(URL); + // Prepare the HTTP request. + HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.getRawPath()); + request.headers().set(HttpHeaderNames.HOST, "localhost"); + StringBuilder foo = new StringBuilder(); + for (int i = 0; i < defaultRequestHeaderSize + 1; i++) { + foo.append("a"); + } + request.headers().set("foo", foo.toString()); + + // Send the HTTP request. + ch.writeAndFlush(request); + assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertEquals(fail, clientHandler.body.toString().contains("431")); + } + + /* It's not clear how to create a functional test for the response header size so this test simply ensures the + * configuration is passed through as expected. + */ + @Test + public void testLargeResponseHeaderConfiguration() throws Exception { + BindingDTO bindingDTO = new BindingDTO(); + bindingDTO.uri = "http://localhost:0"; + WebServerDTO webServerDTO = new WebServerDTO(); + webServerDTO.setBindings(Collections.singletonList(bindingDTO)); + webServerDTO.path = "webapps"; + webServerDTO.webContentEnabled = true; + webServerDTO.maxResponseHeaderSize = 123; + WebServerComponent webServerComponent = new WebServerComponent(); + assertFalse(webServerComponent.isStarted()); + webServerComponent.configure(webServerDTO, "./src/test/resources/", "./src/test/resources/"); + testedComponents.add(webServerComponent); + webServerComponent.start(); + assertEquals(123, ((HttpConnectionFactory)webServerComponent.getWebServer().getConnectors()[0].getConnectionFactories().iterator().next()).getHttpConfiguration().getResponseHeaderSize()); + } + private Channel getChannel(int port, ClientHandler clientHandler) throws InterruptedException { EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap();