diff --git a/CMakeLists.txt b/CMakeLists.txt index 6345770..82e7a13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,6 +146,9 @@ set(SOURCE_FILES user/jstring.c user/jstring.h user/character_sets.h + user/ini_parser.h + user/ini_parser.c + user/ini_parser.rl user/utf8.h user/utf8.c user/cgi_logging.h user/config_xmacros.h user/config_xmacros.c) diff --git a/build_parser.sh b/build_parser.sh index ebcbdc2..af37cf3 100755 --- a/build_parser.sh +++ b/build_parser.sh @@ -3,6 +3,10 @@ echo "-- Building parser from Ragel source --" ragel -L -G0 user/ansi_parser.rl -o user/ansi_parser.c +ragel -L -G0 user/ini_parser.rl -o user/ini_parser.c sed -i "s/static const char _ansi_actions\[\]/static const char _ansi_actions\[\] ESP_CONST_DATA/" user/ansi_parser.c sed -i "s/static const char _ansi_eof_actions\[\]/static const char _ansi_eof_actions\[\] ESP_CONST_DATA/" user/ansi_parser.c + +sed -i "s/static const char _ini_actions\[\]/static const char _ini_actions\[\] ESP_CONST_DATA/" user/ini_parser.c +sed -i "s/static const char _ini_eof_actions\[\]/static const char _ini_eof_actions\[\] ESP_CONST_DATA/" user/ini_parser.c diff --git a/esphttpdconfig.mk.example b/esphttpdconfig.mk.example index d95d005..d6f0315 100644 --- a/esphttpdconfig.mk.example +++ b/esphttpdconfig.mk.example @@ -40,6 +40,7 @@ ESP_SPI_FLASH_SIZE_K = 1024 GLOBAL_CFLAGS = \ -DASYNC_LOG=1 \ + -DDEBUG_INI=0 \ -DDEBUG_D2D=0 \ -DDEBUG_ROUTER=0 \ -DDEBUG_CAPTDNS=0 \ diff --git a/libesphttpd b/libesphttpd index e4ecf07..d6651ca 160000 --- a/libesphttpd +++ b/libesphttpd @@ -1 +1 @@ -Subproject commit e4ecf0724e36c828be5222eddce58a6a5cd2386f +Subproject commit d6651ca0d11b7950078247b8305f5b23a6be6c6c diff --git a/user/cgi_persist.c b/user/cgi_persist.c index 4f5e56f..0b6b9dc 100644 --- a/user/cgi_persist.c +++ b/user/cgi_persist.c @@ -10,6 +10,7 @@ Cgi/template routines for configuring non-wifi settings #include "version.h" #include "screen.h" #include "config_xmacros.h" +#include "ini_parser.h" #define SET_REDIR_SUC "/cfg/system" @@ -155,10 +156,17 @@ cgiPersistExport(HttpdConnData *connData) 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) \ - if (allow) { \ + do { if (allow) { \ xget(buff, deref XSTRUCT->name); \ + if (streq(#name, "ap_ssid") && streq(buff, defSSID)) break; \ \ quoted = false; \ quoted |= streq(#type, "char"); \ @@ -173,7 +181,7 @@ cgiPersistExport(HttpdConnData *connData) } else { \ httpdSend(connData, buff, -1); \ } \ - } + } } while(0); #define admin 1 #define tpl 1 @@ -207,3 +215,52 @@ cgiPersistExport(HttpdConnData *connData) connData->cgiData = (void *) (step + 1); return HTTPD_CGI_MORE; } + + +void iniCb(const char *section, const char *key, const char *value, void *userData) +{ + dbg(">>> SET: [%s] %s = %s", section, key, value); +} + + +httpd_cgi_state ICACHE_FLASH_ATTR +postRecvHdl(HttpdConnData *connData, char *data, int len) +{ + ini_parse(data, (size_t) len); + ini_parse_end(); + return HTTPD_CGI_DONE; +} + +/** + * Import settings from INI + * + * @param connData + * @return status + */ +httpd_cgi_state ICACHE_FLASH_ATTR +cgiPersistImport(HttpdConnData *connData) +{ + if (connData->conn == NULL) { + //Connection aborted. Clean up. + return HTTPD_CGI_DONE; + } + + httpdStartResponse(connData, 200); + httpdHeader(connData, "Content-Type", "text/plain"); + httpdEndHeaders(connData); + + char *start = strstr(connData->post->buff, "\r\n\r\n"); + if (start == NULL) { + error("Malformed attachment POST!"); + goto end; + } + + ini_parse_begin(iniCb, NULL); + ini_parse(start, (size_t) connData->post->buffLen - (start - connData->post->buff)); + + connData->recvHdl = postRecvHdl; + +end: + // TODO redirect + return HTTPD_CGI_DONE; +} diff --git a/user/cgi_persist.h b/user/cgi_persist.h index b6f626e..e37c0df 100644 --- a/user/cgi_persist.h +++ b/user/cgi_persist.h @@ -7,5 +7,6 @@ httpd_cgi_state cgiPersistWriteDefaults(HttpdConnData *connData); httpd_cgi_state cgiPersistRestoreDefaults(HttpdConnData *connData); httpd_cgi_state cgiPersistRestoreHard(HttpdConnData *connData); httpd_cgi_state cgiPersistExport(HttpdConnData *connData); +httpd_cgi_state cgiPersistImport(HttpdConnData *connData); #endif diff --git a/user/ini_parser.c b/user/ini_parser.c new file mode 100644 index 0000000..fc99c5c --- /dev/null +++ b/user/ini_parser.c @@ -0,0 +1,526 @@ + +/* #line 1 "user/ini_parser.rl" */ + +/* Ragel constants block */ +#include "ini_parser.h" + +// Ragel setup + +/* #line 10 "user/ini_parser.c" */ +static const char _ini_actions[] ESP_CONST_DATA = { + 0, 1, 1, 1, 2, 1, 3, 1, + 4, 1, 5, 1, 6, 1, 7, 1, + 8, 1, 9, 1, 10, 1, 11, 1, + 13, 2, 0, 4, 2, 12, 4 +}; + +static const char _ini_eof_actions[] ESP_CONST_DATA = { + 0, 23, 5, 5, 15, 15, 15, 15, + 19, 19, 0, 0, 0, 0, 0, 0, + 0 +}; + +static const int ini_start = 1; +static const int ini_first_final = 12; +static const int ini_error = 0; + +static const int ini_en_section = 2; +static const int ini_en_keyvalue = 4; +static const int ini_en_comment = 8; +static const int ini_en_discard2eol = 10; +static const int ini_en_main = 1; + + +/* #line 10 "user/ini_parser.rl" */ + + +// Persistent state +static int8_t cs = -1; //!< Ragel's Current State variable +static uint32_t buff_i = 0; //!< Write pointer for the buffers +static char value_quote = 0; //!< Quote character of the currently collected value +static bool value_nextesc = false; //!< Next character is escaped, trated specially, and if quote, as literal quote character +static IniParserCallback keyCallback = NULL; //!< Currently assigned callback +static void *userdata = NULL; //!< Currently assigned user data for the callback + +// Buffers +static char keybuf[INI_KEY_MAX]; +static char secbuf[INI_KEY_MAX]; +static char valbuf[INI_VALUE_MAX]; + +// See header for doxygen! + +void ICACHE_FLASH_ATTR +ini_parse_reset_partial(void) +{ + buff_i = 0; + value_quote = 0; + value_nextesc = false; +} + +void ICACHE_FLASH_ATTR +ini_parse_reset(void) +{ + ini_parse_reset_partial(); + keybuf[0] = secbuf[0] = valbuf[0] = 0; + +/* #line 67 "user/ini_parser.c" */ + { + cs = ini_start; + } + +/* #line 41 "user/ini_parser.rl" */ +} + +void ICACHE_FLASH_ATTR +ini_parser_error(const char* msg) +{ + ini_error("Parser error: %s", msg); + ini_parse_reset_partial(); +} + + +void ICACHE_FLASH_ATTR +ini_parse_begin(IniParserCallback callback, void *userData) +{ + keyCallback = callback; + userdata = userData; + ini_parse_reset(); +} + + +void ICACHE_FLASH_ATTR +*ini_parse_end(void) +{ + ini_parse("\n", 1); + if (keyCallback) { + keyCallback = NULL; + } + + void *ud = userdata; + userdata = NULL; + return ud; +} + + +void ICACHE_FLASH_ATTR +ini_parse_file(const char *text, size_t len, IniParserCallback callback, void *userData) +{ + ini_parse_begin(callback, userData); + ini_parse(text, len); + ini_parse_end(); +} + +static void ICACHE_FLASH_ATTR +rtrim_buf(char *buf, int32_t end) +{ + if (end > 0) { + while ((uint8_t)buf[--end] < 33); + end++; // go past the last character + } + + buf[end] = 0; +} + + +void ICACHE_FLASH_ATTR +ini_parse(const char *newstr, size_t len) +{ + int32_t i; + char c; + bool isnl; + bool isquot; + + // Load new data to Ragel vars + const uint8_t *p; + const uint8_t *eof; + const uint8_t *pe; + + if (len == 0) while(newstr[++len] != 0); // alternative to strlen + + p = (const uint8_t *) newstr; + eof = NULL; + pe = (const uint8_t *) (newstr + len); + + // Init Ragel on the first run + if (cs == -1) { + ini_parse_reset(); + } + + // The parser + +/* #line 152 "user/ini_parser.c" */ + { + const char *_acts; + unsigned int _nacts; + + if ( p == pe ) + goto _test_eof; + if ( cs == 0 ) + goto _out; +_resume: + switch ( cs ) { +case 1: + switch( (*p) ) { + case 32u: goto tr1; + case 35u: goto tr3; + case 58u: goto tr0; + case 59u: goto tr3; + case 61u: goto tr0; + case 91u: goto tr4; + } + if ( (*p) < 9u ) { + if ( (*p) <= 8u ) + goto tr0; + } else if ( (*p) > 13u ) { + if ( 14u <= (*p) && (*p) <= 31u ) + goto tr0; + } else + goto tr1; + goto tr2; +case 0: + goto _out; +case 12: + goto tr0; +case 2: + switch( (*p) ) { + case 9u: goto tr6; + case 32u: goto tr6; + case 93u: goto tr5; + } + if ( (*p) <= 31u ) + goto tr5; + goto tr7; +case 3: + if ( (*p) == 93u ) + goto tr8; + if ( (*p) > 8u ) { + if ( 10u <= (*p) && (*p) <= 31u ) + goto tr5; + } else + goto tr5; + goto tr7; +case 13: + goto tr5; +case 4: + switch( (*p) ) { + case 10u: goto tr10; + case 58u: goto tr11; + case 61u: goto tr11; + } + goto tr9; +case 5: + switch( (*p) ) { + case 9u: goto tr13; + case 10u: goto tr14; + case 13u: goto tr15; + case 32u: goto tr13; + } + goto tr12; +case 6: + switch( (*p) ) { + case 10u: goto tr14; + case 13u: goto tr15; + } + goto tr12; +case 14: + goto tr10; +case 7: + if ( (*p) == 10u ) + goto tr14; + goto tr10; +case 8: + switch( (*p) ) { + case 10u: goto tr17; + case 13u: goto tr18; + } + goto tr16; +case 15: + goto tr19; +case 9: + if ( (*p) == 10u ) + goto tr17; + goto tr19; +case 10: + switch( (*p) ) { + case 10u: goto tr21; + case 13u: goto tr22; + } + goto tr20; +case 16: + goto tr23; +case 11: + if ( (*p) == 10u ) + goto tr21; + goto tr23; + } + + tr23: cs = 0; goto _again; + tr0: cs = 0; goto f0; + tr5: cs = 0; goto f4; + tr10: cs = 0; goto f7; + tr19: cs = 0; goto f11; + tr1: cs = 1; goto _again; + tr6: cs = 2; goto _again; + tr7: cs = 3; goto f5; + tr9: cs = 4; goto f8; + tr13: cs = 5; goto _again; + tr11: cs = 5; goto f9; + tr12: cs = 6; goto f10; + tr15: cs = 7; goto _again; + tr16: cs = 8; goto _again; + tr18: cs = 9; goto _again; + tr20: cs = 10; goto _again; + tr22: cs = 11; goto _again; + tr2: cs = 12; goto f1; + tr3: cs = 12; goto f2; + tr4: cs = 12; goto f3; + tr8: cs = 13; goto f6; + tr14: cs = 14; goto f10; + tr17: cs = 15; goto f12; + tr21: cs = 16; goto f13; + + f5: _acts = _ini_actions + 1; goto execFuncs; + f6: _acts = _ini_actions + 3; goto execFuncs; + f4: _acts = _ini_actions + 5; goto execFuncs; + f1: _acts = _ini_actions + 7; goto execFuncs; + f8: _acts = _ini_actions + 9; goto execFuncs; + f9: _acts = _ini_actions + 11; goto execFuncs; + f10: _acts = _ini_actions + 13; goto execFuncs; + f7: _acts = _ini_actions + 15; goto execFuncs; + f12: _acts = _ini_actions + 17; goto execFuncs; + f11: _acts = _ini_actions + 19; goto execFuncs; + f13: _acts = _ini_actions + 21; goto execFuncs; + f0: _acts = _ini_actions + 23; goto execFuncs; + f3: _acts = _ini_actions + 25; goto execFuncs; + f2: _acts = _ini_actions + 28; goto execFuncs; + +execFuncs: + _nacts = *_acts++; + while ( _nacts-- > 0 ) { + switch ( *_acts++ ) { + case 0: +/* #line 130 "user/ini_parser.rl" */ + { + buff_i = 0; + {cs = 2;goto _again;} + } + break; + case 1: +/* #line 135 "user/ini_parser.rl" */ + { + if (buff_i >= INI_KEY_MAX) { + ini_parser_error("Section name too long"); + {cs = 10;goto _again;} + } + keybuf[buff_i++] = (*p); + } + break; + case 2: +/* #line 143 "user/ini_parser.rl" */ + { + // we need a separate buffer for the result, otherwise a failed + // partial parse would corrupt the section string + rtrim_buf(keybuf, buff_i); + for (i = 0; (c = keybuf[i]) != 0; i++) secbuf[i] = c; + secbuf[i] = 0; + {cs = 1;goto _again;} + } + break; + case 3: +/* #line 155 "user/ini_parser.rl" */ + { + ini_parser_error("Syntax error in [section]"); + if((*p) == '\n') {cs = 1;goto _again;} else {cs = 10;goto _again;} + } + break; + case 4: +/* #line 162 "user/ini_parser.rl" */ + { + buff_i = 0; + keybuf[buff_i++] = (*p); // add the first char + {cs = 4;goto _again;} + } + break; + case 5: +/* #line 168 "user/ini_parser.rl" */ + { + if (buff_i >= INI_KEY_MAX) { + ini_parser_error("Key too long"); + {cs = 10;goto _again;} + } + keybuf[buff_i++] = (*p); + } + break; + case 6: +/* #line 176 "user/ini_parser.rl" */ + { + rtrim_buf(keybuf, buff_i); + + // --- Value begin --- + buff_i = 0; + value_quote = 0; + value_nextesc = false; + } + break; + case 7: +/* #line 185 "user/ini_parser.rl" */ + { + isnl = ((*p) == '\r' || (*p) == '\n'); + isquot = ((*p) == '\'' || (*p) == '"'); + + // detect our starting quote + if (isquot && !value_nextesc && buff_i == 0 && value_quote == 0) { + value_quote = (*p); + goto valueCharDone; + } + + if (buff_i >= INI_VALUE_MAX) { + ini_parser_error("Value too long"); + {cs = 10;goto _again;} + } + + // end of string - clean up and report + if ((!value_nextesc && (*p) == value_quote) || isnl) { + if (isnl && value_quote) { + ini_parser_error("Unterminated string"); + {cs = 1;goto _again;} + } + + // unquoted: trim from the end + if (!value_quote) { + rtrim_buf(valbuf, buff_i); + } else { + valbuf[buff_i] = 0; + } + + if (keyCallback) { + keyCallback(secbuf, keybuf, valbuf, userdata); + } + + // we don't want to discard to eol if the string was terminated by eol + // - would delete the next line + + if (isnl) {cs = 1;goto _again;} else {cs = 10;goto _again;} + } + + c = (*p); + // escape... + if (value_nextesc) { + if ((*p) == 'n') c = '\n'; + else if ((*p) == 'r') c = '\r'; + else if ((*p) == 't') c = '\t'; + else if ((*p) == 'e') c = '\033'; + } + + // collecting characters... + if (value_nextesc || (*p) != '\\') { // is quoted, or is not a quoting backslash - literal character + valbuf[buff_i++] = c; + } + + value_nextesc = (!value_nextesc && (*p) == '\\'); +valueCharDone:; + } + break; + case 8: +/* #line 247 "user/ini_parser.rl" */ + { + ini_parser_error("Syntax error in key=value"); + if((*p) == '\n') {cs = 1;goto _again;} else {cs = 10;goto _again;} + } + break; + case 9: +/* #line 257 "user/ini_parser.rl" */ + { {cs = 1;goto _again;} } + break; + case 10: +/* #line 258 "user/ini_parser.rl" */ + { + ini_parser_error("Syntax error in comment"); + if((*p) == '\n') {cs = 1;goto _again;} else {cs = 10;goto _again;} + } + break; + case 11: +/* #line 265 "user/ini_parser.rl" */ + { {cs = 1;goto _again;} } + break; + case 12: +/* #line 273 "user/ini_parser.rl" */ + { {cs = 8;goto _again;} } + break; + case 13: +/* #line 276 "user/ini_parser.rl" */ + { + ini_parser_error("Syntax error in root"); + {cs = 10;goto _again;} + } + break; +/* #line 458 "user/ini_parser.c" */ + } + } + goto _again; + +_again: + if ( cs == 0 ) + goto _out; + if ( ++p != pe ) + goto _resume; + _test_eof: {} + if ( p == eof ) + { + const char *__acts = _ini_actions + _ini_eof_actions[cs]; + unsigned int __nacts = (unsigned int) *__acts++; + while ( __nacts-- > 0 ) { + switch ( *__acts++ ) { + case 3: +/* #line 155 "user/ini_parser.rl" */ + { + ini_parser_error("Syntax error in [section]"); + if((*p) == '\n') {cs = 1; if ( p == pe ) + goto _test_eof; +goto _again;} else {cs = 10; if ( p == pe ) + goto _test_eof; +goto _again;} + } + break; + case 8: +/* #line 247 "user/ini_parser.rl" */ + { + ini_parser_error("Syntax error in key=value"); + if((*p) == '\n') {cs = 1; if ( p == pe ) + goto _test_eof; +goto _again;} else {cs = 10; if ( p == pe ) + goto _test_eof; +goto _again;} + } + break; + case 10: +/* #line 258 "user/ini_parser.rl" */ + { + ini_parser_error("Syntax error in comment"); + if((*p) == '\n') {cs = 1; if ( p == pe ) + goto _test_eof; +goto _again;} else {cs = 10; if ( p == pe ) + goto _test_eof; +goto _again;} + } + break; + case 13: +/* #line 276 "user/ini_parser.rl" */ + { + ini_parser_error("Syntax error in root"); + {cs = 10; if ( p == pe ) + goto _test_eof; +goto _again;} + } + break; +/* #line 517 "user/ini_parser.c" */ + } + } + } + + _out: {} + } + +/* #line 283 "user/ini_parser.rl" */ + +} diff --git a/user/ini_parser.h b/user/ini_parser.h new file mode 100644 index 0000000..2be0f5b --- /dev/null +++ b/user/ini_parser.h @@ -0,0 +1,65 @@ +#ifndef INIPARSE_STREAM_H +#define INIPARSE_STREAM_H + +#include + +#ifdef DEBUG_INI +#define ini_error(fmt, ...) error("[INI] "#fmt, ##__VA_ARGS__) +#else +#define ini_error(fmt, ...) +#endif + +// buffer sizes +#define INI_KEY_MAX 64 +#define INI_VALUE_MAX 256 + +/** + * INI parser callback, called for each found key-value pair. + * + * @param section - current section, empty string for global keys + * @param key - found key (trimmed of whitespace) + * @param value - value, trimmed of quotes or whitespace + * @param userData - opaque user data pointer, general purpose + */ +typedef void (*IniParserCallback)(const char *section, const char *key, const char *value, void *userData); + +/** + * Begin parsing a stream + * + * @param callback - key callback to assign + * @param userData - optional user data that willb e passed to the callback + */ +void ini_parse_begin(IniParserCallback callback, void *userData); + +/** + * End parse stream. + * Flushes what remains in the buffer and removes callback. + * + * @returns userData or NULL if none + */ +void* ini_parse_end(void); + +/** + * Parse a string (needn't be complete line or file) + * + * @param data - string to parse + * @param len - string length (0 = use strlen) + */ +void ini_parse(const char *data, size_t len); + +/** + * Parse a complete file loaded to string + * + * @param text - entire file as string + * @param len - file length (0 = use strlen) + * @param callback - key callback + * @param userData - optional user data for key callback + */ +void ini_parse_file(const char *text, size_t len, IniParserCallback callback, void *userData); + +/** + * Explicitly reset the parser + */ +void ini_parse_reset(void); + +#endif // INIPARSE_STREAM_H diff --git a/user/ini_parser.rl b/user/ini_parser.rl new file mode 100644 index 0000000..e85e035 --- /dev/null +++ b/user/ini_parser.rl @@ -0,0 +1,284 @@ + +/* Ragel constants block */ +#include "ini_parser.h" + +// Ragel setup +%%{ + machine ini; + write data; + alphtype unsigned char; +}%% + +// Persistent state +static int8_t cs = -1; //!< Ragel's Current State variable +static uint32_t buff_i = 0; //!< Write pointer for the buffers +static char value_quote = 0; //!< Quote character of the currently collected value +static bool value_nextesc = false; //!< Next character is escaped, trated specially, and if quote, as literal quote character +static IniParserCallback keyCallback = NULL; //!< Currently assigned callback +static void *userdata = NULL; //!< Currently assigned user data for the callback + +// Buffers +static char keybuf[INI_KEY_MAX]; +static char secbuf[INI_KEY_MAX]; +static char valbuf[INI_VALUE_MAX]; + +// See header for doxygen! + +void ICACHE_FLASH_ATTR +ini_parse_reset_partial(void) +{ + buff_i = 0; + value_quote = 0; + value_nextesc = false; +} + +void ICACHE_FLASH_ATTR +ini_parse_reset(void) +{ + ini_parse_reset_partial(); + keybuf[0] = secbuf[0] = valbuf[0] = 0; + %% write init; +} + +void ICACHE_FLASH_ATTR +ini_parser_error(const char* msg) +{ + ini_error("Parser error: %s", msg); + ini_parse_reset_partial(); +} + + +void ICACHE_FLASH_ATTR +ini_parse_begin(IniParserCallback callback, void *userData) +{ + keyCallback = callback; + userdata = userData; + ini_parse_reset(); +} + + +void ICACHE_FLASH_ATTR +*ini_parse_end(void) +{ + ini_parse("\n", 1); + if (keyCallback) { + keyCallback = NULL; + } + + void *ud = userdata; + userdata = NULL; + return ud; +} + + +void ICACHE_FLASH_ATTR +ini_parse_file(const char *text, size_t len, IniParserCallback callback, void *userData) +{ + ini_parse_begin(callback, userData); + ini_parse(text, len); + ini_parse_end(); +} + +static void ICACHE_FLASH_ATTR +rtrim_buf(char *buf, int32_t end) +{ + if (end > 0) { + while ((uint8_t)buf[--end] < 33); + end++; // go past the last character + } + + buf[end] = 0; +} + + +void ICACHE_FLASH_ATTR +ini_parse(const char *newstr, size_t len) +{ + int32_t i; + char c; + bool isnl; + bool isquot; + + // Load new data to Ragel vars + const uint8_t *p; + const uint8_t *eof; + const uint8_t *pe; + + if (len == 0) while(newstr[++len] != 0); // alternative to strlen + + p = (const uint8_t *) newstr; + eof = NULL; + pe = (const uint8_t *) (newstr + len); + + // Init Ragel on the first run + if (cs == -1) { + ini_parse_reset(); + } + + // The parser + %%{ +#/ * + ispace = [ \t]; # inline space + wchar = any - 0..8 - 10..31; + #apos = '\''; + #quot = '\"'; + nonl = [^\r\n]; + nl = '\r'? '\n'; + + # ---- [SECTION] ---- + + action sectionStart { + buff_i = 0; + fgoto section; + } + + action sectionChar { + if (buff_i >= INI_KEY_MAX) { + ini_parser_error("Section name too long"); + fgoto discard2eol; + } + keybuf[buff_i++] = fc; + } + + action sectionEnd { + // we need a separate buffer for the result, otherwise a failed + // partial parse would corrupt the section string + rtrim_buf(keybuf, buff_i); + for (i = 0; (c = keybuf[i]) != 0; i++) secbuf[i] = c; + secbuf[i] = 0; + fgoto main; + } + + section := + ( + ispace* <: ((wchar - ']')+ @sectionChar) ']' @sectionEnd + ) $!{ + ini_parser_error("Syntax error in [section]"); + if(fc == '\n') fgoto main; else fgoto discard2eol; + }; + + # ---- KEY=VALUE ---- + + action keyStart { + buff_i = 0; + keybuf[buff_i++] = fc; // add the first char + fgoto keyvalue; + } + + action keyChar { + if (buff_i >= INI_KEY_MAX) { + ini_parser_error("Key too long"); + fgoto discard2eol; + } + keybuf[buff_i++] = fc; + } + + action keyEnd { + rtrim_buf(keybuf, buff_i); + + // --- Value begin --- + buff_i = 0; + value_quote = 0; + value_nextesc = false; + } + + action valueChar { + isnl = (fc == '\r' || fc == '\n'); + isquot = (fc == '\'' || fc == '"'); + + // detect our starting quote + if (isquot && !value_nextesc && buff_i == 0 && value_quote == 0) { + value_quote = fc; + goto valueCharDone; + } + + if (buff_i >= INI_VALUE_MAX) { + ini_parser_error("Value too long"); + fgoto discard2eol; + } + + // end of string - clean up and report + if ((!value_nextesc && fc == value_quote) || isnl) { + if (isnl && value_quote) { + ini_parser_error("Unterminated string"); + fgoto main; + } + + // unquoted: trim from the end + if (!value_quote) { + rtrim_buf(valbuf, buff_i); + } else { + valbuf[buff_i] = 0; + } + + if (keyCallback) { + keyCallback(secbuf, keybuf, valbuf, userdata); + } + + // we don't want to discard to eol if the string was terminated by eol + // - would delete the next line + + if (isnl) fgoto main; else fgoto discard2eol; + } + + c = fc; + // escape... + if (value_nextesc) { + if (fc == 'n') c = '\n'; + else if (fc == 'r') c = '\r'; + else if (fc == 't') c = '\t'; + else if (fc == 'e') c = '\033'; + } + + // collecting characters... + if (value_nextesc || fc != '\\') { // is quoted, or is not a quoting backslash - literal character + valbuf[buff_i++] = c; + } + + value_nextesc = (!value_nextesc && fc == '\\'); +valueCharDone:; + } + + # use * for key, first char is already consumed. + keyvalue := + ( + ([^\n=:]* @keyChar %keyEnd) + [=:] ispace* <: nonl* @valueChar nl @valueChar + ) $!{ + ini_parser_error("Syntax error in key=value"); + if(fc == '\n') fgoto main; else fgoto discard2eol; + }; + + # ---- COMMENT ---- + + comment := + ( + nonl* nl + @{ fgoto main; } + ) $!{ + ini_parser_error("Syntax error in comment"); + if(fc == '\n') fgoto main; else fgoto discard2eol; + }; + + # ---- CLEANUP ---- + + discard2eol := nonl* nl @{ fgoto main; }; + + # ---- ROOT ---- + + main := + (space* + ( + '[' @sectionStart | + [#;] @{ fgoto comment; } | + (wchar - [\t =:]) @keyStart + ) + ) $!{ + ini_parser_error("Syntax error in root"); + fgoto discard2eol; + }; + + write exec; +#*/ + }%% +} diff --git a/user/routes.c b/user/routes.c index ea9748c..add3c97 100644 --- a/user/routes.c +++ b/user/routes.c @@ -132,6 +132,7 @@ const HttpdBuiltInUrl routes[] ESP_CONST_DATA = { ROUTE_TPL_FILE("/cfg/system/?", tplSystemCfg, "/cfg_system.tpl"), ROUTE_CGI("/cfg/system/set", cgiSystemCfgSetParams), ROUTE_CGI("/cfg/system/export", cgiPersistExport), + ROUTE_CGI("/cfg/system/import", cgiPersistImport), ROUTE_CGI("/cfg/system/write_defaults", cgiPersistWriteDefaults), ROUTE_CGI("/cfg/system/restore_defaults", cgiPersistRestoreDefaults), ROUTE_CGI("/cfg/system/restore_hard", cgiPersistRestoreHard), diff --git a/user/syscfg.h b/user/syscfg.h index e9ce0d0..2bb0684 100644 --- a/user/syscfg.h +++ b/user/syscfg.h @@ -25,7 +25,7 @@ enum pwlock { PWLOCK_MAX = 5, }; -//....Type................Name..Suffix...............Deref..XGET..........Cast..XSET.........................NOTIFY....Allow +//....Type................Name..Suffix...............Deref..XGET............XSET.........................NOTIFY....Allow // Deref is used to pass the field to xget. Cast is used to convert the &'d field to what xset wants (needed for static arrays) #define XTABLE_SYSCONF \ X(u32, uart_baudrate, /**/, /**/, xget_dec, xset_sys_baudrate, NULL, uart_changed=true, 1) \