31#include "http/MultipartParser.h"
32#include <lwip/sockets.h>
56 size_t boundaryPos = contentType.find(
"boundary=");
57 if (boundaryPos != std::string::npos) {
58 boundary = contentType.substr(boundaryPos + 9);
65 TRACE(
"No boundary found in Content-Type header\n");
73 std::string contentType = req.
getHeader(
"Content-Type");
87 const std::string &initialBody = req.
getBody();
88 if (!initialBody.empty())
90 std::string chunk = initialBody;
93 TRACE(
"Error handling chunk\n");
102 std::string chunk(buf, len);
107 TRACE(
"Error handling chunk\n");
114 vTaskDelay(pdMS_TO_TICKS(10));
124 TRACE(
"Multipart: Successfully received file '%s'\n",
filename.c_str());
133 TRACE(
"Handling chunk, size: %zu bytes\n", chunkData.size());
134 const std::string boundaryPrefix =
"--" +
boundary;
135 const std::string finalBoundary = boundaryPrefix +
"--";
143 size_t boundaryPos = chunkData.find(boundaryPrefix);
144 if (boundaryPos != std::string::npos)
146 size_t skip = boundaryPos + boundaryPrefix.length();
147 if (chunkData.substr(skip, 2) ==
"\r\n")
149 chunkData = chunkData.substr(skip);
151 TRACE(
"Found initial boundary, buffer size now: %zu bytes\n", chunkData.size());
159 size_t headersEnd = chunkData.find(
"\r\n\r\n");
160 if (headersEnd == std::string::npos)
163 std::string headers = chunkData.substr(0, headersEnd);
164 std::istringstream stream(headers);
166 bool gotFilename =
false;
168 while (std::getline(stream, line))
170 line.erase(std::remove(line.begin(), line.end(),
'\r'), line.end());
171 if (line.find(
"Content-Disposition:") != std::string::npos)
183 sendHttpResponse(400,
"Invalid upload: no filename or filename exists already");
188 chunkData = chunkData.substr(headersEnd + 4);
195 size_t finalPos = chunkData.find(finalBoundary);
196 size_t nextBoundaryPos = chunkData.find(boundaryPrefix);
198 size_t boundaryPos = std::string::npos;
199 bool isFinal =
false;
201 if (finalPos != std::string::npos &&
202 (nextBoundaryPos == std::string::npos || finalPos < nextBoundaryPos))
204 boundaryPos = finalPos;
207 else if (nextBoundaryPos != std::string::npos)
209 boundaryPos = nextBoundaryPos;
212 if (boundaryPos == std::string::npos)
224 size_t dataEnd = boundaryPos;
225 if (dataEnd >= 2 && chunkData.substr(dataEnd - 2, 2) ==
"\r\n")
228 std::string fileData = chunkData.substr(0, dataEnd);
232 size_t skip = boundaryPos + boundaryPrefix.length();
233 isFinal = (chunkData.substr(skip, 2) ==
"--");
237 if (chunkData.substr(skip, 2) ==
"\r\n")
240 chunkData = chunkData.substr(skip);
242 TRACE(
"Final boundary reached — exiting multipart parser\n");
246 TRACE(
"Final boundary reached — exiting multipart parser\n");
261 std::size_t start = contentDisposition.find(
"filename=\"");
262 if (start == std::string::npos)
266 std::size_t end = contentDisposition.find(
"\"", start);
267 if (end == std::string::npos)
272 printf(
"[MultipartParser] StorageManager service not available\n");
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());
288 TRACE(
"checking if filename: %s exists\n",
filename.c_str());
291 printf(
"[MultipartParser] File already exists: %s\n",
filename.c_str());
300 TRACE(
"Processing file data, size: %zu bytes\n", fileData.size());
302 if (!storage->appendToFile(
filename, (uint8_t *)fileData.c_str(), fileData.size()))
304 if (!storage->isMounted())
306 TRACE(
"SD card not mounted — cannot handle multipart upload\n");
311 TRACE(
"Failed to append file during multipart upload\n");
323 TRACE(
"[MultipartParser] Checking if file exists: %s\n",
filename.c_str());
330 std::size_t pos = currentData.find(
"\r\n\r\n");
331 return (pos != std::string::npos) ?
static_cast<int>(pos + 4) : -1;
337 std::size_t finalBoundaryPos = fileData.find(
boundary +
"--");
338 if (finalBoundaryPos != std::string::npos)
340 fileData = fileData.substr(0, finalBoundaryPos);
348 std::ostringstream oss;
351 if (statusCode >= 200 && statusCode < 300) {
353 body = R
"({"success":true,"image":")" + messageOrFilename + R"("})";
355 std::string code = std::to_string(statusCode);
356 body = R
"({"success":false,"error":{"code":")" + code + R"(","message":")" + messageOrFilename + R"("}})";
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"
365 std::string response = oss.str();
367 panic(
"Attempted to send on invalid socket");
370 tcp->
send(response.c_str(), response.length());
371 vTaskDelay(pdMS_TO_TICKS(50));
Macro-based debug trace system with optional SD file logging.
#define TRACE_INIT(MODULE_NAME)
Declare trace usage in a source file for a given module.
#define TRACE(...)
Default trace (INFO level).
Abstract interface for file and directory storage backends.
static constexpr std::uintptr_t getTypeKey()
Forward declaration for potential routing needs.
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).
Represents an HTTP response object.
void send(const std::string &body)
Send a full response including headers and body.
HttpResponse & status(int code)
Set the HTTP status code.
Parses and processes multipart/form-data uploads over HTTP.
Tcp * tcp
The client TCP connection.
void sendHttpResponse(int statusCode, const std::string &message)
Send a simple HTTP response to the client.
bool processFileData(const std::string &fileData)
Append a chunk of file data to the output file.
void setBoundaryFromContentType(const std::string &contentType)
sets the boundary from the Content-Type header.
MultipartParser()
Construct a new Multipart Parser object.
bool handleFinalBoundary(std::string &fileData)
Detect and handle the final multipart boundary.
int findDataStart(std::string ¤tData)
Find the start of the file content in a chunk (after headers).
bool handleChunk(std::string &chunkData)
Handle an incoming chunk of multipart data.
bool handleMultipart(HttpRequest &req, HttpResponse &res)
Begin processing the multipart upload from the socket.
int file_exists(const std::string &filename)
Check if a file already exists in storage.
static State currentState
bool extractFilename(const std::string &contentDisposition)
Extract the filename from a Content-Disposition header.
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.
bool isConnected() const
Check if the socket is connected.
int send(const char *buffer, size_t size)
Send data over the connection.
int recv(char *buffer, size_t size, uint32_t timeout_ms)
Receive data from the connection.
Delegates to user or system configuration.
#define MULTIPART_UPLOAD_PATH
This setting defines where uploads go The default is "/uploads" This is used by the MultipartParser c...
#define HTTP_RECEIVE_TIMEOUT
Timeout for receiving HTTP data in milliseconds.