mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-09-02 15:02:51 +08:00
httpd: buffer a plain POST body that arrives after the headers
A POST /cmd whose body arrives in a separate TCP segment from the headers executed nothing: handle_post() read the body from the segment that carried the request line, found it empty, and answered 200 OK. Python's urllib and requests both write headers and body separately, so every scripted command from those clients became a silent no-op. POST /login had the same hole. The multipart endpoints were fixed earlier; this covers the plain ones. When the first segment holds fewer body bytes than Content-Length announces, the bytes accumulate in config_buf (idle outside multipart uploads) and the connection waits in TSTATE_POSTBODY; once the announced length is in, the endpoint runs against the buffer. The /cmd and /login executions move into run_cmd_body() and run_login_body() so both paths share them; the 200 OK builder they duplicated becomes send_ok(), and run_login_body() checks the pwd= prefix instead of skipping four bytes blindly. The announced length is the contract on both paths. A body that arrives with the headers is terminated at Content-Length before it runs, so pipelined bytes after it are not executed as commands the way the old code did. A request without a usable Content-Length answers 411: without one there is no way to know when the body has arrived, and the old 200 OK for an empty body is the bug this fixes. Bodies announced at or above the buffer size answer 400 before anything is buffered. The wait state has a deadline. uIP is built with a single connection, and an ESTABLISHED connection with nothing in flight never times out on its own, so headers followed by silence (a script interrupted, a link dropped mid-request, or someone holding the socket on purpose) would otherwise keep the slot until reboot; POST /login reaches the wait before authentication. The poll handler aborts the connection when the body has made no progress for five seconds, measured on the 200 Hz system tick rather than on poll count, which runs faster under interrupt load.
This commit is contained in:
+121
-24
@@ -62,6 +62,12 @@ __xdata uint8_t verify_crc;
|
|||||||
__xdata uint32_t max_upload;
|
__xdata uint32_t max_upload;
|
||||||
__xdata uint16_t short_parsed;
|
__xdata uint16_t short_parsed;
|
||||||
|
|
||||||
|
#define POSTBODY_CMD 1
|
||||||
|
#define POSTBODY_LOGIN 2
|
||||||
|
#define POSTBODY_TIMEOUT (5 * SYS_TICK_HZ)
|
||||||
|
__xdata uint8_t postbody_endpoint;
|
||||||
|
__xdata uint16_t postbody_start;
|
||||||
|
|
||||||
__xdata char passwd[21];
|
__xdata char passwd[21];
|
||||||
// Set when a verified firmware upload awaits its response ACK, after
|
// Set when a verified firmware upload awaits its response ACK, after
|
||||||
// which the chip resets to apply the staged image
|
// which the chip resets to apply the staged image
|
||||||
@@ -78,6 +84,7 @@ __xdata uint32_t last_session_use;
|
|||||||
#define TSTATE_CLOSED 3
|
#define TSTATE_CLOSED 3
|
||||||
#define TSTATE_POST 4
|
#define TSTATE_POST 4
|
||||||
#define TSTATE_MULTIPART 5
|
#define TSTATE_MULTIPART 5
|
||||||
|
#define TSTATE_POSTBODY 6
|
||||||
|
|
||||||
extern __xdata uint16_t crc_value;
|
extern __xdata uint16_t crc_value;
|
||||||
__xdata uint16_t crc_final;
|
__xdata uint16_t crc_final;
|
||||||
@@ -268,6 +275,18 @@ void send_unauthorized(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void send_length_required(void)
|
||||||
|
{
|
||||||
|
slen = strtox(outbuf, "HTTP/1.1 411 Length Required\r\nConnection: close\r\n\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void send_ok(void)
|
||||||
|
{
|
||||||
|
slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
__xdata uint8_t *scan_header(__xdata uint8_t * __xdata p)
|
__xdata uint8_t *scan_header(__xdata uint8_t * __xdata p)
|
||||||
{
|
{
|
||||||
__xdata uint8_t *v;
|
__xdata uint8_t *v;
|
||||||
@@ -559,7 +578,7 @@ static void handle_config_fragment(__xdata uint8_t *p)
|
|||||||
send_bad_request();
|
send_bad_request();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n");
|
send_ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -604,12 +623,95 @@ static void handle_firmware_fragment(__xdata uint8_t *p)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void run_cmd_body(__xdata uint8_t *body)
|
||||||
|
{
|
||||||
|
execute_commands(body);
|
||||||
|
if (err_status != ERR_OK) {
|
||||||
|
send_bad_request();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
send_ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void run_login_body(__xdata uint8_t *body)
|
||||||
|
{
|
||||||
|
if (strstart(body, "pwd=") && is_url_word_x(body + 4, passwd)) {
|
||||||
|
dbg_string("Password accepted!\n");
|
||||||
|
read_reg_timer(&last_session_use);
|
||||||
|
gen_random_hex_chars(session_id, SESSION_ID_LENGTH);
|
||||||
|
session_id[SESSION_ID_LENGTH] = NUL;
|
||||||
|
slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nConnection: close\r\nLocation: index.html\r\n" \
|
||||||
|
"Set-Cookie: session=");
|
||||||
|
for (uint8_t i = 0; i < SESSION_ID_LENGTH; i++)
|
||||||
|
outbuf[slen++] = session_id[i];
|
||||||
|
slen += strtox(outbuf + slen, "; SameSite=Strict\r\n\r\n");
|
||||||
|
} else {
|
||||||
|
dbg_string("Password invalid!\n");
|
||||||
|
slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nConnection: close\r\nLocation: login.html\r\n\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint8_t post_body_take(__xdata uint8_t *p)
|
||||||
|
{
|
||||||
|
uint16_t have;
|
||||||
|
|
||||||
|
if (!content_length) {
|
||||||
|
send_length_required();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (content_length >= CONFIG_UPLOAD_BUF) {
|
||||||
|
send_bad_request();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
have = uip_len - (p - uip_appdata);
|
||||||
|
if (have >= content_length) {
|
||||||
|
p[content_length] = NUL;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
memcpy(config_buf, p, have);
|
||||||
|
pre_acc = have;
|
||||||
|
postbody_start = ticks;
|
||||||
|
uip_conn->appstate.tstate = TSTATE_POSTBODY;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void post_body_continue(void)
|
||||||
|
{
|
||||||
|
uint16_t take;
|
||||||
|
|
||||||
|
// no header scan runs while the body is pending: content_length is this request's
|
||||||
|
take = content_length - pre_acc;
|
||||||
|
if (take > uip_len)
|
||||||
|
take = uip_len;
|
||||||
|
memcpy(config_buf + pre_acc, uip_appdata, take);
|
||||||
|
pre_acc += take;
|
||||||
|
if (pre_acc < content_length) {
|
||||||
|
postbody_start = ticks;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
config_buf[pre_acc] = NUL;
|
||||||
|
uip_conn->appstate.tstate = TSTATE_NONE;
|
||||||
|
if (postbody_endpoint == POSTBODY_CMD)
|
||||||
|
run_cmd_body(config_buf);
|
||||||
|
else
|
||||||
|
run_login_body(config_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void handle_post(void)
|
void handle_post(void)
|
||||||
{
|
{
|
||||||
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
|
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
|
||||||
__xdata uint8_t *p = uip_appdata;
|
__xdata uint8_t *p = uip_appdata;
|
||||||
__xdata uint8_t *request_path = p + 6;
|
__xdata uint8_t *request_path = p + 6;
|
||||||
|
|
||||||
|
if (s->tstate == TSTATE_POSTBODY) {
|
||||||
|
post_body_continue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Was the multipart header sent in multiple packets?
|
// Was the multipart header sent in multiple packets?
|
||||||
if (s->tstate != TSTATE_MULTIPART) {
|
if (s->tstate != TSTATE_MULTIPART) {
|
||||||
dbg_string("Is POST\n");
|
dbg_string("Is POST\n");
|
||||||
@@ -662,11 +764,11 @@ void handle_post(void)
|
|||||||
send_unauthorized();
|
send_unauthorized();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
execute_commands(p);
|
postbody_endpoint = POSTBODY_CMD;
|
||||||
if (err_status != ERR_OK) {
|
if (!post_body_take(p))
|
||||||
send_bad_request();
|
|
||||||
return;
|
return;
|
||||||
}
|
run_cmd_body(p);
|
||||||
|
return;
|
||||||
} else if (is_word(request_path, "login")) {
|
} else if (is_word(request_path, "login")) {
|
||||||
dbg_string("POST login\n");
|
dbg_string("POST login\n");
|
||||||
|
|
||||||
@@ -676,21 +778,11 @@ void handle_post(void)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
p += 8; // Read also over "pwd="
|
p += 4;
|
||||||
if (is_url_word_x(p, passwd)) {
|
postbody_endpoint = POSTBODY_LOGIN;
|
||||||
dbg_string("Password accepted!\n");
|
if (!post_body_take(p))
|
||||||
read_reg_timer(&last_session_use);
|
return;
|
||||||
gen_random_hex_chars(session_id, SESSION_ID_LENGTH);
|
run_login_body(p);
|
||||||
session_id[SESSION_ID_LENGTH] = NUL;
|
|
||||||
slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nConnection: close\r\nLocation: index.html\r\n" \
|
|
||||||
"Set-Cookie: session=");
|
|
||||||
for (uint8_t i = 0; i < SESSION_ID_LENGTH; i++)
|
|
||||||
outbuf[slen++] = session_id[i];
|
|
||||||
slen += strtox(outbuf + slen, "; SameSite=Strict\r\n\r\n");
|
|
||||||
} else {
|
|
||||||
dbg_string("Password invalid!\n");
|
|
||||||
slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nConnection: close\r\nLocation: login.html\r\n\r\n");
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
} else if (s->tstate == TSTATE_MULTIPART || is_word(request_path, "upload") || is_word(request_path, "config")) {
|
} else if (s->tstate == TSTATE_MULTIPART || is_word(request_path, "upload") || is_word(request_path, "config")) {
|
||||||
dbg_string("POST upload/config request\n");
|
dbg_string("POST upload/config request\n");
|
||||||
@@ -712,8 +804,6 @@ void handle_post(void)
|
|||||||
send_not_found();
|
send_not_found();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -744,6 +834,11 @@ void httpd_appcall(void)
|
|||||||
dbg_string("Closing because everything has been transmitted\n");
|
dbg_string("Closing because everything has been transmitted\n");
|
||||||
uip_close();
|
uip_close();
|
||||||
s->tstate = TSTATE_CLOSED;
|
s->tstate = TSTATE_CLOSED;
|
||||||
|
} else if (s->tstate == TSTATE_POSTBODY
|
||||||
|
&& (uint16_t)ticks - postbody_start > POSTBODY_TIMEOUT) {
|
||||||
|
dbg_string("Body never arrived\n");
|
||||||
|
uip_abort();
|
||||||
|
s->tstate = TSTATE_CLOSED;
|
||||||
}
|
}
|
||||||
} else if (uip_acked() && s->tstate == TSTATE_TX) {
|
} else if (uip_acked() && s->tstate == TSTATE_TX) {
|
||||||
dbg_string("ACK\n");
|
dbg_string("ACK\n");
|
||||||
@@ -811,10 +906,12 @@ void httpd_appcall(void)
|
|||||||
dbg_char('\n');
|
dbg_char('\n');
|
||||||
#endif
|
#endif
|
||||||
p = uip_appdata;
|
p = uip_appdata;
|
||||||
if (is_word(p, "POST") || s->tstate == TSTATE_MULTIPART) {
|
if (is_word(p, "POST") || s->tstate == TSTATE_MULTIPART
|
||||||
|
|| s->tstate == TSTATE_POSTBODY) {
|
||||||
handle_post();
|
handle_post();
|
||||||
// If this is an ongoing post stream, then wait for the next packet
|
// If this is an ongoing post stream, then wait for the next packet
|
||||||
if (s->tstate == TSTATE_POST || s->tstate == TSTATE_MULTIPART) {
|
if (s->tstate == TSTATE_POST || s->tstate == TSTATE_MULTIPART
|
||||||
|
|| s->tstate == TSTATE_POSTBODY) {
|
||||||
uip_len = 0;
|
uip_len = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user