mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-10-29 17:54:14 +08:00
httpd: handle headers split across segments correctly
Signed-off-by: João Silva <jgc3silva@gmail.com>
This commit is contained in:
2 files changed
+229
-7
No files matched your search
+53
-7
@@ -90,6 +90,7 @@ __xdata uint32_t last_session_use;
|
||||
#define TSTATE_POST 4
|
||||
#define TSTATE_MULTIPART 5
|
||||
#define TSTATE_POSTBODY 6
|
||||
#define TSTATE_POSTHEADER 7
|
||||
|
||||
extern __xdata uint16_t crc_value;
|
||||
__xdata uint16_t crc_final;
|
||||
@@ -746,11 +747,47 @@ static void post_body_continue(void)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Buffer a POST request header across TCP receive events. Stop copying at
|
||||
* CRLFCRLF so any body bytes in the final segment stay in uip_appdata.
|
||||
*
|
||||
* Return the body offset in the current receive buffer, 0 while incomplete,
|
||||
* or 0xffff after sending an error response.
|
||||
*/
|
||||
static uint16_t post_header_take(void)
|
||||
{
|
||||
uint16_t i;
|
||||
|
||||
for (i = 0; i < uip_len; i++) {
|
||||
if (pre_acc + 1 >= CONFIG_UPLOAD_BUF) {
|
||||
uip_conn->appstate.tstate = TSTATE_NONE;
|
||||
send_bad_request();
|
||||
return 0xffff;
|
||||
}
|
||||
config_buf[pre_acc++] = ((__xdata uint8_t *)uip_appdata)[i];
|
||||
config_buf[pre_acc] = NUL;
|
||||
if (pre_acc >= 4
|
||||
&& config_buf[pre_acc - 4] == '\r'
|
||||
&& config_buf[pre_acc - 3] == '\n'
|
||||
&& config_buf[pre_acc - 2] == '\r'
|
||||
&& config_buf[pre_acc - 1] == '\n') {
|
||||
uip_conn->appstate.tstate = TSTATE_NONE;
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
postbody_start = ticks;
|
||||
uip_conn->appstate.tstate = TSTATE_POSTHEADER;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void handle_post(void)
|
||||
{
|
||||
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
|
||||
__xdata uint8_t *p = uip_appdata;
|
||||
__xdata uint8_t *request_path = p + 6;
|
||||
uint16_t body_off;
|
||||
|
||||
if (s->tstate == TSTATE_POSTBODY) {
|
||||
post_body_continue();
|
||||
@@ -759,6 +796,14 @@ void handle_post(void)
|
||||
|
||||
// Was the multipart header sent in multiple packets?
|
||||
if (s->tstate != TSTATE_MULTIPART) {
|
||||
if (s->tstate != TSTATE_POSTHEADER)
|
||||
pre_acc = 0;
|
||||
body_off = post_header_take();
|
||||
if (!body_off || body_off == 0xffff)
|
||||
return;
|
||||
p = config_buf;
|
||||
request_path = p + 6;
|
||||
|
||||
dbg_string("Is POST\n");
|
||||
p += 5; // Skip post
|
||||
// Find end of request path
|
||||
@@ -772,9 +817,12 @@ void handle_post(void)
|
||||
dbg_string("Boundary: >"); dbg_string_x(boundary); dbg_string("<\n");
|
||||
if (!*p || !content_type) {
|
||||
dbg_string("Bad Request!\n");
|
||||
send_not_found();
|
||||
send_bad_request();
|
||||
return;
|
||||
}
|
||||
|
||||
pre_acc = 0;
|
||||
p = (__xdata uint8_t *)uip_appdata + body_off;
|
||||
if (is_word(request_path, "upload")) {
|
||||
if (!authenticated) {
|
||||
send_unauthorized();
|
||||
@@ -824,7 +872,6 @@ void handle_post(void)
|
||||
handle_firmware_fragment(p);
|
||||
return;
|
||||
} else if (is_word(request_path, "cmd")) {
|
||||
p += 4;
|
||||
if (!authenticated) {
|
||||
send_unauthorized();
|
||||
return;
|
||||
@@ -843,7 +890,6 @@ void handle_post(void)
|
||||
return;
|
||||
}
|
||||
|
||||
p += 4;
|
||||
postbody_endpoint = POSTBODY_LOGIN;
|
||||
if (!post_body_take(p))
|
||||
return;
|
||||
@@ -890,9 +936,9 @@ void httpd_appcall(void)
|
||||
dbg_string("Closing because everything has been transmitted\n");
|
||||
uip_close();
|
||||
s->tstate = TSTATE_CLOSED;
|
||||
} else if (s->tstate == TSTATE_POSTBODY
|
||||
} else if ((s->tstate == TSTATE_POSTBODY || s->tstate == TSTATE_POSTHEADER)
|
||||
&& (uint16_t)ticks - postbody_start > POSTBODY_TIMEOUT) {
|
||||
dbg_string("Body never arrived\n");
|
||||
dbg_string("POST request never completed\n");
|
||||
uip_abort();
|
||||
s->tstate = TSTATE_CLOSED;
|
||||
}
|
||||
@@ -964,11 +1010,11 @@ void httpd_appcall(void)
|
||||
#endif
|
||||
p = uip_appdata;
|
||||
if (is_word(p, "POST") || s->tstate == TSTATE_MULTIPART
|
||||
|| s->tstate == TSTATE_POSTBODY) {
|
||||
|| s->tstate == TSTATE_POSTBODY || s->tstate == TSTATE_POSTHEADER) {
|
||||
handle_post();
|
||||
// If this is an ongoing post stream, then wait for the next packet
|
||||
if (s->tstate == TSTATE_POST || s->tstate == TSTATE_MULTIPART
|
||||
|| s->tstate == TSTATE_POSTBODY) {
|
||||
|| s->tstate == TSTATE_POSTBODY || s->tstate == TSTATE_POSTHEADER) {
|
||||
uip_len = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -156,6 +156,7 @@ void send_vlanlist(void) { }
|
||||
void send_lldp(void) {}
|
||||
|
||||
extern uint8_t authenticated;
|
||||
extern char passwd[21];
|
||||
|
||||
/* uip.c defines the listen table but no header declares it; the bench reads it
|
||||
* to confirm the port httpd_init() asked for is the one uIP is watching. */
|
||||
@@ -362,6 +363,22 @@ static int first_body_mismatch(void)
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int stream_contains(const char *needle)
|
||||
{
|
||||
size_t n = strlen(needle);
|
||||
|
||||
for (int i = 0; i + (int)n <= stream_len; i++)
|
||||
if (!memcmp(stream + i, needle, n))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void login_session_start(void)
|
||||
{
|
||||
session_start(MSS_FULL);
|
||||
strcpy(passwd, "1234");
|
||||
}
|
||||
|
||||
static void report(const char *tag)
|
||||
{
|
||||
if (verbose)
|
||||
@@ -493,6 +510,157 @@ static void scenario_bad_l4_checksum(void)
|
||||
"bad checksum control: the file that follows is intact");
|
||||
}
|
||||
|
||||
|
||||
static void scenario_fragmented_post_header(void)
|
||||
{
|
||||
static const char first[] =
|
||||
"POST /login HTTP/1.1\r\n"
|
||||
"Host: sw\r\n"
|
||||
"Content-Len";
|
||||
static const char second[] =
|
||||
"gth: 8\r\n"
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
"\r\n"
|
||||
"pwd=1234";
|
||||
|
||||
login_session_start();
|
||||
client_send(TCP_ACK | TCP_PSH, first, (int)strlen(first));
|
||||
CHECK(stream_len == 0,
|
||||
"fragmented header: incomplete first fragment has no response");
|
||||
|
||||
client_send(TCP_ACK | TCP_PSH, second, (int)strlen(second));
|
||||
CHECK(stream_contains("HTTP/1.1 302 Found\r\n"),
|
||||
"fragmented header: split Content-Length is accepted");
|
||||
CHECK(stream_contains("Location: index.html\r\n"),
|
||||
"fragmented header: login body is parsed after reassembly");
|
||||
}
|
||||
|
||||
static void scenario_split_content_type(void)
|
||||
{
|
||||
static const char first[] =
|
||||
"POST /login HTTP/1.1\r\n"
|
||||
"Content-Length: 8\r\n"
|
||||
"Content-Ty";
|
||||
static const char second[] =
|
||||
"pe: application/x-www-form-urlencoded\r\n"
|
||||
"\r\n"
|
||||
"pwd=1234";
|
||||
|
||||
login_session_start();
|
||||
client_send(TCP_ACK | TCP_PSH, first, (int)strlen(first));
|
||||
CHECK(stream_len == 0,
|
||||
"split Content-Type: incomplete header has no response");
|
||||
client_send(TCP_ACK | TCP_PSH, second, (int)strlen(second));
|
||||
CHECK(stream_contains("Location: index.html\r\n"),
|
||||
"split Content-Type: field name can span receive events");
|
||||
}
|
||||
|
||||
static void scenario_split_header_terminator(void)
|
||||
{
|
||||
static const char first[] =
|
||||
"POST /login HTTP/1.1\r\n"
|
||||
"Content-Length: 8\r\n"
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
"\r";
|
||||
static const char second[] = "\npwd=1234";
|
||||
|
||||
login_session_start();
|
||||
client_send(TCP_ACK | TCP_PSH, first, (int)strlen(first));
|
||||
CHECK(stream_len == 0,
|
||||
"split terminator: CRLFCR fragment has no premature response");
|
||||
client_send(TCP_ACK | TCP_PSH, second, (int)strlen(second));
|
||||
CHECK(stream_contains("Location: index.html\r\n"),
|
||||
"split terminator: CRLFCRLF can span receive events");
|
||||
}
|
||||
|
||||
static void scenario_header_then_body(void)
|
||||
{
|
||||
static const char header[] =
|
||||
"POST /login HTTP/1.1\r\n"
|
||||
"Content-Length: 8\r\n"
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
"\r\n";
|
||||
|
||||
login_session_start();
|
||||
client_send(TCP_ACK | TCP_PSH, header, (int)strlen(header));
|
||||
CHECK(stream_len == 0,
|
||||
"header-only segment: waits for the declared body");
|
||||
client_send(TCP_ACK | TCP_PSH, "pwd=1234", 8);
|
||||
CHECK(stream_contains("Location: index.html\r\n"),
|
||||
"header-only segment: following body completes login");
|
||||
}
|
||||
|
||||
static void scenario_header_with_partial_body(void)
|
||||
{
|
||||
static const char first[] =
|
||||
"POST /login HTTP/1.1\r\n"
|
||||
"Content-Length: 8\r\n"
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
"\r\n"
|
||||
"pwd=";
|
||||
|
||||
login_session_start();
|
||||
client_send(TCP_ACK | TCP_PSH, first, (int)strlen(first));
|
||||
CHECK(stream_len == 0,
|
||||
"partial body: waits when final header segment has only part of body");
|
||||
client_send(TCP_ACK | TCP_PSH, "1234", 4);
|
||||
CHECK(stream_contains("Location: index.html\r\n"),
|
||||
"partial body: existing body reassembly takes over correctly");
|
||||
}
|
||||
|
||||
static void scenario_post_header_timeout(void)
|
||||
{
|
||||
static const char partial[] =
|
||||
"POST /login HTTP/1.1\r\n"
|
||||
"Content-Length: 8\r\n"
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n";
|
||||
|
||||
login_session_start();
|
||||
ticks = 0;
|
||||
client_send(TCP_ACK | TCP_PSH, partial, (int)strlen(partial));
|
||||
CHECK(stream_len == 0,
|
||||
"header timeout: incomplete header has no immediate response");
|
||||
|
||||
/* The firmware timeout is 5 seconds; 2000 host ticks is safely beyond it. */
|
||||
ticks = 2000;
|
||||
run_periodic(1);
|
||||
CHECK(!uip_conn_active(0),
|
||||
"header timeout: connection is aborted when the header never completes");
|
||||
}
|
||||
|
||||
static void scenario_oversized_post_header(void)
|
||||
{
|
||||
static const char start[] =
|
||||
"POST /login HTTP/1.1\r\n"
|
||||
"X-Fill: ";
|
||||
char fill[1200];
|
||||
|
||||
login_session_start();
|
||||
memset(fill, 'a', sizeof(fill));
|
||||
client_send(TCP_ACK | TCP_PSH, start, (int)strlen(start));
|
||||
|
||||
/* Keep extending a header with no CRLFCRLF until the fixed buffer refuses it. */
|
||||
for (int i = 0; i < 16 && !stream_contains("400 Bad Request"); i++)
|
||||
client_send(TCP_ACK | TCP_PSH, fill, (int)sizeof(fill));
|
||||
|
||||
CHECK(stream_contains("HTTP/1.1 400 Bad Request\r\n"),
|
||||
"oversized header: rejected before the header buffer overflows");
|
||||
}
|
||||
|
||||
static void scenario_fragmented_missing_content_length(void)
|
||||
{
|
||||
static const char first[] =
|
||||
"POST /login HTTP/1.1\r\n"
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n";
|
||||
static const char second[] = "\r\npwd=1234";
|
||||
|
||||
login_session_start();
|
||||
client_send(TCP_ACK | TCP_PSH, first, (int)strlen(first));
|
||||
client_send(TCP_ACK | TCP_PSH, second, (int)strlen(second));
|
||||
CHECK(stream_contains("HTTP/1.1 411 Length Required\r\n"),
|
||||
"fragmented header: missing Content-Length still returns 411");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc > 1 && !strcmp(argv[1], "-v"))
|
||||
@@ -504,6 +672,14 @@ int main(int argc, char **argv)
|
||||
scenario_growing_window();
|
||||
scenario_rexmit_after_shrink();
|
||||
scenario_bad_l4_checksum();
|
||||
scenario_fragmented_post_header();
|
||||
scenario_split_content_type();
|
||||
scenario_split_header_terminator();
|
||||
scenario_header_then_body();
|
||||
scenario_header_with_partial_body();
|
||||
scenario_post_header_timeout();
|
||||
scenario_oversized_post_header();
|
||||
scenario_fragmented_missing_content_length();
|
||||
|
||||
printf("\n%s (%d failure%s)\n",
|
||||
failures ? "BENCH: FAILURES" : "BENCH: ALL PASS",
|
||||
|
||||
Reference in new issue
Block a user