esp32 firmware for a toaster reflow oven WIP!!!!!
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 
 
 
reflower/components/fileserver/src/token_subs.c

625 lignes
21 KiB

//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include <esp_log.h>
#include <esp_http_server.h>
#include <rom/queue.h>
#include <lwip/ip4_addr.h>
#include <sys/param.h>
#include <common_utils/utils.h>
#include <fileserver/token_subs.h>
#include "fileserver/embedded_files.h"
#include "fileserver/token_subs.h"
#define ESP_TRY(x) \
do { \
esp_err_t try_er = (x); \
if (try_er != ESP_OK) return try_er; \
} while(0)
static const char* TAG = "token_subs";
// TODO implement buffering to avoid sending many tiny chunks when escaping
/* encode for HTML. returns 0 or 1 - 1 = success */
static esp_err_t send_html_chunk(httpd_req_t *r, const char *data, ssize_t len)
{
assert(r);
assert(data);
int start = 0, end = 0;
char c;
if (len < 0) len = (int) strlen(data);
if (len==0) return ESP_OK;
for (end = 0; end < len; end++) {
c = data[end];
if (c == 0) {
// we found EOS
break; // not return - the last chunk is printed after the loop
}
if (c == '"' || c == '\'' || c == '<' || c == '>') {
if (start < end) ESP_TRY(httpd_resp_send_chunk(r, data + start, end - start));
start = end + 1;
}
if (c == '"') ESP_TRY(httpd_resp_send_chunk(r, "&#34;", 5));
else if (c == '\'') ESP_TRY(httpd_resp_send_chunk(r, "&#39;", 5));
else if (c == '<') ESP_TRY(httpd_resp_send_chunk(r, "&lt;", 4));
else if (c == '>') ESP_TRY(httpd_resp_send_chunk(r, "&gt;", 4));
}
if (start < end) ESP_TRY(httpd_resp_send_chunk(r, data + start, end - start));
return ESP_OK;
}
/* encode for JS. returns 0 or 1 - 1 = success */
static esp_err_t send_js_chunk(httpd_req_t *r, const char *data, ssize_t len)
{
assert(r);
assert(data);
int start = 0, end = 0;
char c;
if (len < 0) len = (int) strlen(data);
if (len==0) return ESP_OK;
for (end = 0; end < len; end++) {
c = data[end];
if (c == 0) {
// we found EOS
break; // not return - the last chunk is printed after the loop
}
if (c == '"' || c == '\\' || c == '/' || c == '\'' || c == '<' || c == '>' || c == '\n' || c == '\r') {
if (start < end) ESP_TRY(httpd_resp_send_chunk(r, data + start, end - start));
start = end + 1;
}
if (c == '"') ESP_TRY(httpd_resp_send_chunk(r, "\\\"", 2));
else if (c == '\'') ESP_TRY(httpd_resp_send_chunk(r, "\\'", 2));
else if (c == '\\') ESP_TRY(httpd_resp_send_chunk(r, "\\\\", 2));
else if (c == '/') ESP_TRY(httpd_resp_send_chunk(r, "\\/", 2));
else if (c == '<') ESP_TRY(httpd_resp_send_chunk(r, "\\u003C", 6));
else if (c == '>') ESP_TRY(httpd_resp_send_chunk(r, "\\u003E", 6));
else if (c == '\n') ESP_TRY(httpd_resp_send_chunk(r, "\\n", 2));
else if (c == '\r') ESP_TRY(httpd_resp_send_chunk(r, "\\r", 2));
}
if (start < end) ESP_TRY(httpd_resp_send_chunk(r, data + start, end - start));
return ESP_OK;
}
esp_err_t httpd_resp_send_chunk_escaped(httpd_req_t *r, const char *buf, ssize_t len, tpl_escape_t escape)
{
switch (escape) {
default: // this enum should be exhaustive, but in case something went wrong, just print it verbatim
case TPL_ESCAPE_NONE:
return httpd_resp_send_chunk(r, buf, len);
case TPL_ESCAPE_HTML:
return send_html_chunk(r, buf, len);
case TPL_ESCAPE_JS:
return send_js_chunk(r, buf, len);
}
}
esp_err_t httpd_send_static_file(httpd_req_t *r, int file_index, tpl_escape_t escape, uint32_t opts)
{
assert(file_index < EMBEDDED_FILE_LOOKUP_LEN);
const struct embedded_file_info *file = &EMBEDDED_FILE_LOOKUP[file_index];
return httpd_send_static_file_struct(r, file, escape, opts);
}
esp_err_t httpd_send_static_file_struct(httpd_req_t *r, const struct embedded_file_info *file, tpl_escape_t escape, uint32_t opts)
{
if (0 == (opts & HTOPT_NO_HEADERS)) {
ESP_TRY(httpd_resp_set_type(r, file->mime));
ESP_TRY(httpd_resp_set_hdr(r, "Cache-Control", "max-age=86400, public, must-revalidate"));
}
ESP_TRY(httpd_resp_send_chunk_escaped(r, (const char *) file->start, (size_t)(file->end - file->start), escape));
if (0 == (opts & HTOPT_NO_CLOSE)) {
ESP_TRY(httpd_resp_send_chunk(r, NULL, 0));
}
return ESP_OK;
}
esp_err_t httpd_send_template_file(httpd_req_t *r,
int file_index,
template_subst_t replacer,
void *context,
uint32_t opts)
{
assert(file_index < EMBEDDED_FILE_LOOKUP_LEN);
const struct embedded_file_info *file = &EMBEDDED_FILE_LOOKUP[file_index];
return httpd_send_template_file_struct(r,file,replacer,context,opts);
}
esp_err_t httpd_send_template_file_struct(httpd_req_t *r,
const struct embedded_file_info *file,
template_subst_t replacer,
void *context,
uint32_t opts)
{
if (0 == (opts & HTOPT_NO_HEADERS)) {
ESP_TRY(httpd_resp_set_type(r, file->mime));
ESP_TRY(httpd_resp_set_hdr(r, "Cache-Control", "no-cache, no-store, must-revalidate"));
}
return httpd_send_template(r, (const char *) file->start, (size_t)(file->end - file->start), replacer, context, opts);
}
esp_err_t tpl_kv_replacer(httpd_req_t *r, void *context, const char *token, tpl_escape_t escape)
{
assert(context);
assert(token);
struct tpl_kv_entry *entry;
struct tpl_kv_list *head = context;
SLIST_FOREACH(entry, head, link) {
if (0==strcmp(entry->key, token)) {
if (entry->subst_heap) {
if (entry->subst_heap[0]) {
return httpd_resp_send_chunk_escaped(r, entry->subst_heap, -1, escape);
}
} else {
if (entry->subst[0]) {
return httpd_resp_send_chunk_escaped(r, entry->subst, -1, escape);
}
return ESP_OK;
}
return ESP_OK;
}
}
return ESP_ERR_NOT_FOUND;
}
struct stacked_replacer_context {
template_subst_t replacer0;
void *context0;
template_subst_t replacer1;
void *context1;
};
esp_err_t stacked_replacer(httpd_req_t *r, void *context, const char *token, tpl_escape_t escape)
{
assert(context);
assert(token);
struct stacked_replacer_context *combo = context;
if (ESP_OK == combo->replacer0(r, combo->context0, token, escape)) {
return ESP_OK;
}
if (ESP_OK == combo->replacer1(r, combo->context1, token, escape)) {
return ESP_OK;
}
return ESP_ERR_NOT_FOUND;
}
esp_err_t httpd_send_template(httpd_req_t *r,
const char *template, ssize_t template_len,
template_subst_t replacer,
void *context,
uint32_t opts)
{
if (template_len < 0) template_len = strlen(template);
// replacer and context may be NULL
assert(template);
assert(r);
// data end
const char * const end = template + template_len;
// start of to-be-processed data
const char * pos = template;
// start position for finding opening braces, updated after a failed match to avoid infinite loop on the same bad token
const char * searchpos = pos;
// tokens must be copied to a buffer to allow adding the terminating null byte
char token_buf[MAX_TOKEN_LEN];
while (pos < end) {
const char * openbr = strchr(searchpos, '{');
if (openbr == NULL) {
// no more templates
ESP_TRY(httpd_resp_send_chunk(r, pos, (size_t) (end - pos)));
break;
}
// this brace could start a valid template. check if it seems valid...
const char * closebr = strchr(openbr, '}');
if (closebr == NULL) {
// there are no further closing braces, so it can't be a template
// we also know there can't be any more substitutions, because they would lack a closing } too
ESP_TRY(httpd_resp_send_chunk(r, pos, (size_t) (end - pos)));
break;
}
// see if the braces content looks like a token
const char *t = openbr + 1;
bool token_valid = true;
struct tpl_kv_list substitutions_head = tpl_kv_init();
struct tpl_kv_entry *new_subst_pair = NULL;
// a token can be either a name for replacement by the replacer func, or an include with static kv replacements
bool is_include = false;
bool token_is_optional = false;
const char *token_end = NULL; // points one char after the end of the token
// parsing the token
{
if (*t == '@') {
ESP_LOGD(TAG, "Parsing an Include token");
is_include = true;
t++;
}
enum {
P_NAME, P_KEY, P_VALUE
} state = P_NAME;
const char *kv_start = NULL;
while (t != closebr || state == P_VALUE) {
char c = *t;
if (state == P_NAME) {
if (!((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '.' || c == '_' || c == '-' || c == ':')) {
if (!is_include && c == '?') {
token_end = t;
token_is_optional = true;
} else {
if (is_include && c == '|') {
token_end = t;
state = P_KEY;
kv_start = t + 1;
// pipe separates the include's filename and literal substitutions
// we know there is a closing } somewhere, and {@....| doesn't occur normally, so let's assume it's correct
}
else {
token_valid = false;
break;
}
}
}
}
else if (state == P_KEY) {
if (!((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '.' || c == '_' || c == '-')) {
if (c == '=') {
new_subst_pair = calloc(1, sizeof(struct tpl_kv_entry));
const size_t klen = MIN(TPL_KV_KEY_LEN, t - kv_start);
strncpy(new_subst_pair->key, kv_start, klen);
new_subst_pair->key[klen] = 0;
kv_start = t + 1;
state = P_VALUE;
// pipe separates the include's filename and literal substitutions
// we know there is a closing } somewhere, and {@....| doesn't occur normally, so let's assume it's correct
}
}
}
else if (state == P_VALUE) {
if (c == '|' || c == '}') {
const size_t vlen = MIN(TPL_KV_SUBST_LEN, t - kv_start);
strncpy(new_subst_pair->subst, kv_start, vlen);
new_subst_pair->subst[vlen] = 0;
// attach the kv pair to the list
SLIST_INSERT_HEAD(&substitutions_head, new_subst_pair, link);
ESP_LOGD(TAG, "Adding subs kv %s -> %s", new_subst_pair->key, new_subst_pair->subst);
new_subst_pair = NULL;
kv_start = t + 1; // go past the pipe
state = P_KEY;
if (t == closebr) {
break; // found the ending brace, so let's quit the kv parse loop
}
}
}
t++;
}
// clean up after a messed up subs kv pairs syntax
if (new_subst_pair != NULL) {
free(new_subst_pair);
}
}
if (!token_valid) {
// false match, include it in the block to send before the next token
searchpos = openbr + 1;
ESP_LOGD(TAG, "Skip invalid token near %10s", openbr);
continue;
}
// now we know it looks like a substitution token
// flush data before the token
if (pos != openbr) ESP_TRY(httpd_resp_send_chunk(r, pos, (size_t) (openbr - pos)));
const char *token_start = openbr;
tpl_escape_t escape = TPL_ESCAPE_NONE;
// extract and terminate the token
size_t token_len = MIN(MAX_TOKEN_LEN-1, closebr - openbr - 1);
if (token_end) {
token_len = MIN(token_len, token_end - openbr - 1);
}
if (is_include) {
token_start += 1; // skip the @
token_len -= 1;
} else {
if (0 == strncmp("h:", openbr + 1, 2)) {
escape = TPL_ESCAPE_HTML;
token_start += 2;
token_len -= 2;
}
else if (0 == strncmp("j:", openbr + 1, 2)) {
escape = TPL_ESCAPE_JS;
token_start += 2;
token_len -= 2;
}
}
strncpy(token_buf, token_start+1, token_len);
token_buf[token_len] = 0;
ESP_LOGD(TAG, "Token: %s", token_buf);
esp_err_t rv;
if (is_include) {
ESP_LOGD(TAG, "Trying to include a sub-file");
const struct embedded_file_info *file = NULL;
rv = www_get_static_file(token_buf, FILE_ACCESS_PROTECTED, &file);
if (rv != ESP_OK) {
ESP_LOGE(TAG, "Failed to statically include \"%s\" in a template - %s", token_buf, esp_err_to_name(rv));
// this will cause the token to be emitted verbatim
} else {
ESP_LOGD(TAG, "Descending...");
// combine the two replacers
struct stacked_replacer_context combo = {
.replacer0 = replacer,
.context0 = context,
.replacer1 = tpl_kv_replacer,
.context1 = &substitutions_head
};
rv = httpd_send_template_file_struct(r, file, stacked_replacer, &combo, HTOPT_INCLUDE);
ESP_LOGD(TAG, "...back in parent");
}
// tear down the list
tpl_kv_free(&substitutions_head);
if (rv != ESP_OK) {
// just send it verbatim...
ESP_TRY(httpd_resp_send_chunk(r, openbr, (size_t) (closebr - openbr + 1)));
}
} else {
if (replacer) {
ESP_LOGD(TAG, "Running replacer for \"%s\" with escape %d", token_buf, escape);
rv = replacer(r, context, token_buf, escape);
if (rv != ESP_OK) {
if (rv == ESP_ERR_NOT_FOUND) {
ESP_LOGD(TAG, "Token rejected");
// optional token becomes empty string if not replaced
if (!token_is_optional) {
ESP_LOGD(TAG, "Not optional, keeping verbatim");
// replacer rejected the token, keep it verbatim
ESP_TRY(httpd_resp_send_chunk(r, openbr, (size_t) (closebr - openbr + 1)));
}
}
else {
ESP_LOGE(TAG, "Unexpected error from replacer func: 0x%02x - %s", rv, esp_err_to_name(rv));
return rv;
}
}
} else {
ESP_LOGD(TAG, "Not replacer");
// no replacer, only includes - used for 'static' files
if (!token_is_optional) {
ESP_LOGD(TAG, "Token not optional, keeping verbatim");
ESP_TRY(httpd_resp_send_chunk(r, openbr, (size_t) (closebr - openbr + 1)));
}
}
}
searchpos = pos = closebr + 1;
}
if (0 == (opts & HTOPT_NO_CLOSE)) {
ESP_TRY(httpd_resp_send_chunk(r, NULL, 0));
}
return ESP_OK;
}
esp_err_t tpl_kv_add_int(struct tpl_kv_list *head, const char *key, int32_t num)
{
char buf[12];
itoa(num, buf, 10);
return tpl_kv_add(head, key, buf);
}
esp_err_t tpl_kv_add_long(struct tpl_kv_list *head, const char *key, int64_t num)
{
char buf[21];
sprintf(buf, "%"PRIi64, num);
return tpl_kv_add(head, key, buf);
}
esp_err_t tpl_kv_add_ipv4str(struct tpl_kv_list *head, const char *key, uint32_t ip4h)
{
char buf[IP4ADDR_STRLEN_MAX];
ip4_addr_t addr;
addr.addr = lwip_htonl(ip4h);
ip4addr_ntoa_r(&addr, buf, IP4ADDR_STRLEN_MAX);
return tpl_kv_add(head, key, buf);
}
esp_err_t tpl_kv_add(struct tpl_kv_list *head, const char *key, const char *subst)
{
ESP_LOGD(TAG, "kv add subs %s := %s", key, subst);
struct tpl_kv_entry *entry = calloc(1, sizeof(struct tpl_kv_entry));
if (entry == NULL) return ESP_ERR_NO_MEM;
assert(strlen(key) < TPL_KV_KEY_LEN);
assert(strlen(subst) < TPL_KV_SUBST_LEN);
strncpy(entry->key, key, TPL_KV_KEY_LEN);
entry->key[TPL_KV_KEY_LEN - 1] = 0;
strncpy(entry->subst, subst, TPL_KV_SUBST_LEN - 1);
entry->subst[TPL_KV_KEY_LEN - 1] = 0;
SLIST_INSERT_HEAD(head, entry, link);
return ESP_OK;
}
esp_err_t tpl_kv_add_heapstr(struct tpl_kv_list *head, const char *key, char *subst)
{
ESP_LOGD(TAG, "kv add subs %s := (heap str)", key);
struct tpl_kv_entry *entry = calloc(1, sizeof(struct tpl_kv_entry));
if (entry == NULL) return ESP_ERR_NO_MEM;
assert(strlen(key) < TPL_KV_KEY_LEN);
strncpy(entry->key, key, TPL_KV_KEY_LEN);
entry->key[TPL_KV_KEY_LEN - 1] = 0;
entry->subst_heap = subst;
SLIST_INSERT_HEAD(head, entry, link);
return ESP_OK;
}
esp_err_t tpl_kv_sprintf(struct tpl_kv_list *head, const char *key, const char *format, ...)
{
ESP_LOGD(TAG, "kv printf %s := %s", key, format);
struct tpl_kv_entry *entry = calloc(1, sizeof(struct tpl_kv_entry));
if (entry == NULL) return ESP_ERR_NO_MEM;
assert(strlen(key) < TPL_KV_KEY_LEN);
strncpy(entry->key, key, TPL_KV_KEY_LEN);
entry->key[TPL_KV_KEY_LEN - 1] = 0;
va_list list;
va_start(list, format);
vsnprintf(entry->subst, TPL_KV_SUBST_LEN, format, list);
va_end(list);
entry->subst[TPL_KV_KEY_LEN - 1] = 0;
SLIST_INSERT_HEAD(head, entry, link);
return ESP_OK;
}
void tpl_kv_free(struct tpl_kv_list *head)
{
struct tpl_kv_entry *item, *next;
SLIST_FOREACH_SAFE(item, head, link, next) {
if (item->subst_heap) {
free(item->subst_heap);
item->subst_heap = NULL;
}
free(item);
}
}
esp_err_t tpl_kv_send_as_ascii_map(httpd_req_t *req, struct tpl_kv_list *head)
{
httpd_resp_set_type(req, "text/plain; charset=utf-8");
#define BUF_CAP 512
char *buf_head = malloc(BUF_CAP);
if (!buf_head) {
ESP_LOGE(TAG, "Malloc err");
return ESP_FAIL;
}
char *buf = buf_head;
size_t cap = BUF_CAP;
struct tpl_kv_entry *entry;
// GCC nested function
esp_err_t send_part() {
esp_err_t suc = httpd_resp_send_chunk(req, buf_head, BUF_CAP-cap);
buf = buf_head; // buf is assigned to buf head
cap = BUF_CAP;
if (suc != ESP_OK) {
ESP_LOGE(TAG, "Error sending buffer");
free(buf_head);
httpd_resp_send_chunk(req, NULL, 0);
}
return suc;
}
SLIST_FOREACH(entry, head, link) {
while(NULL == (buf = append(buf, entry->key, &cap))) ESP_TRY(send_part());
while(NULL == (buf = append(buf, "\x1f", &cap))) ESP_TRY(send_part());
if (entry->subst_heap) {
if (strlen(entry->subst_heap) >= BUF_CAP) {
// send what we have
ESP_TRY(send_part());
esp_err_t suc = httpd_resp_send_chunk(req, entry->subst_heap, -1);
if (suc != ESP_OK) {
ESP_LOGE(TAG, "Error sending buffer");
free(buf_head);
httpd_resp_send_chunk(req, NULL, 0);
}
} else {
while (NULL == (buf = append(buf, entry->subst_heap, &cap))) ESP_TRY(send_part());
}
} else {
while(NULL == (buf = append(buf, entry->subst, &cap))) ESP_TRY(send_part());
}
if (entry->link.sle_next) {
while(NULL == (buf = append(buf, "\x1e", &cap))) ESP_TRY(send_part());
}
}
// send leftovers
if (buf != buf_head) {
esp_err_t suc = httpd_resp_send_chunk(req, buf_head, BUF_CAP-cap);
if (suc != ESP_OK) {
ESP_LOGE(TAG, "Error sending buffer");
}
}
// Commit
httpd_resp_send_chunk(req, NULL, 0);
free(buf_head);
return ESP_OK;
}