diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index bf65ae67c0..8d88a10b27 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -96,10 +96,16 @@ void CaptivePortal::start() { } void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { - if (req->url() == ESPHOME_F("/config.json")) { +#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")) { this->handle_config(req); return; - } else if (req->url() == ESPHOME_F("/wifisave")) { + } else if (url == ESPHOME_F("/wifisave")) { this->handle_wifisave(req); return; } diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index fc48ad67e3..7aecab99d1 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -41,12 +41,14 @@ 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) { - if (request->url() == "/metrics") - return true; - } - - return false; + 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 } void handleRequest(AsyncWebServerRequest *req) override; diff --git a/esphome/components/web_server/ota/ota_web_server.cpp b/esphome/components/web_server/ota/ota_web_server.cpp index 3793f01eb5..4be162ccd3 100644 --- a/esphome/components/web_server/ota/ota_web_server.cpp +++ b/esphome/components/web_server/ota/ota_web_server.cpp @@ -32,8 +32,15 @@ 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 { - // Check if this is an OTA update request - bool is_ota_request = request->url() == "/update" && request->method() == HTTP_POST; + 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 #if defined(USE_WEBSERVER_OTA_DISABLED) && defined(USE_CAPTIVE_PORTAL) // IMPORTANT: USE_WEBSERVER_OTA_DISABLED only disables OTA for the web_server component diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index a19c1c9b17..d30cb524f4 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -2187,7 +2187,12 @@ 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 @@ -2323,30 +2328,35 @@ 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 == "/") { + if (url == ESPHOME_F("/")) { this->handle_index_request(request); return; } #if !defined(USE_ESP32) && defined(USE_ARDUINO) - if (url == "/events") { + if (url == ESPHOME_F("/events")) { this->events_.add_new_client(this, request); return; } #endif #ifdef USE_WEBSERVER_CSS_INCLUDE - if (url == "/0.css") { + if (url == ESPHOME_F("/0.css")) { this->handle_css_request(request); return; } #endif #ifdef USE_WEBSERVER_JS_INCLUDE - if (url == "/0.js") { + if (url == ESPHOME_F("/0.js")) { this->handle_js_request(request); return; } diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index abeda5fc46..2e5a74cbef 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -246,21 +246,16 @@ optional AsyncWebServerRequest::get_header(const char *name) const return request_get_header(*this, name); } -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); - } +StringRef AsyncWebServerRequest::url_to(std::span buffer) const { + const char *uri = this->req_->uri; + const char *query_start = strchr(uri, '?'); + size_t uri_len = query_start ? static_cast(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'; // Decode URL-encoded characters in-place (e.g., %20 -> space) - // This matches AsyncWebServer behavior on Arduino - if (!result.empty()) { - size_t new_len = url_decode(&result[0]); - result.resize(new_len); - } - return result; + size_t decoded_len = url_decode(buffer.data()); + return StringRef(buffer.data(), decoded_len); } std::string AsyncWebServerRequest::host() const { return this->get_header("Host").value(); } diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index a6c984792a..e38913ef4a 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -3,12 +3,14 @@ #include "esphome/core/defines.h" #include "esphome/core/helpers.h" +#include "esphome/core/string_ref.h" #include #include #include #include #include +#include #include #include #include @@ -110,7 +112,15 @@ class AsyncWebServerRequest { ~AsyncWebServerRequest(); http_method method() const { return static_cast(this->req_->method); } - std::string url() const; + 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 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 host() const; // NOLINTNEXTLINE(readability-identifier-naming) size_t contentLength() const { return this->req_->content_len; } @@ -306,7 +316,10 @@ class AsyncEventSource : public AsyncWebHandler { // NOLINTNEXTLINE(readability-identifier-naming) bool canHandle(AsyncWebServerRequest *request) const override { - return request->method() == HTTP_GET && request->url() == this->url_; + if (request->method() != HTTP_GET) + return false; + char url_buf[AsyncWebServerRequest::URL_BUF_SIZE]; + return request->url_to(url_buf) == this->url_; } // NOLINTNEXTLINE(readability-identifier-naming) void handleRequest(AsyncWebServerRequest *request) override;