Replace Web Basic Auth With Cookie Sessions
Add bounded login challenges, CSRF/origin enforcement, logout, and session-bound WebSocket admission. Isolate private HTTPD access behind a version-guarded adapter and add focused host coverage. Also let empty admin SSH input reach the normal console handler.
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# Cookie authentication and HTTPD adapter host checks
|
||||
|
||||
Run from the project root:
|
||||
|
||||
```sh
|
||||
python3 tests/web_cookie_auth/run.py
|
||||
```
|
||||
|
||||
Requires Python 3, a C11 compiler (`cc`), OpenSSL headers/libcrypto, and the pinned ESP-IDF source installation. The runner uses `IDF_PATH` when set, otherwise `~/.platformio/packages/framework-espidf`. It writes only an automatically removed temporary directory. No network, device, pip/npm packages or server is needed. Do not disable C assertions.
|
||||
|
||||
The runner compiles production `web_cookie_auth`, `web_session_store`, `web_auth_parse` and `web_httpd_adapter` with bounded HTTPD/database/time/RNG doubles. It also executes the session-store public API suite. The installed IDF header getters, append-only response-header setter and right-aligned pending-data reader are extracted verbatim and compiled into the harness.
|
||||
|
||||
Coverage includes challenge reuse/consumption/expiry, capacities without eviction, global throttle, fragmented login bodies, secure cookie attributes and two simultaneous Set-Cookie fields, session-specific logout, duplicate fields/cookies, Origin/CSRF/method/Fetch Metadata rejection, Basic denial, currentness, stop/login and failure paths, six-header login budget, upgrade-state installation, and request cleanup preserving all 0–128 pending lengths through partial reads.
|
||||
|
||||
This is **not** the full IDF parser/dispatcher, real handshake/TLS/socket, browser, multicore task or hardware test. The private struct doubles do not prove binary layout; firmware compilation uses the actual pinned headers, and the version guard requires a new audit on SDK changes. Handshake sending and transport revocation are doubled. Actual on-wire pre-101 rejection, frame routing, pipelining/early bytes, cookie/CSP/browser recovery and loaded expiry latency remain M1 target gates. No sanitizer or runtime memory-reserve result is implied.
|
||||
|
||||
See `docs/phase8d3_implementation.md` for source verification, other suite commands, build accounting and the target checklist.
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Production policy/store/adapter with bounded HTTPD and database doubles.
|
||||
|
||||
Getter and append-header functions are extracted verbatim from installed IDF,
|
||||
not reimplemented with convenient merging or overwrite semantics.
|
||||
"""
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
os.environ["CCACHE_DISABLE"] = "1"
|
||||
HERE = pathlib.Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
sys.path.insert(0, str(HERE.parent / "web_session_store"))
|
||||
from run import HEADERS
|
||||
from serial_headers import SERIAL_HEADERS
|
||||
|
||||
HEADERS.update(SERIAL_HEADERS)
|
||||
HEADERS["esp_http_server.h"] += """
|
||||
#define ESP_ERR_HTTPD_INVALID_REQ 0x200
|
||||
#define ESP_ERR_HTTPD_RESULT_TRUNC 0x201
|
||||
#define ESP_ERR_HTTPD_RESP_HDR 0x202
|
||||
esp_err_t httpd_resp_sendstr(httpd_req_t *, const char *);
|
||||
int httpd_req_recv(httpd_req_t *, char *, size_t);
|
||||
"""
|
||||
HEADERS["esp_idf_version.h"] = """
|
||||
#define ESP_IDF_VERSION_VAL(a,b,c) ((a)*10000+(b)*100+(c))
|
||||
#define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(5,5,0)
|
||||
"""
|
||||
HEADERS["esp_httpd_priv.h"] = """#pragma once
|
||||
#include "esp_http_server.h"
|
||||
struct sock_db { bool ws_handshake_done; esp_err_t (*ws_handler)(httpd_req_t *);
|
||||
bool ws_control_frames; void *ws_user_ctx; char pending_data[128]; size_t pending_len; };
|
||||
struct httpd_req_aux { struct sock_db *sd; char *scratch; size_t scratch_cur_size, remaining_len;
|
||||
unsigned req_hdrs_count, resp_hdrs_count; bool ws_handshake_detect;
|
||||
struct resp_hdr { const char *field, *value; } *resp_hdrs; };
|
||||
struct httpd_data { struct { unsigned max_resp_headers; } config; };
|
||||
esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *, const char *);
|
||||
"""
|
||||
|
||||
def function(source, name):
|
||||
start = source.index(name + "(")
|
||||
start = source.rfind("\n", 0, start) + 1
|
||||
end = source.index("\n}", start) + 2
|
||||
return source[start:end]
|
||||
|
||||
idf = pathlib.Path(os.environ.get("IDF_PATH", str(pathlib.Path.home() / ".platformio/packages/framework-espidf")))
|
||||
parse = (idf / "components/esp_http_server/src/httpd_parse.c").read_text()
|
||||
txrx = (idf / "components/esp_http_server/src/httpd_txrx.c").read_text()
|
||||
extracted = """
|
||||
#pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include "esp_httpd_priv.h"
|
||||
#define ESP_LOGD(...) ((void)0)
|
||||
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
||||
static bool httpd_valid_req(httpd_req_t *r) { return r && r->aux; }
|
||||
static size_t strlcpy(char *d, const char *s, size_t n) {
|
||||
size_t len = strlen(s); if (n) { size_t m = len < n-1 ? len : n-1; memcpy(d,s,m); d[m]=0; } return len;
|
||||
}
|
||||
"""
|
||||
for name in ["httpd_req_get_hdr_value_len", "httpd_req_get_hdr_value_str"]:
|
||||
# The declarations start at line beginning; avoid earlier calls in parser.
|
||||
prefix = "size_t " if name.endswith("len") else "esp_err_t "
|
||||
extracted += function(parse[parse.index(prefix + name):], name) + "\n"
|
||||
extracted += function(txrx[txrx.index("esp_err_t httpd_resp_set_hdr"):], "httpd_resp_set_hdr")
|
||||
extracted += "\n" + function(txrx[txrx.index("static size_t httpd_recv_pending"):], "httpd_recv_pending")
|
||||
extracted += "\nsize_t host_read_pending(httpd_req_t *r, char *out, size_t n) { return httpd_recv_pending(r, out, n); }\n"
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="web-cookie-auth-") as directory:
|
||||
tmp = pathlib.Path(directory)
|
||||
for name, text in HEADERS.items():
|
||||
path = tmp / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(text)
|
||||
(tmp / "installed_httpd.c").write_text(extracted)
|
||||
sources = [HERE / "test.c", tmp / "installed_httpd.c"]
|
||||
sources += [ROOT / "src" / name for name in ["web_session_store.c", "web_auth_parse.c", "web_cookie_auth.c", "web_httpd_adapter.c"]]
|
||||
subprocess.run(["cc", "-std=c11", "-Wall", "-Wextra", "-Werror", "-g", "-DHOST_OPENSSL",
|
||||
"-I" + str(tmp), "-I" + str(ROOT / "src"), *map(str, sources), "-lcrypto",
|
||||
"-o", str(tmp / "test")], check=True, timeout=30)
|
||||
subprocess.run([str(tmp / "test")], check=True, timeout=20)
|
||||
@@ -0,0 +1,257 @@
|
||||
/* Production store dependency doubles and its existing public API suite. */
|
||||
#define main store_tests
|
||||
#include "../web_session_store/test.c"
|
||||
#undef main
|
||||
#include "web_cookie_auth.h"
|
||||
#include "web_httpd_adapter.h"
|
||||
#include "esp_httpd_priv.h"
|
||||
|
||||
static struct httpd_data server = {.config.max_resp_headers = 8};
|
||||
static struct sock_db socket_state;
|
||||
static struct resp_hdr response_headers[8];
|
||||
static char scratch[1024], output[1024], cookie_values[2][200];
|
||||
static const char *request_body;
|
||||
static size_t body_offset;
|
||||
static unsigned password_calls, cookie_count, sends, upgrades;
|
||||
static unsigned fail_header, setter_calls;
|
||||
static bool send_fail, recv_fail;
|
||||
static void (*password_hook)(void);
|
||||
static char response_status[48];
|
||||
static struct httpd_req_aux aux;
|
||||
static httpd_req_t req;
|
||||
size_t host_read_pending(httpd_req_t *r, char *out, size_t n);
|
||||
|
||||
esp_err_t httpd_resp_set_status(httpd_req_t *r, const char *status) {
|
||||
(void)r; if (fail_header && ++setter_calls == fail_header) return ESP_FAIL;
|
||||
snprintf(response_status, sizeof(response_status), "%s", status); return ESP_OK;
|
||||
}
|
||||
esp_err_t httpd_resp_set_type(httpd_req_t *r, const char *type) {
|
||||
(void)r; (void)type; return ESP_OK;
|
||||
}
|
||||
esp_err_t httpd_resp_sendstr(httpd_req_t *r, const char *body) {
|
||||
(void)r; ++sends; assert(strlen(body) < sizeof(output)); strcpy(output, body);
|
||||
cookie_count = 0;
|
||||
for (unsigned i = 0; i < aux.resp_hdrs_count; ++i) {
|
||||
assert(response_headers[i].value);
|
||||
assert(strcmp(response_headers[i].field, "WWW-Authenticate"));
|
||||
if (!strcmp(response_headers[i].field, "Set-Cookie")) {
|
||||
assert(cookie_count < 2);
|
||||
snprintf(cookie_values[cookie_count++], 200, "%s", response_headers[i].value);
|
||||
}
|
||||
}
|
||||
return send_fail ? ESP_FAIL : ESP_OK;
|
||||
}
|
||||
int httpd_req_recv(httpd_req_t *r, char *out, size_t size) {
|
||||
(void)r; if (recv_fail) return -1;
|
||||
if (size > 7) size = 7; /* Fragment every login body. */
|
||||
memcpy(out, request_body + body_offset, size); body_offset += size;
|
||||
aux.remaining_len -= size; return (int)size;
|
||||
}
|
||||
esp_err_t web_login_ui_send_response(httpd_req_t *r) { return httpd_resp_sendstr(r, "login document"); }
|
||||
esp_err_t web_serial_transport_revoke_web_session(web_session_id_t id) {
|
||||
web_session_store_invalidate(id); return ESP_OK;
|
||||
}
|
||||
esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *r, const char *protocol) {
|
||||
(void)r; (void)protocol; ++upgrades; return ESP_OK;
|
||||
}
|
||||
esp_err_t user_database_authenticate_password(const uint8_t *u, size_t un,
|
||||
const uint8_t *p, size_t pn, user_principal_t *principal, bool *authenticated) {
|
||||
assert(!host_lock_depth); ++password_calls;
|
||||
if (password_hook) { void (*hook)(void) = password_hook; password_hook = NULL; hook(); }
|
||||
*authenticated = un == 5 && !memcmp(u, "alice", 5) && pn == 12 && !memcmp(p, "password1234", 12);
|
||||
if (*authenticated) *principal = alice;
|
||||
return db_fail ? ESP_FAIL : ESP_OK;
|
||||
}
|
||||
|
||||
static void begin(const char *uri, int method, const char *body) {
|
||||
memset(scratch, 0, sizeof(scratch)); memset(response_headers, 0, sizeof(response_headers));
|
||||
memset(&socket_state, 0, sizeof(socket_state));
|
||||
aux = (struct httpd_req_aux){.sd = &socket_state, .scratch = scratch,
|
||||
.scratch_cur_size = sizeof(scratch), .resp_hdrs = response_headers};
|
||||
req = (httpd_req_t){.handle = &server, .aux = &aux, .uri = uri, .method = method,
|
||||
.content_len = body ? strlen(body) : 0};
|
||||
aux.remaining_len = req.content_len;
|
||||
request_body = body; body_offset = 0;
|
||||
response_status[0] = output[0] = 0; cookie_count = 0;
|
||||
}
|
||||
static void add(const char *key, const char *value) {
|
||||
char *at = scratch;
|
||||
for (unsigned i = 0; i < aux.req_hdrs_count; ++i) at += strlen(at) + 1;
|
||||
assert((size_t)(at - scratch) + strlen(key) + strlen(value) + 3 < sizeof(scratch));
|
||||
sprintf(at, "%s: %s", key, value); ++aux.req_hdrs_count;
|
||||
}
|
||||
static void same_origin(void) { add("Host", "device.example"); add("Origin", origin); }
|
||||
static void expect(const char *status) {
|
||||
(void)web_cookie_auth_handler(&req);
|
||||
assert(!strcmp(response_status, status));
|
||||
}
|
||||
static void token_from(const char *value, char token[65]) {
|
||||
const char *start = strchr(value, '='); assert(start && strlen(start + 1) >= 64);
|
||||
memcpy(token, start + 1, 64); token[64] = 0;
|
||||
}
|
||||
static void csrf_from(char csrf[65]) {
|
||||
const char *start = strstr(output, "\"csrf\":\""); assert(start);
|
||||
memcpy(csrf, start + 8, 64); csrf[64] = 0;
|
||||
}
|
||||
static void challenge(char token[65], char csrf[65]) {
|
||||
begin("/api/login-challenge", HTTP_GET, NULL); add("Host", "device.example");
|
||||
add("X-Login-Bootstrap", "1"); expect("200 OK");
|
||||
assert(cookie_count == 1); token_from(cookie_values[0], token); csrf_from(csrf);
|
||||
}
|
||||
static const char good_body[] = "{\"username\":\"alice\",\"password\":\"password1234\"}";
|
||||
static void login_request(const char *token, const char *csrf, const char *body) {
|
||||
begin("/api/login", HTTP_POST, body); same_origin(); add("Content-Type", "application/json");
|
||||
add("X-CSRF-Token", csrf);
|
||||
char cookies[100]; snprintf(cookies, sizeof(cookies), "__Host-sak-prelogin=%s", token); add("Cookie", cookies);
|
||||
}
|
||||
static void auth_reset(void) {
|
||||
web_cookie_auth_stop(); reset(); assert(web_cookie_auth_start() == ESP_OK);
|
||||
password_calls = 0; password_hook = NULL;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
assert(store_tests() == 0); auth_reset();
|
||||
char token[65], csrf[65], session[65], cookies[200];
|
||||
challenge(token, csrf);
|
||||
begin("/api/login-challenge", HTTP_GET, NULL); add("Host", "device.example"); add("X-Login-Bootstrap", "1");
|
||||
snprintf(cookies, sizeof(cookies), "__Host-sak-prelogin=%s", token); add("Cookie", cookies);
|
||||
expect("200 OK"); assert(cookie_count == 0);
|
||||
login_request(token, csrf, good_body); expect("200 OK");
|
||||
assert(password_calls == 1 && cookie_count == 2 && snapshot().active == 1);
|
||||
assert(strstr(cookie_values[0], "__Host-sak-prelogin="));
|
||||
assert(strstr(cookie_values[0], "Max-Age=0"));
|
||||
assert(strstr(cookie_values[1], "Secure; HttpOnly; SameSite=Strict; Path=/; Max-Age=3600"));
|
||||
token_from(cookie_values[1], session);
|
||||
begin("/api/session", HTTP_GET, NULL); same_origin();
|
||||
snprintf(cookies, sizeof(cookies), "__Host-sak-session=%s", session); add("Cookie", cookies);
|
||||
expect("200 OK"); csrf_from(csrf);
|
||||
issued_t other = mint(&alice);
|
||||
begin("/api/logout", HTTP_POST, NULL); same_origin(); add("Cookie", cookies); add("X-CSRF-Token", csrf);
|
||||
expect("204 No Content"); present(&other); assert(snapshot().active == 1);
|
||||
begin("/api/session", HTTP_GET, NULL); same_origin(); add("Cookie", cookies); expect("401 Unauthorized");
|
||||
puts("PASS: challenge reuse, fragmented login, two independent Set-Cookie fields, session and isolated logout");
|
||||
|
||||
auth_reset();
|
||||
for (unsigned i = 0; i < 5; ++i) {
|
||||
challenge(token, csrf); login_request(token, csrf, "{\"username\":\"alice\",\"password\":\"wrong\"}");
|
||||
expect("401 Unauthorized");
|
||||
login_request(token, csrf, good_body); expect("403 Forbidden");
|
||||
}
|
||||
challenge(token, csrf); login_request(token, csrf, good_body); expect("429 Too Many Requests");
|
||||
assert(password_calls == 5); now += 60000000;
|
||||
challenge(token, csrf); login_request(token, csrf, good_body); expect("200 OK");
|
||||
auth_reset(); for (unsigned i = 0; i < 4; ++i) challenge(token, csrf);
|
||||
begin("/api/login-challenge", HTTP_GET, NULL); same_origin(); add("X-Login-Bootstrap", "1"); expect("503 Service Unavailable");
|
||||
now += 120000000; challenge(token, csrf);
|
||||
auth_reset(); for (unsigned i = 0; i < 4; ++i) (void)mint(&alice);
|
||||
challenge(token, csrf); login_request(token, csrf, good_body); expect("503 Service Unavailable"); assert(snapshot().active == 4);
|
||||
puts("PASS: consumed challenges, global five/60s throttle, expiry and no live challenge/session eviction");
|
||||
|
||||
auth_reset(); challenge(token, csrf);
|
||||
const char *keys[] = {"Host", "Origin", "Cookie", "Content-Type", "X-CSRF-Token"};
|
||||
for (unsigned i = 0; i < sizeof(keys)/sizeof(keys[0]); ++i) {
|
||||
login_request(token, csrf, good_body); add(keys[i], "ambiguous"); expect("400 Bad Request");
|
||||
}
|
||||
assert(password_calls == 0);
|
||||
login_request(token, csrf, good_body); add("Transfer-Encoding", "chunked"); expect("400 Bad Request");
|
||||
login_request(token, csrf, good_body); add("Sec-Fetch-Site", "cross-site"); expect("403 Forbidden");
|
||||
login_request(token, "invalid", good_body); expect("403 Forbidden");
|
||||
login_request(token, csrf, good_body); req.method = HTTP_GET; expect("400 Bad Request");
|
||||
login_request(token, csrf, good_body); req.content_len = 513; expect("413 Payload Too Large");
|
||||
login_request(token, csrf, "{\"username\":\"alice\",\"password\":\"x\",\"unknown\":1}"); expect("400 Bad Request");
|
||||
assert(password_calls == 0);
|
||||
begin("/", HTTP_GET, NULL); same_origin(); add("Authorization", "Basic ignored");
|
||||
web_session_view_t view; bool allowed;
|
||||
assert(web_cookie_auth_require(&req, false, false, &view, &allowed) == ESP_OK && !allowed);
|
||||
assert(!strcmp(response_status, "303 See Other"));
|
||||
begin("/assets/app.js", HTTP_GET, NULL); same_origin();
|
||||
assert(web_cookie_auth_require(&req, false, false, &view, &allowed) == ESP_OK && !allowed);
|
||||
assert(!strcmp(response_status, "401 Unauthorized"));
|
||||
puts("PASS: duplicate security headers, framing, methods, metadata, CSRF, strict JSON and no Basic bypass");
|
||||
|
||||
auth_reset(); other = mint(&alice);
|
||||
snprintf(cookies, sizeof(cookies), "__Host-sak-session=%s", other.token);
|
||||
begin("/api/logout", HTTP_POST, NULL); same_origin(); add("Cookie", cookies);
|
||||
expect("403 Forbidden"); present(&other);
|
||||
begin("/api/logout", HTTP_POST, NULL); add("Host", "device.example");
|
||||
add("Cookie", cookies); add("X-CSRF-Token", other.view.csrf);
|
||||
expect("403 Forbidden"); present(&other);
|
||||
begin("/api/logout", HTTP_POST, NULL); add("Host", "device.example"); add("Origin", "https://foreign.example");
|
||||
add("Cookie", cookies); add("X-CSRF-Token", other.view.csrf);
|
||||
expect("403 Forbidden"); present(&other);
|
||||
begin("/api/session", HTTP_GET, NULL); add("Host", "device.example"); add("Cookie", cookies);
|
||||
expect("200 OK");
|
||||
begin("/api/session", HTTP_GET, NULL); add("Host", "alias.local"); add("Cookie", cookies);
|
||||
expect("401 Unauthorized"); present(&other);
|
||||
challenge(token, csrf);
|
||||
begin("/api/login", HTTP_POST, good_body); same_origin(); add("Content-Type", "application/json");
|
||||
add("X-CSRF-Token", csrf);
|
||||
snprintf(cookies, sizeof(cookies), "__Host-sak-session=%s; __Host-sak-prelogin=%s", other.token, token);
|
||||
add("Cookie", cookies); expect("409 Conflict"); assert(password_calls == 0); present(&other);
|
||||
begin("/api/login", HTTP_POST, good_body); same_origin(); add("Content-Type", "application/json");
|
||||
add("X-CSRF-Token", csrf);
|
||||
snprintf(cookies, sizeof(cookies), "__Host-sak-session=%s; __Host-sak-session=%s", other.token, other.token);
|
||||
add("Cookie", cookies); expect("400 Bad Request"); assert(password_calls == 0);
|
||||
snprintf(cookies, sizeof(cookies), "__Host-sak-session=%s", other.token);
|
||||
now = other.view.expires_at_us;
|
||||
begin("/api/session", HTTP_GET, NULL); same_origin(); add("Cookie", cookies); expect("401 Unauthorized");
|
||||
auth_reset(); other = mint(&alice); stale_user = alice.user_id;
|
||||
snprintf(cookies, sizeof(cookies), "__Host-sak-session=%s", other.token);
|
||||
begin("/api/session", HTTP_GET, NULL); same_origin(); add("Cookie", cookies); expect("401 Unauthorized");
|
||||
puts("PASS: mandatory mutation Origin/CSRF, origin binding, explicit account switching, duplicate named cookies, expiry/currentness");
|
||||
|
||||
auth_reset(); challenge(token, csrf); login_request(token, csrf, good_body);
|
||||
password_hook = web_cookie_auth_stop; expect("503 Service Unavailable"); assert(!snapshot().active);
|
||||
auth_reset(); rng_fail = true;
|
||||
begin("/api/login-challenge", HTTP_GET, NULL); same_origin(); add("X-Login-Bootstrap", "1"); expect("503 Service Unavailable");
|
||||
auth_reset(); challenge(token, csrf); login_request(token, csrf, good_body); send_fail = true;
|
||||
assert(web_cookie_auth_handler(&req) != ESP_OK); send_fail = false; assert(!snapshot().active);
|
||||
auth_reset(); challenge(token, csrf); login_request(token, csrf, good_body); recv_fail = true;
|
||||
expect("400 Bad Request"); recv_fail = false; assert(!password_calls);
|
||||
begin("/api/session", HTTP_GET, NULL); add("Host", "first"); add("host", "second");
|
||||
char value[32]; assert(httpd_req_get_hdr_value_str(&req, "Host", value, sizeof(value)) == ESP_OK);
|
||||
assert(!strcmp(value, "first") && !web_httpd_headers_valid(&req));
|
||||
begin("/ws/serial", HTTP_GET, NULL); same_origin();
|
||||
add("Sec-WebSocket-Version", "13"); add("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==");
|
||||
assert(!web_httpd_upgrade_requested(&req)); aux.ws_handshake_detect = true;
|
||||
assert(web_httpd_upgrade(&req, web_cookie_auth_handler) == ESP_OK && upgrades == 1);
|
||||
assert(socket_state.ws_handshake_done && !web_httpd_upgrade_requested(&req));
|
||||
begin("/ws/serial", HTTP_GET, NULL); same_origin(); aux.ws_handshake_detect = true;
|
||||
add("Sec-WebSocket-Version", "130"); add("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==");
|
||||
assert(!web_httpd_upgrade_requested(&req));
|
||||
for (size_t keep = 0; keep <= sizeof(socket_state.pending_data); ++keep) {
|
||||
memset(socket_state.pending_data, 's', sizeof(socket_state.pending_data));
|
||||
size_t offset = sizeof(socket_state.pending_data) - keep;
|
||||
memset(socket_state.pending_data + offset, 'p', keep);
|
||||
socket_state.pending_len = keep;
|
||||
web_httpd_wipe_request(&req, false);
|
||||
zero(socket_state.pending_data, offset);
|
||||
char received[128];
|
||||
size_t first = host_read_pending(&req, received, 1);
|
||||
assert(first == (keep ? 1U : 0U));
|
||||
if (first) assert(received[0] == 'p');
|
||||
web_httpd_wipe_request(&req, false);
|
||||
zero(socket_state.pending_data, offset + first);
|
||||
size_t remaining = host_read_pending(&req, received, sizeof(received));
|
||||
assert(remaining == keep - first);
|
||||
for (size_t i = 0; i < remaining; ++i) assert(received[i] == 'p');
|
||||
}
|
||||
memset(socket_state.pending_data, 's', sizeof(socket_state.pending_data));
|
||||
socket_state.pending_len = 3; web_httpd_wipe_request(&req, true);
|
||||
zero(socket_state.pending_data, sizeof(socket_state.pending_data));
|
||||
zero(scratch, sizeof(scratch));
|
||||
puts("PASS: request wiping preserves right-aligned pending data through actual IDF reader (all lengths/partial reads)");
|
||||
puts("PASS: stop/login race, RNG/send/receive failure, actual IDF first-header semantics and explicit upgrade adapter");
|
||||
for (unsigned limit = 0; limit < 6; ++limit) {
|
||||
auth_reset(); challenge(token, csrf); login_request(token, csrf, good_body);
|
||||
server.config.max_resp_headers = limit;
|
||||
assert(web_cookie_auth_handler(&req) != ESP_OK);
|
||||
assert(snapshot().active == 0);
|
||||
server.config.max_resp_headers = 8;
|
||||
}
|
||||
auth_reset(); challenge(token, csrf); login_request(token, csrf, good_body);
|
||||
server.config.max_resp_headers = 6; expect("200 OK"); assert(cookie_count == 2);
|
||||
server.config.max_resp_headers = 8;
|
||||
puts("PASS: exact six-header successful login budget; all smaller header capacities invalidate unpublished login");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user