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 \
|
||||
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 \
|
||||
|
||||
|
||||
@@ -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