From 39d6c3c08415c5ae1f7e800503d1c0d581f4fe43 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 11 Jul 2025 15:10:11 -0400 Subject: [PATCH 1/3] Add size limit to `Context#bodyAsBytes()` --- .../main/java/io/avaje/jex/DJexConfig.java | 12 ++++ .../src/main/java/io/avaje/jex/JexConfig.java | 10 ++++ .../java/io/avaje/jex/core/JdkContext.java | 11 +++- .../io/avaje/jex/core/ServiceManager.java | 12 +++- .../jex/core/ContextRequestTooBigTest.java | 60 +++++++++++++++++++ 5 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 avaje-jex/src/test/java/io/avaje/jex/core/ContextRequestTooBigTest.java diff --git a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java index 1198bdc2..1b88794f 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/DJexConfig.java @@ -29,6 +29,7 @@ final class DJexConfig implements JexConfig { private int bufferInitial = 256; private long bufferMax = 4096L; private int rangeChunkSize = 990_000; + private long maxRequestSize = 1_000_000L; private HttpServerProvider serverProvider; @Override @@ -211,4 +212,15 @@ public JexConfig rangeChunkSize(int rangeChunkSize) { this.rangeChunkSize = rangeChunkSize; return this; } + + @Override + public JexConfig maxRequestSize(long maxRequestSize) { + this.maxRequestSize = maxRequestSize; + return this; + } + + @Override + public long maxRequestSize() { + return maxRequestSize; + } } diff --git a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java index fc48c480..3edf50c8 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java +++ b/avaje-jex/src/main/java/io/avaje/jex/JexConfig.java @@ -149,6 +149,16 @@ public interface JexConfig { /** The configured rangeChunk size */ int rangeChunkSize(); + /** + * Sets the the max size of request body that can be accessed without using using an InputStream + * + * @param maxRequestSize The size. + */ + JexConfig maxRequestSize(long maxRequestSize); + + /** The configured maxRequestSize size */ + long maxRequestSize(); + /** * Set the chunk size on range requests, set to a high number to reduce the amount of range * requests (especially for video streaming) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java index 0da73ed8..0aeae922 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/JdkContext.java @@ -34,6 +34,7 @@ import com.sun.net.httpserver.HttpsExchange; import io.avaje.jex.http.Context; +import io.avaje.jex.http.HttpResponseException; import io.avaje.jex.http.HttpStatus; import io.avaje.jex.http.RedirectException; import io.avaje.jex.security.BasicAuthCredentials; @@ -124,6 +125,14 @@ public String body() { public byte[] bodyAsBytes() { try { if (bodyBytes == null) { + var contentLength = contentLength(); + long maxRequestSize = mgr.maxRequestSize(); + if (contentLength > maxRequestSize || contentLength < 0) { + throw new HttpResponseException( + HttpStatus.REQUEST_ENTITY_TOO_LARGE_413.status(), + "Body content length unknown or greater than max configured size (%s bytes)" + .formatted(maxRequestSize)); + } bodyBytes = exchange.getRequestBody().readAllBytes(); } return bodyBytes; @@ -157,7 +166,7 @@ private Charset characterEncoding() { @Override public long contentLength() { final String len = header(Constants.CONTENT_LENGTH); - return len == null ? 0 : Long.parseLong(len); + return len == null ? -1 : Long.parseLong(len); } @Override diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java index ba70fedd..d27fdf15 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java @@ -39,6 +39,7 @@ final class ServiceManager { private final int bufferInitial; private final long bufferMax; private final int rangeChunks; + private final long maxRequestSize; static ServiceManager create(Jex jex) { return new Builder(jex).build(); @@ -52,7 +53,8 @@ static ServiceManager create(Jex jex) { String scheme, long bufferMax, int bufferInitial, - int rangeChunks) { + int rangeChunks, + long maxRequestSize) { this.compressionConfig = compressionConfig; this.jsonService = jsonService; this.exceptionHandler = manager; @@ -61,6 +63,7 @@ static ServiceManager create(Jex jex) { this.bufferInitial = bufferInitial; this.bufferMax = bufferMax; this.rangeChunks = rangeChunks; + this.maxRequestSize = maxRequestSize; } OutputStream createOutputStream(JdkContext jdkContext) { @@ -169,6 +172,10 @@ String scheme() { return scheme; } + public long maxRequestSize() { + return maxRequestSize; + } + private static final class Builder { private final Jex jex; @@ -186,7 +193,8 @@ ServiceManager build() { jex.config().scheme(), jex.config().maxStreamBufferSize(), jex.config().initialStreamBufferSize(), - jex.config().rangeChunkSize()); + jex.config().rangeChunkSize(), + jex.config().maxRequestSize()); } JsonService initJsonService() { diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/ContextRequestTooBigTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ContextRequestTooBigTest.java new file mode 100644 index 00000000..6a286066 --- /dev/null +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ContextRequestTooBigTest.java @@ -0,0 +1,60 @@ +package io.avaje.jex.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.http.HttpResponse; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; + +import io.avaje.jex.Jex; + +class ContextRequestTooBigTest { + + static final TestPair pair = init(); + + static TestPair init() { + final Jex app = + Jex.create().config(c -> c.maxRequestSize(5)).post("/", ctx -> ctx.text(ctx.body())); + + return TestPair.create(app); + } + + @AfterAll + static void end() { + pair.shutdown(); + } + + @Test + void noBody() { + HttpResponse res = pair.request().POST().asString(); + assertThat(res.statusCode()).isEqualTo(200); + } + + @Test + void overSized() { + HttpResponse res = pair.request().body("amogus").POST().asString(); + assertThat(res.statusCode()).isEqualTo(413); + } + + @Test + void transferEncoding() throws IOException { + HttpURLConnection connection = + (HttpURLConnection) URI.create(pair.url()).toURL().openConnection(); + + // 3. Configure the connection for a POST request + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setChunkedStreamingMode(2); + + try (OutputStream os = connection.getOutputStream()) { + os.write("hi".getBytes()); + } + + assertThat(connection.getResponseCode()).isEqualTo(413); + } +} From fd2acc5f24468d0132b109628ddfc079f9af0ab9 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Fri, 11 Jul 2025 13:55:11 -0700 Subject: [PATCH 2/3] Update ContextRequestTooBigTest.java --- .../test/java/io/avaje/jex/core/ContextRequestTooBigTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/avaje-jex/src/test/java/io/avaje/jex/core/ContextRequestTooBigTest.java b/avaje-jex/src/test/java/io/avaje/jex/core/ContextRequestTooBigTest.java index 6a286066..c733bcf5 100644 --- a/avaje-jex/src/test/java/io/avaje/jex/core/ContextRequestTooBigTest.java +++ b/avaje-jex/src/test/java/io/avaje/jex/core/ContextRequestTooBigTest.java @@ -46,7 +46,6 @@ void transferEncoding() throws IOException { HttpURLConnection connection = (HttpURLConnection) URI.create(pair.url()).toURL().openConnection(); - // 3. Configure the connection for a POST request connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setChunkedStreamingMode(2); From d96c0bd7d45e5c65c5e73590e4098ce620816a27 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 14 Jul 2025 07:56:23 +1200 Subject: [PATCH 3/3] Method does not need to be public --- avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java index d27fdf15..13a2723a 100644 --- a/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java +++ b/avaje-jex/src/main/java/io/avaje/jex/core/ServiceManager.java @@ -172,7 +172,7 @@ String scheme() { return scheme; } - public long maxRequestSize() { + long maxRequestSize() { return maxRequestSize; }