diff --git a/src/gateway/asap_http_body.c b/src/gateway/asap_http_body.c index 7ca5a2d..e62f562 100644 --- a/src/gateway/asap_http_body.c +++ b/src/gateway/asap_http_body.c @@ -44,6 +44,13 @@ int asap_http_body_parse_content_length(const char *cl_buf, long *cl_out) return 0; } +int asap_http_body_exceeds_static_cap(const asap_http_body_t *body, long content_length) +{ + if (!body || body->use_dyn_body) + return 0; + return content_length > (long)BODY_BUF_SIZE; +} + int asap_http_body_init_from_request(struct lws *wsi, asap_http_body_t *body) { char cl_buf[32] = {0}; diff --git a/src/gateway/asap_http_body.h b/src/gateway/asap_http_body.h index 1eb474d..8ad59aa 100644 --- a/src/gateway/asap_http_body.h +++ b/src/gateway/asap_http_body.h @@ -30,6 +30,18 @@ typedef struct asap_http_body { */ int asap_http_body_parse_content_length(const char *cl_buf, long *cl_out); +/** + * True when Content-Length exceeds the static POST/PUT cap (BODY_BUF_SIZE). + * POST /asap uses a 1 MiB dynamic buffer (use_dyn_body); skip the static cap + * so envelopes between 64 KiB and ASAP_BODY_MAX are not 413'd. + * + * Example: asap_http_body_exceeds_static_cap(&body, 70000) is 0 when + * body.use_dyn_body is set, and 1 for the default static buffer. + * + * @return 1 if the static cap applies and is exceeded; 0 otherwise. + */ +int asap_http_body_exceeds_static_cap(const asap_http_body_t *body, long content_length); + /** * For POST /asap: validate Content-Length and allocate dynamic buffer. * @return 0 ok, -1 body too large, -2 allocation failure; non-/asap returns 0. diff --git a/src/gateway/http_lws.c b/src/gateway/http_lws.c index dee91d6..54a74f5 100644 --- a/src/gateway/http_lws.c +++ b/src/gateway/http_lws.c @@ -273,7 +273,8 @@ int http_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, lws_callback_on_writable(wsi); } else { long cl = 0; - if (http_body_content_length(wsi, &cl) == 0 && cl > (long)BODY_BUF_SIZE) + if (http_body_content_length(wsi, &cl) == 0 && + asap_http_body_exceeds_static_cap(&conn->body, cl)) conn->body.body_too_large = 1; conn->body.body[0] = '\0'; conn->body.body_len = 0; diff --git a/tests/test_asap_http_body.c b/tests/test_asap_http_body.c index 5e7df11..9af8c37 100644 --- a/tests/test_asap_http_body.c +++ b/tests/test_asap_http_body.c @@ -6,6 +6,7 @@ #include "gateway/asap_http_body.h" #include +#include #include #define ASSERT(c) do { \ @@ -67,6 +68,19 @@ static int test_static_append_sets_too_large(void) return 0; } +static int test_exceeds_static_cap(void) +{ + asap_http_body_t body; + + memset(&body, 0, sizeof(body)); + ASSERT(asap_http_body_exceeds_static_cap(NULL, (long)BODY_BUF_SIZE + 1) == 0); + ASSERT(asap_http_body_exceeds_static_cap(&body, (long)BODY_BUF_SIZE) == 0); + ASSERT(asap_http_body_exceeds_static_cap(&body, (long)BODY_BUF_SIZE + 1) == 1); + body.use_dyn_body = 1; + ASSERT(asap_http_body_exceeds_static_cap(&body, (long)BODY_BUF_SIZE + 1) == 0); + return 0; +} + int main(void) { int failed = 0; @@ -86,6 +100,10 @@ int main(void) fprintf(stderr, "test_static_append_sets_too_large failed\n"); failed++; } + if (test_exceeds_static_cap() != 0) { + fprintf(stderr, "test_exceeds_static_cap failed\n"); + failed++; + } if (failed == 0) printf("test_asap_http_body: all tests passed\n"); return failed ? 1 : 0; diff --git a/tests/test_gateway_http.c b/tests/test_gateway_http.c index 13b3abf..a11efd9 100644 --- a/tests/test_gateway_http.c +++ b/tests/test_gateway_http.c @@ -468,7 +468,8 @@ static int test_asap_body_over_max(void) long code; char *body = NULL; const char payload[] = "{}"; - int r = http_post_raw(gw_url("/asap"), payload, sizeof(payload) - 1, "1000001", + /* ASAP_BODY_MAX is 1 MiB (1048576). 1000001 is still under that cap. */ + int r = http_post_raw(gw_url("/asap"), payload, sizeof(payload) - 1, "1048577", &code, &body); ASSERT(r == 0); ASSERT(code == 413); @@ -477,6 +478,46 @@ static int test_asap_body_over_max(void) return 0; } +/* + * POST /asap allocates a 1 MiB dynamic buffer. A leftover Content-Length check + * still compared against the 64 KiB static cap used by PUT /api/config, so a + * valid state.query just over 64 KiB was 413'd before parse. + */ +static int test_asap_body_over_static_cap_accepted(void) +{ + enum { PAD = 70000 }; + const char prefix[] = + "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"asap.send\",\"params\":{" + "\"id\":\"e1\",\"asap_version\":\"2.1\"," + "\"sender\":\"urn:from\",\"recipient\":\"urn:to\"," + "\"payload_type\":\"state.query\",\"payload\":{\"pad\":\""; + const char suffix[] = "\"}}}"; + size_t prefix_len = strlen(prefix); + size_t suffix_len = strlen(suffix); + size_t total = prefix_len + (size_t)PAD + suffix_len; + char *payload; + long code = 0; + char *body = NULL; + int r; + + payload = malloc(total + 1U); + ASSERT(payload != NULL); + memcpy(payload, prefix, prefix_len); + memset(payload + prefix_len, 'A', (size_t)PAD); + memcpy(payload + prefix_len + (size_t)PAD, suffix, suffix_len + 1U); + ASSERT(total > 65536U); + ASSERT(total < (1024U * 1024U)); + r = http_post(gw_url("/asap"), payload, &code, &body); + free(payload); + ASSERT(r == 0); + ASSERT(code == 200); + ASSERT(body != NULL); + ASSERT(strstr(body, "\"result\"") != NULL); + ASSERT(strstr(body, "sessions") != NULL); + free(body); + return 0; +} + static int test_health_wellknown(void) { long code; @@ -1054,6 +1095,10 @@ int main(int argc, char **argv) fprintf(stderr, "test_asap_body_over_max failed\n"); failed++; } + if (test_asap_body_over_static_cap_accepted() != 0) { + fprintf(stderr, "test_asap_body_over_static_cap_accepted failed\n"); + failed++; + } if (test_asap_invalid_body() != 0) { fprintf(stderr, "test_asap_invalid_body failed\n"); failed++; } if (test_asap_missing_fields() != 0) { fprintf(stderr, "test_asap_missing_fields failed\n"); failed++; } if (test_api_asap_log_401() != 0) { fprintf(stderr, "test_api_asap_log_401 failed\n"); failed++; }