add webserver components, some webserver fices and improvements in templating, add real time history chart
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user