feat: (better) http validation

This commit is contained in:
Quinten 2025-10-30 17:20:59 +01:00
parent a09ff469ee
commit 1b123d84e3
8 changed files with 78 additions and 12 deletions

View File

@ -81,8 +81,7 @@ void Client::request()
buffer[bytesRead] = '\0'; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)
httpRequest_->receiveData(static_cast<const char *>(buffer), static_cast<size_t>(bytesRead));
if (httpRequest_->getState() == HttpRequest::State::Complete
|| httpRequest_->getState() == HttpRequest::State::ParseError)
if (httpRequest_->getState() == HttpRequest::State::Complete)
{
Log::info("Received request: " + httpRequest_->getHttpVersion() + " " + httpRequest_->getMethod() + " "
+ httpRequest_->getTarget() + " ");
@ -92,7 +91,7 @@ void Client::request()
{"request_target", httpRequest_->getTarget()},
{"http_version", httpRequest_->getHttpVersion()},
{"headers", httpRequest_->getHeaders().toString()},
// {"body", httpRequest_->getBody()},
// {"body", httpRequest_->getBody()},
{"state", std::to_string(static_cast<uint8_t>(httpRequest_->getState()))},
});

View File

@ -34,10 +34,16 @@ void HttpRequest::setState(State state)
{
if (state == State::Complete)
{
if (! headers_.getHost().has_value())
{
client_->getHttpResponse().setError(Http::StatusCode::BAD_REQUEST);
state_ = State::ParseError;
return;
}
ServerConfig *serverConfig = getServerConfig();
if (serverConfig == nullptr)
{
Log::error("No matching server config found for host");
client_->getHttpResponse().setError(Http::StatusCode::NOT_FOUND);
state_ = State::ParseError;
return;
}
@ -135,6 +141,7 @@ bool HttpRequest::parseBufferforRequestLine()
if (parts.size() != 3)
{
Log::warning("Invalid request line: " + requestLine_);
client_->getHttpResponse().setError(Http::StatusCode::BAD_REQUEST);
state_ = State::ParseError; // Mark as complete to avoid further processing
return true;
}
@ -194,6 +201,7 @@ bool HttpRequest::parseBufferforChunkedBody()
catch (const std::exception &e)
{
Log::warning("Invalid chunk size: " + chunkSizeStr + " - " + e.what());
client_->getHttpResponse().setError(400);
setState(State::ParseError);
return false;
}

View File

@ -1,5 +1,6 @@
#pragma once
#include "webserv/http/HttpResponse.hpp"
#include <webserv/config/ServerConfig.hpp>
#include <webserv/http/HttpHeaders.hpp> // for HttpHeaders

View File

@ -13,16 +13,31 @@ void HttpResponse::addHeader(const std::string &key, const std::string &value)
void HttpResponse::appendBody(const std::vector<uint8_t> &data)
{
if (complete_)
{
Log::warning("Attempt to set body on a completed HttpResponse");
return;
}
body_.insert(body_.end(), data.begin(), data.end());
}
void HttpResponse::appendBody(const std::string &body)
{
if (complete_)
{
Log::warning("Attempt to set body on a completed HttpResponse");
return;
}
body_.insert(body_.end(), body.begin(), body.end());
}
void HttpResponse::setBody(const std::vector<uint8_t> &data) // TODO: validate headers
{
if (complete_)
{
Log::warning("Attempt to set body on a completed HttpResponse");
return;
}
body_ = data;
setComplete();
}
@ -43,6 +58,12 @@ void HttpResponse::setComplete()
complete_ = true;
}
void HttpResponse::setError(uint16_t statusCode)
{
statusCode_ = statusCode;
complete_ = true;
}
bool HttpResponse::isComplete() const noexcept
{
return complete_;

View File

@ -31,6 +31,7 @@ class HttpResponse
void setBody(const std::string &body);
void setComplete();
void setError(uint16_t statusCode);
void setStatus(uint16_t statusCode);

View File

@ -1,8 +1,9 @@
#include "webserv/log/Log.hpp"
#include <webserv/config/AConfig.hpp>
#include <webserv/http/RequestValidator.hpp>
#include <webserv/config/AConfig.hpp>
#include <cstddef>
#include <optional>
#include <string>
#include <vector>
@ -20,9 +21,43 @@ std::optional<RequestValidator::ValidationError> RequestValidator::validate() co
{
return error;
}
if (auto error = validateHttpVersion())
{
return error;
}
if (auto error = validateHostHeader())
{
return error;
}
return std::nullopt; // No validation errors
}
std::optional<RequestValidator::ValidationError> RequestValidator::validateHostHeader() const
{
if (!request->getHeaders().has("Host"))
{
return ValidationError{400, "Bad Request: Missing Host header"};
}
std::string hostHeader = request->getHeaders().get("Host");
// Basic validation: check if host header is not empty
if (hostHeader.empty())
{
return ValidationError{400, "Bad Request: Empty Host header"};
}
Log::debug("Host header validated: " + hostHeader);
return std::nullopt;
}
std::optional<RequestValidator::ValidationError> RequestValidator::validateHttpVersion() const
{
std::string httpVersion = request->getHttpVersion();
if (httpVersion != "HTTP/1.1" && httpVersion != "HTTP/1.0")
{
return ValidationError{505, "HTTP Version Not Supported"};
}
return std::nullopt;
}
std::optional<RequestValidator::ValidationError> RequestValidator::validateContentLength() const
{
size_t bodySize = request->getBody().size();
@ -48,6 +83,10 @@ std::optional<RequestValidator::ValidationError> RequestValidator::validateMetho
auto allowedMethodsOpt = config->get<std::vector<std::string>>("allowed_methods");
std::vector<std::string> allowedMethods;
if (request->getMethod().empty())
{
return ValidationError{400, "Bad Request: Empty HTTP Method"};
}
if (allowedMethodsOpt.has_value())
{
allowedMethods = std::move(*allowedMethodsOpt);

View File

@ -39,4 +39,6 @@ class RequestValidator
[[nodiscard]] std::optional<ValidationError> validateContentLength() const;
[[nodiscard]] std::optional<ValidationError> validateMethod() const;
[[nodiscard]] std::optional<ValidationError> validateHttpVersion() const;
[[nodiscard]] std::optional<ValidationError> validateHostHeader() const;
};

View File

@ -45,13 +45,8 @@ std::unique_ptr<AHandler> Router::handleRequest()
Log::trace(LOCATION);
HttpRequest &request = client_->getHttpRequest();
if (request.getState() == HttpRequest::State::ParseError)
{
Log::error("Router::handleRequest() called with incomplete request");
return nullptr;
}
HttpResponse &response = client_->getHttpResponse();
const AConfig *config = request.getUri().getConfig();
auto validator = std::make_unique<RequestValidator>(config, &request);