mirror of
https://github.com/phantomptr/ps5upload.git
synced 2026-10-19 02:20:02 -05:00
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RxCGb8Asp1A1AQYiv1kbgS
212 lines
8.0 KiB
C
212 lines
8.0 KiB
C
/* Host-side tests for the console web UI HTTP request parser / response head.
|
|
* The parser is fed growing prefixes of a request by the server loop, so
|
|
* MORE vs TOO_BIG must be right on partial input. */
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "../src/webui_http.c"
|
|
|
|
static int failures = 0;
|
|
|
|
#define CHECK(expr) \
|
|
do { \
|
|
if (!(expr)) { \
|
|
fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #expr); \
|
|
failures++; \
|
|
} \
|
|
} while (0)
|
|
|
|
static int parse_str(const char *s, webui_req_t *r) {
|
|
return webui_http_parse(s, strlen(s), r);
|
|
}
|
|
|
|
static void parses_get_with_headers(void) {
|
|
const char *req =
|
|
"GET /api/fs/list?a=1 HTTP/1.1\r\n"
|
|
"Host: 127.0.0.1:9180\r\n"
|
|
"Origin: http://127.0.0.1:9180\r\n"
|
|
"X-PS5U-Token: abcdef0123\r\n"
|
|
"\r\n";
|
|
webui_req_t r;
|
|
CHECK(parse_str(req, &r) == WEBUI_PARSE_OK);
|
|
CHECK(strcmp(r.method, "GET") == 0);
|
|
CHECK(strcmp(r.path, "/api/fs/list") == 0);
|
|
CHECK(strcmp(r.query, "a=1") == 0);
|
|
CHECK(strcmp(r.host, "127.0.0.1:9180") == 0);
|
|
CHECK(strcmp(r.origin, "http://127.0.0.1:9180") == 0);
|
|
CHECK(strcmp(r.token, "abcdef0123") == 0);
|
|
CHECK(r.content_length == 0);
|
|
CHECK(r.header_len == strlen(req));
|
|
}
|
|
|
|
static void post_body_not_consumed(void) {
|
|
const char *req = "POST /api/x HTTP/1.1\r\nContent-Length: 5\r\n\r\nhello";
|
|
webui_req_t r;
|
|
CHECK(parse_str(req, &r) == WEBUI_PARSE_OK);
|
|
CHECK(r.content_length == 5);
|
|
CHECK(r.header_len == strlen(req) - 5);
|
|
CHECK(r.query[0] == '\0');
|
|
}
|
|
|
|
static void incomplete_headers_need_more(void) {
|
|
webui_req_t r;
|
|
CHECK(parse_str("GET / HTTP/1.1\r\nHost: x\r\n", &r) == WEBUI_PARSE_MORE);
|
|
CHECK(webui_http_parse("", 0, &r) == WEBUI_PARSE_MORE);
|
|
CHECK(parse_str("GET /a", &r) == WEBUI_PARSE_MORE);
|
|
}
|
|
|
|
static void oversized_headers_too_big(void) {
|
|
char big[8193];
|
|
webui_req_t r;
|
|
memset(big, 'a', sizeof big);
|
|
memcpy(big, "GET / HTTP/1.1\r\nX: ", 19);
|
|
CHECK(webui_http_parse(big, sizeof big, &r) == WEBUI_PARSE_TOO_BIG);
|
|
/* 8192 bytes without a terminator may still complete: MORE. */
|
|
CHECK(webui_http_parse(big, 8192, &r) == WEBUI_PARSE_MORE);
|
|
}
|
|
|
|
static void long_path_too_big(void) {
|
|
static char req[1100 + 64];
|
|
webui_req_t r;
|
|
size_t n = 0;
|
|
memcpy(req, "GET /", 5); n = 5;
|
|
memset(req + n, 'p', 1025); n += 1025; /* path = 1026 chars */
|
|
memcpy(req + n, " HTTP/1.1\r\n\r\n", 13); n += 13;
|
|
CHECK(webui_http_parse(req, n, &r) == WEBUI_PARSE_TOO_BIG);
|
|
/* Exactly 1024 is fine. */
|
|
n = 5;
|
|
memcpy(req, "GET /", 5);
|
|
memset(req + n, 'p', 1023); n += 1023;
|
|
memcpy(req + n, " HTTP/1.1\r\n\r\n", 13); n += 13;
|
|
CHECK(webui_http_parse(req, n, &r) == WEBUI_PARSE_OK);
|
|
CHECK(strlen(r.path) == 1024);
|
|
}
|
|
|
|
static void body_over_64k_too_big(void) {
|
|
webui_req_t r;
|
|
CHECK(parse_str("POST /a HTTP/1.1\r\nContent-Length: 65537\r\n\r\n", &r)
|
|
== WEBUI_PARSE_TOO_BIG);
|
|
CHECK(parse_str("POST /a HTTP/1.1\r\nContent-Length: 65536\r\n\r\n", &r)
|
|
== WEBUI_PARSE_OK);
|
|
CHECK(r.content_length == 65536);
|
|
CHECK(parse_str("POST /a HTTP/1.1\r\nContent-Length: 99999999999999\r\n\r\n",
|
|
&r) == WEBUI_PARSE_TOO_BIG);
|
|
}
|
|
|
|
static void chunked_is_bad(void) {
|
|
webui_req_t r;
|
|
CHECK(parse_str("POST /a HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n", &r)
|
|
== WEBUI_PARSE_BAD);
|
|
CHECK(parse_str("POST /a HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n", &r)
|
|
== WEBUI_PARSE_BAD);
|
|
}
|
|
|
|
static void header_names_case_insensitive(void) {
|
|
webui_req_t r;
|
|
CHECK(parse_str("GET / HTTP/1.1\r\nHOST: h:9180\r\norigin: o\r\n"
|
|
"x-ps5u-token: tok\r\ncontent-length: 7\r\n\r\n", &r)
|
|
== WEBUI_PARSE_OK);
|
|
CHECK(strcmp(r.host, "h:9180") == 0);
|
|
CHECK(strcmp(r.origin, "o") == 0);
|
|
CHECK(strcmp(r.token, "tok") == 0);
|
|
CHECK(r.content_length == 7);
|
|
}
|
|
|
|
static void malformed_is_bad(void) {
|
|
webui_req_t r;
|
|
CHECK(parse_str("GET /\r\n\r\n", &r) == WEBUI_PARSE_BAD);
|
|
CHECK(parse_str("GET x HTTP/1.1\r\n\r\n", &r) == WEBUI_PARSE_BAD);
|
|
CHECK(parse_str("get / HTTP/1.1\r\n\r\n", &r) == WEBUI_PARSE_BAD);
|
|
CHECK(parse_str("GET / HTTP/2\r\n\r\n", &r) == WEBUI_PARSE_BAD);
|
|
CHECK(parse_str("GET / HTTP/1.1\r\nNoColon\r\n\r\n", &r) == WEBUI_PARSE_BAD);
|
|
CHECK(parse_str("POST / HTTP/1.1\r\nContent-Length: 1x\r\n\r\n", &r)
|
|
== WEBUI_PARSE_BAD);
|
|
CHECK(parse_str("POST / HTTP/1.1\r\nContent-Length: 1\r\n"
|
|
"Content-Length: 2\r\n\r\n", &r) == WEBUI_PARSE_BAD);
|
|
CHECK(parse_str("GET / HTTP/1.1\r\nX-PS5U-Token: "
|
|
"00000000000000000000000000000000"
|
|
"000000000000000000000000000000000\r\n\r\n", &r)
|
|
== WEBUI_PARSE_BAD);
|
|
}
|
|
|
|
static void head_has_connection_close(void) {
|
|
char out[512];
|
|
int n = webui_http_head(out, sizeof out, 200, "application/json", 42, NULL);
|
|
CHECK(n > 0 && (size_t)n == strlen(out));
|
|
CHECK(strncmp(out, "HTTP/1.1 200 OK\r\n", 17) == 0);
|
|
CHECK(strstr(out, "Connection: close\r\n") != NULL);
|
|
CHECK(strstr(out, "Cache-Control: no-store\r\n") != NULL);
|
|
CHECK(strstr(out, "X-Content-Type-Options: nosniff\r\n") != NULL);
|
|
CHECK(strstr(out, "Content-Type: application/json\r\n") != NULL);
|
|
CHECK(strstr(out, "Content-Length: 42\r\n") != NULL);
|
|
CHECK(strstr(out, "Access-Control") == NULL);
|
|
n = (int)strlen(out);
|
|
CHECK(n >= 4 && strcmp(out + n - 4, "\r\n\r\n") == 0);
|
|
|
|
n = webui_http_head(out, sizeof out, 404, "text/plain", 0,
|
|
"X-Foo: bar\r\n");
|
|
CHECK(n > 0 && strstr(out, "404 Not Found") != NULL);
|
|
CHECK(strstr(out, "X-Foo: bar\r\n") != NULL);
|
|
|
|
CHECK(webui_http_head(out, 20, 200, "text/plain", 1, NULL) == -1);
|
|
CHECK(webui_http_head(out, sizeof out, 200, NULL, 1, NULL) > 0);
|
|
}
|
|
|
|
static void every_prefix_of_long_request(void) {
|
|
static char req[2300];
|
|
webui_req_t r;
|
|
size_t n = 0, k, term;
|
|
memcpy(req, "GET /", 5); n = 5;
|
|
memset(req + n, 'p', 99); n += 99; /* path 100 */
|
|
req[n++] = '?';
|
|
memset(req + n, 'q', 1000); n += 1000; /* query 1000 */
|
|
memcpy(req + n, " HTTP/1.1\r\nHost: h\r\n\r\n", 22); n += 22;
|
|
term = n;
|
|
for (k = 0; k < term; k++)
|
|
CHECK(webui_http_parse(req, k, &r) == WEBUI_PARSE_MORE);
|
|
CHECK(webui_http_parse(req, n, &r) == WEBUI_PARSE_OK);
|
|
CHECK(strlen(r.query) == 1000);
|
|
|
|
/* Largest legal line (path 1024, query 1024) also never flips. */
|
|
n = 0;
|
|
memcpy(req, "DELETE /", 8); n = 8;
|
|
memset(req + n, 'p', 1023); n += 1023;
|
|
req[n++] = '?';
|
|
memset(req + n, 'q', 1024); n += 1024;
|
|
memcpy(req + n, " HTTP/1.1\r\n\r\n", 13); n += 13;
|
|
for (k = 0; k < n; k++)
|
|
CHECK(webui_http_parse(req, k, &r) == WEBUI_PARSE_MORE);
|
|
CHECK(webui_http_parse(req, n, &r) == WEBUI_PARSE_OK);
|
|
}
|
|
|
|
static void duplicate_identity_headers_bad(void) {
|
|
webui_req_t r;
|
|
CHECK(parse_str("GET / HTTP/1.1\r\nHost: a\r\nhost: b\r\n\r\n", &r)
|
|
== WEBUI_PARSE_BAD);
|
|
CHECK(parse_str("GET / HTTP/1.1\r\nOrigin: a\r\nORIGIN: a\r\n\r\n", &r)
|
|
== WEBUI_PARSE_BAD);
|
|
CHECK(parse_str("GET / HTTP/1.1\r\nX-PS5U-Token: a\r\nx-ps5u-token: b\r\n\r\n",
|
|
&r) == WEBUI_PARSE_BAD);
|
|
}
|
|
|
|
int main(void) {
|
|
every_prefix_of_long_request();
|
|
duplicate_identity_headers_bad();
|
|
parses_get_with_headers();
|
|
post_body_not_consumed();
|
|
incomplete_headers_need_more();
|
|
oversized_headers_too_big();
|
|
long_path_too_big();
|
|
body_over_64k_too_big();
|
|
chunked_is_bad();
|
|
header_names_case_insensitive();
|
|
malformed_is_bad();
|
|
head_has_connection_close();
|
|
if (failures) {
|
|
fprintf(stderr, "%d failure(s)\n", failures);
|
|
return 1;
|
|
}
|
|
printf("webui_http_selftest: all passed\n");
|
|
return 0;
|
|
}
|