one more parser
This commit is contained in:
@@ -55,9 +55,8 @@ CLANG_STDLIB ?= -stdlib=libc++
|
|||||||
SRCS := src/Log.cpp src/main.cpp src/Server.cpp \
|
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/config/Config.cpp src/config/ConfigurationParser.cpp src/config/LocationConfig.cpp src/config/ServerConfig.cpp \
|
||||||
src/event/Epoll.cpp src/event/EventToken.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/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
|
||||||
# src/http/HttpStatus.cpp \
|
|
||||||
|
|
||||||
|
|
||||||
OBJS := $(SRCS:$(SRC_DIR)/%.cpp=$(OBJ_DIR)/%.o)
|
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)
|
DEPS := $(OBJS:.o=.d)
|
||||||
HDRS := include/config/Config.hpp include/config/ConfigurationParser.hpp include/config/LocationConfig.hpp include/config/ServerConfig.hpp \
|
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/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/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 \
|
include/Log.hpp include/Result.hpp include/Server.hpp \
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user