mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-09-02 15:02:51 +08:00
The preamble scan cursor and the buffered-length parameter carry no state between calls, so they do not need static xdata slots. As plain locals the compiler places both in registers, trimming 64 bytes of BANK1 code and two bytes of xdata. The offsets shared by config_take() stay static: direct data space is fully allocated on machines like the SWTGW218AS, so plain locals there add overlay bytes that no longer link, and xdata-class locals spill three temporaries into direct space while growing the code by roughly 120 bytes. Document pre_acc, whose accumulation across TCP segments is why it must remain global.
892 lines
24 KiB
C
892 lines
24 KiB
C
|
|
#include "httpd.h"
|
|
#include "page_impl.h"
|
|
#include "rtl837x_common.h"
|
|
#include "rtl837x_regs.h"
|
|
#include "cmd_parser.h"
|
|
#include "rtl837x_flash.h"
|
|
#include "uip.h"
|
|
#include "html_data.h"
|
|
|
|
// #define DEBUG
|
|
#include "debug.h"
|
|
|
|
#define SESSION_ID_LENGTH 12
|
|
#define SESSION_TIMEOUT 200
|
|
|
|
#define CMARK_S 6
|
|
|
|
#pragma codeseg BANK1
|
|
#pragma constseg BANK1
|
|
|
|
extern volatile __xdata uint8_t sfr_data[4];
|
|
extern __code uint8_t * __code hex;
|
|
extern __code struct f_data f_data[];
|
|
extern __code char * __code mime_strings[];
|
|
extern __xdata struct flash_region_t flash_region;
|
|
extern __xdata uint32_t flash_size;
|
|
|
|
// Flash buffer to optimize flash writing speed, write_len is the current filling position
|
|
extern __xdata uint8_t flash_buf[FLASH_BUF_SIZE];
|
|
__xdata uint32_t uptr; // Current flash write position
|
|
__xdata uint16_t write_len;
|
|
|
|
__xdata uint8_t outbuf[TCP_OUTBUF_SIZE];
|
|
__xdata uint8_t entry;
|
|
__xdata uint16_t slen;
|
|
__xdata uint16_t o_idx;
|
|
__xdata uint16_t len_left;
|
|
__xdata uint16_t cont_len;
|
|
__xdata uint32_t cont_addr;
|
|
|
|
// HTTP header properties
|
|
__xdata uint8_t boundary[72];
|
|
|
|
// a client may split the request anywhere, including inside a boundary or a
|
|
// part header, so a configuration upload is parsed only once it is complete;
|
|
// sized for a full config sector plus the multipart framing around it
|
|
#define CONFIG_UPLOAD_BUF (CONFIG_LEN + 384)
|
|
__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;
|
|
// part-header bytes buffered so far; accumulates across TCP segments
|
|
__xdata uint16_t pre_acc;
|
|
__xdata uint8_t cfg_bl;
|
|
__xdata uint8_t * __xdata content_type = 0;
|
|
__xdata uint8_t * __xdata session = 0;
|
|
|
|
// Global variables holding POST state
|
|
__xdata uint16_t bindex; // Current index into the boundary
|
|
__xdata uint8_t verify_crc;
|
|
__xdata uint32_t max_upload;
|
|
__xdata uint16_t short_parsed;
|
|
|
|
__xdata char passwd[21];
|
|
// Set when a verified firmware upload awaits its response ACK, after
|
|
// which the chip resets to apply the staged image
|
|
__xdata uint8_t fw_reset_pending;
|
|
__xdata char session_id[SESSION_ID_LENGTH + 1];
|
|
__xdata uint8_t authenticated;
|
|
__xdata uint32_t now;
|
|
__xdata uint8_t * __xdata timeptr;
|
|
__xdata uint32_t last_session_use;
|
|
|
|
#define TSTATE_NONE 0
|
|
#define TSTATE_TX 1
|
|
#define TSTATE_ACKED 2
|
|
#define TSTATE_CLOSED 3
|
|
#define TSTATE_POST 4
|
|
#define TSTATE_MULTIPART 5
|
|
|
|
extern __xdata uint16_t crc_value;
|
|
__xdata uint16_t crc_final;
|
|
void crc16(__xdata uint8_t *v) __naked;
|
|
|
|
|
|
inline uint8_t is_separator(uint8_t c)
|
|
{
|
|
return c == ' ' || c == '\t' || c == '?' || c == '=';
|
|
}
|
|
|
|
|
|
void httpd_init(void) __banked
|
|
{
|
|
config_upload = 0; // xdata is not zeroed by the startup code
|
|
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
|
|
// Start listening to port 80
|
|
uip_listen(HTONS(80));
|
|
s->tstate = TSTATE_CLOSED;
|
|
fw_reset_pending = 0; // xdata is not zeroed by the startup code
|
|
}
|
|
|
|
|
|
uint8_t find_entry(__xdata uint8_t *e)
|
|
{
|
|
uint8_t i, j;
|
|
|
|
for (i = 0; f_data[i].len; i++) {
|
|
j = 0;
|
|
while (f_data[i].file[j] && (f_data[i].file[j] == e[j])) {
|
|
j++;
|
|
}
|
|
if ((!f_data[i].file[j]) && (!e[j])) {
|
|
return i;
|
|
}
|
|
}
|
|
return 0xff;
|
|
}
|
|
|
|
|
|
bool is_word(__xdata uint8_t *xdata_str_p, __code uint8_t * __xdata code_str_p)
|
|
{
|
|
uint8_t u, c;
|
|
|
|
while (1) {
|
|
u = *xdata_str_p++;
|
|
c = *code_str_p++;
|
|
|
|
if (c == NUL) {
|
|
if (u != NUL && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r')
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
if (c != u) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool is_url_word_x(__xdata uint8_t *uri_str_p, __xdata uint8_t *src_str_p)
|
|
{
|
|
uint8_t u, s;
|
|
|
|
while(1) {
|
|
u = *uri_str_p++;
|
|
s = *src_str_p++;
|
|
|
|
if (s == NUL) {
|
|
if (u != NUL && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r')
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
if (u == '%') {
|
|
bool again = true;
|
|
u = 0;
|
|
|
|
while(1) {
|
|
// Swap instruction is fine for rotation
|
|
u = (u << 4) | (u >> 4);
|
|
|
|
uint8_t p = *uri_str_p++;
|
|
u |= p - '0' < 10 ? (p - '0') : (p - 'A' + 10);
|
|
|
|
// force `jbc`-instruction.
|
|
if (again) {
|
|
again = false;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
} else if (u == '+') {
|
|
u = ' ';
|
|
}
|
|
|
|
if (s != u) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool is_word_x(__xdata uint8_t * lhs_str_p, __xdata uint8_t * rhs_str_p)
|
|
{
|
|
uint8_t u, c;
|
|
|
|
while (1) {
|
|
u = *lhs_str_p++;
|
|
c = *rhs_str_p++;
|
|
|
|
if (c == NUL) {
|
|
/* ';' separates cookies in a Cookie header, so it ends a value too. */
|
|
if (u != NUL && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r' && u != ';')
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
if (c != u) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
uint8_t parse_short(__xdata uint8_t *p)
|
|
{
|
|
uint8_t err = 1;
|
|
uint8_t c = 0;
|
|
|
|
short_parsed = 0;
|
|
while(1) {
|
|
c = *p++ - '0';
|
|
if (c > 9) { break; }
|
|
err = 0;
|
|
short_parsed = (short_parsed * 10) + c;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
|
|
void send_not_found(void)
|
|
{
|
|
slen = strtox(outbuf, "HTTP/1.1 404 Not found\r\nConnection: close\r\nContent-Type: text/html\r\n\r\n" \
|
|
"<!DOCTYPE HTML PUBLIC>\n<title>404 Not Found</title>\n<h1>Not Found</h1>\n");
|
|
}
|
|
|
|
|
|
void send_bad_request(void)
|
|
{
|
|
slen = strtox(outbuf, "HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Type: text/html\r\n\r\n" \
|
|
"<!DOCTYPE HTML PUBLIC>\n<title>400 Bad Request</title>\n<h1>Bad Request</h1>\n");
|
|
}
|
|
|
|
|
|
void send_to_login(void)
|
|
{
|
|
slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nConnection: close\r\n" \
|
|
"Location: login.html\r\n\r\n");
|
|
}
|
|
|
|
|
|
void send_unauthorized(void)
|
|
{
|
|
slen = strtox(outbuf, "HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
|
|
}
|
|
|
|
|
|
__xdata uint8_t *scan_header(__xdata uint8_t * __xdata p)
|
|
{
|
|
content_type = 0;
|
|
session = 0;
|
|
authenticated = 0;
|
|
|
|
while (*p != '\r' || *(p + 1) != '\n' || *(p + 2) != '\r' || *(p + 3) != '\n') {
|
|
dbg_char(*p);
|
|
if (!*p++)
|
|
break;
|
|
if (is_word(p, "\nContent-Type:"))
|
|
content_type = p + 15;
|
|
else if (is_word(p, "\nCookie:")) {
|
|
/* Scan for the "session" key: the header may hold several
|
|
* cookies in any order. Match "session" not "session=" -
|
|
* is_word() requires a separator after the match and '=' is
|
|
* one, so this also rejects a longer key like "sessionx". */
|
|
__xdata uint8_t *c = p + 8; /* past "\nCookie:" */
|
|
while (*c && *c != '\r' && *c != '\n') {
|
|
if (is_word(c, "session")) {
|
|
session = c + 8; /* past "session=" */
|
|
break;
|
|
}
|
|
c++;
|
|
}
|
|
}
|
|
}
|
|
if (content_type && is_word(content_type, "multipart/form-data; boundary")) {
|
|
dbg_string("\nFound multipart\n");
|
|
content_type += 30;
|
|
uint8_t i = 0;
|
|
while (i < (sizeof(boundary) - 5) &&
|
|
content_type[i] != '\r' && content_type[i] != '\n') {
|
|
boundary[i + 4] = content_type[i];
|
|
i++;
|
|
}
|
|
// The boundary between parts is "\r\n--" + the boundary given in the header
|
|
boundary[0] = '\r';
|
|
boundary[1] = '\n';
|
|
boundary[2] = '-';
|
|
boundary[3] = '-';
|
|
boundary[i + 4] = 0;
|
|
}
|
|
|
|
read_reg_timer(&now);
|
|
|
|
if (session) {
|
|
if (now - last_session_use > SESSION_TIMEOUT) {
|
|
dbg_string("Session expired\n");
|
|
} else {
|
|
if (is_word_x(session, session_id))
|
|
authenticated = 1;
|
|
else
|
|
dbg_string("Invalid session cookie!\n");
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Generate random HEX-chars at the buffer location.
|
|
*/
|
|
void gen_random_hex_chars(__xdata uint8_t * b, __xdata uint8_t bytes)
|
|
{
|
|
uint8_t i = 0;
|
|
while (bytes) {
|
|
if (!i)
|
|
get_random_32();
|
|
b[--bytes] = itohex(sfr_data[i]);
|
|
if (!bytes) { break; }
|
|
b[--bytes] = itohex(sfr_data[i] >> 4 | sfr_data[i] << 4);
|
|
i = (i + 1) & 0x3;
|
|
}
|
|
}
|
|
|
|
|
|
/* 0: body incomplete, 1: configuration stored, 2: malformed */
|
|
static uint8_t config_take(void)
|
|
{
|
|
cfg_bl = strlen_x(boundary);
|
|
|
|
// the body is complete once the closing boundary has arrived
|
|
cfg_last = 0;
|
|
while (1) {
|
|
if (cfg_last + cfg_bl + 1 >= write_len)
|
|
return 0;
|
|
if (strstart_x(&config_buf[cfg_last], boundary)
|
|
&& strstart(&config_buf[cfg_last + cfg_bl], "--"))
|
|
break;
|
|
cfg_last++;
|
|
}
|
|
|
|
// every part lies ahead of the closing boundary, so it bounds the walk
|
|
cfg_pos = 0;
|
|
while (cfg_pos < cfg_last) {
|
|
if (!strstart_x(&config_buf[cfg_pos], boundary)) {
|
|
cfg_pos++;
|
|
continue;
|
|
}
|
|
cfg_hdr = cfg_pos + cfg_bl;
|
|
cfg_body = cfg_hdr;
|
|
while (1) {
|
|
if (cfg_body + 3 >= cfg_last)
|
|
return 2;
|
|
if (strstart(&config_buf[cfg_body], "\r\n\r\n"))
|
|
break;
|
|
cfg_body++;
|
|
}
|
|
cfg_end = cfg_body;
|
|
cfg_body += 4;
|
|
// reaching cfg_last is a match: the last part ends at the closing boundary
|
|
while (cfg_end < cfg_last && !strstart_x(&config_buf[cfg_end], boundary))
|
|
cfg_end++;
|
|
while (cfg_hdr + 8 < cfg_body) {
|
|
// the part carrying a filename holds the configuration
|
|
if (strstart(&config_buf[cfg_hdr], "filename")) {
|
|
// the payload plus its terminator must fit the sector
|
|
if (cfg_end - cfg_body + 1 > CONFIG_LEN)
|
|
return 2;
|
|
config_buf[cfg_end] = 0;
|
|
flash_region.addr = CONFIG_START;
|
|
flash_sector_erase();
|
|
flash_region.addr = CONFIG_START;
|
|
flash_region.len = cfg_end - cfg_body + 1;
|
|
flash_write_bytes(config_buf + cfg_body);
|
|
return 1;
|
|
}
|
|
cfg_hdr++;
|
|
}
|
|
cfg_pos = cfg_end;
|
|
}
|
|
return 2;
|
|
}
|
|
|
|
|
|
// unlike scan_header(), keeps no auth state, so it may run on every buffered segment
|
|
static uint16_t preamble_payload_start(uint16_t n)
|
|
{
|
|
uint16_t pos;
|
|
|
|
for (pos = 0; pos + 24 <= n; pos++) {
|
|
if (strstart(&config_buf[pos], "application/octet-stream"))
|
|
break;
|
|
}
|
|
if (pos + 24 > n)
|
|
return 0;
|
|
pos += 24;
|
|
while (pos + 3 < n && !strstart(&config_buf[pos], "\r\n\r\n"))
|
|
pos++;
|
|
if (pos + 3 >= n)
|
|
return 0;
|
|
return 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(__xdata uint8_t *p, uint16_t bptr, uint16_t plen)
|
|
{
|
|
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
|
|
|
|
dbg_string("Stream_upload called: ");
|
|
dbg_short(bptr); dbg_char('\n');
|
|
|
|
do {
|
|
if (bptr >= plen) {
|
|
s->tstate = TSTATE_POST;
|
|
return 1;
|
|
}
|
|
// Have we reached the end of the part?
|
|
if (!boundary[bindex]) {
|
|
s->tstate = TSTATE_NONE;
|
|
dbg_string("len 2: "); dbg_short(write_len); dbg_char(' ');
|
|
flash_region.addr = uptr;
|
|
flash_region.len = write_len;
|
|
flash_write_bytes(flash_buf);
|
|
uptr += write_len;
|
|
write_len = 0;
|
|
if (verify_crc) {
|
|
dbg_string("CRC16: "); dbg_short(crc_final); dbg_char('\n');
|
|
// Both bodies are 33 bytes; Content-Length lets the
|
|
// browser complete the response without waiting for
|
|
// the connection close (which a reset would swallow)
|
|
if (crc_final == 0xb001) {
|
|
print_string("Checksum OK.\nUpload to flash done, will reset!\n");
|
|
slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nContent-Length: 33\r\n"
|
|
"Content-Type: text/plain\r\n\r\n"
|
|
"OK: checksum verified, rebooting\n");
|
|
// Reset once the response is fully ACKed
|
|
fw_reset_pending = 1;
|
|
} else {
|
|
print_string("Checksum incorrect! Aborting.\n");
|
|
slen = strtox(outbuf, "HTTP/1.1 400 Bad Request\r\nContent-Length: 33\r\n"
|
|
"Content-Type: text/plain\r\n\r\n"
|
|
"NO: checksum failed, not applied\n");
|
|
}
|
|
}
|
|
// Make sure there is a 0 at the end of the uploaded data
|
|
flash_buf[0] = 0;
|
|
flash_region.addr = uptr;
|
|
flash_region.len = 1;
|
|
flash_write_bytes(flash_buf);
|
|
if (bptr >= plen)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
if (p[bptr] == boundary[bindex]) {
|
|
if (!bindex)
|
|
crc_final = crc_value;
|
|
crc16(p + bptr);
|
|
bptr++;
|
|
bindex++;
|
|
} else {
|
|
if (bindex) {
|
|
memcpy(flash_buf + write_len, boundary, bindex);
|
|
write_len += bindex;
|
|
bindex = 0;
|
|
}
|
|
crc16(p + bptr);
|
|
flash_buf[write_len++] = p[bptr++];
|
|
if (write_len >= FLASH_PAGE_SIZE) {
|
|
dbg_string("len: "); dbg_short(write_len); dbg_char(' ');
|
|
dbg_string("CRC16: "); dbg_short(crc_value); dbg_char('\n');
|
|
if (uptr % FLASH_SECTOR_SIZE == 0) {
|
|
flash_region.addr = uptr;
|
|
flash_sector_erase();
|
|
}
|
|
flash_region.addr = uptr;
|
|
flash_region.len = FLASH_PAGE_SIZE;
|
|
flash_write_bytes(flash_buf);
|
|
uptr += FLASH_PAGE_SIZE;
|
|
write_len -= FLASH_PAGE_SIZE;
|
|
|
|
// Copy the remaining byte for the next page to the beginning of the buffer.
|
|
if (write_len > 0) {
|
|
memcpy(flash_buf, flash_buf + FLASH_PAGE_SIZE, write_len);
|
|
}
|
|
}
|
|
bindex = 0;
|
|
}
|
|
} while(1);
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
// Was the multipart header sent in multiple packets?
|
|
if (s->tstate != TSTATE_MULTIPART) {
|
|
dbg_string("Is POST\n");
|
|
p += 5; // Skip post
|
|
// Find end of request path
|
|
while (*p && !is_separator(*p))
|
|
p++;
|
|
*p++ = NUL;
|
|
|
|
// Find end of request header
|
|
boundary[0] =NUL;
|
|
p = scan_header(p);
|
|
dbg_string("Boundary: >"); dbg_string_x(boundary); dbg_string("<\n");
|
|
if (!*p || !content_type) {
|
|
dbg_string("Bad Request!\n");
|
|
send_not_found();
|
|
return;
|
|
}
|
|
if (is_word(request_path, "upload")) {
|
|
if (flash_size < FIRMWARE_UPLOAD_START*2)
|
|
{
|
|
print_string("Flash too small for firmware upload!\n");
|
|
send_bad_request();
|
|
return;
|
|
}
|
|
print_string("Firmware upload started.");
|
|
config_upload = 0;
|
|
uptr = FIRMWARE_UPLOAD_START;
|
|
verify_crc = 1;
|
|
max_upload = 1024576;
|
|
pre_acc = 0;
|
|
} else if (is_word(request_path, "config")) {
|
|
if (!authenticated) {
|
|
send_unauthorized();
|
|
return;
|
|
}
|
|
dbg_string("Configuration upload\n");
|
|
verify_crc = 0;
|
|
config_upload = 1;
|
|
write_len = 0;
|
|
}
|
|
// Check for other POST requests, which are not multipart, below
|
|
} else {
|
|
dbg_string("Multipart request\n");
|
|
}
|
|
|
|
if (is_word(request_path, "cmd")) {
|
|
p += 4;
|
|
if (!authenticated) {
|
|
send_unauthorized();
|
|
return;
|
|
}
|
|
execute_commands(p);
|
|
if (err_status != ERR_OK) {
|
|
send_bad_request();
|
|
return;
|
|
}
|
|
} else if (is_word(request_path, "login")) {
|
|
dbg_string("POST login\n");
|
|
|
|
if (!content_type || !is_word(content_type, "application/x-www-form-urlencoded")) {
|
|
dbg_string("Bad request!\n");
|
|
send_bad_request();
|
|
return;
|
|
}
|
|
|
|
p += 8; // Read also over "pwd="
|
|
if (is_url_word_x(p, 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");
|
|
}
|
|
return;
|
|
} else if (s->tstate == TSTATE_MULTIPART || is_word(request_path, "upload") || is_word(request_path, "config")) {
|
|
dbg_string("POST upload/config request\n");
|
|
if (!authenticated) {
|
|
send_unauthorized();
|
|
return;
|
|
}
|
|
if (!boundary[0]) {
|
|
dbg_string("Bad request, no boundary!\n");
|
|
send_bad_request();
|
|
return;
|
|
}
|
|
if (config_upload) {
|
|
cfg_pos = uip_len - (p - uip_appdata);
|
|
if (write_len + cfg_pos >= CONFIG_UPLOAD_BUF) {
|
|
print_string("Configuration too large, aborting.\n");
|
|
config_upload = 0;
|
|
s->tstate = TSTATE_NONE;
|
|
send_bad_request();
|
|
return;
|
|
}
|
|
memcpy(config_buf + write_len, p, cfg_pos);
|
|
write_len += cfg_pos;
|
|
uint8_t taken = config_take();
|
|
|
|
if (!taken) {
|
|
s->tstate = TSTATE_MULTIPART;
|
|
return;
|
|
}
|
|
config_upload = 0;
|
|
s->tstate = TSTATE_NONE;
|
|
if (taken == 2) {
|
|
send_bad_request();
|
|
return;
|
|
}
|
|
slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n");
|
|
return;
|
|
}
|
|
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");
|
|
|
|
flash_init(0); // Re-initialize flash for non-DIO operation, otherwise flashing fails
|
|
set_sys_led_state(SYS_LED_FAST);
|
|
|
|
crc_value = 0;
|
|
bindex = 0;
|
|
write_len = 0;
|
|
// A verdict is only built once the upload part completes;
|
|
// clear any stale response so the completion check in the
|
|
// appcall POST branch cannot send leftovers
|
|
slen = 0;
|
|
stream_upload(config_buf, cfg_end, pre_acc);
|
|
|
|
dbg_string("Done reading first fragment\n");
|
|
return;
|
|
|
|
} else {
|
|
send_not_found();
|
|
return;
|
|
}
|
|
slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n");
|
|
return;
|
|
}
|
|
|
|
|
|
void httpd_appcall(void)
|
|
{
|
|
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
|
|
|
|
dbg_char('P');
|
|
#ifdef DEBUG
|
|
if (uip_newdata())
|
|
write_char('N');
|
|
print_byte(s->tstate);
|
|
write_char(' ');
|
|
#endif
|
|
if(uip_connected() && s->tstate == TSTATE_CLOSED) {
|
|
dbg_string("Connected...\n");
|
|
s->tstate = TSTATE_NONE;
|
|
} else if (uip_closed()) {
|
|
dbg_string("Connection closed\n");
|
|
s->tstate = TSTATE_CLOSED;
|
|
} else if (uip_aborted()) {
|
|
dbg_string("Connection aborted\n");
|
|
uip_close();
|
|
s->tstate = TSTATE_CLOSED;
|
|
} else if (uip_poll()) {
|
|
uip_len = 0;
|
|
if (s->tstate == TSTATE_ACKED) {
|
|
dbg_string("Closing because everything has been transmitted\n");
|
|
uip_close();
|
|
s->tstate = TSTATE_CLOSED;
|
|
}
|
|
} else if (uip_acked() && s->tstate == TSTATE_TX) {
|
|
dbg_string("ACK\n");
|
|
if (slen > uip_mss()) {
|
|
slen -= uip_mss();
|
|
o_idx += uip_mss();
|
|
} else {
|
|
slen = 0;
|
|
o_idx += slen;
|
|
}
|
|
|
|
s->tstate = TSTATE_ACKED;
|
|
|
|
if (slen > uip_mss()) {
|
|
dbg_string("Sending A: "); dbg_short(slen); dbg_char('\n');
|
|
uip_send(outbuf + o_idx, uip_mss());
|
|
s->tstate = TSTATE_TX;
|
|
} else if (slen > 0) {
|
|
dbg_string("Sending B: "); dbg_short(slen); dbg_char('\n');
|
|
uip_send(outbuf + o_idx, slen);
|
|
s->tstate = TSTATE_TX;
|
|
} else if (cont_len) {
|
|
dbg_string("CONT cont_len: "); dbg_short(cont_len);
|
|
slen = cont_len > uip_mss() ? uip_mss() : cont_len;
|
|
if (slen > TCP_OUTBUF_SIZE)
|
|
slen = TCP_OUTBUF_SIZE;
|
|
flash_region.addr = cont_addr;
|
|
flash_region.len = slen;
|
|
flash_read_bulk(outbuf);
|
|
uip_send(outbuf, slen);
|
|
cont_len -= slen;
|
|
cont_addr += slen;
|
|
s->tstate = TSTATE_TX;
|
|
} else if (fw_reset_pending) {
|
|
// The upload verdict has been fully ACKed by the client;
|
|
// now it is safe to reset and apply the staged image
|
|
print_string("Resetting to apply update\n");
|
|
reset_chip();
|
|
}
|
|
} 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(uip_appdata, 0, uip_len);
|
|
// A completed part with a built verdict must go out
|
|
// through the normal TX path
|
|
if (s->tstate == TSTATE_NONE && slen)
|
|
goto do_send;
|
|
write_char('.');
|
|
} else {
|
|
send_bad_request();
|
|
goto do_send;
|
|
}
|
|
} else if (uip_newdata() && s->tstate != TSTATE_TX) {
|
|
cont_len = 0;
|
|
dbg_char('<'); dbg_short(uip_len); dbg_char('\n');
|
|
__xdata uint8_t *p = uip_appdata;
|
|
// Mark end of request header with \0
|
|
p[uip_len] = 0;
|
|
#ifdef DEBUG
|
|
while (*p)
|
|
dbg_char(*p++);
|
|
dbg_char('\n');
|
|
#endif
|
|
p = uip_appdata;
|
|
if (is_word(p, "POST") || s->tstate == TSTATE_MULTIPART) {
|
|
handle_post();
|
|
// If this is an ongoing post stream, then wait for the next packet
|
|
if (s->tstate == TSTATE_POST || s->tstate == TSTATE_MULTIPART) {
|
|
uip_len = 0;
|
|
return;
|
|
}
|
|
goto do_send;
|
|
}
|
|
|
|
if (is_word(p, "GET"))
|
|
dbg_string("GET request ");
|
|
p += 4;
|
|
scan_header(p);
|
|
__xdata uint8_t *q = p;
|
|
while (*p && !is_separator(*p))
|
|
p++;
|
|
*p = NUL;
|
|
dbg_string_x(q);
|
|
dbg_char('\n');
|
|
|
|
s->tstate = TSTATE_NONE;
|
|
entry = find_entry(q);
|
|
dbg_string("Entry is: "); dbg_byte(entry); dbg_char('\n');
|
|
if (entry == 0xff) {
|
|
if (!authenticated) {
|
|
dbg_string("Not authorized!\n");
|
|
send_unauthorized();
|
|
goto do_send;
|
|
}
|
|
dbg_string("Not file entry\n");
|
|
if (!strcmp(q, "/status.json")) {
|
|
send_status();
|
|
} else if (!strcmp(q, "/information.json")) {
|
|
send_basic_info();
|
|
} else if (!strcmp(q, "/vlan.json")) {
|
|
parse_short(q + 15);
|
|
send_vlan(short_parsed);
|
|
} else if (is_word(q, "/counters.json")) {
|
|
uint8_t cport = q[20] - '0';
|
|
if (send_counters(cport))
|
|
send_bad_request();
|
|
} else if (is_word(q, "/eee.json")) {
|
|
send_eee();
|
|
} else if (is_word(q, "/bandwidth.json")) {
|
|
send_bandwidth();
|
|
} else if (is_word(q, "/l2.json")) {
|
|
parse_short(q + 13); // e.g.: /l2.json?idx=10
|
|
send_l2(short_parsed);
|
|
} else if (is_word(q, "/l2_del.json")) {
|
|
parse_short(q + 17);
|
|
l2_delete(short_parsed);
|
|
} else if (is_word(q, "/mirror.json")) {
|
|
send_mirror();
|
|
} else if (is_word(q, "/mtu.json")) {
|
|
send_mtu();
|
|
} else if (is_word(q, "/lag.json")) {
|
|
send_lag();
|
|
} else if (is_word(q, "/vlanlist")) {
|
|
send_vlanlist();
|
|
} else if (is_word(q, "/config")) {
|
|
send_config();
|
|
} else if (is_word(q, "/cmd_log")) {
|
|
send_cmd_log();
|
|
} else if (is_word(q, "/cmd_log_clear")) {
|
|
clear_command_history();
|
|
send_mtu(); // dummy response
|
|
} else if (is_word(q, "/reset")) {
|
|
uip_close();
|
|
delay(1000); //wait for the close packet to be sent, otherwise the browser will retry
|
|
reset_chip();
|
|
} else {
|
|
send_not_found();
|
|
}
|
|
} else {
|
|
dbg_string("Have entry, authenticated: "); dbg_byte(authenticated); dbg_char('\n');
|
|
if (!authenticated && !(f_data[entry].start == FDATA_START_login_html
|
|
|| f_data[entry].start == FDATA_START_port_svg
|
|
|| f_data[entry].start == FDATA_START_sfp_svg
|
|
|| f_data[entry].start == FDATA_START_style_css)) {
|
|
send_to_login();
|
|
goto do_send;
|
|
}
|
|
// A web-page is actively accessed, we can reset session time-out
|
|
reg_read_m(RTL837X_REG_SEC_COUNTER);
|
|
timeptr = (uint8_t*)&last_session_use; // last_session_use is Little endian
|
|
timeptr[0] = sfr_data[3]; timeptr[1] = sfr_data[2]; timeptr[2] = sfr_data[1]; timeptr[3] = sfr_data[0];
|
|
|
|
slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nContent-Type: ");
|
|
slen += strtox(outbuf + slen, mime_strings[f_data[entry].mime]);
|
|
/* 'unsafe-inline' is needed for the inline onclick handlers
|
|
* and the inline <script> on login.html. Connection: close is
|
|
* required because this httpd closes the connection after every
|
|
* response; without advertising it a browser reuses the socket
|
|
* from its keep-alive pool and the next request hits the already
|
|
* closed connection (a POST is then dropped without a retry). */
|
|
slen += strtox(outbuf + slen, "; charset=UTF-8\r\nCache-Control: max-age=60, must-revalidate\r\nConnection: close\r\nAccess-Control-Allow-Origin: *\r\nContent-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; form-action 'self'\r\n\r\n");
|
|
|
|
len_left = f_data[entry].len;
|
|
if (len_left > (TCP_OUTBUF_SIZE - slen)) {
|
|
cont_len = len_left - (TCP_OUTBUF_SIZE - slen);
|
|
len_left = TCP_OUTBUF_SIZE - slen;
|
|
cont_addr = f_data[entry].start + len_left;
|
|
}
|
|
dbg_string("MIME: "); dbg_string(mime_strings[f_data[entry].mime]); dbg_char('\n');
|
|
flash_region.addr = f_data[entry].start;
|
|
flash_region.len = len_left;
|
|
flash_read_bulk(outbuf + slen);
|
|
slen += len_left;
|
|
}
|
|
do_send:
|
|
dbg_string("slen: "); dbg_short(slen); dbg_char('\n');
|
|
o_idx = 0;
|
|
if (slen > uip_mss()) {
|
|
dbg_string("Sending a: "); dbg_short(slen); dbg_char('\n');
|
|
uip_send(outbuf + o_idx, uip_mss());
|
|
dbg_string("Sending a done\n");
|
|
} else {
|
|
dbg_string("Sending b: "); dbg_short(slen); dbg_char('\n');
|
|
uip_send(outbuf + o_idx, slen);
|
|
dbg_string("Sending b done\n");
|
|
}
|
|
s->tstate = TSTATE_TX;
|
|
} else if (uip_rexmit()) { // Connection established, need to rexmit?
|
|
dbg_string("RETRANSMIT requested\n");
|
|
if (slen > uip_mss()) {
|
|
dbg_string("Sending C: "); dbg_short(slen); dbg_char('\n');
|
|
uip_send(outbuf + o_idx, uip_mss());
|
|
dbg_string("Sending C done\n");
|
|
} else if (slen > 0) {
|
|
dbg_string("Sending D: "); dbg_short(slen); dbg_char('\n');
|
|
uip_send(outbuf + o_idx, slen);
|
|
dbg_string("Sending D done\n");
|
|
}
|
|
s->tstate = TSTATE_TX;
|
|
uip_len = 0;
|
|
} else {
|
|
uip_len = 0;
|
|
}
|
|
}
|