273 lines
7.6 KiB
C++
273 lines
7.6 KiB
C++
#include <webserv/http/HttpRequest.hpp>
|
|
|
|
#include <webserv/config/ConfigManager.hpp> // for ConfigManager
|
|
#include <webserv/handler/URI.hpp> // for URI
|
|
#include <webserv/http/HttpConstants.hpp> // for CRLF, DOUBLE_CRLF
|
|
#include <webserv/log/Log.hpp> // for Log, LOCATION
|
|
#include <webserv/utils/utils.hpp> // for stoul
|
|
|
|
#include <exception> // for exception
|
|
#include <map> // for map
|
|
#include <memory> // for allocator, make_unique, unique_ptr
|
|
#include <optional> // for optional
|
|
#include <sstream> // for basic_stringstream, basic_istream, stringstream
|
|
#include <vector> // for vector
|
|
|
|
HttpRequest::HttpRequest(Client *client) : client_(client), uri_(nullptr)
|
|
{
|
|
Log::trace(LOCATION);
|
|
}
|
|
|
|
HttpRequest::~HttpRequest()
|
|
{
|
|
Log::trace(LOCATION);
|
|
}
|
|
|
|
HttpRequest::State HttpRequest::getState() const noexcept
|
|
{
|
|
return state_;
|
|
}
|
|
|
|
void HttpRequest::setState(State state)
|
|
{
|
|
if (state == State::Complete)
|
|
{
|
|
// TODO: segfault if server does not exist
|
|
std::string hostHeader = getHeaders().getHost().value_or("");
|
|
ServerConfig *serverConfig = ConfigManager::getInstance().getMatchingServerConfig(hostHeader);
|
|
if (hostHeader.empty() || serverConfig == nullptr)
|
|
{
|
|
Log::error("No matching server config found for host: " + hostHeader);
|
|
state_ = State::ParseError;
|
|
return;
|
|
}
|
|
uri_ = std::make_unique<URI>(*this, *serverConfig);
|
|
}
|
|
state_ = state;
|
|
}
|
|
|
|
const HttpHeaders &HttpRequest::getHeaders() const noexcept
|
|
{
|
|
return headers_;
|
|
}
|
|
|
|
const std::string &HttpRequest::getBody() const noexcept
|
|
{
|
|
return body_;
|
|
}
|
|
|
|
void HttpRequest::receiveData(const char *data, size_t length)
|
|
{
|
|
Log::trace(LOCATION);
|
|
buffer_.append(data, length);
|
|
parseBuffer();
|
|
}
|
|
|
|
void HttpRequest::parseBuffer()
|
|
{
|
|
Log::trace(LOCATION);
|
|
while (true)
|
|
{
|
|
try
|
|
{
|
|
switch (state_)
|
|
{
|
|
case State::RequestLine:
|
|
if (!parseBufferforRequestLine())
|
|
{
|
|
return; // Wait for more data
|
|
}
|
|
break;
|
|
case State::Headers:
|
|
if (!parseBufferforHeaders())
|
|
{
|
|
return; // Wait for more data
|
|
}
|
|
break;
|
|
case State::Body:
|
|
if (!parseBufferforBody())
|
|
{
|
|
return; // Wait for more data
|
|
}
|
|
break;
|
|
case State::Chunked:
|
|
if (!parseBufferforChunkedBody())
|
|
{
|
|
return; // Wait for more data
|
|
}
|
|
break;
|
|
case State::Complete:
|
|
Log::debug("HttpRequest::parseBuffer() request is complete");
|
|
return; // Request is complete
|
|
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()
|
|
{
|
|
Log::trace(LOCATION);
|
|
size_t pos = buffer_.find(Http::Protocol::CRLF);
|
|
if (pos == std::string::npos)
|
|
{
|
|
Log::debug("RequestLine waiting for more data");
|
|
return false;
|
|
}
|
|
std::string requestLine_ = buffer_.substr(0, pos);
|
|
|
|
buffer_.erase(0, pos + Http::Protocol::CRLF.size());
|
|
state_ = State::Headers;
|
|
|
|
std::vector<std::string> parts;
|
|
std::string part;
|
|
std::stringstream ss(requestLine_);
|
|
while (ss >> part)
|
|
{
|
|
parts.push_back(part);
|
|
}
|
|
if (parts.size() != 3)
|
|
{
|
|
Log::warning("Invalid request line: " + requestLine_);
|
|
state_ = State::ParseError; // Mark as complete to avoid further processing
|
|
return true;
|
|
}
|
|
method_ = parts[0];
|
|
target_ = parts[1];
|
|
httpVersion_ = parts[2];
|
|
Log::debug("Parsed Request Line: Method=" + method_ + " Target=" + target_ + " Version=" + httpVersion_);
|
|
return true;
|
|
}
|
|
|
|
bool HttpRequest::parseBufferforHeaders()
|
|
{
|
|
Log::trace(LOCATION);
|
|
size_t pos = buffer_.find(Http::Protocol::DOUBLE_CRLF);
|
|
if (pos == std::string::npos)
|
|
{
|
|
Log::debug("Headers waiting for more data: " + LOCATION);
|
|
return false; // Wait for more data
|
|
}
|
|
headers_.parse(buffer_.substr(0, pos + Http::Protocol::CRLF.size()));
|
|
buffer_.erase(0, pos + Http::Protocol::DOUBLE_CRLF.size());
|
|
|
|
if (this->headers_.getContentLength().value_or(0) > 0)
|
|
{
|
|
state_ = State::Body;
|
|
return true;
|
|
}
|
|
if (this->headers_.has("Transfer-Encoding") && this->headers_.get("Transfer-Encoding") == "chunked")
|
|
{
|
|
Log::debug("HttpRequest::parseBuffer() in state Headers with chunked encoding");
|
|
state_ = State::Chunked;
|
|
return true;
|
|
}
|
|
Log::debug("No body to read, marking request as complete");
|
|
setState(State::Complete);
|
|
return false; // No body to read
|
|
}
|
|
|
|
bool HttpRequest::parseBufferforChunkedBody()
|
|
{
|
|
Log::trace(LOCATION);
|
|
while (true)
|
|
{
|
|
size_t pos = buffer_.find(Http::Protocol::CRLF);
|
|
if (pos == std::string::npos)
|
|
{
|
|
Log::debug("Chunked body waiting for more data: " + LOCATION);
|
|
return false;
|
|
}
|
|
std::string chunkSizeStr = buffer_.substr(0, pos);
|
|
Log::debug("Chunk size string: " + chunkSizeStr);
|
|
size_t chunkSize = 0;
|
|
try
|
|
{
|
|
chunkSize = utils::stoul(chunkSizeStr, 16);
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
Log::warning("Invalid chunk size: " + chunkSizeStr + " - " + e.what());
|
|
setState(State::ParseError);
|
|
return false;
|
|
}
|
|
if (chunkSize == 0)
|
|
{
|
|
setState(State::Complete); // Last chunk
|
|
buffer_.erase(0, pos + Http::Protocol::CRLF.size());
|
|
return true;
|
|
}
|
|
if (buffer_.size() < pos + Http::Protocol::CRLF.size() + chunkSize + Http::Protocol::CRLF.size())
|
|
{
|
|
Log::debug("Chunked body waiting for more data: " + LOCATION);
|
|
return false;
|
|
}
|
|
body_ += buffer_.substr(pos + Http::Protocol::CRLF.size(), chunkSize);
|
|
buffer_.erase(0, pos + Http::Protocol::CRLF.size() + chunkSize + Http::Protocol::CRLF.size());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool HttpRequest::parseBufferforBody()
|
|
{
|
|
if (!headers_.getContentLength().has_value())
|
|
{
|
|
Log::warning("HttpRequest::parseBuffer() in state Body but no Content-Length header found");
|
|
setState(State::Complete);
|
|
return true;
|
|
}
|
|
Log::trace(LOCATION, {{"Content-Length", std::to_string(*headers_.getContentLength())}});
|
|
if (buffer_.size() < *headers_.getContentLength())
|
|
{
|
|
Log::debug("Body waiting for more data: " + LOCATION);
|
|
return false; // Wait for more data
|
|
}
|
|
body_ = buffer_.substr(0, *headers_.getContentLength());
|
|
buffer_.erase(0, *headers_.getContentLength());
|
|
setState(State::Complete);
|
|
|
|
return true;
|
|
}
|
|
|
|
void HttpRequest::reset()
|
|
{
|
|
Log::trace(LOCATION);
|
|
state_ = State::RequestLine;
|
|
buffer_.clear();
|
|
// requestLine_.clear();
|
|
// headers_.clear();
|
|
body_.clear();
|
|
// contentLength_ = 0;
|
|
}
|
|
|
|
const URI &HttpRequest::getUri() const noexcept
|
|
{
|
|
return *uri_;
|
|
}
|
|
|
|
const std::string &HttpRequest::getMethod() const noexcept
|
|
{
|
|
return method_;
|
|
}
|
|
|
|
const std::string &HttpRequest::getTarget() const noexcept
|
|
{
|
|
return target_;
|
|
}
|
|
|
|
const std::string &HttpRequest::getHttpVersion() const noexcept
|
|
{
|
|
return httpVersion_;
|
|
}
|
|
|
|
Client &HttpRequest::getClient() const noexcept
|
|
{
|
|
return *client_;
|
|
}
|