From d1ff959f4c1840e1aacc59fb7e205af60fa6cf15 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 22 Dec 2025 17:13:03 -1000 Subject: [PATCH] two segment sub dev --- esphome/components/web_server/web_server.cpp | 1 + esphome/components/web_server/web_server.h | 34 +++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index c925a11322..170b621d89 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -62,6 +62,7 @@ static UrlMatch match_url(const char *url_ptr, size_t url_len, bool only_domain) #ifdef USE_DEVICES match.device_name = nullptr; match.device_name_len = 0; + match.matched_as_two_segment_subdevice = false; #endif // URL must start with '/' diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 7981801bc7..78d19fdb7e 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -50,6 +50,7 @@ struct UrlMatch { uint8_t method_len; ///< Length of method string #ifdef USE_DEVICES uint8_t device_name_len; ///< Length of device name string (NAME_MAX_LENGTH must be < 255) + mutable bool matched_as_two_segment_subdevice{false}; ///< Set when 2-segment URL matched as device/entity #endif bool valid; ///< Whether this match is valid @@ -67,20 +68,36 @@ struct UrlMatch { used_deprecated_format = false; #ifdef USE_DEVICES - // Check device match first (faster rejection) + // Check device match Device *entity_device = entity->get_device(); bool url_has_device = (device_name_len > 0); bool entity_has_device = (entity_device != nullptr); - if (url_has_device != entity_has_device) { - return false; // Mismatch: one has device, other doesn't - } if (url_has_device) { + // URL has explicit device segment - must match + if (!entity_has_device) { + return false; // URL has device but entity doesn't + } const char *entity_device_name = entity_device->get_name(); if (device_name_len != strlen(entity_device_name) || memcmp(device_name, entity_device_name, device_name_len) != 0) { return false; // Device name doesn't match } + } else if (entity_has_device && method_len > 0) { + // URL has 2 segments (id/method), entity has device + // Try interpreting as device/entity: id=device_name, method=entity_name + const char *entity_device_name = entity_device->get_name(); + if (id_len == strlen(entity_device_name) && memcmp(id, entity_device_name, id_len) == 0) { + // Device name matches, check if method matches entity name + const StringRef &name_ref = entity->get_name(); + if (method_len == name_ref.size() && memcmp(method, name_ref.c_str(), method_len) == 0) { + matched_as_two_segment_subdevice = true; // Mark for method_empty() check + return true; // Matched as device/entity (2-segment sub-device URL) + } + } + return false; // Entity has device but URL doesn't match as device/entity + } else if (entity_has_device) { + return false; // Entity has device but URL has no device info } #endif @@ -108,7 +125,14 @@ struct UrlMatch { return method && method_len == strlen(str) && memcmp(method, str, method_len) == 0; } - bool method_empty() const { return method_len == 0; } + bool method_empty() const { +#ifdef USE_DEVICES + // For 2-segment sub-device URLs, method field contains entity name, not actual method + if (matched_as_two_segment_subdevice) + return true; +#endif + return method_len == 0; + } }; #ifdef USE_WEBSERVER_SORTING