Compare commits

..

1 Commits

Author SHA1 Message Date
J. Nick Koston
0b60fd0c8c [core] Avoid heap allocation in str_equals_case_insensitive with string literals (#13312) 2026-01-20 21:49:14 -10:00
7 changed files with 34 additions and 63 deletions

View File

@@ -96,16 +96,10 @@ void CaptivePortal::start() {
}
void CaptivePortal::handleRequest(AsyncWebServerRequest *req) {
#ifdef USE_ESP32
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
StringRef url = req->url_to(url_buf);
#else
const auto &url = req->url();
#endif
if (url == ESPHOME_F("/config.json")) {
if (req->url() == ESPHOME_F("/config.json")) {
this->handle_config(req);
return;
} else if (url == ESPHOME_F("/wifisave")) {
} else if (req->url() == ESPHOME_F("/wifisave")) {
this->handle_wifisave(req);
return;
}

View File

@@ -41,14 +41,12 @@ class PrometheusHandler : public AsyncWebHandler, public Component {
void add_label_name(EntityBase *obj, const std::string &value) { relabel_map_name_.insert({obj, value}); }
bool canHandle(AsyncWebServerRequest *request) const override {
if (request->method() != HTTP_GET)
return false;
#ifdef USE_ESP32
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
return request->url_to(url_buf) == "/metrics";
#else
return request->url() == ESPHOME_F("/metrics");
#endif
if (request->method() == HTTP_GET) {
if (request->url() == "/metrics")
return true;
}
return false;
}
void handleRequest(AsyncWebServerRequest *req) override;

View File

@@ -32,15 +32,8 @@ class OTARequestHandler : public AsyncWebHandler {
void handleUpload(AsyncWebServerRequest *request, const PlatformString &filename, size_t index, uint8_t *data,
size_t len, bool final) override;
bool canHandle(AsyncWebServerRequest *request) const override {
if (request->method() != HTTP_POST)
return false;
// Check if this is an OTA update request
#ifdef USE_ESP32
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
bool is_ota_request = request->url_to(url_buf) == "/update";
#else
bool is_ota_request = request->url() == ESPHOME_F("/update");
#endif
// Check if this is an OTA update request
bool is_ota_request = request->url() == "/update" && request->method() == HTTP_POST;
#if defined(USE_WEBSERVER_OTA_DISABLED) && defined(USE_CAPTIVE_PORTAL)
// IMPORTANT: USE_WEBSERVER_OTA_DISABLED only disables OTA for the web_server component

View File

@@ -2175,12 +2175,7 @@ std::string WebServer::update_json_(update::UpdateEntity *obj, JsonDetail start_
#endif
bool WebServer::canHandle(AsyncWebServerRequest *request) const {
#ifdef USE_ESP32
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
StringRef url = request->url_to(url_buf);
#else
const auto &url = request->url();
#endif
const auto method = request->method();
// Static URL checks - use ESPHOME_F to keep strings in flash on ESP8266
@@ -2316,35 +2311,30 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) const {
return false;
}
void WebServer::handleRequest(AsyncWebServerRequest *request) {
#ifdef USE_ESP32
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
StringRef url = request->url_to(url_buf);
#else
const auto &url = request->url();
#endif
// Handle static routes first
if (url == ESPHOME_F("/")) {
if (url == "/") {
this->handle_index_request(request);
return;
}
#if !defined(USE_ESP32) && defined(USE_ARDUINO)
if (url == ESPHOME_F("/events")) {
if (url == "/events") {
this->events_.add_new_client(this, request);
return;
}
#endif
#ifdef USE_WEBSERVER_CSS_INCLUDE
if (url == ESPHOME_F("/0.css")) {
if (url == "/0.css") {
this->handle_css_request(request);
return;
}
#endif
#ifdef USE_WEBSERVER_JS_INCLUDE
if (url == ESPHOME_F("/0.js")) {
if (url == "/0.js") {
this->handle_js_request(request);
return;
}

View File

@@ -246,16 +246,21 @@ optional<std::string> AsyncWebServerRequest::get_header(const char *name) const
return request_get_header(*this, name);
}
StringRef AsyncWebServerRequest::url_to(std::span<char, URL_BUF_SIZE> buffer) const {
const char *uri = this->req_->uri;
const char *query_start = strchr(uri, '?');
size_t uri_len = query_start ? static_cast<size_t>(query_start - uri) : strlen(uri);
size_t copy_len = std::min(uri_len, URL_BUF_SIZE - 1);
memcpy(buffer.data(), uri, copy_len);
buffer[copy_len] = '\0';
std::string AsyncWebServerRequest::url() const {
auto *query_start = strchr(this->req_->uri, '?');
std::string result;
if (query_start == nullptr) {
result = this->req_->uri;
} else {
result = std::string(this->req_->uri, query_start - this->req_->uri);
}
// Decode URL-encoded characters in-place (e.g., %20 -> space)
size_t decoded_len = url_decode(buffer.data());
return StringRef(buffer.data(), decoded_len);
// This matches AsyncWebServer behavior on Arduino
if (!result.empty()) {
size_t new_len = url_decode(&result[0]);
result.resize(new_len);
}
return result;
}
std::string AsyncWebServerRequest::host() const { return this->get_header("Host").value(); }

View File

@@ -3,14 +3,12 @@
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/string_ref.h"
#include <esp_http_server.h>
#include <atomic>
#include <functional>
#include <list>
#include <map>
#include <span>
#include <string>
#include <utility>
#include <vector>
@@ -112,15 +110,7 @@ class AsyncWebServerRequest {
~AsyncWebServerRequest();
http_method method() const { return static_cast<http_method>(this->req_->method); }
static constexpr size_t URL_BUF_SIZE = CONFIG_HTTPD_MAX_URI_LEN + 1; ///< Buffer size for url_to()
/// Write URL (without query string) to buffer, returns StringRef pointing to buffer.
/// URL is decoded (e.g., %20 -> space).
StringRef url_to(std::span<char, URL_BUF_SIZE> buffer) const;
/// Get URL as std::string. Prefer url_to() to avoid heap allocation.
std::string url() const {
char buffer[URL_BUF_SIZE];
return std::string(this->url_to(buffer));
}
std::string url() const;
std::string host() const;
// NOLINTNEXTLINE(readability-identifier-naming)
size_t contentLength() const { return this->req_->content_len; }
@@ -316,10 +306,7 @@ class AsyncEventSource : public AsyncWebHandler {
// NOLINTNEXTLINE(readability-identifier-naming)
bool canHandle(AsyncWebServerRequest *request) const override {
if (request->method() != HTTP_GET)
return false;
char url_buf[AsyncWebServerRequest::URL_BUF_SIZE];
return request->url_to(url_buf) == this->url_;
return request->method() == HTTP_GET && request->url() == this->url_;
}
// NOLINTNEXTLINE(readability-identifier-naming)
void handleRequest(AsyncWebServerRequest *request) override;

View File

@@ -572,6 +572,10 @@ template<typename T> constexpr T convert_little_endian(T val) {
bool str_equals_case_insensitive(const std::string &a, const std::string &b);
/// Compare StringRefs for equality in case-insensitive manner.
bool str_equals_case_insensitive(StringRef a, StringRef b);
/// Compare C strings for equality in case-insensitive manner (no heap allocation).
inline bool str_equals_case_insensitive(const char *a, const char *b) { return strcasecmp(a, b) == 0; }
inline bool str_equals_case_insensitive(const std::string &a, const char *b) { return strcasecmp(a.c_str(), b) == 0; }
inline bool str_equals_case_insensitive(const char *a, const std::string &b) { return strcasecmp(a, b.c_str()) == 0; }
/// Check whether a string starts with a value.
bool str_startswith(const std::string &str, const std::string &start);