httpd: buffer a firmware upload's part header before streaming

A firmware image cannot be buffered whole, so the upload is streamed to
flash, and stream_upload() already resumes across TCP segments. The
multipart preamble did not: handle_post() walked the part headers from the
start of whichever segment it held, so a client that split inside the
octet-stream part header lost its place, never started streaming, and the
request hung. Firefox splits exactly there, right after filename=".

Buffer the multipart body only until the octet-stream part header is
complete, locate the payload, then stream from that point; later segments
stream as before. The header reuses the configuration buffer, which is idle
during a firmware upload, so no extra memory is needed.
This commit is contained in:
bloqaudio
2026-08-30 04:09:30 -05:00
parent 1db30f22de
commit 1bd4a8d3cc
+36 -31
View File
@@ -49,6 +49,7 @@ __xdata uint8_t boundary[72];
__xdata uint8_t config_upload; __xdata uint8_t config_upload;
__xdata uint8_t config_buf[CONFIG_UPLOAD_BUF]; __xdata uint8_t config_buf[CONFIG_UPLOAD_BUF];
__xdata uint16_t cfg_pos, cfg_hdr, cfg_body, cfg_end, cfg_last; __xdata uint16_t cfg_pos, cfg_hdr, cfg_body, cfg_end, cfg_last;
__xdata uint16_t pre_acc;
__xdata uint8_t cfg_bl; __xdata uint8_t cfg_bl;
__xdata uint8_t * __xdata content_type = 0; __xdata uint8_t * __xdata content_type = 0;
__xdata uint8_t * __xdata session = 0; __xdata uint8_t * __xdata session = 0;
@@ -240,17 +241,6 @@ void send_unauthorized(void)
} }
__xdata uint8_t *skip_boundary(__xdata uint8_t *p)
{
while (*p) {
if (is_word_x(p, boundary))
return p + strlen_x(boundary);
p++;
}
return p;
}
__xdata uint8_t *scan_header(__xdata uint8_t * __xdata p) __xdata uint8_t *scan_header(__xdata uint8_t * __xdata p)
{ {
content_type = 0; content_type = 0;
@@ -386,21 +376,38 @@ static uint8_t config_take(void)
} }
// unlike scan_header(), keeps no auth state, so it may run on every buffered segment
static uint16_t preamble_payload_start(__xdata uint16_t n)
{
for (cfg_pos = 0; cfg_pos + 24 <= n; cfg_pos++) {
if (strstart(&config_buf[cfg_pos], "application/octet-stream"))
break;
}
if (cfg_pos + 24 > n)
return 0;
cfg_pos += 24;
while (cfg_pos + 3 < n && !strstart(&config_buf[cfg_pos], "\r\n\r\n"))
cfg_pos++;
if (cfg_pos + 3 >= n)
return 0;
return cfg_pos + 4;
}
/* /*
* Reads post data from the http stream and writes it into flash memory * Reads post data from the http stream and writes it into flash memory
* Input: the current position in the TCP buffer (uip_appdata) * Input: the current position in the TCP buffer (uip_appdata)
* Returns 1: More data to read, 0: Upload complete, all parts reads * Returns 1: More data to read, 0: Upload complete, all parts reads
*/ */
uint8_t stream_upload(uint16_t bptr) uint8_t stream_upload(__xdata uint8_t *p, uint16_t bptr, uint16_t plen)
{ {
__xdata uint8_t *p = uip_appdata;
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate); __xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
dbg_string("Stream_upload called: "); dbg_string("Stream_upload called: ");
dbg_short(bptr); dbg_char('\n'); dbg_short(bptr); dbg_char('\n');
do { do {
if (bptr >= uip_len) { if (bptr >= plen) {
s->tstate = TSTATE_POST; s->tstate = TSTATE_POST;
return 1; return 1;
} }
@@ -431,7 +438,7 @@ uint8_t stream_upload(uint16_t bptr)
flash_region.addr = uptr; flash_region.addr = uptr;
flash_region.len = 1; flash_region.len = 1;
flash_write_bytes(flash_buf); flash_write_bytes(flash_buf);
if (bptr >= uip_len) if (bptr >= plen)
return 0; return 0;
if(!verify_crc) if(!verify_crc)
//ugly hack to signal connection finished after config upload. //ugly hack to signal connection finished after config upload.
@@ -512,6 +519,7 @@ void handle_post(void)
uptr = FIRMWARE_UPLOAD_START; uptr = FIRMWARE_UPLOAD_START;
verify_crc = 1; verify_crc = 1;
max_upload = 1024576; max_upload = 1024576;
pre_acc = 0;
} else if (is_word(request_path, "config")) { } else if (is_word(request_path, "config")) {
if (!authenticated) { if (!authenticated) {
send_unauthorized(); send_unauthorized();
@@ -600,21 +608,21 @@ void handle_post(void)
slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n"); slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n");
return; return;
} }
// We skip the intial parts as part of the header cfg_pos = uip_len - (p - uip_appdata);
do { if (pre_acc + cfg_pos >= CONFIG_UPLOAD_BUF) {
p = skip_boundary(p); print_string("Firmware upload header too large, aborting.\n");
if (!*p) { s->tstate = TSTATE_NONE;
send_bad_request();
return;
}
memcpy(config_buf + pre_acc, p, cfg_pos);
pre_acc += cfg_pos;
cfg_end = preamble_payload_start(pre_acc);
if (!cfg_end) {
s->tstate = TSTATE_MULTIPART; s->tstate = TSTATE_MULTIPART;
return; return;
} }
p = scan_header(p);
if (!*p)
goto bad_request;
if (!content_type) // We are waiting for the part with the octet stream
continue;
} while (!is_word(content_type, "application/octet-stream"));
dbg_string("Have content octets\n"); dbg_string("Have content octets\n");
p += 4; // Skip \r\n\r\n sequence at end of preamble of part
flash_init(0); // Re-initialize flash for non-DIO operation, otherwise flashing fails flash_init(0); // Re-initialize flash for non-DIO operation, otherwise flashing fails
set_sys_led_state(SYS_LED_FAST); set_sys_led_state(SYS_LED_FAST);
@@ -622,7 +630,7 @@ void handle_post(void)
crc_value = 0; crc_value = 0;
bindex = 0; bindex = 0;
write_len = 0; write_len = 0;
stream_upload(p - uip_appdata); stream_upload(config_buf, cfg_end, pre_acc);
dbg_string("Done reading first fragment\n"); dbg_string("Done reading first fragment\n");
return; return;
@@ -633,9 +641,6 @@ void handle_post(void)
} }
slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n"); slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n");
return; return;
bad_request:
send_bad_request();
return;
} }
@@ -703,7 +708,7 @@ void httpd_appcall(void)
} else if (uip_newdata() && s->tstate == TSTATE_POST) { } else if (uip_newdata() && s->tstate == TSTATE_POST) {
// Check here maxupload by subtracting uip_len and close socekt if fails! // Check here maxupload by subtracting uip_len and close socekt if fails!
if (max_upload - uip_len > 0) { if (max_upload - uip_len > 0) {
stream_upload(0); stream_upload(uip_appdata, 0, uip_len);
write_char('.'); write_char('.');
} else { } else {
send_bad_request(); send_bad_request();