Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can user set it in code without use of servlets? pure handlers? if so, ignoring it here does not seem good idea.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If so, than it might not be optimal to remove it, we would need way to handle it both here and in servlet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check and potential rejection was only more recently added with UNDERTOW-2556 but it's too soon to reject here without considering a possible servlet config at all. So removing this a light revert to what it historically had been. I don't think we lose anything significant by moving the check and fail to the ServletIntialHandler instead of the HttpTransferEncoding here since in the ServletIntialHandler, we'll be able to check and fail the request based on all option sources (the listener config, any handler's applied option, or the servlet config) and still fail earlier than waiting on an apps read as we had done before UNDERTOW-2556

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, my point is that removing it from here denies this part usability for pure handler server. We need a way to leverage this in both types IMHO. If this particular version obstructs upstream, we might need a way to bypass it in case servlet is up in chain?
@fl4via ^^

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down