/* SPDX-License-Identifier: GPL-3.0-only */ #include #include "alloc.h" #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define HTTPD_SOCK_ERR_TIMEOUT -10 #define HTTPD_SOCK_ERR_FAIL -11 #define HTTPD_408_REQ_TIMEOUT 408 struct sock_db { char pending_data[128]; size_t pending_len; void *ctx; void (*free_ctx)(void *); bool ignore_sess_ctx_changes; }; struct httpd_req_aux { struct sock_db *sd; char *scratch; size_t scratch_cur_size, scratch_size_limit, remaining_len; }; typedef struct httpd_req { struct httpd_req_aux *aux; void *sess_ctx, *handle, *user_ctx; void (*free_ctx)(void *); bool ignore_sess_ctx_changes; } httpd_req_t; typedef struct { void *data; } http_parser; typedef struct { struct { char *at; } last; } parser_data_t; static int receive_result = 1, receive_calls; static int httpd_req_handle_err(httpd_req_t *r, int err) { return ESP_FAIL; } static void httpd_sess_free_ctx(void **ctx, void (*fn)(void *)) { assert(!*ctx); } static int httpd_recv_with_opt(httpd_req_t *r, char *buf, size_t n, bool halt_after_pending); /* SDK_FUNCTIONS */ static int httpd_recv_with_opt(httpd_req_t *r, char *buf, size_t n, bool halt_after_pending) { ++receive_calls; assert(halt_after_pending); if (r->aux->sd->pending_len) return (int)httpd_recv_pending(r, buf, n); if (receive_result <= 0) return receive_result; memset(buf, 'x', n); return (int)n; } static void cleanup(httpd_req_t *r, struct httpd_req_aux *ra, struct sock_db *sd) { r->aux = ra; ra->sd = sd; httpd_req_cleanup(r); assert(!ra->scratch && !ra->scratch_cur_size && !r->aux && !live); } int main(void) { all_secret = true; struct sock_db sd = {0}; struct httpd_req_aux ra = {.sd=&sd, .scratch_size_limit=64, .remaining_len=37}; httpd_req_t r = {.aux=&ra}; parser_data_t data = {0}; http_parser parser = {.data=&data}; /* Equivalent pointer/size initialization to parse_init/init_req_aux: * the first read must allocate its own scratch, with no parser position. */ assert(!data.last.at && !ra.scratch && !ra.scratch_cur_size); unsigned initial_wipes = wiped_frees; int initial_reads = receive_calls; fail_at = calls + 1; assert(read_block(&r, &parser, 0, 8) == 0); assert(!data.last.at && !ra.scratch && !ra.scratch_cur_size && !live); assert(receive_calls == initial_reads && wiped_frees == initial_wipes); cleanup(&r, &ra, &sd); assert(wiped_frees == initial_wipes); fail_at = 0; r.aux = &ra; ra.sd = &sd; ra.scratch_size_limit = 64; assert(read_block(&r, &parser, 0, 8) == 8); assert(ra.scratch && ra.scratch_cur_size == 8 && !data.last.at); assert(receive_calls == initial_reads + 1 && wiped_frees == initial_wipes); assert(!memcmp(ra.scratch, "xxxxxxxx", 8)); /* A fragmented request can need another read before the URL callback. */ char *initial = ra.scratch; fail_at = calls + 1; assert(read_block(&r, &parser, 8, 8) == 0); assert(ra.scratch == initial && ra.scratch_cur_size == 8 && !data.last.at); assert(receive_calls == initial_reads + 1 && wiped_frees == initial_wipes); fail_at = 0; assert(read_block(&r, &parser, 8, 8) == 8); assert(ra.scratch != initial && ra.scratch_cur_size == 16 && !data.last.at); assert(!memcmp(ra.scratch, "xxxxxxxxxxxxxxxx", 16)); assert(wiped_frees == initial_wipes + 1); cleanup(&r, &ra, &sd); assert(wiped_frees == initial_wipes + 2); r.aux = &ra; ra.sd = &sd; ra.scratch_size_limit = 64; assert(read_block(&r, &parser, 0, 16) == 16); assert(!data.last.at); memcpy(ra.scratch, "Cookie: secret!!", 16); data.last.at = ra.scratch + 7; char *old = ra.scratch; unsigned before = wiped_frees; assert(read_block(&r, &parser, 16, 8) == 8); assert(ra.scratch != old && ra.scratch_cur_size == 24); assert(!memcmp(ra.scratch, "Cookie: secret!!", 16)); assert(data.last.at == ra.scratch + 7 && wiped_frees == before + 1); old = ra.scratch; unsigned saved_calls = calls; assert(security_override_resize_scratch(&ra, 24) && ra.scratch == old && calls == saved_calls); fail_at = calls + 1; int reads = receive_calls; assert(read_block(&r, &parser, 24, 8) == 0); assert(ra.scratch == old && ra.scratch_cur_size == 24 && receive_calls == reads); assert(data.last.at == old + 7 && !memcmp(old, "Cookie: secret!!", 16)); assert(ra.remaining_len == 37); fail_at = 0; assert(httpd_unrecv(&r, "NEXT-REQUEST", 12) == 12); assert(read_block(&r, &parser, 4, 4) == 4); /* actual shrink + pending RX */ assert(ra.scratch_cur_size == 8 && !memcmp(ra.scratch, "CookNEXT", 8)); assert(sd.pending_len == 8 && !memcmp(sd.pending_data + 120, "-REQUEST", 8)); assert(ra.remaining_len == 37); char pending[128]; memcpy(pending, sd.pending_data, sizeof(pending)); cleanup(&r, &ra, &sd); assert(sd.pending_len == 8 && !memcmp(pending, sd.pending_data, sizeof(pending))); r.aux = &ra; ra.sd = &sd; char out[12] = {0}; assert(httpd_recv_pending(&r, out, 8) == 8 && !memcmp(out, "-REQUEST", 8)); ra.scratch_size_limit = 64; fail_at = calls + 1; assert(!security_override_resize_scratch(&ra, 8) && !ra.scratch); cleanup(&r, &ra, &sd); fail_at = 0; for (int result = 0; result >= -2; --result) { r.aux = &ra; ra.sd = &sd; ra.scratch_size_limit = 64; assert(security_override_resize_scratch(&ra, 8)); data.last.at = ra.scratch; receive_result = result == -2 ? HTTPD_SOCK_ERR_TIMEOUT : result; assert(read_block(&r, &parser, 0, 8) == HTTPD_SOCK_ERR_FAIL); int before_reads = receive_calls; assert(read_block(&r, &parser, 64, 1) == 0 && before_reads == receive_calls); cleanup(&r, &ra, &sd); } puts("HTTPD null-initial read/grow/shrink/failure/final wipe/bounds/pending-unread matrix PASS"); }