From 73867c62bee8ce2bfdb1b51c3fab73beb7b387f2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Feb 2026 18:10:36 -0600 Subject: [PATCH 1/6] Pass c_str() and size() directly to date/time/datetime setters These setters have (const char*, size_t) overloads that do the actual work. Skip the std::string& overload indirection. --- esphome/components/web_server/web_server.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 6bddb23ab2..b83c11f260 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1179,7 +1179,7 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat request->send(409); return; } - call.set_date(value); + call.set_date(value.c_str(), value.size()); DEFER_ACTION(call, call.perform()); request->send(200); @@ -1240,7 +1240,7 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat request->send(409); return; } - call.set_time(value); + call.set_time(value.c_str(), value.size()); DEFER_ACTION(call, call.perform()); request->send(200); @@ -1300,7 +1300,7 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur request->send(409); return; } - call.set_datetime(value); + call.set_datetime(value.c_str(), value.size()); DEFER_ACTION(call, call.perform()); request->send(200); From 5585b5967ed4f77ceb6aca40babb2f13f358178c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Feb 2026 18:11:49 -0600 Subject: [PATCH 2/6] Avoid std::string copy in date/time/datetime handlers Use const auto& to bind directly to arg() result (std::string on IDF, Arduino String on Arduino) and pass c_str()/length() to the setter. No intermediate std::string copy needed. --- esphome/components/web_server/web_server.cpp | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index b83c11f260..d8fe9b8c7e 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1173,13 +1173,13 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat auto call = obj->make_call(); - // .c_str() is required for Arduino framework where arg() returns Arduino String instead of std::string - std::string value = request->arg(ESPHOME_F("value")).c_str(); // NOLINT(readability-redundant-string-cstr) - if (value.empty()) { + const auto &value = request->arg(ESPHOME_F("value")); + // Arduino String has isEmpty() not empty(), use length() for cross-platform compatibility + if (value.length() == 0) { // NOLINT(readability-container-size-empty) request->send(409); return; } - call.set_date(value.c_str(), value.size()); + call.set_date(value.c_str(), value.length()); DEFER_ACTION(call, call.perform()); request->send(200); @@ -1234,13 +1234,13 @@ void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMat auto call = obj->make_call(); - // .c_str() is required for Arduino framework where arg() returns Arduino String instead of std::string - std::string value = request->arg(ESPHOME_F("value")).c_str(); // NOLINT(readability-redundant-string-cstr) - if (value.empty()) { + const auto &value = request->arg(ESPHOME_F("value")); + // Arduino String has isEmpty() not empty(), use length() for cross-platform compatibility + if (value.length() == 0) { // NOLINT(readability-container-size-empty) request->send(409); return; } - call.set_time(value.c_str(), value.size()); + call.set_time(value.c_str(), value.length()); DEFER_ACTION(call, call.perform()); request->send(200); @@ -1294,13 +1294,13 @@ void WebServer::handle_datetime_request(AsyncWebServerRequest *request, const Ur auto call = obj->make_call(); - // .c_str() is required for Arduino framework where arg() returns Arduino String instead of std::string - std::string value = request->arg(ESPHOME_F("value")).c_str(); // NOLINT(readability-redundant-string-cstr) - if (value.empty()) { + const auto &value = request->arg(ESPHOME_F("value")); + // Arduino String has isEmpty() not empty(), use length() for cross-platform compatibility + if (value.length() == 0) { // NOLINT(readability-container-size-empty) request->send(409); return; } - call.set_datetime(value.c_str(), value.size()); + call.set_datetime(value.c_str(), value.length()); DEFER_ACTION(call, call.perform()); request->send(200); From 892804e02eb3a8d0a323e644a18c106cb64c0685 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Feb 2026 18:12:47 -0600 Subject: [PATCH 3/6] Remove remaining readability-redundant-string-cstr NOLINTs Use const auto& to bind arg() result directly, avoiding unnecessary std::string intermediate copies. Construct std::string only where needed (lambda capture, setter call). --- esphome/components/web_server/web_server.cpp | 8 ++++---- esphome/components/web_server/web_server.h | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index d8fe9b8c7e..5271912f0f 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -2002,11 +2002,11 @@ void WebServer::handle_infrared_request(AsyncWebServerRequest *request, const Ur // Parse base64url-encoded raw timings (required) // Base64url is URL-safe: uses A-Za-z0-9-_ (no special characters needing escaping) - // .c_str() is required for Arduino framework where arg() returns Arduino String instead of std::string - std::string encoded = request->arg(ESPHOME_F("data")).c_str(); // NOLINT(readability-redundant-string-cstr) + const auto &data_arg = request->arg(ESPHOME_F("data")); // Validate base64url is not empty (also catches missing parameter since arg() returns empty string) - if (encoded.empty()) { + // Arduino String has isEmpty() not empty(), use length() for cross-platform compatibility + if (data_arg.length() == 0) { // NOLINT(readability-container-size-empty) request->send(400, ESPHOME_F("text/plain"), ESPHOME_F("Missing or empty 'data' parameter")); return; } @@ -2015,7 +2015,7 @@ void WebServer::handle_infrared_request(AsyncWebServerRequest *request, const Ur // it outlives the call - set_raw_timings_base64url stores a pointer, so the string // must remain valid until perform() completes. // ESP8266 also needs this because ESPAsyncWebServer callbacks run in "sys" context. - this->defer([call, encoded = std::move(encoded)]() mutable { + this->defer([call, encoded = std::string(data_arg.c_str(), data_arg.length())]() mutable { call.set_raw_timings_base64url(encoded); call.perform(); }); diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 8ce792cb1c..33f10fc4c9 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -543,10 +543,10 @@ class WebServer : public Controller, template void parse_string_param_(AsyncWebServerRequest *request, ParamNameType param_name, T &call, Ret (T::*setter)(const std::string &)) { - // .c_str() is required for Arduino framework where arg() returns Arduino String instead of std::string - std::string value = request->arg(param_name).c_str(); // NOLINT(readability-redundant-string-cstr) - if (!value.empty()) { - (call.*setter)(value); + const auto &value = request->arg(param_name); + // Arduino String has isEmpty() not empty(), use length() for cross-platform compatibility + if (value.length() > 0) { // NOLINT(readability-container-size-empty) + (call.*setter)(std::string(value.c_str(), value.length())); } } From f35dfefdf341627e7562a0663bfefffb3228243e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Feb 2026 18:13:32 -0600 Subject: [PATCH 4/6] Remove readability-redundant-string-cstr NOLINTs from captive_portal Use const auto& to bind arg() result directly. Construct std::string only in deferred lambda capture where ownership is needed. --- esphome/components/captive_portal/captive_portal.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 8d88a10b27..95f7863102 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -47,8 +47,8 @@ void CaptivePortal::handle_config(AsyncWebServerRequest *request) { request->send(stream); } void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { - std::string ssid = request->arg("ssid").c_str(); // NOLINT(readability-redundant-string-cstr) - std::string psk = request->arg("psk").c_str(); // NOLINT(readability-redundant-string-cstr) + const auto &ssid = request->arg("ssid"); + const auto &psk = request->arg("psk"); ESP_LOGI(TAG, "Requested WiFi Settings Change:\n" " SSID='%s'\n" @@ -56,10 +56,12 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { ssid.c_str(), psk.c_str()); #ifdef USE_ESP8266 // ESP8266 is single-threaded, call directly - wifi::global_wifi_component->save_wifi_sta(ssid, psk); + wifi::global_wifi_component->save_wifi_sta(ssid.c_str(), psk.c_str()); #else // Defer save to main loop thread to avoid NVS operations from HTTP thread - this->defer([ssid, psk]() { wifi::global_wifi_component->save_wifi_sta(ssid, psk); }); + this->defer([ssid = std::string(ssid.c_str(), ssid.length()), psk = std::string(psk.c_str(), psk.length())]() { + wifi::global_wifi_component->save_wifi_sta(ssid, psk); + }); #endif request->redirect(ESPHOME_F("/?save")); } From 91a0b0989e9896c30e99a61e4dd1624124399356 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Feb 2026 18:14:46 -0600 Subject: [PATCH 5/6] Simplify captive_portal lambda capture Capture auto type directly and use c_str() overload of save_wifi_sta. Works on both std::string and Arduino String. --- esphome/components/captive_portal/captive_portal.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 95f7863102..5af6ab29a2 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -59,9 +59,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { wifi::global_wifi_component->save_wifi_sta(ssid.c_str(), psk.c_str()); #else // Defer save to main loop thread to avoid NVS operations from HTTP thread - this->defer([ssid = std::string(ssid.c_str(), ssid.length()), psk = std::string(psk.c_str(), psk.length())]() { - wifi::global_wifi_component->save_wifi_sta(ssid, psk); - }); + this->defer([ssid, psk]() { wifi::global_wifi_component->save_wifi_sta(ssid.c_str(), psk.c_str()); }); #endif request->redirect(ESPHOME_F("/?save")); } From 592d5ec24c5da5c17c2569406a0c17f89c98fc24 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 11 Feb 2026 18:17:24 -0600 Subject: [PATCH 6/6] Restore hasArg guard in parse_string_param_ Empty string is a valid value for string params (e.g. select options). Must use hasArg to distinguish missing from empty. --- esphome/components/web_server/web_server.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 33f10fc4c9..026da763ea 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -543,9 +543,8 @@ class WebServer : public Controller, template void parse_string_param_(AsyncWebServerRequest *request, ParamNameType param_name, T &call, Ret (T::*setter)(const std::string &)) { - const auto &value = request->arg(param_name); - // Arduino String has isEmpty() not empty(), use length() for cross-platform compatibility - if (value.length() > 0) { // NOLINT(readability-container-size-empty) + if (request->hasArg(param_name)) { + const auto &value = request->arg(param_name); (call.*setter)(std::string(value.c_str(), value.length())); } }