This commit is contained in:
J. Nick Koston 2025-06-29 10:57:00 -05:00
parent 9047b02c92
commit 614a2f66a3
No known key found for this signature in database
2 changed files with 157 additions and 25 deletions

View File

@ -1,6 +1,7 @@
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
#ifdef USE_WEBSERVER_OTA #ifdef USE_WEBSERVER_OTA
#include "multipart_parser.h" #include "multipart_parser.h"
#include "multipart_parser_utils.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
namespace esphome { namespace esphome {
@ -141,35 +142,38 @@ bool MultipartParser::parse_headers() {
return true; return true;
} }
// Parse Content-Disposition header // Parse Content-Disposition header (case-insensitive)
if (line.find("Content-Disposition:") == 0) { if (str_startswith_case_insensitive(line, "content-disposition:")) {
// Extract name // Extract name parameter
size_t name_pos = line.find("name=\""); std::string name = extract_header_param(line, "name");
if (name_pos != std::string::npos) { if (!name.empty()) {
name_pos += 6; current_name_ = name;
size_t name_end = line.find("\"", name_pos);
if (name_end != std::string::npos) {
current_name_ = line.substr(name_pos, name_end - name_pos);
}
} }
// Extract filename if present // Extract filename parameter if present
size_t filename_pos = line.find("filename=\""); std::string filename = extract_header_param(line, "filename");
if (filename_pos != std::string::npos) { if (!filename.empty()) {
filename_pos += 10; current_filename_ = filename;
size_t filename_end = line.find("\"", filename_pos);
if (filename_end != std::string::npos) {
current_filename_ = line.substr(filename_pos, filename_end - filename_pos);
}
} }
} }
// Parse Content-Type header // Parse Content-Type header (case-insensitive)
else if (line.find("Content-Type:") == 0) { else if (str_startswith_case_insensitive(line, "content-type:")) {
current_content_type_ = line.substr(14); // Find the colon and skip it
// Trim whitespace size_t colon_pos = line.find(':');
size_t start = current_content_type_.find_first_not_of(" \t"); if (colon_pos != std::string::npos) {
if (start != std::string::npos) { current_content_type_ = line.substr(colon_pos + 1);
current_content_type_ = current_content_type_.substr(start); // 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);
}
} }
} }
} }

View File

@ -0,0 +1,128 @@
#pragma once
#ifdef USE_ESP_IDF
#ifdef USE_WEBSERVER_OTA
#include <string>
#include <cctype>
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 &param) {
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