diff --git a/src/main/java/org/unlaxer/tinyexpression/mcp/McpServer.java b/src/main/java/org/unlaxer/tinyexpression/mcp/McpServer.java index 322e0393..d6b0cb6f 100644 --- a/src/main/java/org/unlaxer/tinyexpression/mcp/McpServer.java +++ b/src/main/java/org/unlaxer/tinyexpression/mcp/McpServer.java @@ -901,6 +901,7 @@ private static String readBody(HttpExchange ex) throws IOException { try { long len = Long.parseLong(contentLength.trim()); if (len > MAX_REQUEST_BODY_BYTES) { + drainRequestBody(ex.getRequestBody()); throw new IOException("Request body too large: " + len + " > " + MAX_REQUEST_BODY_BYTES); } } catch (NumberFormatException ignored) { @@ -908,7 +909,21 @@ private static String readBody(HttpExchange ex) throws IOException { } } try (InputStream is = ex.getRequestBody()) { - return new String(is.readNBytes((int) MAX_REQUEST_BODY_BYTES), StandardCharsets.UTF_8); + byte[] body = is.readNBytes((int) MAX_REQUEST_BODY_BYTES + 1); + if (body.length > MAX_REQUEST_BODY_BYTES) { + drainRequestBody(is); + throw new IOException("Request body too large: more than " + MAX_REQUEST_BODY_BYTES); + } + return new String(body, StandardCharsets.UTF_8); + } + } + + private static void drainRequestBody(InputStream is) throws IOException { + try (InputStream body = is) { + byte[] buffer = new byte[8192]; + while (body.read(buffer) != -1) { + // Consume the request before sending the response so the HTTP exchange can finish cleanly. + } } } diff --git a/src/test/java/org/unlaxer/tinyexpression/mcp/McpServerTest.java b/src/test/java/org/unlaxer/tinyexpression/mcp/McpServerTest.java index edbb93d4..15173a4b 100644 --- a/src/test/java/org/unlaxer/tinyexpression/mcp/McpServerTest.java +++ b/src/test/java/org/unlaxer/tinyexpression/mcp/McpServerTest.java @@ -346,6 +346,37 @@ public void oversizedBody_rejectedWith413() throws Exception { .build(); HttpResponse resp = client.send(req, HttpResponse.BodyHandlers.ofString()); assertEquals(413, resp.statusCode()); + assertEquals(resp.body().getBytes(java.nio.charset.StandardCharsets.UTF_8).length, + Integer.parseInt(resp.headers().firstValue("Content-Length").orElseThrow())); + } + + @Test + public void oversizedChunkedBody_rejectedWith413() throws Exception { + long max = McpServer.MAX_REQUEST_BODY_BYTES; + HttpRequest.BodyPublisher publisher = HttpRequest.BodyPublishers.fromPublisher( + subscriber -> subscriber.onSubscribe(new java.util.concurrent.Flow.Subscription() { + private boolean sent; + + @Override + public void request(long n) { + if (!sent && n > 0) { + sent = true; + subscriber.onNext(java.nio.ByteBuffer.wrap(new byte[(int) max + 1])); + subscriber.onComplete(); + } + } + + @Override + public void cancel() { + } + })); + HttpRequest req = HttpRequest.newBuilder() + .uri(URI.create("http://127.0.0.1:" + port + "/mcp")) + .header("Content-Type", "application/json") + .POST(publisher) + .build(); + HttpResponse resp = client.send(req, HttpResponse.BodyHandlers.ofString()); + assertEquals(413, resp.statusCode()); } // ─── helpers ──────────────────────────────────────────────────