Logo Pico-Framework A web-first embedded framework for C++
Loading...
Searching...
No Matches
MultipartParser Class Reference

Parses and processes multipart/form-data uploads over HTTP. More...

#include <MultipartParser.h>

+ Collaboration diagram for MultipartParser:

Public Member Functions

 MultipartParser ()
 Construct a new Multipart Parser object.
 
bool handleMultipart (HttpRequest &req, HttpResponse &res)
 Begin processing the multipart upload from the socket.
 
void setBoundaryFromContentType (const std::string &contentType)
 sets the boundary from the Content-Type header.
 

Private Member Functions

bool extractFilename (const std::string &contentDisposition)
 Extract the filename from a Content-Disposition header.
 
bool processFileData (const std::string &fileData)
 Append a chunk of file data to the output file.
 
bool handleChunk (std::string &chunkData)
 Handle an incoming chunk of multipart data.
 
int appendFileToSD (const char *filename, const char *data, size_t size)
 Append file data to storage (e.g., SD card).
 
int file_exists (const std::string &filename)
 Check if a file already exists in storage.
 
int findDataStart (std::string &currentData)
 Find the start of the file content in a chunk (after headers).
 
bool handleFinalBoundary (std::string &fileData)
 Detect and handle the final multipart boundary.
 
void sendHttpResponse (int statusCode, const std::string &message)
 Send a simple HTTP response to the client.
 

Private Attributes

Tcptcp
 The client TCP connection.
 
std::string boundary
 
std::string filename
 
std::string leftoverData
 
std::string buffer
 

Static Private Attributes

static State currentState = SEARCHING_FOR_BOUNDARY
 

Detailed Description

MultipartParser is responsible for:

  • Detecting boundaries and parsing multipart headers
  • Extracting uploaded filenames
  • Writing uploaded file data to SD or other storage
  • Sending appropriate HTTP responses

Definition at line 43 of file MultipartParser.h.

Constructor & Destructor Documentation

◆ MultipartParser()

MultipartParser::MultipartParser ( )

Construct a new Multipart Parser object.

Parameters
clientSocketThe socket for the HTTP client connection.
requestThe full HTTP request object.
Parameters
clientSocketThe socket for the HTTP client connection.
requestThe full HTTP request object.

Definition at line 49 of file MultipartParser.cpp.

51{
52
53}

Member Function Documentation

◆ appendFileToSD()

int MultipartParser::appendFileToSD ( const char *  filename,
const char *  data,
size_t  size 
)
private
Parameters
filenameName of the file to write to.
dataPointer to raw data.
sizeLength of the data buffer.
Returns
0 on success, non-zero on failure.

◆ extractFilename()

bool MultipartParser::extractFilename ( const std::string &  contentDisposition)
private

Extract the filename from a Content-Disposition header.

Parameters
contentDispositionThe raw header line.
Returns
true if a valid filename is found.
Parameters
contentDispositionThe raw header line.
Returns
true if a valid filename is found.

Definition at line 259 of file MultipartParser.cpp.

260{
261 std::size_t start = contentDisposition.find("filename=\"");
262 if (start == std::string::npos)
263 return false;
264
265 start += 10;
266 std::size_t end = contentDisposition.find("\"", start);
267 if (end == std::string::npos)
268 return false;
269 // Ensure upload directory exists
270 auto storage = AppContext::get<StorageManager>();
271 if(!storage) {
272 printf("[MultipartParser] StorageManager service not available\n");
273 sendHttpResponse(500, "StorageManager service not available");
274 return false;
275 }
276 storage->mount(); // ensure mounted
277 std::string uploads(MULTIPART_UPLOAD_PATH);
278 TRACE("Checking if upload directory exists: %s\n", uploads.c_str());
279 if (!storage->exists(uploads)) {
280 TRACE("Upload directory does not exist, creating: %s\n", uploads.c_str());
281 if(!(storage->createDirectory(uploads))) {
282 TRACE("Failed to create upload directory: %s\n", uploads.c_str());
283 sendHttpResponse(500, "Failed to create upload directory");
284 return false;
285 }
286 }
287 filename = std::string(MULTIPART_UPLOAD_PATH) + "/" + contentDisposition.substr(start, end - start);
288 TRACE("checking if filename: %s exists\n", filename.c_str());
290 {
291 printf("[MultipartParser] File already exists: %s\n", filename.c_str());
292 return false;
293 }
294 return true;
295}
#define TRACE(...)
Default trace (INFO level).
Definition DebugTrace.h:187
static constexpr std::uintptr_t getTypeKey()
Definition AppContext.h:91
std::string filename
void sendHttpResponse(int statusCode, const std::string &message)
Send a simple HTTP response to the client.
int file_exists(const std::string &filename)
Check if a file already exists in storage.
#define MULTIPART_UPLOAD_PATH
This setting defines where uploads go The default is "/uploads" This is used by the MultipartParser c...

References file_exists(), filename, AppContext::getTypeKey(), MULTIPART_UPLOAD_PATH, sendHttpResponse(), and TRACE.

Referenced by handleChunk().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ file_exists()

int MultipartParser::file_exists ( const std::string &  filename)
private

Check if a file already exists in storage.

Parameters
filenameName of the file to check.
Returns
1 if exists, 0 if not.
Parameters
filenameName of the file to check.
Returns
1 if exists, 0 if not.

Definition at line 320 of file MultipartParser.cpp.

321{
323 TRACE("[MultipartParser] Checking if file exists: %s\n", filename.c_str());
324 return storage->exists(filename);
325}
Abstract base class for storage access and file operations.
virtual bool exists(const std::string &path)=0
Check whether a file or directory exists at the given path.

References StorageManager::exists(), filename, AppContext::getTypeKey(), and TRACE.

Referenced by extractFilename().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ findDataStart()

int MultipartParser::findDataStart ( std::string &  currentData)
private

Find the start of the file content in a chunk (after headers).

Parameters
currentDataThe buffer containing headers + data.
Returns
Byte offset of start of data, or -1 if not found.
Parameters
currentDataThe buffer containing headers + data.
Returns
Byte offset of start of data, or -1 if not found.

Definition at line 328 of file MultipartParser.cpp.

329{
330 std::size_t pos = currentData.find("\r\n\r\n");
331 return (pos != std::string::npos) ? static_cast<int>(pos + 4) : -1;
332}

◆ handleChunk()

bool MultipartParser::handleChunk ( std::string &  chunkData)
private

Handle an incoming chunk of multipart data.

Parameters
chunkDataThe raw data received.
Returns
true if processing should continue.
Parameters
chunkDataThe raw data received.
Returns
true if processing should continue.

Definition at line 131 of file MultipartParser.cpp.

132{
133 TRACE("Handling chunk, size: %zu bytes\n", chunkData.size());
134 const std::string boundaryPrefix = "--" + boundary;
135 const std::string finalBoundary = boundaryPrefix + "--";
136
137 while (!chunkData.empty() && currentState != COMPLETE)
138 {
139 switch (currentState)
140 {
142 {
143 size_t boundaryPos = chunkData.find(boundaryPrefix);
144 if (boundaryPos != std::string::npos)
145 {
146 size_t skip = boundaryPos + boundaryPrefix.length();
147 if (chunkData.substr(skip, 2) == "\r\n")
148 skip += 2;
149 chunkData = chunkData.substr(skip);
151 TRACE("Found initial boundary, buffer size now: %zu bytes\n", chunkData.size());
152 continue;
153 }
154 return true; // Wait for more data
155 }
156
157 case FOUND_BOUNDARY:
158 {
159 size_t headersEnd = chunkData.find("\r\n\r\n");
160 if (headersEnd == std::string::npos)
161 return true; // wait for more data
162
163 std::string headers = chunkData.substr(0, headersEnd);
164 std::istringstream stream(headers);
165 std::string line;
166 bool gotFilename = false;
167
168 while (std::getline(stream, line))
169 {
170 line.erase(std::remove(line.begin(), line.end(), '\r'), line.end());
171 if (line.find("Content-Disposition:") != std::string::npos)
172 {
173 gotFilename = extractFilename(line);
174 if (gotFilename)
175 {
176 TRACE("Filename extracted: %s\n", filename.c_str());
177 }
178 }
179 }
180
181 if (!gotFilename)
182 {
183 sendHttpResponse(400, "Invalid upload: no filename or filename exists already");
185 return false;
186 }
187
188 chunkData = chunkData.substr(headersEnd + 4); // skip headers
190 continue;
191 }
192
193 case FOUND_DATA_START:
194 {
195 size_t finalPos = chunkData.find(finalBoundary);
196 size_t nextBoundaryPos = chunkData.find(boundaryPrefix);
197
198 size_t boundaryPos = std::string::npos;
199 bool isFinal = false;
200
201 if (finalPos != std::string::npos &&
202 (nextBoundaryPos == std::string::npos || finalPos < nextBoundaryPos))
203 {
204 boundaryPos = finalPos;
205 isFinal = true;
206 }
207 else if (nextBoundaryPos != std::string::npos)
208 {
209 boundaryPos = nextBoundaryPos;
210 }
211
212 if (boundaryPos == std::string::npos)
213 {
214 // No boundary, stream all data
215 if (processFileData(chunkData))
216 {
217 chunkData.clear();
218 return true;
219 }
220 return false; // Error processing data;
221 }
222
223 // Process data up to boundary
224 size_t dataEnd = boundaryPos;
225 if (dataEnd >= 2 && chunkData.substr(dataEnd - 2, 2) == "\r\n")
226 dataEnd -= 2;
227
228 std::string fileData = chunkData.substr(0, dataEnd);
229 processFileData(fileData);
230
231 // Advance past boundary
232 size_t skip = boundaryPos + boundaryPrefix.length();
233 isFinal = (chunkData.substr(skip, 2) == "--");
234 if (isFinal)
235 skip += 2;
236
237 if (chunkData.substr(skip, 2) == "\r\n")
238 skip += 2;
239
240 chunkData = chunkData.substr(skip);
241
242 TRACE("Final boundary reached — exiting multipart parser\n");
244 break;
245
246 TRACE("Final boundary reached — exiting multipart parser\n");
247 break;
248 }
249
250 default:
251 return false;
252 }
253 }
254
255 return true;
256}
@ COMPLETE
@ FOUND_BOUNDARY
@ FOUND_DATA_START
@ SEARCHING_FOR_BOUNDARY
bool processFileData(const std::string &fileData)
Append a chunk of file data to the output file.
std::string boundary
static State currentState
bool extractFilename(const std::string &contentDisposition)
Extract the filename from a Content-Disposition header.

References boundary, COMPLETE, currentState, extractFilename(), filename, FOUND_BOUNDARY, FOUND_DATA_START, processFileData(), SEARCHING_FOR_BOUNDARY, sendHttpResponse(), and TRACE.

Referenced by handleMultipart().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ handleFinalBoundary()

bool MultipartParser::handleFinalBoundary ( std::string &  fileData)
private

Detect and handle the final multipart boundary.

Parameters
fileDataThe current chunk of file data.
Returns
true if final boundary was found and handled.
Parameters
fileDataThe current chunk of file data.
Returns
true if final boundary was found and handled.

Definition at line 335 of file MultipartParser.cpp.

336{
337 std::size_t finalBoundaryPos = fileData.find(boundary + "--");
338 if (finalBoundaryPos != std::string::npos)
339 {
340 fileData = fileData.substr(0, finalBoundaryPos);
341 return true;
342 }
343 return false;
344}

References boundary.

◆ handleMultipart()

bool MultipartParser::handleMultipart ( HttpRequest req,
HttpResponse res 
)

This method reads from the client socket in chunks, processes headers and boundaries, and writes file contents to storage.

Returns
true if upload succeeds.
false on failure (mazlformed request, existing file, or write error).

Definition at line 69 of file MultipartParser.cpp.

70{
71 tcp = req.getTcp(); // extract here — don't store in ctor
72
73 std::string contentType = req.getHeader("Content-Type");
74 setBoundaryFromContentType(contentType);
75
76 if (boundary.empty()) {
77 res.status(400).send("Missing boundary");
78 return false;
79 }
80
81 char buf[1460];
82 int len;
83
85
86 // Handle any body data included with the initial request
87 const std::string &initialBody = req.getBody();
88 if (!initialBody.empty())
89 {
90 std::string chunk = initialBody;
91 if (!handleChunk(chunk))
92 {
93 TRACE("Error handling chunk\n");
94 return false;
95 }
96 }
97
98 // Stream remaining data from socket
99 while ((len = tcp->recv(buf, sizeof(buf) - 1, HTTP_RECEIVE_TIMEOUT)) > 0)
100 {
101 buf[len] = '\0';
102 std::string chunk(buf, len);
103
104 if (!handleChunk(chunk))
105 {
106 sendHttpResponse(400, "Upload incomplete or failed");
107 TRACE("Error handling chunk\n");
108 return false;
109 }
110
111 if (currentState == COMPLETE)
112 break;
113
114 vTaskDelay(pdMS_TO_TICKS(10)); // yield to allow other tasks to run
115 }
116
117 if (currentState != COMPLETE)
118 {
119 sendHttpResponse(400, "Upload incomplete or failed");
120 return false;
121 }
122
123 // Only send 200 when we're truly done
124 TRACE("Multipart: Successfully received file '%s'\n", filename.c_str());
125 std::string filenameOnly = filename.substr(filename.find_last_of('/') + 1);
126 sendHttpResponse(200, filenameOnly);
127 return true;
128}
const std::string & getBody() const
Get the request body (copy).
std::string getHeader(const std::string &field) const
Get a specific header field (case-insensitive).
Tcp * getTcp() const
void send(const std::string &body)
Send a full response including headers and body.
HttpResponse & status(int code)
Set the HTTP status code.
Tcp * tcp
The client TCP connection.
void setBoundaryFromContentType(const std::string &contentType)
sets the boundary from the Content-Type header.
bool handleChunk(std::string &chunkData)
Handle an incoming chunk of multipart data.
int recv(char *buffer, size_t size, uint32_t timeout_ms)
Receive data from the connection.
Definition Tcp.cpp:389
#define HTTP_RECEIVE_TIMEOUT
Timeout for receiving HTTP data in milliseconds.

References boundary, COMPLETE, currentState, filename, HttpRequest::getBody(), HttpRequest::getHeader(), HttpRequest::getTcp(), handleChunk(), HTTP_RECEIVE_TIMEOUT, Tcp::recv(), SEARCHING_FOR_BOUNDARY, HttpResponse::send(), sendHttpResponse(), setBoundaryFromContentType(), HttpResponse::status(), tcp, and TRACE.

Referenced by HttpRequest::handle_multipart().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ processFileData()

bool MultipartParser::processFileData ( const std::string &  fileData)
private

Append a chunk of file data to the output file.

Parameters
fileDataData to append.
Returns
true on success.
Parameters
fileDataData to append.
Returns
true on success.

Definition at line 298 of file MultipartParser.cpp.

299{
300 TRACE("Processing file data, size: %zu bytes\n", fileData.size());
301 auto *storage = AppContext::get<StorageManager>();
302 if (!storage->appendToFile(filename, (uint8_t *)fileData.c_str(), fileData.size()))
303 {
304 if (!storage->isMounted())
305 {
306 TRACE("SD card not mounted — cannot handle multipart upload\n");
307 sendHttpResponse(500, "SD card not mounted");
308 }
309 else
310 {
311 TRACE("Failed to append file during multipart upload\n");
312 sendHttpResponse(500, "Failed to write file data");
313 }
314 return false;
315 }
316 return true;
317}

References filename, AppContext::getTypeKey(), sendHttpResponse(), and TRACE.

Referenced by handleChunk().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ sendHttpResponse()

void MultipartParser::sendHttpResponse ( int  statusCode,
const std::string &  messageOrFilename 
)
private

Send a simple HTTP response to the client.

Parameters
statusCodeHTTP status code.
messageMessage body to send.
Parameters
statusCodeHTTP status code.
messageMessage body to send.

Definition at line 347 of file MultipartParser.cpp.

347 {
348 std::ostringstream oss;
349 std::string body;
350
351 if (statusCode >= 200 && statusCode < 300) {
352 // Success: treat message as filename
353 body = R"({"success":true,"image":")" + messageOrFilename + R"("})";
354 } else {
355 std::string code = std::to_string(statusCode);
356 body = R"({"success":false,"error":{"code":")" + code + R"(","message":")" + messageOrFilename + R"("}})";
357 }
358
359 oss << "HTTP/1.1 " << statusCode << (statusCode == 200 ? " OK" : " Bad Request") << "\r\n"
360 << "Content-Type: application/json\r\n"
361 << "Content-Length: " << body.length() << "\r\n"
362 << "Connection: close\r\n\r\n"
363 << body;
364
365 std::string response = oss.str();
366 if (!tcp || !tcp->isConnected()) {
367 panic("Attempted to send on invalid socket");
368 }
369
370 tcp->send(response.c_str(), response.length());
371 vTaskDelay(pdMS_TO_TICKS(50));
372}
bool isConnected() const
Check if the socket is connected.
Definition Tcp.h:119
int send(const char *buffer, size_t size)
Send data over the connection.
Definition Tcp.cpp:279

References Tcp::isConnected(), Tcp::send(), and tcp.

Referenced by extractFilename(), handleChunk(), handleMultipart(), and processFileData().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ setBoundaryFromContentType()

void MultipartParser::setBoundaryFromContentType ( const std::string &  contentType)
Parameters
contentTypeThe Content-Type header value.

Definition at line 55 of file MultipartParser.cpp.

55 {
56 size_t boundaryPos = contentType.find("boundary=");
57 if (boundaryPos != std::string::npos) {
58 boundary = contentType.substr(boundaryPos + 9); // 9 is the length of "boundary="
59 // Remove any leading/trailing whitespace
60 boundary.erase(0, boundary.find_first_not_of(" \t\r\n"));
61 boundary.erase(boundary.find_last_not_of(" \t\r\n") + 1);
62 TRACE("Boundary set to: '%s'\n", boundary.c_str());
63 } else {
64 boundary.clear();
65 TRACE("No boundary found in Content-Type header\n");
66 }
67}

References boundary, and TRACE.

Referenced by handleMultipart().

+ Here is the caller graph for this function:

Member Data Documentation

◆ boundary

std::string MultipartParser::boundary
private

◆ buffer

std::string MultipartParser::buffer
private

Definition at line 76 of file MultipartParser.h.

◆ currentState

State MultipartParser::currentState = SEARCHING_FOR_BOUNDARY
staticprivate

Definition at line 79 of file MultipartParser.h.

Referenced by handleChunk(), and handleMultipart().

◆ filename

std::string MultipartParser::filename
private

◆ leftoverData

std::string MultipartParser::leftoverData
private

Definition at line 75 of file MultipartParser.h.

◆ tcp

Tcp* MultipartParser::tcp
private

Definition at line 72 of file MultipartParser.h.

Referenced by handleMultipart(), and sendHttpResponse().


The documentation for this class was generated from the following files: