diff --git a/esphome/components/web_server_idf/multipart_parser_utils.h b/esphome/components/web_server_idf/multipart_parser_utils.h index 5787e3d880..d58232a067 100644 --- a/esphome/components/web_server_idf/multipart_parser_utils.h +++ b/esphome/components/web_server_idf/multipart_parser_utils.h @@ -14,201 +14,31 @@ namespace web_server_idf { inline bool char_equals_ci(char a, char b) { return ::tolower(a) == ::tolower(b); } // Helper function for case-insensitive string region comparison -inline bool str_ncmp_ci(const char *s1, const char *s2, size_t n) { - for (size_t i = 0; i < n; i++) { - if (!char_equals_ci(s1[i], s2[i])) { - return false; - } - } - return true; -} +bool str_ncmp_ci(const char *s1, const char *s2, size_t n); // Case-insensitive string prefix check -inline bool str_startswith_case_insensitive(const std::string &str, const std::string &prefix) { - if (str.length() < prefix.length()) { - return false; - } - return str_ncmp_ci(str.c_str(), prefix.c_str(), prefix.length()); -} +bool str_startswith_case_insensitive(const std::string &str, const std::string &prefix); // Find a substring case-insensitively -inline size_t str_find_case_insensitive(const std::string &haystack, const std::string &needle, size_t pos = 0) { - if (needle.empty() || pos >= haystack.length()) { - return std::string::npos; - } - - const size_t needle_len = needle.length(); - const size_t max_pos = haystack.length() - needle_len; - - for (size_t i = pos; i <= max_pos; i++) { - if (str_ncmp_ci(haystack.c_str() + i, needle.c_str(), needle_len)) { - return i; - } - } - - return std::string::npos; -} +size_t str_find_case_insensitive(const std::string &haystack, const std::string &needle, size_t pos = 0); // Extract a parameter value from a header line // Handles both quoted and unquoted values -inline std::string extract_header_param(const std::string &header, const std::string ¶m) { - size_t search_pos = 0; - - while (search_pos < header.length()) { - // Look for param name - size_t pos = str_find_case_insensitive(header, param, search_pos); - if (pos == std::string::npos) { - return ""; - } - - // Check if this is a word boundary (not part of another parameter) - if (pos > 0 && header[pos - 1] != ' ' && header[pos - 1] != ';' && header[pos - 1] != '\t') { - search_pos = pos + 1; - continue; - } - - // Move past param name - pos += param.length(); - - // Skip whitespace and find '=' - while (pos < header.length() && (header[pos] == ' ' || header[pos] == '\t')) { - pos++; - } - - if (pos >= header.length() || header[pos] != '=') { - search_pos = pos; - continue; - } - - pos++; // Skip '=' - - // Skip whitespace after '=' - while (pos < header.length() && (header[pos] == ' ' || header[pos] == '\t')) { - pos++; - } - - if (pos >= header.length()) { - return ""; - } - - // Check if value is quoted - if (header[pos] == '"') { - pos++; - size_t end = header.find('"', pos); - if (end != std::string::npos) { - return header.substr(pos, end - pos); - } - // Malformed - no closing quote - return ""; - } - - // Unquoted value - find the end (semicolon, comma, or end of string) - size_t end = pos; - while (end < header.length() && header[end] != ';' && header[end] != ',' && header[end] != ' ' && - header[end] != '\t') { - end++; - } - - return header.substr(pos, end - pos); - } - - return ""; -} +std::string extract_header_param(const std::string &header, const std::string ¶m); // Case-insensitive string search (like strstr but case-insensitive) -inline const char *stristr(const char *haystack, const char *needle) { - if (!haystack || !needle) { - return nullptr; - } - - size_t needle_len = strlen(needle); - if (needle_len == 0) { - return haystack; - } - - for (const char *p = haystack; *p; p++) { - if (str_ncmp_ci(p, needle, needle_len)) { - return p; - } - } - - return nullptr; -} +const char *stristr(const char *haystack, const char *needle); // Parse boundary from Content-Type header // Returns true if boundary found, false otherwise // boundary_start and boundary_len will point to the boundary value -inline bool parse_multipart_boundary(const char *content_type, const char **boundary_start, size_t *boundary_len) { - if (!content_type) { - return false; - } - - // Check for multipart/form-data (case-insensitive) - if (!stristr(content_type, "multipart/form-data")) { - return false; - } - - // Look for boundary parameter - const char *b = stristr(content_type, "boundary="); - if (!b) { - return false; - } - - const char *start = b + 9; // Skip "boundary=" - - // Skip whitespace - while (*start == ' ' || *start == '\t') { - start++; - } - - if (!*start) { - return false; - } - - // Find end of boundary - const char *end = start; - if (*end == '"') { - // Quoted boundary - start++; - end++; - while (*end && *end != '"') { - end++; - } - *boundary_len = end - start; - } else { - // Unquoted boundary - while (*end && *end != ' ' && *end != ';' && *end != '\r' && *end != '\n' && *end != '\t') { - end++; - } - *boundary_len = end - start; - } - - if (*boundary_len == 0) { - return false; - } - - *boundary_start = start; - return true; -} +bool parse_multipart_boundary(const char *content_type, const char **boundary_start, size_t *boundary_len); // Check if content type is form-urlencoded (case-insensitive) -inline bool is_form_urlencoded(const char *content_type) { - if (!content_type) { - return false; - } - - return stristr(content_type, "application/x-www-form-urlencoded") != nullptr; -} +bool is_form_urlencoded(const char *content_type); // Trim whitespace from both ends of a string -inline std::string str_trim(const std::string &str) { - size_t start = str.find_first_not_of(" \t\r\n"); - if (start == std::string::npos) { - return ""; - } - size_t end = str.find_last_not_of(" \t\r\n"); - return str.substr(start, end - start + 1); -} +std::string str_trim(const std::string &str); } // namespace web_server_idf } // namespace esphome