forked from electro/esp-irblaster
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
220 lines
6.2 KiB
220 lines
6.2 KiB
//#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 <sys/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 = calloc(sizeof(struct session), 1);
|
|
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);
|
|
}
|
|
|