#pragma once #include "esphome/core/defines.h" #if defined(USE_ESP32) && defined(USE_WEBSERVER_OTA) #include #include #include #include #include #include #include namespace esphome::web_server_idf { // Wrapper around zorxx/multipart-parser for ESP-IDF OTA uploads class MultipartReader { public: struct Part { std::string name; std::string filename; std::string content_type; }; // IMPORTANT: The data pointer in DataCallback is only valid during the callback! // The multipart parser passes pointers to its internal buffer which will be // overwritten after the callback returns. Callbacks MUST process or copy the // data immediately - storing the pointer for deferred processing will result // in use-after-free bugs. using DataCallback = std::function; using PartCompleteCallback = std::function; explicit MultipartReader(const std::string &boundary); ~MultipartReader(); // Set callbacks for handling data void set_data_callback(DataCallback &&callback) { data_callback_ = std::move(callback); } void set_part_complete_callback(PartCompleteCallback &&callback) { part_complete_callback_ = std::move(callback); } // Parse incoming data size_t parse(const char *data, size_t len); // Get current part info const Part &get_current_part() const { return current_part_; } // Check if we found a file upload bool has_file() const { return !current_part_.filename.empty(); } private: static int on_header_field(multipart_parser *parser, const char *at, size_t length); static int on_header_value(multipart_parser *parser, const char *at, size_t length); static int on_part_data(multipart_parser *parser, const char *at, size_t length); static int on_part_data_end(multipart_parser *parser); multipart_parser *parser_{nullptr}; multipart_parser_settings settings_{}; Part current_part_; std::string current_header_field_; DataCallback data_callback_; PartCompleteCallback part_complete_callback_; void process_header_(const char *value, size_t length); }; // ========== Utility Functions ========== // Case-insensitive string prefix check bool str_startswith_case_insensitive(const char *str, size_t str_len, const char *prefix); // Extract a parameter value from a header line // Handles both quoted and unquoted values // Assigns to out if found, clears out otherwise void extract_header_param(const char *header, size_t header_len, const char *param, std::string &out); // Parse boundary from Content-Type header // Returns true if boundary found, false otherwise // boundary_start and boundary_len will point to the boundary value bool parse_multipart_boundary(const char *content_type, const char **boundary_start, size_t *boundary_len); // Trim whitespace from both ends, assign result to out void str_trim(const char *str, size_t len, std::string &out); } // namespace esphome::web_server_idf #endif // defined(USE_ESP32) && defined(USE_WEBSERVER_OTA)