diff --git a/core/src/main/java/io/undertow/server/protocol/http/HttpTransferEncoding.java b/core/src/main/java/io/undertow/server/protocol/http/HttpTransferEncoding.java index 4a89dff151..44612d666a 100644 --- a/core/src/main/java/io/undertow/server/protocol/http/HttpTransferEncoding.java +++ b/core/src/main/java/io/undertow/server/protocol/http/HttpTransferEncoding.java @@ -18,10 +18,7 @@ package io.undertow.server.protocol.http; -import java.io.IOException; - import io.undertow.UndertowLogger; -import io.undertow.UndertowMessages; import io.undertow.UndertowOptions; import io.undertow.conduits.ChunkedStreamSinkConduit; import io.undertow.conduits.ChunkedStreamSourceConduit; @@ -64,7 +61,7 @@ class HttpTransferEncoding { private HttpTransferEncoding() { } - public static void setupRequest(final HttpServerExchange exchange) throws IOException { + public static void setupRequest(final HttpServerExchange exchange) { final HeaderMap requestHeaders = exchange.getRequestHeaders(); final String connectionHeader = requestHeaders.getFirst(Headers.CONNECTION); final String transferEncodingHeader = requestHeaders.getLast(Headers.TRANSFER_ENCODING); @@ -106,7 +103,7 @@ public static void setupRequest(final HttpServerExchange exchange) throws IOExce } - private static boolean handleRequestEncoding(final HttpServerExchange exchange, String transferEncodingHeader, String contentLengthHeader, HttpServerConnection connection, PipeliningBufferingStreamSinkConduit pipeliningBuffer, boolean persistentConnection) throws IOException { + private static boolean handleRequestEncoding(final HttpServerExchange exchange, String transferEncodingHeader, String contentLengthHeader, HttpServerConnection connection, PipeliningBufferingStreamSinkConduit pipeliningBuffer, boolean persistentConnection) { HttpString transferEncoding = Headers.IDENTITY; if (transferEncodingHeader != null) { @@ -123,15 +120,9 @@ private static boolean handleRequestEncoding(final HttpServerExchange exchange, // no content - immediately start the next request, returning an empty stream for this one Connectors.terminateRequest(exchange); } else { - if (exchange.getMaxEntitySize() > 0 && exchange.getMaxEntitySize() < contentLength && exchange.isResponseChannelAvailable()) { - persistentConnection = false; - Connectors.terminateRequest(exchange); - throw UndertowMessages.MESSAGES.requestEntityWasTooLarge(exchange.getMaxEntitySize()); - } else { - // fixed-length content - add a wrapper for a fixed-length stream - ConduitStreamSourceChannel sourceChannel = ((HttpServerConnection) exchange.getConnection()).getChannel().getSourceChannel(); - sourceChannel.setConduit(fixedLengthStreamSourceConduitWrapper(contentLength, sourceChannel.getConduit(), exchange)); - } + // fixed-length content - add a wrapper for a fixed-length stream + ConduitStreamSourceChannel sourceChannel = ((HttpServerConnection) exchange.getConnection()).getChannel().getSourceChannel(); + sourceChannel.setConduit(fixedLengthStreamSourceConduitWrapper(contentLength, sourceChannel.getConduit(), exchange)); } } else if (transferEncodingHeader != null) { //identity transfer encoding diff --git a/core/src/test/java/io/undertow/server/MaxRequestSizeTestCase.java b/core/src/test/java/io/undertow/server/MaxRequestSizeTestCase.java index 59db5928ac..ce3df141a6 100644 --- a/core/src/test/java/io/undertow/server/MaxRequestSizeTestCase.java +++ b/core/src/test/java/io/undertow/server/MaxRequestSizeTestCase.java @@ -121,7 +121,7 @@ public void testMaxRequestEntitySize() throws IOException { post = new HttpPost(DefaultServer.getDefaultServerURL() + "/notamatchingpath"); post.setEntity(new StringEntity(A_MESSAGE)); result = client.execute(post); - Assert.assertEquals(StatusCodes.BAD_REQUEST, result.getStatusLine().getStatusCode()); + Assert.assertEquals(StatusCodes.INTERNAL_SERVER_ERROR, result.getStatusLine().getStatusCode()); HttpClientUtils.readResponse(result); maxSize = OptionMap.create(UndertowOptions.MAX_HEADER_SIZE, 1000); diff --git a/servlet/src/main/java/io/undertow/servlet/handlers/ServletInitialHandler.java b/servlet/src/main/java/io/undertow/servlet/handlers/ServletInitialHandler.java index 0dbc9114d7..efa1534c39 100644 --- a/servlet/src/main/java/io/undertow/servlet/handlers/ServletInitialHandler.java +++ b/servlet/src/main/java/io/undertow/servlet/handlers/ServletInitialHandler.java @@ -20,6 +20,7 @@ import io.undertow.UndertowLogger; import io.undertow.UndertowMessages; +import io.undertow.server.Connectors; import io.undertow.server.DefaultByteBufferPool; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; @@ -136,6 +137,7 @@ public Object call(HttpServerExchange exchange, ServletRequestContext context) t @Override public void handleRequest(final HttpServerExchange exchange) throws Exception { final String path = exchange.getRelativePath(); + final long contentLength = exchange.getRequestContentLength(); if (Paths.isForbidden(path)) { exchange.setStatusCode(StatusCodes.NOT_FOUND); return; @@ -154,6 +156,11 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception { if (info.getServletChain().getManagedServlet().getMaxMultipartRequestSize() > 0 && exchange.isMultiPartExchange()) { exchange.setMaxEntitySize(info.getServletChain().getManagedServlet().getMaxMultipartRequestSize()); } + if (contentLength > 0 && exchange.getMaxEntitySize() > 0 && exchange.getMaxEntitySize() < contentLength && exchange.isResponseChannelAvailable()) { + exchange.setPersistent(false); + Connectors.terminateRequest(exchange); + throw UndertowMessages.MESSAGES.requestEntityWasTooLarge(exchange.getMaxEntitySize()); + } exchange.putAttachment(ServletRequestContext.ATTACHMENT_KEY, servletRequestContext); exchange.startBlocking(new ServletBlockingHttpExchange(exchange)); diff --git a/servlet/src/test/java/io/undertow/servlet/test/multipart/MultiPartTestCase.java b/servlet/src/test/java/io/undertow/servlet/test/multipart/MultiPartTestCase.java index 7c63de3e22..8ba708e227 100644 --- a/servlet/src/test/java/io/undertow/servlet/test/multipart/MultiPartTestCase.java +++ b/servlet/src/test/java/io/undertow/servlet/test/multipart/MultiPartTestCase.java @@ -25,6 +25,7 @@ import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; +import io.undertow.UndertowOptions; import io.undertow.servlet.ServletExtension; import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; @@ -47,6 +48,7 @@ import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; +import org.xnio.OptionMap; import static io.undertow.servlet.Servlets.multipartConfig; import static io.undertow.servlet.Servlets.servlet; @@ -79,7 +81,10 @@ public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servl .setMultipartConfig(multipartConfig(null, 0, 3, 0)), servlet("mp3", MultiPartServlet.class) .addMapping("/3") - .setMultipartConfig(multipartConfig(null, 3, 0, 0))); + .setMultipartConfig(multipartConfig(null, 3, 0, 0)), + servlet("mp4", MultiPartServlet.class) + .addMapping("/4") + .setMultipartConfig(multipartConfig(null, 0, 1000000, 0))); } @Test @@ -192,8 +197,7 @@ public void testMultiPartRequestToLarge() throws IOException { post.setEntity(entity); HttpResponse result = client.execute(post); - final String response = HttpClientUtils.readResponse(result); - Assert.assertEquals("EXCEPTION: class java.lang.IllegalStateException", response); + Assert.assertEquals(StatusCodes.INTERNAL_SERVER_ERROR, result.getStatusLine().getStatusCode()); } catch (IOException expected) { //in some environments the forced close of the read side will cause a connection reset }finally { @@ -295,6 +299,50 @@ public void testMultiPartRequestBigPostForm() throws IOException { } } + @Test + public void testMultiPartRequestSizeLimitOverridesServerEntitySize() throws IOException { + OptionMap existing = DefaultServer.getUndertowOptions(); + TestHttpClient client = new TestHttpClient(); + try { + String uri = DefaultServer.getDefaultServerURL() + "/servletContext/4"; + HttpPost post = new HttpPost(uri); + + MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, null, StandardCharsets.UTF_8); + + entity.addPart("formValue", new StringBody("myValue", "text/plain", StandardCharsets.UTF_8)); + entity.addPart("file", new FileBody(new File(MultiPartTestCase.class.getResource("uploadfile.txt").getFile()))); + + // This limit should not apply with the set multipart max-request-size. We surely fail if it does. + OptionMap maxSize = OptionMap.create(UndertowOptions.MAX_ENTITY_SIZE, (long) 1); + DefaultServer.setUndertowOptions(maxSize); + + post.setEntity(entity); + HttpResponse result = client.execute(post); + Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); + final String response = HttpClientUtils.readResponse(result); + Assert.assertEquals("PARAMS:\r\n" + + "parameter count: 1\r\n" + + "parameter name count: 1\r\n" + + "name: formValue\r\n" + + "filename: null\r\n" + + "content-type: null\r\n" + + "Content-Disposition: form-data; name=\"formValue\"\r\n" + + "value: myValue\r\n" + + "size: 7\r\n" + + "content: myValue\r\n" + + "name: file\r\n" + + "filename: uploadfile.txt\r\n" + + "content-type: application/octet-stream\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"uploadfile.txt\"\r\n" + + "Content-Type: application/octet-stream\r\n" + + "size: 13\r\n" + + "content: file contents\r\n", response); + } finally { + DefaultServer.setUndertowOptions(existing); + client.getConnectionManager().shutdown(); + } + } + private String generateContent(String chunk, int size) { int checkLength = chunk.getBytes().length; StringBuilder sb = new StringBuilder();