Merge remote-tracking branch 'upstream/webserver_string_lifetime' into integration

This commit is contained in:
J. Nick Koston 2025-07-03 13:52:04 -05:00
commit 96cf8d97ab
No known key found for this signature in database

View File

@ -48,94 +48,64 @@ static const char *const HEADER_CORS_ALLOW_PNA = "Access-Control-Allow-Private-N
// Helper function to handle the actual URL parsing logic // Helper function to handle the actual URL parsing logic
static UrlMatch parse_url(const char *url_ptr, size_t url_len, bool only_domain) { static UrlMatch parse_url(const char *url_ptr, size_t url_len, bool only_domain) {
UrlMatch match; UrlMatch match{};
match.valid = false;
match.domain = nullptr;
match.id = nullptr;
match.method = nullptr;
match.domain_len = 0;
match.id_len = 0;
match.method_len = 0;
// URL must start with '/' // URL must start with '/'
if (url_len < 2 || url_ptr[0] != '/') { if (url_len < 2 || url_ptr[0] != '/') {
return match; return match;
} }
// Find domain // Skip leading '/'
size_t domain_start = 1; const char *start = url_ptr + 1;
size_t domain_end = domain_start; const char *end = url_ptr + url_len;
// Find the next '/' after domain // Find domain (everything up to next '/' or end)
while (domain_end < url_len && url_ptr[domain_end] != '/') { const char *domain_end = (const char *) memchr(start, '/', end - start);
domain_end++; if (!domain_end) {
} // No more slashes, entire remaining string is domain
match.domain = start;
if (domain_end == url_len) { match.domain_len = end - start;
// URL is just "/domain"
match.domain = url_ptr + domain_start;
match.domain_len = url_len - domain_start;
match.valid = true; match.valid = true;
return match; return match;
} }
// Set domain // Set domain
match.domain = url_ptr + domain_start; match.domain = start;
match.domain_len = domain_end - domain_start; match.domain_len = domain_end - start;
if (only_domain) {
match.valid = true;
return match;
}
// Check if there's anything after domain
if (url_len == domain_end + 1)
return match;
// Find ID
size_t id_begin = domain_end + 1;
size_t id_end = id_begin;
// Find the next '/' after id
while (id_end < url_len && url_ptr[id_end] != '/') {
id_end++;
}
match.valid = true; match.valid = true;
if (id_end == url_len) { if (only_domain) {
// URL is "/domain/id" with no method return match;
match.id = url_ptr + id_begin; }
match.id_len = url_len - id_begin;
// Parse ID if present
if (domain_end + 1 >= end) {
return match; // Nothing after domain slash
}
const char *id_start = domain_end + 1;
const char *id_end = (const char *) memchr(id_start, '/', end - id_start);
if (!id_end) {
// No more slashes, entire remaining string is ID
match.id = id_start;
match.id_len = end - id_start;
return match; return match;
} }
// Set ID // Set ID
match.id = url_ptr + id_begin; match.id = id_start;
match.id_len = id_end - id_begin; match.id_len = id_end - id_start;
// Set method if present // Parse method if present
size_t method_begin = id_end + 1; if (id_end + 1 < end) {
if (method_begin < url_len) { match.method = id_end + 1;
match.method = url_ptr + method_begin; match.method_len = end - (id_end + 1);
match.method_len = url_len - method_begin;
} }
return match; return match;
} }
// Overload for std::string - stores the string to ensure pointers remain valid
UrlMatch match_url(const std::string &url, bool only_domain = false) {
return parse_url(url.c_str(), url.length(), only_domain);
}
#ifdef USE_ARDUINO
// Overload for Arduino String - stores the string to ensure pointers remain valid
UrlMatch match_url(const String &url, bool only_domain = false) {
return parse_url(url.c_str(), url.length(), only_domain);
}
#endif
#ifdef USE_ARDUINO #ifdef USE_ARDUINO
// helper for allowing only unique entries in the queue // helper for allowing only unique entries in the queue
void DeferredUpdateEventSource::deq_push_back_with_dedup_(void *source, message_generator_t *message_generator) { void DeferredUpdateEventSource::deq_push_back_with_dedup_(void *source, message_generator_t *message_generator) {
@ -1786,10 +1756,9 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) const {
// Store the URL to prevent temporary string destruction // Store the URL to prevent temporary string destruction
// request->url() returns a reference to a String (on Arduino) or std::string (on ESP-IDF) // request->url() returns a reference to a String (on Arduino) or std::string (on ESP-IDF)
// If we pass it directly to match_url(), it could create a temporary std::string from Arduino String // UrlMatch stores pointers to the string's data, so we must ensure the string outlives parse_url()
// UrlMatch stores pointers to the string's data, so we must ensure the string outlives match_url()
const auto &url = request->url(); const auto &url = request->url();
UrlMatch match = match_url(url, true); // NOLINT UrlMatch match = parse_url(url.c_str(), url.length(), true);
if (!match.valid) if (!match.valid)
return false; return false;
#ifdef USE_SENSOR #ifdef USE_SENSOR
@ -1930,7 +1899,7 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
// See comment in canHandle() for why we store the URL reference // See comment in canHandle() for why we store the URL reference
const auto &url = request->url(); const auto &url = request->url();
UrlMatch match = match_url(url); // NOLINT UrlMatch match = parse_url(url.c_str(), url.length(), false);
#ifdef USE_SENSOR #ifdef USE_SENSOR
if (match.domain_equals("sensor")) { if (match.domain_equals("sensor")) {