Files
esphome/esphome/components/web_server_idf/multipart_parser.cpp
T

228 lines
6.2 KiB
C++

#ifdef USE_ESP_IDF
#ifdef USE_WEBSERVER_OTA
#include "multipart_parser.h"
#include "esphome/core/log.h"
namespace esphome {
namespace web_server_idf {
static const char *const TAG = "multipart_parser";
bool MultipartParser::parse(const uint8_t *data, size_t len) {
// Append new data to buffer
buffer_.insert(buffer_.end(), data, data + len);
while (state_ != DONE && state_ != ERROR && !buffer_.empty()) {
switch (state_) {
case BOUNDARY_SEARCH:
if (!find_boundary()) {
return false;
}
state_ = HEADERS;
break;
case HEADERS:
if (!parse_headers()) {
return false;
}
state_ = CONTENT;
content_start_ = 0; // Content starts at current buffer position
break;
case CONTENT:
if (!extract_content()) {
return false;
}
break;
default:
break;
}
}
return part_ready_;
}
bool MultipartParser::get_current_part(Part &part) const {
if (!part_ready_ || content_length_ == 0) {
return false;
}
part.name = current_name_;
part.filename = current_filename_;
part.content_type = current_content_type_;
part.data = buffer_.data() + content_start_;
part.length = content_length_;
return true;
}
void MultipartParser::consume_part() {
if (!part_ready_) {
return;
}
// Remove consumed data from buffer
if (content_start_ + content_length_ < buffer_.size()) {
buffer_.erase(buffer_.begin(), buffer_.begin() + content_start_ + content_length_);
} else {
buffer_.clear();
}
// Reset for next part
part_ready_ = false;
content_start_ = 0;
content_length_ = 0;
current_name_.clear();
current_filename_.clear();
current_content_type_.clear();
// Look for next boundary
state_ = BOUNDARY_SEARCH;
}
void MultipartParser::reset() {
buffer_.clear();
state_ = BOUNDARY_SEARCH;
part_ready_ = false;
content_start_ = 0;
content_length_ = 0;
current_name_.clear();
current_filename_.clear();
current_content_type_.clear();
}
bool MultipartParser::find_boundary() {
// Look for boundary in buffer
size_t boundary_pos = find_pattern(reinterpret_cast<const uint8_t *>(boundary_.c_str()), boundary_.length());
if (boundary_pos == std::string::npos) {
// Keep some data for next iteration to handle split boundaries
if (buffer_.size() > boundary_.length() + 4) {
buffer_.erase(buffer_.begin(), buffer_.end() - boundary_.length() - 4);
}
return false;
}
// Remove everything up to and including the boundary
buffer_.erase(buffer_.begin(), buffer_.begin() + boundary_pos + boundary_.length());
// Skip CRLF after boundary
if (buffer_.size() >= 2 && buffer_[0] == '\r' && buffer_[1] == '\n') {
buffer_.erase(buffer_.begin(), buffer_.begin() + 2);
}
// Check if this is the end boundary
if (buffer_.size() >= 2 && buffer_[0] == '-' && buffer_[1] == '-') {
state_ = DONE;
return false;
}
return true;
}
bool MultipartParser::parse_headers() {
while (true) {
std::string line = read_line();
if (line.empty()) {
// Check if we have enough data for a line
auto crlf_pos = find_pattern(reinterpret_cast<const uint8_t *>("\r\n"), 2);
if (crlf_pos == std::string::npos) {
return false; // Need more data
}
// Empty line means headers are done
buffer_.erase(buffer_.begin(), buffer_.begin() + 2);
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);
}
}
// 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);
}
}
}
// 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);
}
}
}
}
bool MultipartParser::extract_content() {
// Look for next boundary
std::string search_boundary = "\r\n" + boundary_;
size_t boundary_pos =
find_pattern(reinterpret_cast<const uint8_t *>(search_boundary.c_str()), search_boundary.length());
if (boundary_pos != std::string::npos) {
// Found complete part
content_length_ = boundary_pos - content_start_;
part_ready_ = true;
return true;
}
// No boundary found yet, but we might have partial content
// Keep enough bytes to ensure we don't split a boundary
size_t safe_length = buffer_.size();
if (safe_length > search_boundary.length() + 4) {
safe_length -= search_boundary.length() + 4;
if (safe_length > content_start_) {
content_length_ = safe_length - content_start_;
// We have partial content but not complete yet
return false;
}
}
return false;
}
std::string MultipartParser::read_line() {
auto crlf_pos = find_pattern(reinterpret_cast<const uint8_t *>("\r\n"), 2);
if (crlf_pos == std::string::npos) {
return "";
}
std::string line(buffer_.begin(), buffer_.begin() + crlf_pos);
buffer_.erase(buffer_.begin(), buffer_.begin() + crlf_pos + 2);
return line;
}
size_t MultipartParser::find_pattern(const uint8_t *pattern, size_t pattern_len, size_t start) const {
if (buffer_.size() < pattern_len + start) {
return std::string::npos;
}
for (size_t i = start; i <= buffer_.size() - pattern_len; ++i) {
if (memcmp(buffer_.data() + i, pattern, pattern_len) == 0) {
return i;
}
}
return std::string::npos;
}
} // namespace web_server_idf
} // namespace esphome
#endif // USE_WEBSERVER_OTA
#endif // USE_ESP_IDF