#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" \
"\n
404 Not Found\nNot Found
\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" \
"\n400 Bad Request\nBad Request
\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