mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Fix web_server URL parsing lifetime issue (#9309)
This commit is contained in:
		| @@ -46,70 +46,58 @@ static const char *const HEADER_CORS_REQ_PNA = "Access-Control-Request-Private-N | ||||
| static const char *const HEADER_CORS_ALLOW_PNA = "Access-Control-Allow-Private-Network"; | ||||
| #endif | ||||
|  | ||||
| UrlMatch match_url(const std::string &url, bool only_domain = false) { | ||||
|   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; | ||||
|  | ||||
|   const char *url_ptr = url.c_str(); | ||||
|   size_t url_len = url.length(); | ||||
| // Parse URL and return match info | ||||
| static UrlMatch match_url(const char *url_ptr, size_t url_len, bool only_domain) { | ||||
|   UrlMatch match{}; | ||||
|  | ||||
|   // URL must start with '/' | ||||
|   if (url_len < 2 || url_ptr[0] != '/') | ||||
|   if (url_len < 2 || url_ptr[0] != '/') { | ||||
|     return match; | ||||
|   } | ||||
|  | ||||
|   // Find domain | ||||
|   size_t domain_start = 1; | ||||
|   size_t domain_end = url.find('/', domain_start); | ||||
|   // Skip leading '/' | ||||
|   const char *start = url_ptr + 1; | ||||
|   const char *end = url_ptr + url_len; | ||||
|  | ||||
|   if (domain_end == std::string::npos) { | ||||
|     // URL is just "/domain" | ||||
|     match.domain = url_ptr + domain_start; | ||||
|     match.domain_len = url_len - domain_start; | ||||
|     match.valid = true; | ||||
|   // Find domain (everything up to next '/' or end) | ||||
|   const char *domain_end = (const char *) memchr(start, '/', end - start); | ||||
|   if (!domain_end) { | ||||
|     // No second slash found - original behavior returns invalid | ||||
|     return match; | ||||
|   } | ||||
|  | ||||
|   // Set domain | ||||
|   match.domain = url_ptr + domain_start; | ||||
|   match.domain_len = domain_end - domain_start; | ||||
|   match.domain = start; | ||||
|   match.domain_len = domain_end - start; | ||||
|   match.valid = true; | ||||
|  | ||||
|   if (only_domain) { | ||||
|     match.valid = true; | ||||
|     return match; | ||||
|   } | ||||
|  | ||||
|   // Check if there's anything after domain | ||||
|   if (url_len == domain_end + 1) | ||||
|     return match; | ||||
|   // Parse ID if present | ||||
|   if (domain_end + 1 >= end) { | ||||
|     return match;  // Nothing after domain slash | ||||
|   } | ||||
|  | ||||
|   // Find ID | ||||
|   size_t id_begin = domain_end + 1; | ||||
|   size_t id_end = url.find('/', id_begin); | ||||
|   const char *id_start = domain_end + 1; | ||||
|   const char *id_end = (const char *) memchr(id_start, '/', end - id_start); | ||||
|  | ||||
|   match.valid = true; | ||||
|  | ||||
|   if (id_end == std::string::npos) { | ||||
|     // URL is "/domain/id" with no method | ||||
|     match.id = url_ptr + id_begin; | ||||
|     match.id_len = url_len - id_begin; | ||||
|   if (!id_end) { | ||||
|     // No more slashes, entire remaining string is ID | ||||
|     match.id = id_start; | ||||
|     match.id_len = end - id_start; | ||||
|     return match; | ||||
|   } | ||||
|  | ||||
|   // Set ID | ||||
|   match.id = url_ptr + id_begin; | ||||
|   match.id_len = id_end - id_begin; | ||||
|   match.id = id_start; | ||||
|   match.id_len = id_end - id_start; | ||||
|  | ||||
|   // Set method if present | ||||
|   size_t method_begin = id_end + 1; | ||||
|   if (method_begin < url_len) { | ||||
|     match.method = url_ptr + method_begin; | ||||
|     match.method_len = url_len - method_begin; | ||||
|   // Parse method if present | ||||
|   if (id_end + 1 < end) { | ||||
|     match.method = id_end + 1; | ||||
|     match.method_len = end - (id_end + 1); | ||||
|   } | ||||
|  | ||||
|   return match; | ||||
| @@ -1759,7 +1747,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) const { | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   UrlMatch match = match_url(request->url().c_str(), true);  // NOLINT | ||||
|   // Store the URL to prevent temporary string destruction | ||||
|   // request->url() returns a reference to a String (on Arduino) or std::string (on ESP-IDF) | ||||
|   // UrlMatch stores pointers to the string's data, so we must ensure the string outlives match_url() | ||||
|   const auto &url = request->url(); | ||||
|   UrlMatch match = match_url(url.c_str(), url.length(), true); | ||||
|   if (!match.valid) | ||||
|     return false; | ||||
| #ifdef USE_SENSOR | ||||
| @@ -1898,7 +1890,10 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   UrlMatch match = match_url(request->url().c_str());  // NOLINT | ||||
|   // See comment in canHandle() for why we store the URL reference | ||||
|   const auto &url = request->url(); | ||||
|   UrlMatch match = match_url(url.c_str(), url.length(), false); | ||||
|  | ||||
| #ifdef USE_SENSOR | ||||
|   if (match.domain_equals("sensor")) { | ||||
|     this->handle_sensor_request(request, match); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user