From 1d8ce2bb7451084bbf2ca3257fbcdc7de00bf06b Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Thu, 15 Jan 2026 13:45:45 +0000 Subject: [PATCH] http inject: add integration test for request_body_limit This commit doesn't change any behavior, it just adds an explicit test for exceeding request_body_limit, and expands on the docs for request_body_limit to clarify the response when the limit is exceeded. --- .../src/test/http_inject_size_limit.rs | 138 ++++++++++++++++++ crates/integration-tests/src/test/mod.rs | 1 + .../start_http_listener/request_body_limit.md | 10 +- 3 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 crates/integration-tests/src/test/http_inject_size_limit.rs diff --git a/crates/integration-tests/src/test/http_inject_size_limit.rs b/crates/integration-tests/src/test/http_inject_size_limit.rs new file mode 100644 index 00000000..03d214f9 --- /dev/null +++ b/crates/integration-tests/src/test/http_inject_size_limit.rs @@ -0,0 +1,138 @@ +use crate::kumod::{DaemonWithMaildir, MailGenParams}; +use anyhow::Context; +use k9::assert_equal; + +/// Asserts that request_body_limit is effective and has a +/// known response +#[tokio::test] +async fn http_inject_size_limit() -> anyhow::Result<()> { + let mut daemon = DaemonWithMaildir::start() + .await + .context("DaemonWithMaildir::start")?; + + let content = MailGenParams { + // Ask for 2MB of mail body. The overall request + // size will therefore be > 2MB, which is the + // default value of `request_body_limit` set + // in the http listener. + size: Some(2 * 1024 * 1024), + ..Default::default() + } + .generate()?; + + let payload = serde_json::json!({ + "envelope_sender": "sender@example.com", + "recipients": [ + { + "email": "user@example.com", + "name": "Test User" + } + ], + "content": content, + }); + + let json_data = serde_json::to_vec(&payload)?; + + let client = reqwest::Client::new(); + let response = client + .post(&format!( + "http://{}/api/inject/v1", + daemon.source.listener("http") + )) + .header("Content-Type", "application/json") + .body(json_data) + .send() + .await?; + + let status = response.status(); + let body_bytes = response + .bytes() + .await + .context("failed to read error response body")?; + + assert_equal!(status, 413, "Should be too large"); + assert_equal!( + &*body_bytes, + b"Failed to buffer the request body: length limit exceeded" + ); + + daemon.stop_both().await.context("stop_both")?; + + Ok(()) +} + +/// Asserts that request_body_limit is effective and has a +/// known response when using compressed requests +#[tokio::test] +async fn http_inject_size_limit_compressed() -> anyhow::Result<()> { + let mut daemon = DaemonWithMaildir::start() + .await + .context("DaemonWithMaildir::start")?; + + let content = MailGenParams { + // Ask for 2MB of mail body. The overall request + // size will therefore be > 2MB, which is the + // default value of `request_body_limit` set + // in the http listener. + size: Some(2 * 1024 * 1024), + ..Default::default() + } + .generate()?; + + let payload = serde_json::json!({ + "envelope_sender": "sender@example.com", + "recipients": [ + { + "email": "user@example.com", + "name": "Test User" + } + ], + "content": content, + }); + + let json_data = serde_json::to_vec(&payload)?; + + // Compress with gzip + use flate2::write::GzEncoder; + use flate2::Compression; + use std::io::Write; + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(&json_data)?; + let compressed_data = encoder.finish()?; + + eprintln!( + "Original size: {}, Compressed size: {}", + json_data.len(), + compressed_data.len() + ); + + let client = reqwest::Client::new(); + let response = client + .post(&format!( + "http://{}/api/inject/v1", + daemon.source.listener("http") + )) + .header("Content-Encoding", "gzip") + .header("Content-Type", "application/json") + .body(compressed_data) + .send() + .await?; + + let status = response.status(); + let body_bytes = response + .bytes() + .await + .context("failed to read error response body")?; + + // We still expect the 2MB limit to apply, even though compression + // means that we sent only about 600KB on the wire. + assert_equal!(status, 413, "Should be too large"); + assert_equal!( + &*body_bytes, + b"Failed to buffer the request body: length limit exceeded" + ); + + daemon.stop_both().await.context("stop_both")?; + + Ok(()) +} diff --git a/crates/integration-tests/src/test/mod.rs b/crates/integration-tests/src/test/mod.rs index dd22f237..b518983a 100644 --- a/crates/integration-tests/src/test/mod.rs +++ b/crates/integration-tests/src/test/mod.rs @@ -15,6 +15,7 @@ mod end_to_end_webhook_batch; mod expires; mod http_auth; mod http_inject_compression; +mod http_inject_size_limit; mod log_oob_arf; mod maildir_batch; mod maildir_batch_452; diff --git a/docs/reference/kumo/start_http_listener/request_body_limit.md b/docs/reference/kumo/start_http_listener/request_body_limit.md index 2d2811af..b174204b 100644 --- a/docs/reference/kumo/start_http_listener/request_body_limit.md +++ b/docs/reference/kumo/start_http_listener/request_body_limit.md @@ -2,9 +2,13 @@ {{since('2024.06.10-84e84b89')}} -Specifies the maximum acceptable size of an incoming HTTP request, in bytes. -The default is 2MB. +Specifies the maximum acceptable size of an incoming HTTP request, *after* +decompressing any compressed body, in bytes. This option limits the RAM +usage rather than the wire usage. + +The default limit is 2MB. If an incoming request exceeds this limit, a `413 Payload Too Large` HTTP -response will be returned. +response will be returned, with the body `Failed to buffer the request body: +length limit exceeded`.