mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-09-02 15:02:51 +08:00
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:
+39
-34
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user