refactor: improve UploadHandler boundary extraction and add utility for quoted values

This commit is contained in:
Quinten 2025-11-12 13:09:42 +01:00
parent 59e29f1639
commit 6c206fa756
4 changed files with 51 additions and 62 deletions

View File

@ -44,17 +44,16 @@ void UploadHandler::handle()
return;
}
// TODO: So wtf does this do for non-multipart uploads?
// TODO: Tester expects 200 OK for non-multipart uploads - weird but sure, okay
if (contentType->find("multipart/form-data") == std::string::npos)
{
// For application/x-www-form-urlencoded or other types, just return success
// The upload endpoint can accept form data without files
Log::debug("Upload request with non-multipart Content-Type: " + *contentType);
response_.setStatus(200);
response_.addHeader("Content-Type", "application/json");
response_.setBody("{\"success\": true, \"message\": \"Form data received\"}\n");
return;
}
if (!FileUtils::isDirectory(uploadStore_))
{
ErrorHandler::createErrorResponse(Http::StatusCode::FORBIDDEN,
@ -82,37 +81,6 @@ void UploadHandler::handleTimeout()
ErrorHandler::createErrorResponse(Http::StatusCode::GATEWAY_TIMEOUT, response_);
}
std::string UploadHandler::extractBoundary(const std::string &contentType) const
{
Log::trace(LOCATION);
size_t boundaryPos = contentType.find("boundary=");
if (boundaryPos == std::string::npos)
{
throw std::runtime_error("No boundary found in Content-Type");
}
std::string boundary = contentType.substr(boundaryPos + std::strlen("boundary=")); // "boundary=" is 9 chars
// Remove quotes if present
if (!boundary.empty() && boundary[0] == '"')
{
boundary = boundary.substr(1);
size_t endQuote = boundary.find('"');
if (endQuote == std::string::npos)
{
throw std::runtime_error("Malformed boundary in Content-Type");
}
boundary = boundary.substr(0, endQuote);
}
// Remove any trailing characters after semicolon or whitespace
size_t endPos = boundary.find_first_of("; \t\r\n");
if (endPos != std::string::npos)
{
boundary = boundary.substr(0, endPos);
}
Log::debug("Extracted boundary: " + boundary);
return boundary;
}
void UploadHandler::parseMultipart()
{
Log::trace(LOCATION);
@ -181,6 +149,27 @@ void UploadHandler::parseMultipart()
Log::info("Parsed " + std::to_string(uploadedFiles_.size()) + " file(s) from multipart form data");
}
std::string UploadHandler::extractBoundary(const std::string &contentType)
{
Log::trace(LOCATION);
size_t boundaryPos = contentType.find("boundary=");
if (boundaryPos == std::string::npos)
{
throw std::runtime_error("No boundary found in Content-Type");
}
std::string boundary = contentType.substr(boundaryPos + std::strlen("boundary=")); // "boundary=" is 9 chars
boundary = utils::extractQuotedValue(boundary);
if (boundary.empty())
{
throw std::runtime_error("Malformed boundary in Content-Type");
}
// Remove any trailing characters after semicolon or whitespace
boundary = utils::trim(boundary, "\t\r\n ");
Log::debug("Extracted boundary: " + boundary);
return boundary;
}
bool UploadHandler::decodeSection(const std::string &part)
{
Log::trace(LOCATION);
@ -280,7 +269,7 @@ std::string UploadHandler::getHeaderValue(const std::string &headers, const std:
return "";
}
std::string UploadHandler::getFileName(const std::string &disposition) const
std::string UploadHandler::getFileName(const std::string &disposition)
{
// Look for filename="..." or filename*=UTF-8''...
size_t filenamePos = disposition.find("filename=");
@ -290,31 +279,18 @@ std::string UploadHandler::getFileName(const std::string &disposition) const
}
// TODO: strlen is extra function call, but magic number otherwise
std::string filename = disposition.substr(filenamePos + std::strlen("filename="));
// TODO: DRY, this is similar to boundary extraction
if (!filename.empty() && filename[0] == '"')
filename = utils::extractQuotedValue(filename);
if (filename.empty())
{
filename = filename.substr(1);
size_t endQuote = filename.find('"');
if (endQuote != std::string::npos)
{
filename = filename.substr(0, endQuote);
}
else
{
// Malformed filename
Log::warning("Malformed filename in Content-Disposition");
return "";
}
}
else
{
// Unquoted - take until semicolon or end
size_t endPos = filename.find(';');
if (endPos != std::string::npos)
{
filename = filename.substr(0, endPos);
}
}
return utils::trim(filename);
}

View File

@ -44,14 +44,12 @@ class UploadHandler : public AHandler
bool save(UploadedFile &info, const std::vector<uint8_t> &data);
std::string sanitizeFilename(const std::string &filename) const;
std::string generateFilename(const std::string &baseFilename) const;
// void sendSuccessResponse();
// void sendErrorResponse(uint16_t statusCode, const std::string &message);
// Multipart parsing helpers
std::string extractBoundary(const std::string &contentType) const;
[[nodiscard]] static std::string extractBoundary(const std::string &contentType);
bool decodeSection(const std::string &part);
std::string getHeaderValue(const std::string &headers, const std::string &headerName) const;
std::string getFileName(const std::string &disposition) const;
[[nodiscard]] std::string getHeaderValue(const std::string &headers, const std::string &key) const;
[[nodiscard]] static std::string getFileName(const std::string &disposition);
std::string getFieldName(const std::string &disposition) const;
static const std::string DEFAULT_UPLOAD_STORE;

View File

@ -51,6 +51,21 @@ std::string trimSemi(const std::string &str)
return str;
}
std::string extractQuotedValue(const std::string &str)
{
size_t first = str.find('"');
if (first == std::string::npos)
{
return str;
}
size_t second = str.find('"', first + 1);
if (second == std::string::npos)
{
return ""; // No closing quote found, return empty string
}
return str.substr(first + 1, second - first - 1);
}
size_t findCorrespondingClosingBrace(const std::string &str, size_t openPos)
{
int braceCount = 1;

View File

@ -11,8 +11,8 @@ namespace utils
{
size_t stoul(const std::string &str, int base = 10);
std::string trimSemi(const std::string &str);
std::string trim(const std::string &str, const std::string &charset = " \t\n\r");
std::string extractQuotedValue(const std::string &str);
size_t findCorrespondingClosingBrace(const std::string &str, size_t openPos);
void removeEmptyLines(std::string &str);
void removeComments(std::string &str);