From 614a2f66a353789e89b3451c9c62496b962fbda4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 29 Jun 2025 10:57:00 -0500 Subject: [PATCH] fixes --- .../web_server_idf/multipart_parser.cpp | 54 ++++---- .../web_server_idf/multipart_parser_utils.h | 128 ++++++++++++++++++ 2 files changed, 157 insertions(+), 25 deletions(-) create mode 100644 esphome/components/web_server_idf/multipart_parser_utils.h diff --git a/esphome/components/web_server_idf/multipart_parser.cpp b/esphome/components/web_server_idf/multipart_parser.cpp index 15492875b8..8dcad5cd1b 100644 --- a/esphome/components/web_server_idf/multipart_parser.cpp +++ b/esphome/components/web_server_idf/multipart_parser.cpp @@ -1,6 +1,7 @@ #ifdef USE_ESP_IDF #ifdef USE_WEBSERVER_OTA #include "multipart_parser.h" +#include "multipart_parser_utils.h" #include "esphome/core/log.h" namespace esphome { @@ -141,35 +142,38 @@ bool MultipartParser::parse_headers() { return true; } - // Parse Content-Disposition header - if (line.find("Content-Disposition:") == 0) { - // Extract name - size_t name_pos = line.find("name=\""); - if (name_pos != std::string::npos) { - name_pos += 6; - size_t name_end = line.find("\"", name_pos); - if (name_end != std::string::npos) { - current_name_ = line.substr(name_pos, name_end - name_pos); - } + // Parse Content-Disposition header (case-insensitive) + if (str_startswith_case_insensitive(line, "content-disposition:")) { + // Extract name parameter + std::string name = extract_header_param(line, "name"); + if (!name.empty()) { + current_name_ = name; } - // Extract filename if present - size_t filename_pos = line.find("filename=\""); - if (filename_pos != std::string::npos) { - filename_pos += 10; - size_t filename_end = line.find("\"", filename_pos); - if (filename_end != std::string::npos) { - current_filename_ = line.substr(filename_pos, filename_end - filename_pos); - } + // Extract filename parameter if present + std::string filename = extract_header_param(line, "filename"); + if (!filename.empty()) { + current_filename_ = filename; } } - // Parse Content-Type header - else if (line.find("Content-Type:") == 0) { - current_content_type_ = line.substr(14); - // Trim whitespace - size_t start = current_content_type_.find_first_not_of(" \t"); - if (start != std::string::npos) { - current_content_type_ = current_content_type_.substr(start); + // Parse Content-Type header (case-insensitive) + else if (str_startswith_case_insensitive(line, "content-type:")) { + // Find the colon and skip it + size_t colon_pos = line.find(':'); + if (colon_pos != std::string::npos) { + current_content_type_ = line.substr(colon_pos + 1); + // Trim leading whitespace + size_t start = current_content_type_.find_first_not_of(" \t"); + if (start != std::string::npos) { + current_content_type_ = current_content_type_.substr(start); + } else { + current_content_type_.clear(); + } + // Trim trailing whitespace + size_t end = current_content_type_.find_last_not_of(" \t\r\n"); + if (end != std::string::npos) { + current_content_type_ = current_content_type_.substr(0, end + 1); + } } } } diff --git a/esphome/components/web_server_idf/multipart_parser_utils.h b/esphome/components/web_server_idf/multipart_parser_utils.h new file mode 100644 index 0000000000..43b7ced03d --- /dev/null +++ b/esphome/components/web_server_idf/multipart_parser_utils.h @@ -0,0 +1,128 @@ +#pragma once +#ifdef USE_ESP_IDF +#ifdef USE_WEBSERVER_OTA + +#include +#include + +namespace esphome { +namespace web_server_idf { + +// Case-insensitive string comparison +inline bool str_equals_case_insensitive(const std::string &a, const std::string &b) { + if (a.length() != b.length()) { + return false; + } + for (size_t i = 0; i < a.length(); i++) { + if (tolower(a[i]) != tolower(b[i])) { + return false; + } + } + return true; +} + +// 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; + } + for (size_t i = 0; i < prefix.length(); i++) { + if (tolower(str[i]) != tolower(prefix[i])) { + return false; + } + } + return true; +} + +// 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; + } + + for (size_t i = pos; i <= haystack.length() - needle.length(); i++) { + bool match = true; + for (size_t j = 0; j < needle.length(); j++) { + if (tolower(haystack[i + j]) != tolower(needle[j])) { + match = false; + break; + } + } + if (match) { + return i; + } + } + + return std::string::npos; +} + +// 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 ""; +} + +} // namespace web_server_idf +} // namespace esphome +#endif // USE_WEBSERVER_OTA +#endif // USE_ESP_IDF \ No newline at end of file