From 1bd4a8d3cc84decd77c3b6f6a7a67f161605c196 Mon Sep 17 00:00:00 2001 From: bloqaudio Date: Fri, 21 Aug 2026 08:49:29 -0500 Subject: [PATCH] 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. --- httpd/httpd.c | 73 +++++++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index 84413a9..585eb62 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -49,6 +49,7 @@ __xdata uint8_t boundary[72]; __xdata uint8_t config_upload; __xdata uint8_t config_buf[CONFIG_UPLOAD_BUF]; __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 * __xdata content_type = 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) { 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 * Input: the current position in the TCP buffer (uip_appdata) * 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); dbg_string("Stream_upload called: "); dbg_short(bptr); dbg_char('\n'); do { - if (bptr >= uip_len) { + if (bptr >= plen) { s->tstate = TSTATE_POST; return 1; } @@ -431,7 +438,7 @@ uint8_t stream_upload(uint16_t bptr) flash_region.addr = uptr; flash_region.len = 1; flash_write_bytes(flash_buf); - if (bptr >= uip_len) + if (bptr >= plen) return 0; if(!verify_crc) //ugly hack to signal connection finished after config upload. @@ -512,6 +519,7 @@ void handle_post(void) uptr = FIRMWARE_UPLOAD_START; verify_crc = 1; max_upload = 1024576; + pre_acc = 0; } else if (is_word(request_path, "config")) { if (!authenticated) { 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"); return; } - // We skip the intial parts as part of the header - do { - p = skip_boundary(p); - if (!*p) { - s->tstate = TSTATE_MULTIPART; - 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")); + cfg_pos = uip_len - (p - uip_appdata); + if (pre_acc + cfg_pos >= CONFIG_UPLOAD_BUF) { + print_string("Firmware upload header too large, aborting.\n"); + 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; + return; + } 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 set_sys_led_state(SYS_LED_FAST); @@ -622,7 +630,7 @@ void handle_post(void) crc_value = 0; bindex = 0; write_len = 0; - stream_upload(p - uip_appdata); + stream_upload(config_buf, cfg_end, pre_acc); dbg_string("Done reading first fragment\n"); return; @@ -633,9 +641,6 @@ void handle_post(void) } slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n"); return; -bad_request: - send_bad_request(); - return; } @@ -703,7 +708,7 @@ void httpd_appcall(void) } else if (uip_newdata() && s->tstate == TSTATE_POST) { // Check here maxupload by subtracting uip_len and close socekt if fails! if (max_upload - uip_len > 0) { - stream_upload(0); + stream_upload(uip_appdata, 0, uip_len); write_char('.'); } else { send_bad_request();