188 lines
5.0 KiB
C
188 lines
5.0 KiB
C
#include "rest_server_internal.h"
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "esp_heap_caps.h"
|
|
#include "mbedtls/base64.h"
|
|
#include "domain.h"
|
|
|
|
#define REST_SERVER_MAX_BODY_BYTES (4 * 1024 * 1024)
|
|
|
|
static void *alloc_prefer_psram(size_t size) {
|
|
void *ptr = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
|
if (ptr == NULL) {
|
|
ptr = malloc(size);
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
static void *calloc_prefer_psram(size_t n, size_t size) {
|
|
void *ptr = heap_caps_calloc(n, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
|
if (ptr == NULL) {
|
|
ptr = calloc(n, size);
|
|
}
|
|
return ptr;
|
|
}
|
|
bool rest_server_auth_ok(httpd_req_t *req) {
|
|
if (strlen(CONFIG_TQ_API_KEY) == 0) {
|
|
return true;
|
|
}
|
|
|
|
char buf[96] = {0};
|
|
if (httpd_req_get_hdr_value_str(req, "X-API-Key", buf, sizeof(buf)) != ESP_OK) {
|
|
return false;
|
|
}
|
|
|
|
return strcmp(buf, CONFIG_TQ_API_KEY) == 0;
|
|
}
|
|
|
|
esp_err_t rest_server_send_json(httpd_req_t *req, const char *status, cJSON *root) {
|
|
char *text = cJSON_PrintUnformatted(root);
|
|
if (text == NULL) {
|
|
return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "json encode failed");
|
|
}
|
|
|
|
httpd_resp_set_type(req, "application/json");
|
|
httpd_resp_set_status(req, status);
|
|
esp_err_t err = httpd_resp_sendstr(req, text);
|
|
cJSON_free(text);
|
|
domain_diag_counter_add(DOMAIN_DIAG_COUNTER_REST_RESPONSES_TOTAL, 1);
|
|
return err;
|
|
}
|
|
|
|
esp_err_t rest_server_send_error(httpd_req_t *req, const char *status, const char *message) {
|
|
cJSON *root = cJSON_CreateObject();
|
|
cJSON_AddBoolToObject(root, "ok", false);
|
|
cJSON_AddStringToObject(root, "error", message != NULL ? message : "unknown");
|
|
esp_err_t err = rest_server_send_json(req, status, root);
|
|
cJSON_Delete(root);
|
|
domain_diag_counter_add(DOMAIN_DIAG_COUNTER_REST_ERRORS_TOTAL, 1);
|
|
domain_diag_record_error("rest_api", ESP_FAIL, message != NULL ? message : "unknown");
|
|
return err;
|
|
}
|
|
|
|
esp_err_t rest_server_read_body(httpd_req_t *req, char **out_body) {
|
|
if (out_body == NULL) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
*out_body = NULL;
|
|
|
|
if (req->content_len <= 0 || req->content_len > REST_SERVER_MAX_BODY_BYTES) {
|
|
return ESP_ERR_INVALID_SIZE;
|
|
}
|
|
|
|
char *buf = (char *)calloc_prefer_psram(1, (size_t)req->content_len + 1);
|
|
if (buf == NULL) {
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
int received = 0;
|
|
while (received < req->content_len) {
|
|
int ret = httpd_req_recv(req, buf + received, req->content_len - received);
|
|
if (ret <= 0) {
|
|
free(buf);
|
|
return ESP_FAIL;
|
|
}
|
|
received += ret;
|
|
}
|
|
|
|
*out_body = buf;
|
|
return ESP_OK;
|
|
}
|
|
|
|
bool rest_server_parse_uri_u32_tail(const char *uri, uint32_t *out_value) {
|
|
if (uri == NULL || out_value == NULL) {
|
|
return false;
|
|
}
|
|
|
|
const char *slash = strrchr(uri, '/');
|
|
if (slash == NULL || *(slash + 1) == '\0') {
|
|
return false;
|
|
}
|
|
|
|
errno = 0;
|
|
char *endptr = NULL;
|
|
unsigned long val = strtoul(slash + 1, &endptr, 10);
|
|
if (errno != 0 || endptr == slash + 1 || *endptr != '\0' || val > UINT32_MAX) {
|
|
return false;
|
|
}
|
|
|
|
*out_value = (uint32_t)val;
|
|
return true;
|
|
}
|
|
|
|
esp_err_t rest_server_base64_decode_alloc(const char *b64, uint8_t **out_raw, size_t *out_len) {
|
|
if (b64 == NULL || out_raw == NULL || out_len == NULL) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
*out_raw = NULL;
|
|
*out_len = 0;
|
|
|
|
size_t decoded_len = 0;
|
|
int rc = mbedtls_base64_decode(NULL,
|
|
0,
|
|
&decoded_len,
|
|
(const unsigned char *)b64,
|
|
strlen(b64));
|
|
if (!(rc == 0 || rc == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL)) {
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
uint8_t *buf = (uint8_t *)alloc_prefer_psram(decoded_len);
|
|
if (buf == NULL) {
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
rc = mbedtls_base64_decode(buf,
|
|
decoded_len,
|
|
&decoded_len,
|
|
(const unsigned char *)b64,
|
|
strlen(b64));
|
|
if (rc != 0) {
|
|
free(buf);
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
*out_raw = buf;
|
|
*out_len = decoded_len;
|
|
return ESP_OK;
|
|
}
|
|
|
|
void rest_server_appendf(char *dst, size_t cap, size_t *offset, const char *fmt, ...) {
|
|
if (dst == NULL || cap == 0 || offset == NULL || fmt == NULL || *offset >= cap) {
|
|
return;
|
|
}
|
|
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
int n = vsnprintf(dst + *offset, cap - *offset, fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (n <= 0) {
|
|
return;
|
|
}
|
|
|
|
size_t written = (size_t)n;
|
|
if (written >= cap - *offset) {
|
|
*offset = cap - 1;
|
|
return;
|
|
}
|
|
*offset += written;
|
|
}
|
|
|
|
bool rest_server_json_bool_with_default(cJSON *item, bool default_value) {
|
|
if (item == NULL) {
|
|
return default_value;
|
|
}
|
|
if (cJSON_IsBool(item)) {
|
|
return cJSON_IsTrue(item);
|
|
}
|
|
return default_value;
|
|
}
|