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"; | static const char *const HEADER_CORS_ALLOW_PNA = "Access-Control-Allow-Private-Network"; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| UrlMatch match_url(const std::string &url, bool only_domain = false) { | // Parse URL and return match info | ||||||
|   UrlMatch match; | static UrlMatch match_url(const char *url_ptr, size_t url_len, bool only_domain) { | ||||||
|   match.valid = false; |   UrlMatch match{}; | ||||||
|   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(); |  | ||||||
|  |  | ||||||
|   // 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 = url.find('/', domain_start); |   const char *end = url_ptr + url_len; | ||||||
|  |  | ||||||
|   if (domain_end == std::string::npos) { |   // Find domain (everything up to next '/' or end) | ||||||
|     // URL is just "/domain" |   const char *domain_end = (const char *) memchr(start, '/', end - start); | ||||||
|     match.domain = url_ptr + domain_start; |   if (!domain_end) { | ||||||
|     match.domain_len = url_len - domain_start; |     // No second slash found - original behavior returns invalid | ||||||
|     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; | ||||||
|  |   match.valid = true; | ||||||
|  |  | ||||||
|   if (only_domain) { |   if (only_domain) { | ||||||
|     match.valid = true; |  | ||||||
|     return match; |     return match; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Check if there's anything after domain |   // Parse ID if present | ||||||
|   if (url_len == domain_end + 1) |   if (domain_end + 1 >= end) { | ||||||
|     return match; |     return match;  // Nothing after domain slash | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // Find ID |   const char *id_start = domain_end + 1; | ||||||
|   size_t id_begin = domain_end + 1; |   const char *id_end = (const char *) memchr(id_start, '/', end - id_start); | ||||||
|   size_t id_end = url.find('/', id_begin); |  | ||||||
|  |  | ||||||
|   match.valid = true; |   if (!id_end) { | ||||||
|  |     // No more slashes, entire remaining string is ID | ||||||
|   if (id_end == std::string::npos) { |     match.id = id_start; | ||||||
|     // URL is "/domain/id" with no method |     match.id_len = end - id_start; | ||||||
|     match.id = url_ptr + id_begin; |  | ||||||
|     match.id_len = url_len - id_begin; |  | ||||||
|     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; | ||||||
| @@ -1759,7 +1747,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) const { | |||||||
|   } |   } | ||||||
| #endif | #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) |   if (!match.valid) | ||||||
|     return false; |     return false; | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
| @@ -1898,7 +1890,10 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { | |||||||
|   } |   } | ||||||
| #endif | #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 | #ifdef USE_SENSOR | ||||||
|   if (match.domain_equals("sensor")) { |   if (match.domain_equals("sensor")) { | ||||||
|     this->handle_sensor_request(request, match); |     this->handle_sensor_request(request, match); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user