add webserver components, some webserver fices and improvements in templating, add real time history chart

This commit is contained in:
2020-01-07 00:21:27 +01:00
parent ed53611e0f
commit ef7866a29f
50 changed files with 3594 additions and 2 deletions
+9
View File
@@ -0,0 +1,9 @@
set(COMPONENT_ADD_INCLUDEDIRS
"include")
set(COMPONENT_SRCDIRS
"src")
set(COMPONENT_REQUIRES tcpip_adapter esp_http_server common_utils)
register_component()
+4
View File
@@ -0,0 +1,4 @@
Functions enriching the HTTP server bundled with ESP-IDF.
This package includes HTTP-related utilities, captive
portal implementation, and a cookie-based session system with a
key-value store and expirations.
+3
View File
@@ -0,0 +1,3 @@
COMPONENT_SRCDIRS := src
COMPONENT_ADD_INCLUDEDIRS := include
@@ -0,0 +1,32 @@
#ifndef HTTPD_UTILS_CAPTIVE_H
#define HTTPD_UTILS_CAPTIVE_H
#include <esp_http_server.h>
#include <esp_err.h>
/**
* Redirect if needed when a captive portal capture is detected
*
* @param r
* @return ESP_OK on redirect, ESP_ERR_NOT_FOUND if not needed, other error on failure
*/
esp_err_t httpd_captive_redirect(httpd_req_t *r);
/**
* Get URL to redirect to. WEAK.
*
* @param r
* @param buf
* @param maxlen
* @return http(s)://foo.bar/
*/
esp_err_t httpd_captive_redirect_get_url(httpd_req_t *r, char *buf, size_t maxlen);
/**
* Get captive portal domain. WEAK.
*
* @return foo.bar
*/
const char * httpd_captive_redirect_get_domain();
#endif //HTTPD_UTILS_CAPTIVE_H
@@ -0,0 +1,13 @@
#ifndef HTTPD_FDIPV4_H
#define HTTPD_FDIPV4_H
/**
* Get IP address for a FD
*
* @param fd
* @param[out] ipv4
* @return success
*/
esp_err_t fd_to_ipv4(int fd, in_addr_t *ipv4);
#endif //HTTPD_FDIPV4_H
@@ -0,0 +1,16 @@
#ifndef HTTPD_UTILS_REDIRECT_H
#define HTTPD_UTILS_REDIRECT_H
#include <esp_http_server.h>
#include <esp_err.h>
/**
* Redirect to other URI - sends a HTTP response from the http server
*
* @param r - request
* @param uri - target uri
* @return success
*/
esp_err_t httpd_redirect_to(httpd_req_t *r, const char *uri);
#endif // HTTPD_UTILS_REDIRECT_H
@@ -0,0 +1,55 @@
/**
* Session store system main include file
*
* Created on 2019/07/13.
*/
#ifndef HTTPD_UTILS_SESSION_H
#define HTTPD_UTILS_SESSION_H
#include "session_kvmap.h"
#include "session_store.h"
// Customary keys
/** Session key for OK flash message */
#define SESS_FLASH_OK "flash_ok"
/** Session key for error flash message */
#define SESS_FLASH_ERR "flash_err"
/** Session key for a "logged in" flag. Value is 1 if logged in. */
#define SESS_AUTHED "authed"
// ..
/**
* Redirect to /login form if unauthed,
* but also retrieve the session key-value store for further use
*/
#define HTTP_GET_AUTHED_SESSION(kvstore, r) do { \
kvstore = httpd_req_find_session_and((r), SESS_GET_DATA); \
if (NULL == kvstore || NULL == sess_kv_map_get(kvstore, SESS_AUTHED)) { \
return httpd_redirect_to((r), "/login"); \
} \
} while(0)
/**
* Start or resume a session without checking for authed status.
* When started, the session cookie is added to the response immediately.
*
* @param[out] kvstore - a place to store the allocated or retrieved session kvmap
* @param[in] r - request
* @return success
*/
esp_err_t HTTP_GET_SESSION(sess_kv_map_t **kvstore, httpd_req_t *r);
/**
* Redirect to the login form if unauthed.
* This is the same as `HTTP_GET_AUTHED_SESSION`, except the kvstore variable is
* not needed in the uri handler calling this, so it is declared internally.
*/
#define HTTP_REDIRECT_IF_UNAUTHED(r) do { \
sess_kv_map_t *_kvstore; \
HTTP_GET_AUTHED_SESSION(_kvstore, r); \
} while(0)
#endif // HTTPD_UTILS_SESSION_H
@@ -0,0 +1,77 @@
/**
* Simple key-value map for session data storage.
* Takes care of dynamic allocation and cleanup.
*
* Created on 2019/01/28.
*/
#ifndef SESSION_KVMAP_H
#define SESSION_KVMAP_H
/**
* Prototype for a free() func to clean up session-held objects
*/
typedef void (*sess_kv_free_func_t)(void *obj);
typedef struct sess_kv_map sess_kv_map_t;
#define SESS_KVMAP_KEY_LEN 16
/**
* Allocate a new session key-value store
*
* @return the store, NULL on error
*/
sess_kv_map_t *sess_kv_map_alloc(void);
/**
* Free the session kv store.
*
* @param head - store head
*/
void sess_kv_map_free(void *head);
/**
* Get a value from the session kv store.
*
* @param head - store head
* @param key - key to get a value for
* @return the value, or NULL if not found
*/
void *sess_kv_map_get(sess_kv_map_t *head, const char *key);
/**
* Get and remove a value from the session store.
*
* The free function is not called in this case and the recipient is
* responsible for cleaning it up correctly.
*
* @param head - store head
* @param key - key to get a value for
* @return the value, or NULL if not found
*/
void * sess_kv_map_take(sess_kv_map_t *head, const char *key);
/**
* Remove an entry from the session by its key name.
* The slot is not free'd yet, but is made available for reuse.
*
* @param head - store head
* @param key - key to remove
* @return success
*/
esp_err_t sess_kv_map_remove(sess_kv_map_t *head, const char *key);
/**
* Set a key value. If there is an old value stored, it will be freed by its free function and replaced by the new one.
* Otherwise a new slot is allocated for it, or a previously released one is reused.
*
* @param head - store head
* @param key - key to assign to
* @param value - new value
* @param free_fn - value free func
* @return success
*/
esp_err_t sess_kv_map_set(sess_kv_map_t *head, const char *key, void *value, sess_kv_free_func_t free_fn);
#endif //SESSION_KVMAP_H
@@ -0,0 +1,100 @@
/**
* Cookie-based session store
*/
#ifndef SESSION_STORE_H
#define SESSION_STORE_H
#include "esp_http_server.h"
#define SESSION_EXPIRY_TIME_S 60*30
#define SESSION_COOKIE_NAME "SESSID"
/** function that frees a session data object */
typedef void (*sess_data_free_fn_t)(void *);
enum session_find_action {
SESS_DROP, SESS_GET_DATA
};
/**
* Find session and either get data, or drop it.
*
* @param cookie
* @param action
* @return
*/
void *session_find_and(const char *cookie, enum session_find_action action);
/**
* Initialize the session store.
* Safely empty it if initialized
*/
void session_store_init(void);
// placeholder for when no data is stored
#define SESSION_DUMMY ((void *) 1)
/**
* Create a new session. Data must not be NULL, because it wouldn't be possible
* to distinguish between NULL value and session not found in return values.
* It can be e.g. 1 if no data storage is needed.
*
* @param data - data object to attach to the session
* @param free_fn - function that disposes of the data when the session expires
* @return NULL on error, or the new session ID. This is a live pointer into the session structure,
* must be copied if stored, as it can become invalid at any time
*/
const char *session_new(void *data, sess_data_free_fn_t free_fn);
/**
* Find a session by it's ID (from a cookie)
*
* @param cookie - session ID string
* @return session data (void*), or NULL
*/
void *session_find(const char *cookie);
/**
* Loop through all sessions and drop these that expired.
*/
void session_drop_expired(void);
/**
* Drop a session by its ID. Does nothing if not found.
*
* @param cookie - session ID string
*/
void session_drop(const char *cookie);
/**
* Parse the Cookie header from a request, and do something with the corresponding session.
*
* To also delete the cookie, use req_delete_session_cookie(r)
*
* @param r - request
* @param action - what to do with the session
* @return session data, NULL if removed or not found
*/
void *httpd_req_find_session_and(httpd_req_t *r, enum session_find_action action);
/**
* Add a header that deletes the session cookie
*
* @param r - request
*/
void httpd_resp_delete_session_cookie(httpd_req_t *r);
/**
* Add a header that sets the session cookie.
*
* This must be called after creating a session (e.g. user logged in) to make it persistent.
*
* @attention NOT RE-ENTRANT, CAN'T BE USED AGAIN UNTIL THE REQUEST IT WAS CALLED FOR IS DISPATCHED.
*
* @param r - request
* @param cookie - cookie ID
*/
void httpd_resp_set_session_cookie(httpd_req_t *r, const char *cookie);
#endif //SESSION_STORE_H
+106
View File
@@ -0,0 +1,106 @@
#include <esp_wifi_types.h>
#include <esp_wifi.h>
#include <esp_log.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/param.h>
#include "httpd_utils/captive.h"
#include "httpd_utils/fd_to_ipv4.h"
#include "httpd_utils/redirect.h"
#include <common_utils/utils.h>
static const char *TAG="captive";
const char * __attribute__((weak))
httpd_captive_redirect_get_domain(void)
{
return "fb_node.captive";
}
esp_err_t __attribute__((weak))
httpd_captive_redirect_get_url(httpd_req_t *r, char *buf, size_t maxlen)
{
buf = append(buf, "http://", &maxlen);
buf = append(buf, httpd_captive_redirect_get_domain(), &maxlen);
append(buf, "/", &maxlen);
return ESP_OK;
}
esp_err_t httpd_captive_redirect(httpd_req_t *r)
{
// must be static to survive being used in the redirect header
static char s_buf[64];
wifi_mode_t mode = 0;
esp_wifi_get_mode(&mode);
// Check if we have an softap interface. No point checking IPs and hostnames if the client can't be on AP.
if (mode == WIFI_MODE_STA || mode == WIFI_MODE_NULL) {
goto no_redirect;
}
int fd = httpd_req_to_sockfd(r);
tcpip_adapter_ip_info_t apip;
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP, &apip);
u32_t client_addr;
if(ESP_OK != fd_to_ipv4(fd, &client_addr)) {
return ESP_FAIL;
}
ESP_LOGD(TAG, "[captive] Client addr = 0x%08x, ap addr 0x%08x, ap nmask 0x%08x",
client_addr,
apip.ip.addr,
apip.netmask.addr
);
// Check if client IP looks like from our AP dhcps
if ((client_addr & apip.netmask.addr) != (apip.ip.addr & apip.netmask.addr)) {
ESP_LOGD(TAG, "[captive] Client not in AP IP range");
goto no_redirect;
}
// Get requested hostname from the header
esp_err_t rv = httpd_req_get_hdr_value_str(r, "Host", s_buf, 64);
if (rv != ESP_OK) {
ESP_LOGW(TAG, "[captive] No host in request?");
goto no_redirect;
}
ESP_LOGD(TAG, "[captive] Candidate for redirect: %s%s", s_buf, r->uri);
// Never redirect if host is an IP
if (strlen(s_buf)>7) {
bool isIP = 1;
for (int x = 0; x < strlen(s_buf); x++) {
if (s_buf[x] != '.' && (s_buf[x] < '0' || s_buf[x] > '9')) {
isIP = 0;
break;
}
}
if (isIP) {
ESP_LOGD(TAG, "[captive] Access via IP, no redirect needed");
goto no_redirect;
}
}
// Redirect if host differs
// - this can be e.g. connectivitycheck.gstatic.com or the equivalent for ios
if (0 != strcmp(s_buf, httpd_captive_redirect_get_domain())) {
ESP_LOGD(TAG, "[captive] Host differs, redirecting...");
httpd_captive_redirect_get_url(r, s_buf, 64);
return httpd_redirect_to(r, s_buf);
} else {
ESP_LOGD(TAG, "[captive] Host is OK");
goto no_redirect;
}
no_redirect:
return ESP_ERR_NOT_FOUND;
}
+42
View File
@@ -0,0 +1,42 @@
#include <esp_wifi_types.h>
#include <esp_wifi.h>
#include <esp_log.h>
#include <fcntl.h>
#include <sys/socket.h>
#include "httpd_utils/fd_to_ipv4.h"
static const char *TAG = "fd2ipv4";
/**
* Get IP address for a FD
*
* @param fd
* @param[out] ipv4
* @return success
*/
esp_err_t fd_to_ipv4(int fd, in_addr_t *ipv4)
{
struct sockaddr_in6 addr;
size_t len = sizeof(addr);
int rv = getpeername(fd, (struct sockaddr *) &addr, &len);
if (rv != 0) {
ESP_LOGE(TAG, "Failed to get IP addr for fd %d", fd);
return ESP_FAIL;
}
uint32_t client_addr = 0;
if (addr.sin6_family == AF_INET6) {
// this would fail in a real ipv6 network
// with ipv4 the addr is simply in the last ipv6 byte
struct sockaddr_in6 *s = &addr;
client_addr = s->sin6_addr.un.u32_addr[3];
}
else {
struct sockaddr_in *s = (struct sockaddr_in *) &addr;
client_addr = s->sin_addr.s_addr;
}
*ipv4 = client_addr;
return ESP_OK;
}
+20
View File
@@ -0,0 +1,20 @@
#include <esp_wifi_types.h>
#include <esp_wifi.h>
#include <esp_log.h>
#include <fcntl.h>
#include <sys/socket.h>
#include "httpd_utils/redirect.h"
static const char *TAG="redirect";
esp_err_t httpd_redirect_to(httpd_req_t *r, const char *uri)
{
ESP_LOGD(TAG, "Redirect to %s", uri);
httpd_resp_set_hdr(r, "Location", uri);
httpd_resp_set_status(r, "303 See Other");
httpd_resp_set_type(r, HTTPD_TYPE_TEXT);
const char *msg = "Redirect";
return httpd_resp_send(r, msg, -1);
}
+181
View File
@@ -0,0 +1,181 @@
//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include <rom/queue.h>
#include <malloc.h>
#include <assert.h>
#include <esp_log.h>
#include <esp_err.h>
#include <string.h>
#include "httpd_utils/session_kvmap.h"
static const char *TAG = "sess_kvmap";
// this struct is opaque, a stub like this is sufficient for the head pointer.
struct sess_kv_entry;
/** Session head structure, dynamically allocated */
SLIST_HEAD(sess_kv_map, sess_kv_entry);
struct sess_kv_entry {
SLIST_ENTRY(sess_kv_entry) link;
char key[SESS_KVMAP_KEY_LEN];
void *value;
sess_kv_free_func_t free_fn;
};
struct sess_kv_map *sess_kv_map_alloc(void)
{
ESP_LOGD(TAG, "kv store alloc");
struct sess_kv_map *map = malloc(sizeof(struct sess_kv_map));
assert(map);
SLIST_INIT(map);
return map;
}
void sess_kv_map_free(void *head_v)
{
struct sess_kv_map* head = head_v;
ESP_LOGD(TAG, "kv store free");
assert(head);
struct sess_kv_entry *item, *tmp;
SLIST_FOREACH_SAFE(item, head, link, tmp) {
if (item->free_fn) {
item->free_fn(item->value);
free(item);
}
}
free(head);
}
void * sess_kv_map_get(struct sess_kv_map *head, const char *key)
{
assert(head);
assert(key);
ESP_LOGD(TAG, "kv store get %s", key);
struct sess_kv_entry *item;
SLIST_FOREACH(item, head, link) {
if (0==strcmp(item->key, key)) {
ESP_LOGD(TAG, "got ok");
return item->value;
}
}
ESP_LOGD(TAG, "not found in store");
return NULL;
}
void * sess_kv_map_take(struct sess_kv_map *head, const char *key)
{
assert(head);
assert(key);
ESP_LOGD(TAG, "kv store take %s", key);
struct sess_kv_entry *item;
SLIST_FOREACH(item, head, link) {
if (0==strcmp(item->key, key)) {
item->key[0] = 0;
item->free_fn = NULL;
ESP_LOGD(TAG, "taken ok");
return item->value;
}
}
ESP_LOGD(TAG, "not found in store");
return NULL;
}
esp_err_t sess_kv_map_remove(struct sess_kv_map *head, const char *key)
{
assert(head);
assert(key);
ESP_LOGD(TAG, "kv store remove %s", key);
struct sess_kv_entry *item;
SLIST_FOREACH(item, head, link) {
if (0==strcmp(item->key, key)) {
if (item->free_fn) {
item->free_fn(item->value);
}
item->key[0] = 0;
item->value = NULL;
item->free_fn = NULL;
return ESP_OK;
}
}
ESP_LOGD(TAG, "couldn't remove, not found: %s", key);
return ESP_ERR_NOT_FOUND;
}
esp_err_t sess_kv_map_set(struct sess_kv_map *head, const char *key, void *value, sess_kv_free_func_t free_fn)
{
assert(head);
assert(key);
ESP_LOGD(TAG, "kv set value for key %s", key);
size_t key_len = strlen(key);
if (key_len > SESS_KVMAP_KEY_LEN-1) {
ESP_LOGE(TAG, "Key too long: %s", key);
// discard illegal key
return ESP_FAIL;
}
if (key_len == 0) {
ESP_LOGE(TAG, "Key too short: \"%s\"", key);
// discard illegal key
return ESP_FAIL;
}
struct sess_kv_entry *item = NULL;
struct sess_kv_entry *empty_item = NULL; // found item with no content
SLIST_FOREACH(item, head, link) {
ESP_LOGD(TAG, "test item with key %s, ptr %p > %p", item->key, item, item->link.sle_next);
if (0 == item->key[0]) {
ESP_LOGD(TAG, "found an empty slot");
empty_item = item;
}
else if (0==strcmp(item->key, key)) {
ESP_LOGD(TAG, "old value replaced");
if (item->free_fn) {
item->free_fn(item->value);
}
item->value = value;
item->free_fn = free_fn;
return ESP_OK;
} else {
ESP_LOGD(TAG, "skip this one");
}
}
struct sess_kv_entry *new_item = NULL;
// insert new or reuse an empty item
if (empty_item) {
new_item = empty_item;
ESP_LOGD(TAG, "empty item reused (%p)", new_item);
} else {
ESP_LOGD(TAG, "alloc new item");
// key not found, add a new entry.
new_item = malloc(sizeof(struct sess_kv_entry));
if (!new_item) {
ESP_LOGE(TAG, "New entry alloc failed");
return ESP_ERR_NO_MEM;
}
}
strcpy(new_item->key, key);
new_item->free_fn = free_fn;
new_item->value = value;
if (!empty_item) {
ESP_LOGD(TAG, "insert new item into list");
// this item was malloc'd
SLIST_INSERT_HEAD(head, new_item, link);
}
return ESP_OK;
}
+220
View File
@@ -0,0 +1,220 @@
//#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include <malloc.h>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <esp_http_server.h>
#include <rom/queue.h>
#include "httpd_utils/session_store.h"
// TODO add a limit on simultaneously open sessions (can cause memory exhaustion DoS)
#define COOKIE_LEN 32
static const char *TAG = "session";
struct session {
char cookie[COOKIE_LEN + 1];
void *data;
TickType_t last_activity_time;
LIST_ENTRY(session) link;
sess_data_free_fn_t free_fn;
};
static LIST_HEAD(sessions_, session) s_store;
static SemaphoreHandle_t sess_store_lock = NULL;
static bool sess_store_inited = false;
void session_store_init(void)
{
if (sess_store_inited) {
xSemaphoreTake(sess_store_lock, portMAX_DELAY);
{
struct session *it, *tit;
LIST_FOREACH_SAFE(it, &s_store, link, tit) {
ESP_LOGW(TAG, "Session cookie expired: \"%s\"", it->cookie);
if (it->free_fn) it->free_fn(it->data);
// no relink, we dont care if the list breaks after this - we're removing all of it
free(it);
}
}
LIST_INIT(&s_store);
xSemaphoreGive(sess_store_lock);
} else {
LIST_INIT(&s_store);
sess_store_lock = xSemaphoreCreateMutex();
sess_store_inited = true;
}
}
/**
* Fill buffer with base64 symbols. Does not add a trailing null byte
*
* @param buf
* @param len
*/
static void esp_fill_random_alnum(char *buf, size_t len)
{
#define alphabet_len 64
static const char alphabet[alphabet_len] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/";
unsigned int seed = xTaskGetTickCount();
assert(buf != NULL);
for (int i = 0; i < len; i++) {
int index = rand_r(&seed) % alphabet_len;
*buf++ = (uint8_t) alphabet[index];
}
}
const char *session_new(void *data, sess_data_free_fn_t free_fn)
{
assert(data != NULL);
struct session *item = malloc(sizeof(struct session));
if (item == NULL) return NULL;
item->data = data;
item->free_fn = free_fn;
esp_fill_random_alnum(item->cookie, COOKIE_LEN);
item->cookie[COOKIE_LEN] = 0; // add the terminator
xSemaphoreTake(sess_store_lock, portMAX_DELAY);
{
item->last_activity_time = xTaskGetTickCount();
LIST_INSERT_HEAD(&s_store, item, link);
}
xSemaphoreGive(sess_store_lock);
ESP_LOGD(TAG, "New HTTP session: %s", item->cookie);
return item->cookie;
}
void *session_find_and(const char *cookie, enum session_find_action action)
{
// no point in searching if the length is wrong
if (strlen(cookie) != COOKIE_LEN) {
ESP_LOGW(TAG, "Wrong session cookie length: \"%s\"", cookie);
return NULL;
}
struct session *it = NULL;
bool found = false;
xSemaphoreTake(sess_store_lock, portMAX_DELAY);
{
LIST_FOREACH(it, &s_store, link) {
if (0==strcmp(it->cookie, cookie)) {
ESP_LOGD(TAG, "Session cookie matched: \"%s\"", cookie);
it->last_activity_time = xTaskGetTickCount();
found = true;
break;
}
}
if (found && action == SESS_DROP) {
if (it->free_fn) it->free_fn(it->data);
LIST_REMOVE(it, link);
free(it);
ESP_LOGD(TAG, "Dropped session: \"%s\"", cookie);
}
}
xSemaphoreGive(sess_store_lock);
if (found) {
if (action == SESS_DROP) {
// it was dropped inside the guarded block
// the return value is not used with DROP
return NULL;
}
else if(action == SESS_GET_DATA) {
return it->data;
}
}
ESP_LOGW(TAG, "Session cookie not found: \"%s\"", cookie);
return NULL;
}
void *session_find(const char *cookie)
{
return session_find_and(cookie, SESS_GET_DATA);
}
void session_drop(const char *cookie)
{
session_find_and(cookie, SESS_DROP);
}
void session_drop_expired(void)
{
struct session *it;
struct session *tit;
xSemaphoreTake(sess_store_lock, portMAX_DELAY);
{
TickType_t now = xTaskGetTickCount();
LIST_FOREACH_SAFE(it, &s_store, link, tit) {
TickType_t elapsed = now - it->last_activity_time;
if (elapsed > pdMS_TO_TICKS(SESSION_EXPIRY_TIME_S*1000)) {
ESP_LOGD(TAG, "Session cookie expired: \"%s\"", it->cookie);
if (it->free_fn) it->free_fn(it->data);
LIST_REMOVE(it, link);
free(it);
}
}
}
xSemaphoreGive(sess_store_lock);
}
void *httpd_req_find_session_and(httpd_req_t *r, enum session_find_action action)
{
// this could be called periodically, but it's sufficient to run it at each request
// it won't slow anything down unless there are hundreds of sessions
session_drop_expired();
static char buf[256];
esp_err_t rv = httpd_req_get_hdr_value_str(r, "Cookie", buf, 256);
if (rv == ESP_OK || rv == ESP_ERR_HTTPD_RESULT_TRUNC) {
ESP_LOGD(TAG, "Cookie header: %s", buf);
// probably OK, see if we have a cookie
char *start = strstr(buf, SESSION_COOKIE_NAME"=");
if (start != 0) {
start += strlen(SESSION_COOKIE_NAME"=");
char *end = strchr(start, ';');
if (end != NULL) *end = 0;
ESP_LOGD(TAG, "Cookie is: %s", start);
return session_find_and(start, action);
}
} else {
ESP_LOGD(TAG, "No cookie.");
}
return NULL;
}
void httpd_resp_delete_session_cookie(httpd_req_t *r)
{
httpd_resp_set_hdr(r, "Set-Cookie", SESSION_COOKIE_NAME"=");
}
// Static because the value is passed and stored by reference, so it wouldn't live long enough if it was on stack,
// and there also isn't any hook for freeing it if we used malloc(). This is an SDK bug.
static char cookie_hdr_buf[COOKIE_LEN + 10];
// !!! this must not be called concurrently from a different thread.
// That is no problem so long as the server stays single-threaded
void httpd_resp_set_session_cookie(httpd_req_t *r, const char *cookie)
{
snprintf(cookie_hdr_buf, COOKIE_LEN + 10, "SESSID=%s", cookie);
httpd_resp_set_hdr(r, "Set-Cookie", cookie_hdr_buf);
}
@@ -0,0 +1,41 @@
/**
* TODO file description
*
* Created on 2019/07/13.
*/
#ifndef SESSION_UTILS_C_H
#define SESSION_UTILS_C_H
#include <esp_err.h>
#include <esp_http_server.h>
#include "httpd_utils/session_kvmap.h"
#include "httpd_utils/session_store.h"
/**
* Start or resume a session.
*/
esp_err_t HTTP_GET_SESSION(sess_kv_map_t **ppkvstore, httpd_req_t *r)
{
sess_kv_map_t *kvstore;
kvstore = httpd_req_find_session_and((r), SESS_GET_DATA);
if (NULL == kvstore) {
kvstore = sess_kv_map_alloc();
if (!kvstore) return ESP_ERR_NO_MEM;
const char *cookie = session_new(kvstore, sess_kv_map_free);
if (cookie == NULL) {
// session alloc failed
sess_kv_map_free(kvstore);
*ppkvstore = NULL;
return ESP_ERR_NO_MEM;
}
httpd_resp_set_session_cookie(r, cookie);
}
*ppkvstore = kvstore;
return ESP_OK;
}
#endif //SESSION_UTILS_C_H