one more parser

This commit is contained in:
2026-09-12 08:29:44 +02:00
parent 751b54db18
commit 72d072bb6c
5 changed files with 287 additions and 4 deletions
+3 -4
View File
@@ -55,9 +55,8 @@ CLANG_STDLIB ?= -stdlib=libc++
SRCS := src/Log.cpp src/main.cpp src/Server.cpp \
src/config/Config.cpp src/config/ConfigurationParser.cpp src/config/LocationConfig.cpp src/config/ServerConfig.cpp \
src/event/Epoll.cpp src/event/EventToken.cpp \
src/net/ByteBuffer.cpp src/net/Connection.cpp src/net/FileDescriptor.cpp src/net/Listener.cpp src/net/ListenerPlan.cpp src/net/PauseMask.cpp src/net/SlotPool.cpp
# src/http/HttpStatus.cpp \
src/net/ByteBuffer.cpp src/net/Connection.cpp src/net/FileDescriptor.cpp src/net/Listener.cpp src/net/ListenerPlan.cpp src/net/PauseMask.cpp src/net/SlotPool.cpp \
src/http/Request.cpp src/http/RequestParser.cpp
OBJS := $(SRCS:$(SRC_DIR)/%.cpp=$(OBJ_DIR)/%.o)
@@ -67,7 +66,7 @@ SRCS := src/Log.cpp src/main.cpp src/Server.cpp \
DEPS := $(OBJS:.o=.d)
HDRS := include/config/Config.hpp include/config/ConfigurationParser.hpp include/config/LocationConfig.hpp include/config/ServerConfig.hpp \
include/event/Epoll.hpp include/event/EventToken.hpp \
include/http/HttpStatus.hpp \
include/http/HttpStatus.hpp include/http/Request.hpp include/http/RequestParser.hpp \
include/net/ByteBuffer.hpp include/net/Connection.hpp include/net/FileDescriptor.hpp include/net/Listener.hpp include/net/ListenerPlan.hpp include/net/PauseMask.hpp include/net/SlotPool.hpp \
include/Log.hpp include/Result.hpp include/Server.hpp \
+40
View File
@@ -0,0 +1,40 @@
/*********************************/
/* */
/* o.riabenkyi@gmail.com */
/* */
/*********************************/
#ifndef REQUEST_HPP
#define REQUEST_HPP
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace webserv {
enum class Method { Get, Post, Delete };
const char* toString(Method method) noexcept;
bool headerNameEquals(std::string_view a, std::string_view b) noexcept;
enum class HttpVersion { Http10, Http11 };
struct Request {
Method method;
std::string target;
HttpVersion version;
std::vector<std::pair<std::string, std::string>> headers;
std::vector<char> body;
std::optional<std::string_view> header(std::string_view name) const noexcept;
};
}
#endif
+29
View File
@@ -0,0 +1,29 @@
/*********************************/
/* */
/* o.riabenkyi@gmail.com */
/* */
/*********************************/
#ifndef REQUESTPARSER_HPP
#define REQUESTPARSER_HPP
#include <cstddef>
#include <optional>
#include <span>
#include "Result.hpp"
#include "http/Request.hpp"
namespace webserv {
struct ParsedRequest {
Request request;
std::size_t consumed;
};
[[nodiscard]] std::optional<Result<ParsedRequest>> parseRequest(
std::span<const char> data, std::size_t limit);
} // namespace webserv
#endif
+44
View File
@@ -0,0 +1,44 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* Request.cpp :+: :+: :+: */
/* +:+ +:+ +:+ */
/* +#+#+#+#+#+ +#+ */
/* */
/* ************************************************************************** */
#include "http/Request.hpp"
#include <algorithm>
#include <cctype>
namespace webserv {
const char* toString(Method method) noexcept {
switch (method) {
case Method::Get:
return "GET";
case Method::Post:
return "POST";
case Method::Delete:
return "DELETE";
}
return "GET";
}
bool headerNameEquals(std::string_view a, std::string_view b) noexcept {
return std::ranges::equal(a, b, [](unsigned char x, unsigned char y) {
return std::tolower(x) == std::tolower(y);
});
}
std::optional<std::string_view> Request::header(std::string_view name) const noexcept {
for (const auto& [fieldName, value] : headers) {
if (headerNameEquals(fieldName, name)) {
return std::string_view(value);
}
}
return std::nullopt;
}
} // namespace webserv
+171
View File
@@ -0,0 +1,171 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* RequestParser.cpp :+: :+: :+: */
/* +:+ +:+ +:+ */
/* +#+#+#+#+#+ +#+ */
/* */
/* ************************************************************************** */
#include "http/RequestParser.hpp"
#include <array>
#include <charconv>
#include <string>
#include <string_view>
#include <utility>
#include "http/HttpStatus.hpp"
namespace webserv {
namespace {
std::optional<Method> parseMethod(std::string_view token) {
if (token == "GET") return Method::Get;
if (token == "POST") return Method::Post;
if (token == "DELETE") return Method::Delete;
return std::nullopt;
}
std::optional<HttpVersion> parseVersion(std::string_view token) {
if (token == "HTTP/1.1") return HttpVersion::Http11;
if (token == "HTTP/1.0") return HttpVersion::Http10;
return std::nullopt;
}
std::string_view trim(std::string_view value) {
while (!value.empty() && (value.front() == ' ' || value.front() == '\t'))
value.remove_prefix(1);
while (!value.empty() && (value.back() == ' ' || value.back() == '\t'))
value.remove_suffix(1);
return value;
}
std::optional<std::array<std::string_view, 3>> splitRequestLine(std::string_view line) {
const std::size_t firstSpace = line.find(' ');
if (firstSpace == std::string_view::npos) return std::nullopt;
const std::size_t secondSpace = line.find(' ', firstSpace + 1);
if (secondSpace == std::string_view::npos) return std::nullopt;
if (line.find(' ', secondSpace + 1) != std::string_view::npos) return std::nullopt;
return std::array<std::string_view, 3>{
line.substr(0, firstSpace),
line.substr(firstSpace + 1, secondSpace - firstSpace - 1),
line.substr(secondSpace + 1)};
}
std::optional<std::pair<std::string, std::string>> parseHeaderLine(std::string_view line) {
const std::size_t colon = line.find(':');
if (colon == std::string_view::npos || colon == 0) return std::nullopt;
const std::string_view name = line.substr(0, colon);
if (name.find(' ') != std::string_view::npos || name.find('\t') != std::string_view::npos)
return std::nullopt;
const std::string_view value = trim(line.substr(colon + 1));
return std::pair<std::string, std::string>(std::string(name), std::string(value));
}
std::optional<std::size_t> parseContentLength(std::string_view value) {
if (value.empty()) return std::nullopt;
std::size_t result = 0;
const auto parsed = std::from_chars(value.data(), value.data() + value.size(), result);
if (parsed.ec != std::errc{} || parsed.ptr != value.data() + value.size())
return std::nullopt;
return result;
}
} // namespace
std::optional<Result<ParsedRequest>> parseRequest(std::span<const char> data, std::size_t limit) {
const std::string_view view(data.data(), data.size());
const std::size_t headEnd = view.find("\r\n\r\n");
if (headEnd == std::string_view::npos) {
if (data.size() >= limit)
return Result<ParsedRequest>(std::unexpected(HttpStatus::RequestHeaderFieldsTooLarge));
return std::nullopt;
}
const std::size_t headBytes = headEnd + 4;
if (headBytes > limit)
return Result<ParsedRequest>(std::unexpected(HttpStatus::RequestHeaderFieldsTooLarge));
std::string_view remaining = view.substr(0, headEnd);
const std::size_t lineEnd = remaining.find("\r\n");
const std::string_view requestLine = remaining.substr(0, lineEnd);
remaining = (lineEnd == std::string_view::npos) ? std::string_view()
: remaining.substr(lineEnd + 2);
const auto fields = splitRequestLine(requestLine);
if (!fields.has_value())
return Result<ParsedRequest>(std::unexpected(HttpStatus::BadRequest));
const auto method = parseMethod((*fields)[0]);
if (!method.has_value())
return Result<ParsedRequest>(std::unexpected(HttpStatus::NotImplemented));
const std::string_view target = (*fields)[1];
if (target.empty() || target.front() != '/')
return Result<ParsedRequest>(std::unexpected(HttpStatus::BadRequest));
const auto version = parseVersion((*fields)[2]);
if (!version.has_value())
return Result<ParsedRequest>(std::unexpected(HttpStatus::HttpVersionNotSupported));
Request request;
request.method = *method;
request.target = std::string(target);
request.version = *version;
while (!remaining.empty()) {
const std::size_t next = remaining.find("\r\n");
const std::string_view headerLine = remaining.substr(0, next);
remaining = (next == std::string_view::npos) ? std::string_view()
: remaining.substr(next + 2);
auto field = parseHeaderLine(headerLine);
if (!field.has_value())
return Result<ParsedRequest>(std::unexpected(HttpStatus::BadRequest));
request.headers.push_back(std::move(*field));
}
if (request.version == HttpVersion::Http11 && !request.header("Host").has_value())
return Result<ParsedRequest>(std::unexpected(HttpStatus::BadRequest));
bool hasTransferEncoding = false;
std::optional<std::size_t> contentLength;
for (const auto& [name, value] : request.headers) {
if (headerNameEquals(name, "transfer-encoding")) {
hasTransferEncoding = true;
} else if (headerNameEquals(name, "content-length")) {
const auto parsed = parseContentLength(value);
if (!parsed.has_value())
return Result<ParsedRequest>(std::unexpected(HttpStatus::BadRequest));
if (contentLength.has_value() && *contentLength != *parsed)
return Result<ParsedRequest>(std::unexpected(HttpStatus::BadRequest));
contentLength = parsed;
}
}
if (hasTransferEncoding && contentLength.has_value())
return Result<ParsedRequest>(std::unexpected(HttpStatus::BadRequest));
if (hasTransferEncoding) {
return Result<ParsedRequest>(std::unexpected(HttpStatus::NotImplemented));
}
const std::size_t bodyLength = contentLength.value_or(0);
if (bodyLength > limit - headBytes)
return Result<ParsedRequest>(std::unexpected(HttpStatus::ContentTooLarge));
if (data.size() < headBytes + bodyLength) return std::nullopt;
request.body.assign(data.begin() + static_cast<std::ptrdiff_t>(headBytes),
data.begin() + static_cast<std::ptrdiff_t>(headBytes + bodyLength));
return Result<ParsedRequest>(ParsedRequest{std::move(request), headBytes + bodyLength});
}
} // namespace webserv