feat(request): catch parse errors

This commit is contained in:
Quinten Mennen 2025-09-24 13:34:07 +02:00
parent 36961fc468
commit 64f5c39fb3
4 changed files with 73 additions and 66 deletions

View File

@ -42,17 +42,19 @@ void Client::request()
buffer[bytesRead] = '\0'; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index) buffer[bytesRead] = '\0'; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
httpRequest_->receiveData(buffer, static_cast<size_t>(bytesRead)); httpRequest_->receiveData(buffer, static_cast<size_t>(bytesRead));
if (httpRequest_->getState() == HttpRequest::State::Complete) if (httpRequest_->getState() == HttpRequest::State::Complete ||
httpRequest_->getState() == HttpRequest::State::ParseError)
{
Log::info("Received complete request",
{ {
Log::info("Received complete request", {
{"request_method", httpRequest_->getMethod()}, {"request_method", httpRequest_->getMethod()},
{"request_target", httpRequest_->getTarget()}, {"request_target", httpRequest_->getTarget()},
{"http_version", httpRequest_->getHttpVersion()}, {"http_version", httpRequest_->getHttpVersion()},
{"headers", httpRequest_->getHeaders().toString()}, {"headers", httpRequest_->getHeaders().toString()},
{"body", httpRequest_->getBody()}, {"body", httpRequest_->getBody()},
{"state", std::to_string(static_cast<uint8_t>(httpRequest_->getState()))},
}); });
server_.responseReady(client_socket_->getFd()); server_.responseReady(client_socket_->getFd());
httpRequest_->reset();
} }
else else
{ {
@ -66,8 +68,20 @@ void Client::request()
std::string Client::getResponse() const std::string Client::getResponse() const
{ {
std::string response = "HTTP/1.1 200 OK\r\nContent-Length: 32\r\n\r\nHello, World!"; Log::trace(LOCATION);
response += " Server port " + std::to_string(server_config_.getPort()) + "\r\n"; std::string response = "HTTP/1.1 ";
if (httpRequest_->getState() == HttpRequest::State::ParseError)
{
response += "400 Bad Request\r\n";
}
else
{
response += "200 OK\r\n";
}
// further validation can be added here
response += "Content-Length: 18\r\n\r\n";
response += "Server port " + std::to_string(server_config_.getPort()) + "\r\n";
Log::debug("Sending response:\n" + response); Log::debug("Sending response:\n" + response);
return response; return response;
} }

View File

@ -1,3 +1,4 @@
#include <webserv/config/utils.hpp> #include <webserv/config/utils.hpp>
#include <stdexcept> #include <stdexcept>
@ -7,11 +8,15 @@ namespace utils
{ {
size_t stoul(const std::string &str, int base) size_t stoul(const std::string &str, int base)
{ {
if (str.find_first_not_of("0123456789abcdefABCDEF") != std::string::npos)
{
throw std::invalid_argument("Invalid number: " + str);
}
size_t idx = 0; size_t idx = 0;
unsigned long value = std::stoul(str, &idx, base); unsigned long value = std::stoul(str, &idx, base);
if (idx != str.length()) if (idx != str.length())
{ {
throw std::invalid_argument("Invalid characters in number: " + str); throw std::invalid_argument("Invalid number with extra characters: " + str);
} }
return value; return value;
} }

View File

@ -16,20 +16,7 @@ std::optional<size_t> HttpHeaders::getContentLength() const
{ {
return std::nullopt; return std::nullopt;
} }
if (value.find_first_not_of("0123456789") != std::string::npos)
{
Log::warning("Non-numeric Content-Length header value: " + value);
return std::nullopt;
}
try
{
return utils::stoul(value); return utils::stoul(value);
}
catch (...)
{
Log::warning("Invalid Content-Length header value: " + value);
return std::nullopt;
}
} }
std::optional<std::string> HttpHeaders::getContentType() const std::optional<std::string> HttpHeaders::getContentType() const

View File

@ -1,3 +1,5 @@
#include "webserv/config/utils.hpp"
#include <webserv/http/HttpConstants.hpp> // for CRLF, DOUBLE_CRLF #include <webserv/http/HttpConstants.hpp> // for CRLF, DOUBLE_CRLF
#include <webserv/http/HttpRequest.hpp> #include <webserv/http/HttpRequest.hpp>
#include <webserv/log/Log.hpp> // for Log, LOCATION #include <webserv/log/Log.hpp> // for Log, LOCATION
@ -46,6 +48,8 @@ void HttpRequest::receiveData(const char *data, size_t length)
void HttpRequest::parseBuffer() void HttpRequest::parseBuffer()
{ {
while (true) while (true)
{
try
{ {
switch (state_) switch (state_)
{ {
@ -79,6 +83,13 @@ void HttpRequest::parseBuffer()
case State::ParseError: Log::warning("Parse error occurred, stopping further processing"); return; case State::ParseError: Log::warning("Parse error occurred, stopping further processing"); return;
} }
} }
catch (...)
{
Log::error("Exception during parsing, marking request as ParseError");
state_ = State::ParseError;
return;
}
}
} }
bool HttpRequest::parseBufferforRequestLine() bool HttpRequest::parseBufferforRequestLine()
@ -124,10 +135,8 @@ bool HttpRequest::parseBufferforHeaders()
Log::debug("Headers waiting for more data: " + LOCATION); Log::debug("Headers waiting for more data: " + LOCATION);
return false; // Wait for more data return false; // Wait for more data
} }
// headers_ = buffer_.substr(0, pos + Http::Protocol::CRLF.size()); // Include the last \r\n
headers_.parse(buffer_.substr(0, pos + Http::Protocol::CRLF.size())); headers_.parse(buffer_.substr(0, pos + Http::Protocol::CRLF.size()));
buffer_.erase(0, pos + Http::Protocol::DOUBLE_CRLF.size()); buffer_.erase(0, pos + Http::Protocol::DOUBLE_CRLF.size());
// parseContentLength();
if (this->headers_.getContentLength().value_or(0) > 0) if (this->headers_.getContentLength().value_or(0) > 0)
{ {
@ -156,17 +165,9 @@ bool HttpRequest::parseBufferforChunkedBody()
return false; return false;
} }
std::string chunkSizeStr = buffer_.substr(0, pos); std::string chunkSizeStr = buffer_.substr(0, pos);
size_t chunkSize = 0; Log::debug("Chunk size string: " + chunkSizeStr);
try size_t chunkSize = utils::stoul(chunkSizeStr, 16);
{ Log::warning("Invalid chunk size: " + chunkSizeStr);
chunkSize = std::stoul(chunkSizeStr, nullptr, 16);
}
catch (const std::exception &e)
{
Log::warning("Invalid chunk size: " + chunkSizeStr + " (" + e.what() + ")");
state_ = State::ParseError; // Mark as complete to avoid further processing
return true;
}
if (chunkSize == 0) if (chunkSize == 0)
{ {
state_ = State::Complete; // Last chunk state_ = State::Complete; // Last chunk