#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 volatile __xdata uint32_t ticks; 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]; // bytes buffered in config_buf so far (config 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; // 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; } } } /* 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 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; 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" \ "\n404 Not Found\n

Not 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\n

Bad 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"); } __xdata uint8_t *scan_header(__xdata uint8_t * __xdata p) { __xdata uint8_t *v; content_type = 0; session = 0; authenticated = 0; while (!strstart(p, "\r\n\r\n")) { dbg_char(*p); if (!*p++) break; if ((v = header_value(p, "\ncontent-type:"))) content_type = v; 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) { 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) { // #386: needs static, otherwise it still lands in SRAM/DSEG static __xdata uint16_t cfg_pos, cfg_hdr, cfg_body, cfg_end, cfg_last; __xdata uint8_t cfg_bl; 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; 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; } // 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 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(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; } crc16(upload_settings.p + upload_settings.bptr); flash_buf[write_len++] = upload_settings.p[upload_settings.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); } 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 - 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; } slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n"); } 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 - 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"); } 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; pre_acc = 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) handle_config_fragment(p); else handle_firmware_fragment(p); 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) { 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; // 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; } // We only expect a GET request here. 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, "/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, "/stp.json")) { send_stp(); } 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