12#include "esp_tls_crypto.h"
13#include <freertos/FreeRTOS.h>
14#include <freertos/task.h>
19#ifdef USE_WEBSERVER_AUTH_DIGEST
20#include <esp_random.h>
21#include <esp_rom_md5.h>
24#ifdef USE_WEBSERVER_OTA
25#include <multipart_parser.h>
36#include <sys/socket.h>
42#define HTTPD_401 "401 Unauthorized"
45#define HTTPD_409 "409 Conflict"
48#define HTTPD_422 "422 Unprocessable Entity"
51#define CRLF_STR "\r\n"
52#define CRLF_LEN (sizeof(CRLF_STR) - 1)
54static const char *
const TAG =
"web_server_idf";
58static constexpr size_t RECV_CHUNK_SIZE = 1460;
59static constexpr size_t YIELD_INTERVAL_BYTES = 16 * 1024;
65DefaultHeaders default_headers_instance;
86[[maybe_unused]]
int nonblocking_send(httpd_handle_t hd,
int sockfd,
const char *buf,
size_t buf_len,
int flags) {
88 return HTTPD_SOCK_ERR_INVALID;
92 int ret = send(sockfd, buf, buf_len,
flags | MSG_DONTWAIT);
94 const int err = errno;
95 if (err == EAGAIN || err == EWOULDBLOCK) {
97 return HTTPD_SOCK_ERR_TIMEOUT;
100 ESP_LOGD(TAG,
"send error: errno %d", err);
101 return HTTPD_SOCK_ERR_FAIL;
124 shutdown(sockfd, SHUT_RD);
143 httpd_config_t config = HTTPD_DEFAULT_CONFIG();
144 config.stack_size = config.stack_size + 256;
145 config.server_port = this->
port_;
146 config.uri_match_fn = [](
const char * ,
const char * ,
size_t ) {
return true; };
151 config.lru_purge_enable =
true;
154 if (httpd_start(&this->
server_, &config) == ESP_OK) {
155 const httpd_uri_t handler_get = {
161 httpd_register_uri_handler(this->
server_, &handler_get);
163 const httpd_uri_t handler_post = {
169 httpd_register_uri_handler(this->
server_, &handler_post);
171 const httpd_uri_t handler_options = {
173 .method = HTTP_OPTIONS,
177 httpd_register_uri_handler(this->
server_, &handler_options);
182 ESP_LOGVV(TAG,
"Enter AsyncWebServer::request_post_handler. uri=%s", r->uri);
186 ESP_LOGW(TAG,
"Content length is required for post: %s", r->uri);
187 httpd_resp_send_err(r, HTTPD_411_LENGTH_REQUIRED,
nullptr);
191 if (content_type.has_value()) {
192 const char *content_type_char = content_type.value().c_str();
195 size_t content_type_len = strlen(content_type_char);
196 if (
strcasestr_n(content_type_char, content_type_len,
"application/x-www-form-urlencoded") !=
nullptr) {
198#ifdef USE_WEBSERVER_OTA
199 }
else if (
strcasestr_n(content_type_char, content_type_len,
"multipart/form-data") !=
nullptr) {
212 if (r->content_len > CONFIG_HTTPD_MAX_REQ_HDR_LEN) {
213 ESP_LOGW(TAG,
"Request size is to big: %zu", r->content_len);
214 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST,
nullptr);
218 std::string post_query;
219 if (r->content_len > 0) {
220 post_query.resize(r->content_len);
221 const int ret = httpd_req_recv(r, &post_query[0], r->content_len + 1);
223 if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
224 httpd_resp_send_err(r, HTTPD_408_REQ_TIMEOUT,
nullptr);
225 return ESP_ERR_TIMEOUT;
227 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST,
nullptr);
237 ESP_LOGVV(TAG,
"Enter AsyncWebServer::request_handler. method=%u, uri=%s", r->method, r->uri);
244 if (handler->canHandle(request)) {
247 handler->handleRequest(request);
255 return ESP_ERR_NOT_FOUND;
262 if (
h->canHandle(&req)) {
268 if (handler ==
nullptr) {
269 ESP_LOGW(TAG,
"Unsupported content type for POST: %s", content_type);
274 const size_t total = r->content_len;
276 auto buffer = std::make_unique_for_overwrite<char[]>(RECV_CHUNK_SIZE);
277 size_t bytes_since_yield = 0;
279 for (
size_t index = 0; index < total;) {
280 int recv_len = httpd_req_recv(r, buffer.get(), std::min(total - index, RECV_CHUNK_SIZE));
283 httpd_resp_send_err(r, recv_len == HTTPD_SOCK_ERR_TIMEOUT ? HTTPD_408_REQ_TIMEOUT : HTTPD_400_BAD_REQUEST,
285 return recv_len == HTTPD_SOCK_ERR_TIMEOUT ? ESP_ERR_TIMEOUT : ESP_FAIL;
288 handler->
handleBody(&req,
reinterpret_cast<uint8_t *
>(buffer.get()), recv_len, index, total);
290 bytes_since_yield += recv_len;
292 if (bytes_since_yield > YIELD_INTERVAL_BYTES) {
294 bytes_since_yield = 0;
305 for (
auto *param : this->
params_) {
317 const char *uri = this->
req_->uri;
318 const char *query_start = strchr(uri,
'?');
319 size_t uri_len = query_start ?
static_cast<size_t>(query_start - uri) : strlen(uri);
321 memcpy(buffer.data(), uri, copy_len);
322 buffer[copy_len] =
'\0';
324 size_t decoded_len =
url_decode(buffer.data());
325 return StringRef(buffer.data(), decoded_len);
329 httpd_resp_set_status(*
this,
"302 Found");
330 httpd_resp_set_hdr(*
this,
"Location", url.c_str());
331 httpd_resp_set_hdr(*
this,
"Connection",
"close");
332 httpd_resp_send(*
this,
nullptr, 0);
364 httpd_resp_set_status(*
this,
status);
366 if (content_type && *content_type) {
367 httpd_resp_set_type(*
this, content_type);
369 httpd_resp_set_hdr(*
this,
"Accept-Ranges",
"none");
372 httpd_resp_set_hdr(*
this, header.name, header.value);
379#ifdef USE_WEBSERVER_AUTH
381#ifdef USE_WEBSERVER_AUTH_DIGEST
388 size_t key_len = strlen(key);
389 const char *base = params.
c_str();
390 size_t n = params.
size();
393 while (i < n && (base[i] ==
' ' || base[i] ==
','))
395 size_t name_start = i;
396 while (i < n && base[i] !=
'=' && base[i] !=
',')
398 if (i >= n || base[i] ==
',')
400 size_t name_len = i - name_start;
401 while (name_len > 0 && base[name_start + name_len - 1] ==
' ')
404 const char *val_start;
406 if (i < n && base[i] ==
'"') {
408 val_start = base + i;
409 while (i < n && base[i] !=
'"')
411 val_len = (base + i) - val_start;
415 val_start = base + i;
416 while (i < n && base[i] !=
',')
418 val_len = (base + i) - val_start;
420 if (name_len == key_len && memcmp(base + name_start, key, key_len) == 0)
421 return StringRef(val_start, val_len);
422 while (i < n && base[i] !=
',')
430bool check_digest_auth(
const char *username,
const char *password,
const std::string &header,
const char *method) {
431 const size_t prefix_len =
sizeof(
"Digest ") - 1;
432 StringRef params(header.c_str() + prefix_len, header.size() - prefix_len);
434 if (digest_param(params,
"username") != username)
437 StringRef realm = digest_param(params,
"realm");
438 StringRef nonce = digest_param(params,
"nonce");
439 StringRef uri = digest_param(params,
"uri");
440 StringRef qop = digest_param(params,
"qop");
441 StringRef nc = digest_param(params,
"nc");
442 StringRef cnonce = digest_param(params,
"cnonce");
443 StringRef response = digest_param(params,
"response");
444 if (response.size() != 32)
454 esp_rom_md5_init(&ctx);
455 esp_rom_md5_update(&ctx, username, strlen(username));
456 esp_rom_md5_update(&ctx,
":", 1);
457 esp_rom_md5_update(&ctx, realm.c_str(), realm.size());
458 esp_rom_md5_update(&ctx,
":", 1);
459 esp_rom_md5_update(&ctx, password, strlen(password));
460 esp_rom_md5_final(digest, &ctx);
465 esp_rom_md5_init(&ctx);
466 esp_rom_md5_update(&ctx, method, strlen(method));
467 esp_rom_md5_update(&ctx,
":", 1);
468 esp_rom_md5_update(&ctx, uri.c_str(), uri.size());
469 esp_rom_md5_final(digest, &ctx);
474 esp_rom_md5_init(&ctx);
475 esp_rom_md5_update(&ctx, ha1, 32);
476 esp_rom_md5_update(&ctx,
":", 1);
477 esp_rom_md5_update(&ctx, nonce.c_str(), nonce.size());
478 esp_rom_md5_update(&ctx,
":", 1);
479 esp_rom_md5_update(&ctx, nc.c_str(), nc.size());
480 esp_rom_md5_update(&ctx,
":", 1);
481 esp_rom_md5_update(&ctx, cnonce.c_str(), cnonce.size());
482 esp_rom_md5_update(&ctx,
":", 1);
483 esp_rom_md5_update(&ctx, qop.c_str(), qop.size());
484 esp_rom_md5_update(&ctx,
":", 1);
485 esp_rom_md5_update(&ctx, ha2, 32);
486 esp_rom_md5_final(digest, &ctx);
491 for (
size_t i = 0; i < 32; i++)
492 result |=
static_cast<uint8_t
>(expected[i] ^ response[i]);
499bool AsyncWebServerRequest::authenticate(
const char *username,
const char *password)
const {
500 if (username ==
nullptr || password ==
nullptr || *username == 0) {
503 auto auth = this->
get_header(
"Authorization");
504 if (!auth.has_value()) {
508 auto *auth_str = auth.value().c_str();
510#ifdef USE_WEBSERVER_AUTH_DIGEST
512 const auto auth_prefix_len =
sizeof(
"Digest ") - 1;
513 if (strncmp(
"Digest ", auth_str, auth_prefix_len) != 0) {
514 ESP_LOGW(TAG,
"Only Digest authorization supported");
517 return check_digest_auth(username, password, auth.value(), http_method_str(this->
method()));
519 const auto auth_prefix_len =
sizeof(
"Basic ") - 1;
520 if (strncmp(
"Basic ", auth_str, auth_prefix_len) != 0) {
521 ESP_LOGW(TAG,
"Only Basic authorization supported");
526 constexpr size_t max_user_info_len = 256;
527 char user_info[max_user_info_len];
528 size_t user_len = strlen(username);
529 size_t pass_len = strlen(password);
530 size_t user_info_len = user_len + 1 + pass_len;
532 if (user_info_len >= max_user_info_len) {
533 ESP_LOGW(TAG,
"Credentials too long for authentication");
537 memcpy(user_info, username, user_len);
538 user_info[user_len] =
':';
539 memcpy(user_info + user_len + 1, password, pass_len);
540 user_info[user_info_len] =
'\0';
544 constexpr size_t max_digest_len = 350;
545 char digest[max_digest_len];
547 esp_crypto_base64_encode(
reinterpret_cast<uint8_t *
>(digest), max_digest_len, &out,
548 reinterpret_cast<const uint8_t *
>(user_info), user_info_len);
553 const char *provided = auth_str + auth_prefix_len;
554 size_t digest_len = out;
557 size_t provided_len = auth.value().size() - auth_prefix_len;
561 volatile size_t result = digest_len ^ provided_len;
565 for (
size_t i = 0; i < digest_len; i++) {
566 char provided_ch = (i < provided_len) ? provided[i] : 0;
567 result |=
static_cast<uint8_t
>(digest[i] ^ provided_ch);
574 httpd_resp_set_hdr(*
this,
"Connection",
"keep-alive");
575#ifdef USE_WEBSERVER_AUTH_DIGEST
588 snprintf(header,
sizeof(header), R
"(Digest realm="Login Required", qop="auth", nonce="%s", opaque="%s")", nonce,
590 httpd_resp_set_hdr(*this,
"WWW-Authenticate", header);
592 httpd_resp_set_hdr(*
this,
"WWW-Authenticate",
"Basic realm=\"Login Required\"");
594 httpd_resp_send_err(*
this, HTTPD_401_UNAUTHORIZED,
nullptr);
600 for (
auto *param : this->
params_) {
601 if (param->name() == name) {
611 if (!
val.has_value()) {
616 this->params_.push_back(param);
623template<
typename Func>
624static auto search_query_sources(httpd_req_t *req,
const std::string &post_query,
const char *name, Func func)
625 ->
decltype(func(
nullptr,
size_t{0}, name)) {
626 if (!post_query.empty()) {
627 auto result = func(post_query.c_str(), post_query.size(), name);
635 auto len = httpd_req_get_url_query_len(req);
639 const char *query = strchr(req->uri,
'?');
640 if (query ==
nullptr) {
644 return func(query,
len, name);
658 if (
val.has_value()) {
659 return std::move(
val.value());
665 httpd_resp_set_hdr(*this->
req_, name, value);
672 int len = snprintf(buf,
sizeof(buf),
"%f", value);
697 for (
auto *ses : *vec) {
722 this->adopt_pending_sessions_main_loop_();
728 for (
size_t i = 0; i < this->
sessions_.size();) {
731 if (ses->fd_.load() == 0) {
745void AsyncEventSource::adopt_pending_sessions_main_loop_() {
746 std::vector<AsyncEventSourceResponse *> incoming;
752 for (
auto *rsp : incoming) {
754 if (rsp->fd_.load() == 0) {
761 rsp->start_session_main_loop_();
772 if (ses->fd_.load() != 0) {
773 ses->try_send_nodefer(
message, message_len, event,
id, reconnect);
784 if (ses->fd_.load() != 0) {
785 ses->deferrable_send_state(source, event_type, message_generator);
793 : server_(server), web_server_(ws), entities_iterator_(ws, server) {
795 httpd_req_t *req = *request;
797 httpd_resp_set_status(req, HTTPD_200);
798 httpd_resp_set_type(req,
"text/event-stream");
799 httpd_resp_set_hdr(req,
"Cache-Control",
"no-cache");
800 httpd_resp_set_hdr(req,
"Connection",
"keep-alive");
803 httpd_resp_set_hdr(req, header.name, header.value);
806 httpd_resp_send_chunk(req, CRLF_STR, CRLF_LEN);
808 req->sess_ctx =
this;
811 this->
hd_ = req->handle;
812 this->
fd_.store(httpd_req_to_sockfd(req));
815 httpd_sess_set_send_override(this->
hd_, this->
fd_.load(), nonblocking_send);
826#ifdef USE_WEBSERVER_SORTING
827 for (
auto &group : ws->sorting_groups_) {
829 JsonObject root = builder.
root();
830 root[
"name"] = group.second.name;
831 root[
"sorting_weight"] = group.second.weight;
846 int fd = rsp->
fd_.exchange(0);
847 ESP_LOGD(TAG,
"Event source connection closed (fd: %d)", fd);
864 this->deferred_queue_.push_back(item);
893 if (bytes_sent == HTTPD_SOCK_ERR_TIMEOUT) {
902 ESP_LOGW(TAG,
"Closing stuck EventSource connection after %" PRIu16
" failed sends",
909 if (bytes_sent == HTTPD_SOCK_ERR_FAIL) {
913 if (bytes_sent <= 0) {
915 ESP_LOGW(TAG,
"Unexpected send result: %d", bytes_sent);
925 ESP_LOGV(TAG,
"Partial send: %d/%zu bytes (total: %zu/%zu)", bytes_sent, remaining,
event_bytes_sent_,
944 if (this->
fd_.load() == 0) {
955 const char chunk_len_header[] =
" " CRLF_STR;
956 const int chunk_len_header_len =
sizeof(chunk_len_header) - 1;
962 constexpr size_t num_buf_size = 32;
963 char num_buf[num_buf_size];
966 int len = snprintf(num_buf, num_buf_size,
"retry: %" PRIu32 CRLF_STR, reconnect);
971 int len = snprintf(num_buf, num_buf_size,
"id: %" PRIu32 CRLF_STR,
id);
975 if (event && *event) {
988 const char *first_n =
static_cast<const char *
>(memchr(
message,
'\n', message_len));
989 const char *first_r =
static_cast<const char *
>(memchr(
message,
'\r', message_len));
991 if (first_n ==
nullptr && first_r ==
nullptr) {
998 const char *line_start =
message;
999 const char *msg_end =
message + message_len;
1002 const char *next_n = first_n;
1003 const char *next_r = first_r;
1005 while (line_start <= msg_end) {
1006 const char *line_end;
1007 const char *next_line;
1009 if (next_n ==
nullptr && next_r ==
nullptr) {
1018 if (next_n !=
nullptr && next_r !=
nullptr) {
1019 if (next_r + 1 == next_n) {
1022 next_line = next_n + 1;
1025 line_end = (next_r < next_n) ? next_r : next_n;
1026 next_line = line_end + 1;
1028 }
else if (next_n !=
nullptr) {
1031 next_line = next_n + 1;
1035 next_line = next_r + 1;
1043 line_start = next_line;
1046 if (line_start >= msg_end) {
1051 next_n =
static_cast<const char *
>(memchr(line_start,
'\n', msg_end - line_start));
1052 next_r =
static_cast<const char *
>(memchr(line_start,
'\r', msg_end - line_start));
1060 if (
event_buffer_.size() ==
static_cast<size_t>(chunk_len_header_len)) {
1069 int chunk_len =
event_buffer_.size() - CRLF_LEN - chunk_len_header_len;
1070 char chunk_len_str[9];
1071 snprintf(chunk_len_str, 9,
"%08x", chunk_len);
1087 if (source ==
nullptr)
1089 if (event_type ==
nullptr)
1091 if (message_generator ==
nullptr)
1094 if (0 != strcmp(event_type,
"state_detail_all") && 0 != strcmp(event_type,
"state")) {
1095 ESP_LOGE(TAG,
"Can't defer non-state event");
1114#ifdef USE_WEBSERVER_OTA
1117 const char *boundary_start;
1118 size_t boundary_len;
1120 ESP_LOGE(TAG,
"Failed to parse multipart boundary");
1121 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST,
nullptr);
1128 if (
h->canHandle(&req)) {
1135 ESP_LOGW(TAG,
"No handler found for OTA request");
1136 httpd_resp_send_err(r, HTTPD_404_NOT_FOUND,
nullptr);
1141 std::string filename;
1144 auto reader = std::make_unique<MultipartReader>(
"--" + std::string(boundary_start, boundary_len));
1147 reader->set_data_callback([&](
const uint8_t *data,
size_t len) {
1148 if (!reader->has_file() || !
len)
1151 if (filename.empty()) {
1152 filename = reader->get_current_part().filename;
1153 ESP_LOGV(TAG,
"Processing file: '%s'", filename.c_str());
1154 handler->
handleUpload(&req, filename, 0,
nullptr, 0,
false);
1157 handler->
handleUpload(&req, filename, index,
const_cast<uint8_t *
>(data),
len,
false);
1161 reader->set_part_complete_callback([&]() {
1163 handler->
handleUpload(&req, filename, index,
nullptr, 0,
true);
1169 auto buffer = std::make_unique_for_overwrite<char[]>(RECV_CHUNK_SIZE);
1170 size_t bytes_since_yield = 0;
1172 for (
size_t remaining = r->content_len; remaining > 0;) {
1173 int recv_len = httpd_req_recv(r, buffer.get(), std::min(remaining, RECV_CHUNK_SIZE));
1175 if (recv_len <= 0) {
1176 httpd_resp_send_err(r, recv_len == HTTPD_SOCK_ERR_TIMEOUT ? HTTPD_408_REQ_TIMEOUT : HTTPD_400_BAD_REQUEST,
1178 return recv_len == HTTPD_SOCK_ERR_TIMEOUT ? ESP_ERR_TIMEOUT : ESP_FAIL;
1181 if (reader->parse(buffer.get(), recv_len) !=
static_cast<size_t>(recv_len)) {
1182 ESP_LOGW(TAG,
"Multipart parser error");
1183 httpd_resp_send_err(r, HTTPD_400_BAD_REQUEST,
nullptr);
1187 remaining -= recv_len;
1188 bytes_since_yield += recv_len;
1190 if (bytes_since_yield > YIELD_INTERVAL_BYTES) {
1192 bytes_since_yield = 0;
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void begin(bool include_internal=false)
Helper class that wraps a mutex with a RAII-style API.
StringRef is a reference to a string owned by something else.
constexpr const char * c_str() const
constexpr size_type size() const
Builder class for creating JSON documents without lambdas.
SerializationBuffer serialize()
Serialize the JSON document to a SerializationBuffer (stack-first allocation) Uses 512-byte stack buf...
This class allows users to create a web server with their ESP nodes.
json::SerializationBuffer get_config_json()
Return the webserver configuration as JSON.
std::vector< AsyncEventSourceResponse * > pending_sessions_
~AsyncEventSource() override
std::atomic< bool > has_pending_sessions_
friend class AsyncEventSourceResponse
bool loop()
Returns true if there are sessions remaining (including pending cleanup).
std::vector< AsyncEventSourceResponse * > sessions_
void try_send_nodefer(const char *message, size_t message_len, const char *event=nullptr, uint32_t id=0, uint32_t reconnect=0)
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator)
esphome::web_server::WebServer * web_server_
void handleRequest(AsyncWebServerRequest *request) override
connect_handler_t on_connect_
static void destroy(void *p)
std::vector< DeferredEvent > deferred_queue_
bool try_send_nodefer(const char *message, size_t message_len, const char *event=nullptr, uint32_t id=0, uint32_t reconnect=0)
void deferrable_send_state(void *source, const char *event_type, message_generator_t *message_generator)
esphome::web_server::WebServer * web_server_
void deq_push_back_with_dedup_(void *source, message_generator_t *message_generator)
void process_deferred_queue_()
AsyncEventSourceResponse(const AsyncWebServerRequest *request, esphome::web_server_idf::AsyncEventSource *server, esphome::web_server::WebServer *ws)
static constexpr uint16_t MAX_CONSECUTIVE_SEND_FAILURES
esphome::web_server::ListEntitiesIterator entities_iterator_
uint16_t consecutive_send_failures_
void start_session_main_loop_()
std::string event_buffer_
void print(const char *str)
void printf(const char *fmt,...) __attribute__((format(printf
virtual void handleRequest(AsyncWebServerRequest *request)
virtual void handleUpload(AsyncWebServerRequest *request, const std::string &filename, size_t index, uint8_t *data, size_t len, bool final)
virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)
std::function< void(AsyncWebServerRequest *request)> on_not_found_
esp_err_t handle_raw_body_(httpd_req_t *r, const char *content_type)
static esp_err_t request_post_handler(httpd_req_t *r)
std::vector< AsyncWebHandler * > handlers_
esp_err_t request_handler_(AsyncWebServerRequest *request) const
esp_err_t handle_multipart_upload_(httpd_req_t *r, const char *content_type)
static void safe_close_with_shutdown(httpd_handle_t hd, int sockfd)
static esp_err_t request_handler(httpd_req_t *r)
AsyncWebParameter * getParam(const char *name)
optional< std::string > get_header(const char *name) const
bool hasArg(const char *name)
StringRef url_to(std::span< char, URL_BUF_SIZE > buffer) const
Write URL (without query string) to buffer, returns StringRef pointing to buffer.
bool hasHeader(const char *name) const
void init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type)
static constexpr size_t URL_BUF_SIZE
Buffer size for url_to()
http_method method() const
std::string arg(const char *name)
optional< std::string > find_query_value_(const char *name) const
ESPDEPRECATED("Use url_to() instead. Removed in 2026.9.0", "2026.3.0") std void requestAuthentication() const
AsyncWebServerResponse * rsp_
std::vector< AsyncWebParameter * > params_
void redirect(const std::string &url)
const AsyncWebServerRequest * req_
void addHeader(const char *name, const char *value)
const LogString * message
bool query_has_key(const char *query_url, size_t query_len, const char *key)
json::SerializationBuffer<>(esphome::web_server::WebServer *, void *) message_generator_t
optional< std::string > request_get_header(httpd_req_t *req, const char *name)
bool parse_multipart_boundary(const char *content_type, const char **boundary_start, size_t *boundary_len)
optional< std::string > query_key_value(const char *query_url, size_t query_len, const char *key)
const char * strcasestr_n(const char *haystack, size_t haystack_len, const char *needle)
size_t url_decode(char *str)
Decode URL-encoded string in-place (e.g., %20 -> space, + -> space) Returns the new length of the dec...
bool request_has_header(httpd_req_t *req, const char *name)
bool random_bytes(uint8_t *data, size_t len)
Generate len random bytes using the platform's secure RNG (hardware RNG or OS CSPRNG).
const char int const __FlashStringHelper va_list args
size_t size_t const char va_start(args, fmt)
size_t size_t const char * fmt
uint32_t IRAM_ATTR HOT millis()
char * format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length)
Format byte array as lowercase hex to buffer (base implementation).
message_generator_t * message_generator_