ESPHome 2025.7.1
Loading...
Searching...
No Matches
multipart.cpp
Go to the documentation of this file.
2#if defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA)
3#include "multipart.h"
4#include "utils.h"
5#include "esphome/core/log.h"
6#include <cstring>
7#include "multipart_parser.h"
8
9namespace esphome {
10namespace web_server_idf {
11
12static const char *const TAG = "multipart";
13
14// ========== MultipartReader Implementation ==========
15
16MultipartReader::MultipartReader(const std::string &boundary) {
17 // Initialize settings with callbacks
18 memset(&settings_, 0, sizeof(settings_));
19 settings_.on_header_field = on_header_field;
20 settings_.on_header_value = on_header_value;
21 settings_.on_part_data = on_part_data;
22 settings_.on_part_data_end = on_part_data_end;
23
24 ESP_LOGV(TAG, "Initializing multipart parser with boundary: '%s' (len: %zu)", boundary.c_str(), boundary.length());
25
26 // Create parser with boundary
27 parser_ = multipart_parser_init(boundary.c_str(), &settings_);
28 if (parser_) {
29 multipart_parser_set_data(parser_, this);
30 } else {
31 ESP_LOGE(TAG, "Failed to initialize multipart parser");
32 }
33}
34
36 if (parser_) {
37 multipart_parser_free(parser_);
38 }
39}
40
41size_t MultipartReader::parse(const char *data, size_t len) {
42 if (!parser_) {
43 ESP_LOGE(TAG, "Parser not initialized");
44 return 0;
45 }
46
47 size_t parsed = multipart_parser_execute(parser_, data, len);
48
49 if (parsed != len) {
50 ESP_LOGW(TAG, "Parser consumed %zu of %zu bytes - possible error", parsed, len);
51 }
52
53 return parsed;
54}
55
56void MultipartReader::process_header_(const char *value, size_t length) {
57 // Process the completed header (field + value pair)
58 std::string value_str(value, length);
59
60 if (str_startswith_case_insensitive(current_header_field_, "content-disposition")) {
61 // Parse name and filename from Content-Disposition
62 current_part_.name = extract_header_param(value_str, "name");
63 current_part_.filename = extract_header_param(value_str, "filename");
64 } else if (str_startswith_case_insensitive(current_header_field_, "content-type")) {
65 current_part_.content_type = str_trim(value_str);
66 }
67
68 // Clear field for next header
69 current_header_field_.clear();
70}
71
72int MultipartReader::on_header_field(multipart_parser *parser, const char *at, size_t length) {
73 MultipartReader *reader = static_cast<MultipartReader *>(multipart_parser_get_data(parser));
74 reader->current_header_field_.assign(at, length);
75 return 0;
76}
77
78int MultipartReader::on_header_value(multipart_parser *parser, const char *at, size_t length) {
79 MultipartReader *reader = static_cast<MultipartReader *>(multipart_parser_get_data(parser));
80 reader->process_header_(at, length);
81 return 0;
82}
83
84int MultipartReader::on_part_data(multipart_parser *parser, const char *at, size_t length) {
85 MultipartReader *reader = static_cast<MultipartReader *>(multipart_parser_get_data(parser));
86 // Only process file uploads
87 if (reader->has_file() && reader->data_callback_) {
88 // IMPORTANT: The 'at' pointer points to data within the parser's input buffer.
89 // This data is only valid during this callback. The callback handler MUST
90 // process or copy the data immediately - it cannot store the pointer for
91 // later use as the buffer will be overwritten.
92 reader->data_callback_(reinterpret_cast<const uint8_t *>(at), length);
93 }
94 return 0;
95}
96
97int MultipartReader::on_part_data_end(multipart_parser *parser) {
98 MultipartReader *reader = static_cast<MultipartReader *>(multipart_parser_get_data(parser));
99 ESP_LOGV(TAG, "Part data end");
100 if (reader->part_complete_callback_) {
101 reader->part_complete_callback_();
102 }
103 // Clear part info for next part
104 reader->current_part_ = Part{};
105 return 0;
106}
107
108// ========== Utility Functions ==========
109
110// Case-insensitive string prefix check
111bool str_startswith_case_insensitive(const std::string &str, const std::string &prefix) {
112 if (str.length() < prefix.length()) {
113 return false;
114 }
115 return str_ncmp_ci(str.c_str(), prefix.c_str(), prefix.length());
116}
117
118// Extract a parameter value from a header line
119// Handles both quoted and unquoted values
120std::string extract_header_param(const std::string &header, const std::string &param) {
121 size_t search_pos = 0;
122
123 while (search_pos < header.length()) {
124 // Look for param name
125 const char *found = stristr(header.c_str() + search_pos, param.c_str());
126 if (!found) {
127 return "";
128 }
129 size_t pos = found - header.c_str();
130
131 // Check if this is a word boundary (not part of another parameter)
132 if (pos > 0 && header[pos - 1] != ' ' && header[pos - 1] != ';' && header[pos - 1] != '\t') {
133 search_pos = pos + 1;
134 continue;
135 }
136
137 // Move past param name
138 pos += param.length();
139
140 // Skip whitespace and find '='
141 while (pos < header.length() && (header[pos] == ' ' || header[pos] == '\t')) {
142 pos++;
143 }
144
145 if (pos >= header.length() || header[pos] != '=') {
146 search_pos = pos;
147 continue;
148 }
149
150 pos++; // Skip '='
151
152 // Skip whitespace after '='
153 while (pos < header.length() && (header[pos] == ' ' || header[pos] == '\t')) {
154 pos++;
155 }
156
157 if (pos >= header.length()) {
158 return "";
159 }
160
161 // Check if value is quoted
162 if (header[pos] == '"') {
163 pos++;
164 size_t end = header.find('"', pos);
165 if (end != std::string::npos) {
166 return header.substr(pos, end - pos);
167 }
168 // Malformed - no closing quote
169 return "";
170 }
171
172 // Unquoted value - find the end (semicolon, comma, or end of string)
173 size_t end = pos;
174 while (end < header.length() && header[end] != ';' && header[end] != ',' && header[end] != ' ' &&
175 header[end] != '\t') {
176 end++;
177 }
178
179 return header.substr(pos, end - pos);
180 }
181
182 return "";
183}
184
185// Parse boundary from Content-Type header
186// Returns true if boundary found, false otherwise
187// boundary_start and boundary_len will point to the boundary value
188bool parse_multipart_boundary(const char *content_type, const char **boundary_start, size_t *boundary_len) {
189 if (!content_type) {
190 return false;
191 }
192
193 // Check for multipart/form-data (case-insensitive)
194 if (!stristr(content_type, "multipart/form-data")) {
195 return false;
196 }
197
198 // Look for boundary parameter
199 const char *b = stristr(content_type, "boundary=");
200 if (!b) {
201 return false;
202 }
203
204 const char *start = b + 9; // Skip "boundary="
205
206 // Skip whitespace
207 while (*start == ' ' || *start == '\t') {
208 start++;
209 }
210
211 if (!*start) {
212 return false;
213 }
214
215 // Find end of boundary
216 const char *end = start;
217 if (*end == '"') {
218 // Quoted boundary
219 start++;
220 end++;
221 while (*end && *end != '"') {
222 end++;
223 }
224 *boundary_len = end - start;
225 } else {
226 // Unquoted boundary
227 while (*end && *end != ' ' && *end != ';' && *end != '\r' && *end != '\n' && *end != '\t') {
228 end++;
229 }
230 *boundary_len = end - start;
231 }
232
233 if (*boundary_len == 0) {
234 return false;
235 }
236
237 *boundary_start = start;
238
239 return true;
240}
241
242// Trim whitespace from both ends of a string
243std::string str_trim(const std::string &str) {
244 size_t start = str.find_first_not_of(" \t\r\n");
245 if (start == std::string::npos) {
246 return "";
247 }
248 size_t end = str.find_last_not_of(" \t\r\n");
249 return str.substr(start, end - start + 1);
250}
251
252} // namespace web_server_idf
253} // namespace esphome
254#endif // defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA)
MultipartReader(const std::string &boundary)
Definition multipart.cpp:16
size_t parse(const char *data, size_t len)
Definition multipart.cpp:41
const char *const TAG
Definition spi.cpp:8
bool str_startswith_case_insensitive(const std::string &str, const std::string &prefix)
bool parse_multipart_boundary(const char *content_type, const char **boundary_start, size_t *boundary_len)
std::string extract_header_param(const std::string &header, const std::string &param)
bool str_ncmp_ci(const char *s1, const char *s2, size_t n)
Definition utils.cpp:94
std::string str_trim(const std::string &str)
const char * stristr(const char *haystack, const char *needle)
Definition utils.cpp:104
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:229
uint8_t end[39]
Definition sun_gtil2.cpp:17
uint16_t length
Definition tt21100.cpp:0