|
|
|
/*
|
|
|
|
Cgi/template routines for configuring non-wifi settings
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <esp8266.h>
|
|
|
|
#include "cgi_persist.h"
|
|
|
|
#include "persist.h"
|
|
|
|
#include "helpers.h"
|
|
|
|
#include "cgi_logging.h"
|
|
|
|
#include "version.h"
|
|
|
|
#include "screen.h"
|
|
|
|
#include "config_xmacros.h"
|
|
|
|
#include "ini_parser.h"
|
|
|
|
|
|
|
|
#define SET_REDIR_SUC "/cfg/system"
|
|
|
|
#define SET_REDIR_ERR SET_REDIR_SUC"?err="
|
|
|
|
|
|
|
|
static bool ICACHE_FLASH_ATTR
|
|
|
|
verify_admin_pw(const char *pw)
|
|
|
|
{
|
|
|
|
return streq(pw, persist.admin.pw);
|
|
|
|
}
|
|
|
|
|
|
|
|
httpd_cgi_state ICACHE_FLASH_ATTR
|
|
|
|
cgiPersistWriteDefaults(HttpdConnData *connData)
|
|
|
|
{
|
|
|
|
char buff[PASSWORD_LEN];
|
|
|
|
|
|
|
|
if (connData->conn == NULL) {
|
|
|
|
//Connection aborted. Clean up.
|
|
|
|
return HTTPD_CGI_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// width and height must always go together so we can do max size validation
|
|
|
|
if (GET_ARG("pw")) {
|
|
|
|
cgi_dbg("Entered password for admin: %s", buff);
|
|
|
|
if (verify_admin_pw(buff)) {
|
|
|
|
cgi_dbg("pw is OK");
|
|
|
|
|
|
|
|
persist_set_as_default();
|
|
|
|
|
|
|
|
httpdRedirect(connData, SET_REDIR_SUC "?msg=Default%20settings%20updated.");
|
|
|
|
return HTTPD_CGI_DONE;
|
|
|
|
}
|
|
|
|
// if pw failed, show the same error as if it's wrong
|
|
|
|
}
|
|
|
|
|
|
|
|
httpdRedirect(connData, SET_REDIR_SUC "?err=Password"); // this will show in the "validation errors" box
|
|
|
|
return HTTPD_CGI_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
httpd_cgi_state ICACHE_FLASH_ATTR
|
|
|
|
cgiPersistRestoreDefaults(HttpdConnData *connData)
|
|
|
|
{
|
|
|
|
if (connData->conn == NULL) {
|
|
|
|
//Connection aborted. Clean up.
|
|
|
|
return HTTPD_CGI_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
persist_restore_default();
|
|
|
|
|
|
|
|
httpdRedirect(connData, SET_REDIR_SUC "?msg=All%20settings%20restored%20to%20saved%20defaults.");
|
|
|
|
return HTTPD_CGI_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
httpd_cgi_state ICACHE_FLASH_ATTR
|
|
|
|
cgiPersistRestoreHard(HttpdConnData *connData)
|
|
|
|
{
|
|
|
|
if (connData->conn == NULL) {
|
|
|
|
//Connection aborted. Clean up.
|
|
|
|
return HTTPD_CGI_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// this only changes live settings (and persists it)
|
|
|
|
// Defaults are not changed.
|
|
|
|
persist_load_hard_default();
|
|
|
|
|
|
|
|
httpdRedirect(connData, SET_REDIR_SUC "?msg=All%20settings%20restored%20to%20factory%20defaults.");
|
|
|
|
return HTTPD_CGI_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// -------------- Export --------------
|
|
|
|
|
|
|
|
#define httpdSend_orDie(conn, data, len) do { if (!httpdSend((conn), (data), (len))) return false; } while (0)
|
|
|
|
|
|
|
|
/* encode for double-quoted string */
|
|
|
|
int ICACHE_FLASH_ATTR httpdSend_dblquot(HttpdConnData *conn, const char *data, int len)
|
|
|
|
{
|
|
|
|
int start = 0, end = 0;
|
|
|
|
char c;
|
|
|
|
if (conn->conn==NULL) return 0;
|
|
|
|
if (len < 0) len = (int) strlen(data);
|
|
|
|
if (len==0) return 0;
|
|
|
|
|
|
|
|
for (end = 0; end < len; end++) {
|
|
|
|
c = data[end];
|
|
|
|
if (c == 0) {
|
|
|
|
// we found EOS
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c == '"' || c == '\'' || c == '\\' || c == '\n' || c == '\r' || c == '\x1b') {
|
|
|
|
if (start < end) httpdSend_orDie(conn, data + start, end - start);
|
|
|
|
start = end + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c == '"') httpdSend_orDie(conn, "\\\"", 2);
|
|
|
|
else if (c == '\'') httpdSend_orDie(conn, "\\'", 2);
|
|
|
|
else if (c == '\\') httpdSend_orDie(conn, "\\\\", 2);
|
|
|
|
else if (c == '\n') httpdSend_orDie(conn, "\\n", 2);
|
|
|
|
else if (c == '\r') httpdSend_orDie(conn, "\\r", 2);
|
|
|
|
else if (c == '\x1b') httpdSend_orDie(conn, "\\e", 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (start < end) httpdSend_orDie(conn, data + start, end - start);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Export settings to INI
|
|
|
|
*
|
|
|
|
* @param connData
|
|
|
|
* @return status
|
|
|
|
*/
|
|
|
|
httpd_cgi_state ICACHE_FLASH_ATTR
|
|
|
|
cgiPersistExport(HttpdConnData *connData)
|
|
|
|
{
|
|
|
|
char buff[256];
|
|
|
|
u8 mac[6];
|
|
|
|
|
|
|
|
int step = (int) connData->cgiData;
|
|
|
|
|
|
|
|
if (connData->conn == NULL) {
|
|
|
|
//Connection aborted. Clean up.
|
|
|
|
return HTTPD_CGI_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (step == 0) {
|
|
|
|
wifi_get_macaddr(SOFTAP_IF, mac);
|
|
|
|
sprintf(buff, "attachment; filename=ESPTerm_%02X%02X%02X_%s.ini",
|
|
|
|
mac[3], mac[4], mac[5], FW_VERSION);
|
|
|
|
|
|
|
|
httpdStartResponse(connData, 200);
|
|
|
|
httpdHeader(connData, "Content-Disposition", buff);
|
|
|
|
httpdHeader(connData, "Content-Type", "text/plain");
|
|
|
|
httpdEndHeaders(connData);
|
|
|
|
|
|
|
|
sprintf(buff, "# == ESPTerm config export ==\r\n"
|
|
|
|
"# Device: %02X%02X%02X - %s\r\n"
|
|
|
|
"# Version: %s\r\n",
|
|
|
|
mac[3], mac[4], mac[5],
|
|
|
|
termconf->title,
|
|
|
|
FW_VERSION);
|
|
|
|
|
|
|
|
httpdSend(connData, buff, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// do not export SSID if unchanged - embeds unique ID that should
|
|
|
|
// not be overwritten in target.
|
|
|
|
|
|
|
|
char defSSID[20];
|
|
|
|
sprintf(defSSID, "TERM-%02X%02X%02X", mac[3], mac[4], mac[5]);
|
|
|
|
|
|
|
|
bool quoted;
|
|
|
|
#define X(type, name, suffix, deref, xget, xset, xsarg, xnotify, allow) \
|
|
|
|
do { if (allow) { \
|
|
|
|
xget(buff, deref XSTRUCT->name); \
|
|
|
|
if (streq(#name, "ap_ssid") && streq(buff, defSSID)) break; \
|
|
|
|
\
|
|
|
|
quoted = false; \
|
|
|
|
quoted |= streq(#type, "char"); \
|
|
|
|
quoted |= streq(#type, "uchar"); \
|
|
|
|
if (strstarts(#name, "bm")) quoted=false; \
|
|
|
|
\
|
|
|
|
httpdSend(connData, "\r\n"#name " = ", -1); \
|
|
|
|
if (quoted) { \
|
|
|
|
httpdSend(connData, "\"", 1); \
|
|
|
|
httpdSend_dblquot(connData, buff, -1); \
|
|
|
|
httpdSend(connData, "\"", 1); \
|
|
|
|
} else { \
|
|
|
|
httpdSend(connData, buff, -1); \
|
|
|
|
} \
|
|
|
|
} } while(0);
|
|
|
|
|
|
|
|
#define admin 1
|
|
|
|
#define tpl 1
|
|
|
|
if (step == 1) {
|
|
|
|
httpdSend(connData, "\r\n[system]", -1);
|
|
|
|
#define XSTRUCT sysconf
|
|
|
|
XTABLE_SYSCONF
|
|
|
|
#undef XSTRUCT
|
|
|
|
httpdSend(connData, "\r\n", -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (step == 2) {
|
|
|
|
httpdSend(connData, "\r\n[wifi]", -1);
|
|
|
|
#define XSTRUCT wificonf
|
|
|
|
XTABLE_WIFICONF
|
|
|
|
#undef XSTRUCT
|
|
|
|
httpdSend(connData, "\r\n", -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (step == 3) {
|
|
|
|
httpdSend(connData, "\r\n[terminal]", -1);
|
|
|
|
#define XSTRUCT termconf
|
|
|
|
XTABLE_TERMCONF
|
|
|
|
#undef XSTRUCT
|
|
|
|
httpdSend(connData, "\r\n", -1);
|
|
|
|
|
|
|
|
return HTTPD_CGI_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
#undef X
|
|
|
|
connData->cgiData = (void *) (step + 1);
|
|
|
|
return HTTPD_CGI_MORE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// -------------- IMPORT --------------
|
|
|
|
|
|
|
|
struct IniUpload {
|
|
|
|
TerminalConfigBundle *term_backup;
|
|
|
|
WiFiConfigBundle *wifi_backup;
|
|
|
|
SystemConfigBundle *sys_backup;
|
|
|
|
bool term_ok;
|
|
|
|
bool wifi_ok;
|
|
|
|
bool sys_ok;
|
|
|
|
bool term_any;
|
|
|
|
bool wifi_any;
|
|
|
|
bool sys_any;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static void ICACHE_FLASH_ATTR iniCb(const char *section, const char *key, const char *value, void *userData)
|
|
|
|
{
|
|
|
|
HttpdConnData *connData = (HttpdConnData *)userData;
|
|
|
|
struct IniUpload *state;
|
|
|
|
if (!connData || !connData->cgiData) {
|
|
|
|
error("userData or state is NULL!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
state = connData->cgiData;
|
|
|
|
|
|
|
|
// cgi_dbg("%s.%s = %s", section, key, value);
|
|
|
|
|
|
|
|
/** used for INI */
|
|
|
|
#define X(type, name, suffix, deref, xget, xset, xsarg, xnotify, allow) \
|
|
|
|
if (streq(#name, key)) { \
|
|
|
|
found = true; \
|
|
|
|
type *_p = (type *) &XSTRUCT->name; \
|
|
|
|
enum xset_result res = xset(#name, _p, value, (const void*) (xsarg)); \
|
|
|
|
if (res == XSET_SET) { changed = true; xnotify; } \
|
|
|
|
else if (res == XSET_FAIL) { ok = false; } \
|
|
|
|
break; \
|
|
|
|
}
|
|
|
|
|
|
|
|
// notify flag, unused here
|
|
|
|
bool uart_changed = false;
|
|
|
|
|
|
|
|
bool found = false;
|
|
|
|
bool ok = true;
|
|
|
|
bool changed = false;
|
|
|
|
|
|
|
|
if (streq(section, "terminal")) {
|
|
|
|
do {
|
|
|
|
#define XSTRUCT termconf
|
|
|
|
XTABLE_TERMCONF
|
|
|
|
#undef XSTRUCT
|
|
|
|
} while (0);
|
|
|
|
|
|
|
|
if (found) {
|
|
|
|
if (!ok) state->term_ok = false;
|
|
|
|
state->term_any |= changed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (streq(section, "wifi")) {
|
|
|
|
do {
|
|
|
|
#define XSTRUCT wificonf
|
|
|
|
XTABLE_WIFICONF
|
|
|
|
#undef XSTRUCT
|
|
|
|
} while (0);
|
|
|
|
|
|
|
|
if (found) {
|
|
|
|
if (!ok) state->wifi_ok = false;
|
|
|
|
state->wifi_any |= changed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (streq(section, "system")) {
|
|
|
|
do {
|
|
|
|
#define XSTRUCT sysconf
|
|
|
|
XTABLE_SYSCONF
|
|
|
|
#undef XSTRUCT
|
|
|
|
} while (0);
|
|
|
|
|
|
|
|
if (found) {
|
|
|
|
if (!ok) state->sys_ok = false;
|
|
|
|
state->sys_any |= changed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!found) cgi_warn("Unknown key %s.%s!", section, key);
|
|
|
|
|
|
|
|
#undef X
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ICACHE_FLASH_ATTR
|
|
|
|
freeIniUploadStruct(HttpdConnData *connData)
|
|
|
|
{
|
|
|
|
cgi_dbg("Free struct...");
|
|
|
|
struct IniUpload *state;
|
|
|
|
if (connData && connData->cgiData) {
|
|
|
|
state = connData->cgiData;
|
|
|
|
if (state->sys_backup != NULL) free(state->sys_backup);
|
|
|
|
if (state->wifi_backup != NULL) free(state->wifi_backup);
|
|
|
|
if (state->term_backup != NULL) free(state->term_backup);
|
|
|
|
free(state);
|
|
|
|
connData->cgiData = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static httpd_cgi_state ICACHE_FLASH_ATTR
|
|
|
|
postRecvHdl(HttpdConnData *connData, char *data, int len)
|
|
|
|
{
|
|
|
|
struct IniUpload *state = connData->cgiData;
|
|
|
|
if (!state) return HTTPD_CGI_DONE;
|
|
|
|
|
|
|
|
cgi_dbg("INI parse - postRecvHdl");
|
|
|
|
|
|
|
|
if (len>0) {
|
|
|
|
// Discard the boundary marker (there is only one)
|
|
|
|
char *bdr = connData->post->multipartBoundary;
|
|
|
|
char *foundat = strstr(data, bdr);
|
|
|
|
if (foundat != NULL) {
|
|
|
|
*foundat = '#'; // make it a comment
|
|
|
|
}
|
|
|
|
|
|
|
|
ini_parse(data, (size_t) len);
|
|
|
|
}
|
|
|
|
|
|
|
|
cgi_dbg("Closing parser");
|
|
|
|
ini_parse_end();
|
|
|
|
|
|
|
|
cgi_dbg("INI parse - end.");
|
|
|
|
|
|
|
|
// abort if bad screen size
|
|
|
|
bool tooLarge = (termconf->width*termconf->height > MAX_SCREEN_SIZE);
|
|
|
|
state->term_ok &= !tooLarge;
|
|
|
|
if (tooLarge) cgi_warn("Bad term screen size!");
|
|
|
|
|
|
|
|
bool suc = state->term_ok && state->wifi_ok && state->sys_ok;
|
|
|
|
bool any = state->term_any || state->wifi_any || state->sys_any;
|
|
|
|
|
|
|
|
cgi_dbg("Evaluating results...");
|
|
|
|
|
|
|
|
if (!state->term_ok) cgi_warn("Terminal settings rejected.");
|
|
|
|
if (!state->wifi_ok) cgi_warn("WiFi settings rejected.");
|
|
|
|
if (!state->sys_ok) cgi_warn("System settings rejected.");
|
|
|
|
|
|
|
|
if (!suc) {
|
|
|
|
cgi_warn("Some validation failed, reverting all!");
|
|
|
|
memcpy(termconf, state->term_backup, sizeof(TerminalConfigBundle));
|
|
|
|
memcpy(wificonf, state->wifi_backup, sizeof(WiFiConfigBundle));
|
|
|
|
memcpy(sysconf, state->sys_backup, sizeof(SystemConfigBundle));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (state->term_any) {
|
|
|
|
cgi_dbg("Applying terminal settings");
|
|
|
|
terminal_apply_settings();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state->sys_any) {
|
|
|
|
cgi_dbg("Applying system settings");
|
|
|
|
sysconf_apply_settings();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state->wifi_any) {
|
|
|
|
cgi_dbg("Applying WiFi settings (scheduling...)");
|
|
|
|
wifimgr_apply_settings_later(2000);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (any) {
|
|
|
|
cgi_dbg("Persisting results");
|
|
|
|
persist_store();
|
|
|
|
} else {
|
|
|
|
cgi_warn("Nothing written.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cgi_dbg("Redirect");
|
|
|
|
char buff[100];
|
|
|
|
char *b = buff;
|
|
|
|
if (suc) {
|
|
|
|
if (any) {
|
|
|
|
httpdRedirect(connData, SET_REDIR_SUC"?msg=Settings%20loaded%20and%20applied.");
|
|
|
|
} else {
|
|
|
|
httpdRedirect(connData, SET_REDIR_SUC"?msg=No%20settings%20changed.");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
b += sprintf(b, SET_REDIR_SUC"?errmsg=Errors%%20in:%%20");
|
|
|
|
bool comma = false;
|
|
|
|
if (!state->sys_ok) {
|
|
|
|
b += sprintf(b, "System%%20config");
|
|
|
|
comma = true;
|
|
|
|
}
|
|
|
|
if (!state->wifi_ok) {
|
|
|
|
if (comma) b += sprintf(b, ",%%20");
|
|
|
|
b += sprintf(b, "WiFi%%20config");
|
|
|
|
}
|
|
|
|
if (!state->term_ok) {
|
|
|
|
if (comma) b += sprintf(b, ",%%20");
|
|
|
|
b += sprintf(b, "Terminal%%20config");
|
|
|
|
}
|
|
|
|
httpdRedirect(connData, buff);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clean up.
|
|
|
|
freeIniUploadStruct(connData);
|
|
|
|
return HTTPD_CGI_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Import settings from INI
|
|
|
|
*
|
|
|
|
* @param connData
|
|
|
|
* @return status
|
|
|
|
*/
|
|
|
|
httpd_cgi_state ICACHE_FLASH_ATTR
|
|
|
|
cgiPersistImport(HttpdConnData *connData)
|
|
|
|
{
|
|
|
|
struct IniUpload *state = NULL;
|
|
|
|
|
|
|
|
if (connData->conn == NULL) {
|
|
|
|
//Connection aborted. Clean up.
|
|
|
|
freeIniUploadStruct(connData);
|
|
|
|
return HTTPD_CGI_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *start = strstr(connData->post->buff, "\r\n\r\n");
|
|
|
|
if (start == NULL) {
|
|
|
|
error("Malformed attachment POST!");
|
|
|
|
|
|
|
|
httpdStartResponse(connData, 400);
|
|
|
|
httpdHeader(connData, "Content-Type", "text/plain");
|
|
|
|
httpdEndHeaders(connData);
|
|
|
|
httpdSend(connData, "Bad format.", -1);
|
|
|
|
return HTTPD_CGI_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
cgi_info("Starting INI parser for uploaded file...");
|
|
|
|
|
|
|
|
state = malloc(sizeof(struct IniUpload));
|
|
|
|
if (!state) {
|
|
|
|
error("state struct alloc fail");
|
|
|
|
return HTTPD_CGI_DONE;
|
|
|
|
}
|
|
|
|
state->sys_backup = NULL;
|
|
|
|
state->wifi_backup = NULL;
|
|
|
|
state->term_backup = NULL;
|
|
|
|
connData->cgiData = state;
|
|
|
|
|
|
|
|
cgi_dbg("Allocating backup buffers");
|
|
|
|
state->sys_backup = malloc(sizeof(SystemConfigBundle));
|
|
|
|
state->wifi_backup = malloc(sizeof(WiFiConfigBundle));
|
|
|
|
state->term_backup = malloc(sizeof(TerminalConfigBundle));
|
|
|
|
|
|
|
|
cgi_dbg("Copying orig data");
|
|
|
|
memcpy(state->sys_backup, sysconf, sizeof(SystemConfigBundle));
|
|
|
|
memcpy(state->wifi_backup, wificonf, sizeof(WiFiConfigBundle));
|
|
|
|
memcpy(state->term_backup, termconf, sizeof(TerminalConfigBundle));
|
|
|
|
|
|
|
|
state->sys_ok = true;
|
|
|
|
state->wifi_ok = true;
|
|
|
|
state->term_ok = true;
|
|
|
|
|
|
|
|
state->sys_any = false;
|
|
|
|
state->wifi_any = false;
|
|
|
|
state->term_any = false;
|
|
|
|
|
|
|
|
cgi_dbg("Parser starts!");
|
|
|
|
ini_parse_begin(iniCb, connData);
|
|
|
|
|
|
|
|
size_t datalen = (size_t) connData->post->buffLen - (start - connData->post->buff);
|
|
|
|
ini_parse(start, datalen);
|
|
|
|
|
|
|
|
connData->recvHdl = postRecvHdl;
|
|
|
|
|
|
|
|
// special case for too short ini
|
|
|
|
int bytes_remain = connData->post->len;
|
|
|
|
bytes_remain -= connData->post->buffLen;
|
|
|
|
|
|
|
|
if (bytes_remain <= 0) {
|
|
|
|
return connData->recvHdl(connData, NULL, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// continues in recvHdl
|
|
|
|
return HTTPD_CGI_MORE;
|
|
|
|
}
|