mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-10-29 17:54:14 +08:00
* stp: select the root port by cost and block redundant links * stp: move the protocol to BANK4 and keep the command line in BANK2 * stp: implement the RSTP state machines of 802.1D-2004 clause 17 * stp: counters, more settings and a fuller Spanning Tree page * stp: make a link aggregation group one port of the RSTP tree * web: serve the Spanning Tree JSON from BANK4 as well Kept in BANK1, the larger Spanning Tree page would leave under 1 kB there for the rest of the web server and every page still to come, so send_stp() and send_stp_counters() move to httpd/page_stp.c in BANK4 with the protocol, as banked calls, with their own copies of the few output helpers and of the JSON header, so nothing in BANK4 calls into BANK1 directly or hands it a pointer into its code space. BANK4 takes the free flash between BANK3 and the web files. KP_9000_6XHML_X2: BANK1 44336, BANK2 36969, BANK3 11942, BANK4 20965 bytes of 49152.
1134 lines
30 KiB
C
1134 lines
30 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 "sessions.h"
|
|
#include "html_data.h"
|
|
|
|
// #define DEBUG
|
|
#include "debug.h"
|
|
|
|
#define SESSION_TIMEOUT_DEFAULT 200
|
|
|
|
/* Seconds of inactivity after which a session cookie stops validating.
|
|
* Configurable with the "session" command, see cmd_parser.c. */
|
|
__xdata uint16_t session_timeout = SESSION_TIMEOUT_DEFAULT;
|
|
|
|
#define CMARK_S 6
|
|
|
|
#pragma codeseg BANK1
|
|
#pragma constseg BANK1
|
|
|
|
extern volatile __xdata uint8_t sfr_data[4];
|
|
extern volatile __xdata uint32_t ticks;
|
|
extern __xdata uint8_t cmd_capture; /* owned by rtlplayground.c, see write_char_no_syslog() */
|
|
extern __code const uint8_t * __code const hex;
|
|
extern __code const struct f_data f_data[];
|
|
extern __code const char * __code const 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 sent_len;
|
|
__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];
|
|
// bytes buffered in config_buf so far (HTTP header, POST body, or a firmware
|
|
// part header); accumulates across TCP segments
|
|
__xdata uint16_t pre_acc;
|
|
__xdata uint8_t * __xdata content_type = 0;
|
|
__xdata uint8_t * __xdata session = 0;
|
|
__xdata uint16_t content_length;
|
|
|
|
// 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;
|
|
|
|
#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];
|
|
// Up to four logins at once, see sessions.c
|
|
__xdata uint8_t authenticated;
|
|
__xdata uint32_t now;
|
|
|
|
#define TSTATE_NONE 0
|
|
#define TSTATE_TX 1
|
|
#define TSTATE_ACKED 2
|
|
#define TSTATE_CLOSED 3
|
|
#define TSTATE_POST 4
|
|
#define TSTATE_MULTIPART 5
|
|
#define TSTATE_POSTBODY 6
|
|
#define TSTATE_HEADER 7
|
|
|
|
extern __xdata uint16_t crc_value;
|
|
__xdata uint16_t crc_final;
|
|
void crc16_bank1(__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
|
|
// Start listening to port 80
|
|
uip_listen(HTONS(80));
|
|
// Not through uip_conn: it only points at a connection while uIP is
|
|
// handling one, and nothing has set it yet at init time.
|
|
for (uint8_t i = 0; i < UIP_CONNS; i++)
|
|
uip_conns[i].appstate.tstate = TSTATE_CLOSED;
|
|
session_init();
|
|
}
|
|
|
|
|
|
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 const 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* name must be lower-case, starting with the '\n' of the previous line's end */
|
|
__xdata uint8_t *header_value(__xdata uint8_t *p, __code const uint8_t *name)
|
|
{
|
|
uint8_t u, c;
|
|
|
|
while ((c = *name++)) {
|
|
u = *p++;
|
|
if (u >= 'A' && u <= 'Z')
|
|
u += 'a' - 'A';
|
|
if (u != c)
|
|
return 0;
|
|
}
|
|
while (*p == ' ' || *p == '\t')
|
|
p++;
|
|
return p;
|
|
}
|
|
|
|
|
|
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;
|
|
if (short_parsed > 6552)
|
|
short_parsed = 0xffff;
|
|
else
|
|
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");
|
|
}
|
|
|
|
|
|
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 *v;
|
|
|
|
content_type = 0;
|
|
content_length = 0;
|
|
session = 0;
|
|
authenticated = 0;
|
|
|
|
while (!strstart(p, "\r\n\r\n")) {
|
|
dbg_char(*p);
|
|
if (!*p)
|
|
break;
|
|
p++;
|
|
if ((v = header_value(p, "\ncontent-type:")))
|
|
content_type = v;
|
|
else if ((v = header_value(p, "\ncontent-length:"))) {
|
|
parse_short(v);
|
|
content_length = short_parsed;
|
|
} else if ((v = header_value(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". */
|
|
while (*v && *v != '\r' && *v != '\n') {
|
|
if (is_word(v, "session")) {
|
|
session = v + 8; /* past "session=" */
|
|
break;
|
|
}
|
|
v++;
|
|
}
|
|
}
|
|
}
|
|
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)
|
|
session_check();
|
|
return p;
|
|
}
|
|
|
|
/* 0: body incomplete, 1: configuration stored, 2: malformed */
|
|
static uint8_t config_take(void)
|
|
{
|
|
// #386: needs static, otherwise it still lands in SRAM/DSEG
|
|
static __xdata uint16_t cfg_pos, cfg_hdr, cfg_body, cfg_end, cfg_last, cfg_i;
|
|
__xdata uint8_t cfg_bl, cfg_run;
|
|
|
|
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 >= pre_acc)
|
|
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;
|
|
cfg_run = 0;
|
|
for (cfg_i = cfg_body; cfg_i < cfg_end; cfg_i++) {
|
|
if (config_buf[cfg_i] == '\n')
|
|
cfg_run = 0;
|
|
else if (++cfg_run >= CMD_BUF_SIZE - 1)
|
|
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)
|
|
{
|
|
// Skip the HTTP header separator retained by handle_post().
|
|
uint16_t pos = 4;
|
|
|
|
while (pos + 3 < n && !strstart(&config_buf[pos], "\r\n\r\n"))
|
|
pos++;
|
|
if (pos + 3 >= n)
|
|
return 0;
|
|
return pos + 4;
|
|
}
|
|
|
|
|
|
// Source window for stream_upload(); filled by the caller before the call
|
|
__xdata struct {
|
|
__xdata uint8_t *p;
|
|
uint16_t bptr;
|
|
uint16_t plen;
|
|
} upload_settings;
|
|
|
|
/*
|
|
* Reads post data from the http stream and writes it into flash memory
|
|
* Input: upload_settings, set by the caller
|
|
* Returns 1: More data to read, 0: Upload complete, all parts reads
|
|
*/
|
|
uint8_t stream_upload(void)
|
|
{
|
|
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
|
|
|
|
dbg_string("Stream_upload called: ");
|
|
dbg_short(upload_settings.bptr); dbg_char('\n');
|
|
|
|
do {
|
|
if (upload_settings.bptr >= upload_settings.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
|
|
s->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 (upload_settings.bptr >= upload_settings.plen)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
if (upload_settings.p[upload_settings.bptr] == boundary[bindex]) {
|
|
if (!bindex)
|
|
crc_final = crc_value;
|
|
crc16_bank1(upload_settings.p + upload_settings.bptr);
|
|
upload_settings.bptr++;
|
|
bindex++;
|
|
} else {
|
|
if (bindex) {
|
|
memcpy(flash_buf + write_len, boundary, bindex);
|
|
write_len += bindex;
|
|
bindex = 0;
|
|
} else {
|
|
crc16_bank1(upload_settings.p + upload_settings.bptr);
|
|
flash_buf[write_len++] = upload_settings.p[upload_settings.bptr++];
|
|
}
|
|
if (write_len >= FLASH_PAGE_SIZE) {
|
|
/* The staged image spans FIRMWARE_UPLOAD_START to twice that,
|
|
* the span check_and_flash_update_image() reads back. Nothing
|
|
* else bounds uptr: a body whose closing boundary never arrives
|
|
* keeps writing, and on a flash exactly this size the address
|
|
* wraps onto the running image at zero. */
|
|
if (uptr >= (uint32_t)FIRMWARE_UPLOAD_START * 2) {
|
|
print_string("Upload runs past the image area! Aborting.\n");
|
|
slen = strtox(outbuf, "HTTP/1.1 400 Bad Request\r\nContent-Length: 30\r\n"
|
|
"Content-Type: text/plain\r\n\r\n"
|
|
"NO: upload exceeds image area\n");
|
|
s->tstate = TSTATE_NONE;
|
|
return 0;
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
} while(1);
|
|
}
|
|
|
|
|
|
static void handle_config_fragment(__xdata uint8_t *p)
|
|
{
|
|
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
|
|
__xdata uint16_t frag_len;
|
|
uint8_t taken;
|
|
|
|
frag_len = uip_len - (p - (__xdata uint8_t *)uip_appdata);
|
|
if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) {
|
|
print_string("Configuration too large, aborting.\n");
|
|
config_upload = 0;
|
|
s->tstate = TSTATE_NONE;
|
|
send_bad_request();
|
|
return;
|
|
}
|
|
memcpy(config_buf + pre_acc, p, frag_len);
|
|
pre_acc += frag_len;
|
|
taken = config_take();
|
|
if (!taken) {
|
|
s->tstate = TSTATE_MULTIPART;
|
|
return;
|
|
}
|
|
config_upload = 0;
|
|
s->tstate = TSTATE_NONE;
|
|
if (taken == 2) {
|
|
send_bad_request();
|
|
return;
|
|
}
|
|
send_ok();
|
|
}
|
|
|
|
|
|
static void handle_firmware_fragment(__xdata uint8_t *p)
|
|
{
|
|
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
|
|
__xdata uint16_t frag_len, payload_start;
|
|
|
|
frag_len = uip_len - (p - (__xdata uint8_t *)uip_appdata);
|
|
if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) {
|
|
print_string("Firmware upload header too large, aborting.\n");
|
|
config_upload = 0;
|
|
s->tstate = TSTATE_NONE;
|
|
send_bad_request();
|
|
return;
|
|
}
|
|
memcpy(config_buf + pre_acc, p, frag_len);
|
|
pre_acc += frag_len;
|
|
payload_start = preamble_payload_start(pre_acc);
|
|
if (!payload_start) {
|
|
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;
|
|
upload_settings.p = config_buf;
|
|
upload_settings.bptr = payload_start;
|
|
upload_settings.plen = pre_acc;
|
|
stream_upload();
|
|
|
|
dbg_string("Done reading first fragment\n");
|
|
}
|
|
|
|
|
|
static void run_cmd_body(__xdata uint8_t *body)
|
|
{
|
|
uint16_t hdr_len = strtox(outbuf, HTTP_RESPONCE_TXT);
|
|
|
|
slen = hdr_len;
|
|
cmd_capture = 1;
|
|
execute_commands(body);
|
|
if (cmd_capture == 2)
|
|
slen += strtox(outbuf + slen, CMD_TRUNCATED);
|
|
cmd_capture = 0;
|
|
/* Commands that configure something print nothing at all. Saying
|
|
* so beats an empty body, which reads the same as "nothing ran".
|
|
* Only on success: a silent failure must not answer with "OK". */
|
|
if (err_status == ERR_OK && slen == hdr_len)
|
|
slen += strtox(outbuf + slen, "OK\n");
|
|
/* On a parse error keep what the parser printed, because that text
|
|
* is the explanation, and only restate the status. "400 NO" is as
|
|
* long as "200 OK", so the header does not have to be rebuilt. */
|
|
if (err_status != ERR_OK) {
|
|
outbuf[9] = '4';
|
|
outbuf[10] = '0';
|
|
outbuf[11] = '0';
|
|
outbuf[13] = 'N';
|
|
outbuf[14] = 'O';
|
|
}
|
|
}
|
|
|
|
|
|
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(&now);
|
|
slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nConnection: close\r\nLocation: index.html\r\n" \
|
|
"Set-Cookie: session=");
|
|
session_new();
|
|
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 - (__xdata uint8_t *)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);
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Buffer an HTTP request header across TCP receive events. Stop copying at
|
|
* CRLFCRLF so any body bytes in the final segment stay in uip_appdata.
|
|
*
|
|
* Return the body offset in the current receive buffer, 0 while incomplete,
|
|
* or 0xffff after sending an error response.
|
|
*/
|
|
static uint16_t request_header_take(void)
|
|
{
|
|
uint16_t i;
|
|
|
|
for (i = 0; i < uip_len; i++) {
|
|
if (pre_acc + 1 >= CONFIG_UPLOAD_BUF) {
|
|
uip_conn->appstate.tstate = TSTATE_NONE;
|
|
send_bad_request();
|
|
return 0xffff;
|
|
}
|
|
config_buf[pre_acc++] = ((__xdata uint8_t *)uip_appdata)[i];
|
|
config_buf[pre_acc] = NUL;
|
|
if (pre_acc >= 4
|
|
&& config_buf[pre_acc - 4] == '\r'
|
|
&& config_buf[pre_acc - 3] == '\n'
|
|
&& config_buf[pre_acc - 2] == '\r'
|
|
&& config_buf[pre_acc - 1] == '\n') {
|
|
uip_conn->appstate.tstate = TSTATE_NONE;
|
|
return i + 1;
|
|
}
|
|
}
|
|
|
|
postbody_start = ticks;
|
|
uip_conn->appstate.tstate = TSTATE_HEADER;
|
|
return 0;
|
|
}
|
|
|
|
void handle_post(uint16_t body_off)
|
|
{
|
|
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
|
|
__xdata uint8_t *p = uip_appdata;
|
|
__xdata uint8_t *request_path = p + 6;
|
|
|
|
if (s->tstate == TSTATE_POSTBODY) {
|
|
post_body_continue();
|
|
return;
|
|
}
|
|
|
|
// Was the multipart header sent in multiple packets?
|
|
if (s->tstate != TSTATE_MULTIPART) {
|
|
p = config_buf;
|
|
request_path = p + 6;
|
|
|
|
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_bad_request();
|
|
return;
|
|
}
|
|
|
|
pre_acc = 0;
|
|
p = (__xdata uint8_t *)uip_appdata + body_off;
|
|
if (is_word(request_path, "upload")) {
|
|
if (!authenticated) {
|
|
send_unauthorized();
|
|
return;
|
|
}
|
|
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;
|
|
// config_take() matches CRLF-prefixed delimiters, including the first.
|
|
config_buf[0] = '\r';
|
|
config_buf[1] = '\n';
|
|
pre_acc = 2;
|
|
}
|
|
// Check for other POST requests, which are not multipart, below
|
|
} else {
|
|
dbg_string("Multipart request\n");
|
|
}
|
|
|
|
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)
|
|
handle_config_fragment(p);
|
|
else
|
|
handle_firmware_fragment(p);
|
|
return;
|
|
} else if (is_word(request_path, "cmd")) {
|
|
if (!authenticated) {
|
|
send_unauthorized();
|
|
return;
|
|
}
|
|
postbody_endpoint = POSTBODY_CMD;
|
|
if (!post_body_take(p))
|
|
return;
|
|
run_cmd_body(p);
|
|
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;
|
|
}
|
|
|
|
postbody_endpoint = POSTBODY_LOGIN;
|
|
if (!post_body_take(p))
|
|
return;
|
|
run_login_body(p);
|
|
return;
|
|
} else {
|
|
send_not_found();
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
static void tx_send(uint16_t len)
|
|
{
|
|
sent_len = len;
|
|
uip_send(outbuf + o_idx, len);
|
|
}
|
|
|
|
|
|
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()) {
|
|
dbg_string("Connected...\n");
|
|
s->tstate = TSTATE_NONE;
|
|
s->fw_reset_pending = 0; // explicitly initialize reused XDATA appstate
|
|
} else if (uip_closed()) {
|
|
dbg_string("Connection closed\n");
|
|
s->tstate = TSTATE_CLOSED;
|
|
} else if (uip_aborted() || uip_timedout()) {
|
|
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 ((s->tstate == TSTATE_POSTBODY || s->tstate == TSTATE_HEADER)
|
|
&& (uint16_t)ticks - postbody_start > POSTBODY_TIMEOUT) {
|
|
dbg_string("HTTP request never completed\n");
|
|
uip_abort();
|
|
s->tstate = TSTATE_CLOSED;
|
|
}
|
|
} else if (uip_acked() && s->tstate == TSTATE_TX) {
|
|
dbg_string("ACK\n");
|
|
if (slen > sent_len) {
|
|
slen -= sent_len;
|
|
o_idx += sent_len;
|
|
} else {
|
|
slen = 0;
|
|
}
|
|
|
|
s->tstate = TSTATE_ACKED;
|
|
|
|
if (slen > uip_mss()) {
|
|
dbg_string("Sending A: "); dbg_short(slen); dbg_char('\n');
|
|
tx_send(uip_mss());
|
|
s->tstate = TSTATE_TX;
|
|
} else if (slen > 0) {
|
|
dbg_string("Sending B: "); dbg_short(slen); dbg_char('\n');
|
|
tx_send(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);
|
|
o_idx = 0;
|
|
tx_send(slen);
|
|
cont_len -= slen;
|
|
cont_addr += slen;
|
|
s->tstate = TSTATE_TX;
|
|
} else if (s->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) {
|
|
if (config_upload || uip_len <= max_upload) {
|
|
if (!config_upload)
|
|
max_upload -= uip_len;
|
|
upload_settings.p = uip_appdata;
|
|
upload_settings.bptr = 0;
|
|
upload_settings.plen = uip_len;
|
|
stream_upload();
|
|
// 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;
|
|
uint16_t body_off = 0;
|
|
// 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 (s->tstate != TSTATE_MULTIPART && s->tstate != TSTATE_POSTBODY) {
|
|
if (s->tstate != TSTATE_HEADER)
|
|
pre_acc = 0;
|
|
body_off = request_header_take();
|
|
if (!body_off) {
|
|
uip_len = 0;
|
|
return;
|
|
}
|
|
if (body_off == 0xffff)
|
|
goto do_send;
|
|
}
|
|
|
|
if (s->tstate == TSTATE_MULTIPART || s->tstate == TSTATE_POSTBODY
|
|
|| is_word(config_buf, "POST")) {
|
|
handle_post(body_off);
|
|
// If this is an ongoing post stream, then wait for the next packet
|
|
if (s->tstate == TSTATE_POST || s->tstate == TSTATE_MULTIPART
|
|
|| s->tstate == TSTATE_POSTBODY) {
|
|
uip_len = 0;
|
|
return;
|
|
}
|
|
goto do_send;
|
|
}
|
|
|
|
p = config_buf;
|
|
if (!is_word(p, "GET")) {
|
|
send_bad_request();
|
|
goto do_send;
|
|
}
|
|
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, "/storm.json")) {
|
|
send_storm();
|
|
} 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, "/isolation.json")) {
|
|
send_isolation();
|
|
} else if (is_word(q, "/mtu.json")) {
|
|
send_mtu();
|
|
} else if (is_word(q, "/lag.json")) {
|
|
send_lag();
|
|
} else if (is_word(q, "/stp.json")) {
|
|
send_stp();
|
|
} else if (is_word(q, "/lldp.json")) {
|
|
send_lldp();
|
|
} else if (is_word(q, "/lacp.json")) {
|
|
send_lacp();
|
|
} else if (is_word(q, "/stpcnt.json")) {
|
|
send_stp_counters();
|
|
} 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_favicon_ico)) {
|
|
send_to_login();
|
|
goto do_send;
|
|
}
|
|
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\n");
|
|
if (f_data[entry].gzip)
|
|
slen += strtox(outbuf + slen, "Content-Encoding: gzip\r\n");
|
|
slen += strtox(outbuf + slen, "Cache-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');
|
|
tx_send(uip_mss());
|
|
dbg_string("Sending a done\n");
|
|
} else {
|
|
dbg_string("Sending b: "); dbg_short(slen); dbg_char('\n');
|
|
tx_send(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 (sent_len) {
|
|
dbg_string("Sending C: "); dbg_short(sent_len); dbg_char('\n');
|
|
tx_send(sent_len);
|
|
dbg_string("Sending C done\n");
|
|
}
|
|
s->tstate = TSTATE_TX;
|
|
uip_len = 0;
|
|
} else {
|
|
uip_len = 0;
|
|
}
|
|
}
|