refactor: uri parser now does parsing

This commit is contained in:
Quinten 2025-10-09 16:26:31 +02:00
parent 6b9ce1dda4
commit c3e5117fcb
11 changed files with 182 additions and 112 deletions

View File

@ -52,7 +52,7 @@ std::unique_ptr<ADirective> DirectiveFactory::create(std::string_view type, cons
{ {
throw std::invalid_argument("No factory found for directive type: " + std::string(type)); throw std::invalid_argument("No factory found for directive type: " + std::string(type));
} }
return it->second(name, utils::trimSemi(utils::trim(arg))); return it->second(name, utils::trim(arg, " \t\n\r;"));
} }
const std::unordered_map<std::string_view, DirectiveFactory::CreatorFunc> &DirectiveFactory::getFactories() const std::unordered_map<std::string_view, DirectiveFactory::CreatorFunc> &DirectiveFactory::getFactories()

View File

@ -1,5 +1,6 @@
#include <webserv/config/LocationConfig.hpp> // for LocationConfig #include "webserv/config/AConfig.hpp"
#include <webserv/handler/ErrorHandler.hpp> // for ErrorHandler
#include <webserv/handler/ErrorHandler.hpp> // for ErrorHandler
#include <webserv/handler/FileHandler.hpp> #include <webserv/handler/FileHandler.hpp>
#include <webserv/handler/MIMETypes.hpp> // for MIMETypes #include <webserv/handler/MIMETypes.hpp> // for MIMETypes
#include <webserv/handler/URIParser.hpp> // for URIParser #include <webserv/handler/URIParser.hpp> // for URIParser
@ -8,16 +9,12 @@
#include <webserv/log/Log.hpp> // for Log, LOCATION #include <webserv/log/Log.hpp> // for Log, LOCATION
#include <webserv/utils/FileUtils.hpp> // for joinPath, getExtension, isFile, readBinaryFile #include <webserv/utils/FileUtils.hpp> // for joinPath, getExtension, isFile, readBinaryFile
#include <functional> // for identity #include <memory> // for unique_ptr, allocator, make_unique
#include <memory> // for unique_ptr, allocator, make_unique #include <ranges>
#include <optional> // for optional #include <string> // for basic_string, string, operator+, char_traits
#include <ranges> // for __find_if_fn, find_if #include <vector> // for vector
#include <string> // for basic_string, string, operator+, char_traits
#include <utility> // for move
#include <vector> // for vector
FileHandler::FileHandler(const LocationConfig *location, const URIParser &uriParser) FileHandler::FileHandler(const AConfig *config, const URIParser &uriParser) : config_(config), uriParser_(uriParser)
: location_(location), uriParser_(uriParser)
{ {
Log::trace(LOCATION); Log::trace(LOCATION);
} }
@ -35,7 +32,7 @@ std::unique_ptr<HttpResponse> FileHandler::handleFile(const std::string &filepat
Log::debug("Serving file: " + filepath + " with MIME type: " + mimeType); Log::debug("Serving file: " + filepath + " with MIME type: " + mimeType);
if (fileData.empty()) if (fileData.empty())
{ {
return ErrorHandler::getErrorResponse(Http::StatusCode::NOT_FOUND, location_); return ErrorHandler::getErrorResponse(Http::StatusCode::NOT_FOUND, config_);
} }
// TODO: annoying: For reading files, vector<char> is preferred, but for http data vector<uint8_t> is preferred // TODO: annoying: For reading files, vector<char> is preferred, but for http data vector<uint8_t> is preferred
response->setBody(std::vector<uint8_t>{fileData.begin(), fileData.end()}); response->setBody(std::vector<uint8_t>{fileData.begin(), fileData.end()});
@ -49,28 +46,28 @@ std::unique_ptr<HttpResponse> FileHandler::handleDirectory(const std::string &di
if (type == DIRECTORY_INDEX) if (type == DIRECTORY_INDEX)
{ {
auto possible_indexes = location_->get<std::vector<std::string>>("index").value(); auto possible_indexes = config_->get<std::vector<std::string>>("index").value();
auto first_matching = std::ranges::find_if(possible_indexes, [&](const std::string &index) { auto first_matching = std::ranges::find_if(possible_indexes, [&](const std::string &index) {
return FileUtils::isFile(FileUtils::joinPath(dirpath, index)); return FileUtils::isFile(FileUtils::joinPath(dirpath, index));
}); });
if (first_matching == possible_indexes.end()) if (first_matching == possible_indexes.end())
{ {
return ErrorHandler::getErrorResponse(Http::StatusCode::FORBIDDEN, location_); return ErrorHandler::getErrorResponse(Http::StatusCode::FORBIDDEN, config_);
} }
return handleFile(FileUtils::joinPath(dirpath, *first_matching)); return handleFile(FileUtils::joinPath(dirpath, *first_matching));
} }
if (type == DIRECTORY_AUTOINDEX) if (type == DIRECTORY_AUTOINDEX)
{ {
Log::debug("Requested path is a directory: " + dirpath); Log::debug("Requested path is a directory: " + dirpath);
return ErrorHandler::getErrorResponse(Http::StatusCode::FORBIDDEN, location_); return ErrorHandler::getErrorResponse(Http::StatusCode::FORBIDDEN, config_);
} }
return ErrorHandler::getErrorResponse(Http::StatusCode::NOT_FOUND, location_); return ErrorHandler::getErrorResponse(Http::StatusCode::NOT_FOUND, config_);
} }
std::unique_ptr<HttpResponse> FileHandler::getResponse() const std::unique_ptr<HttpResponse> FileHandler::getResponse() const
{ {
Log::trace(LOCATION); Log::trace(LOCATION);
std::string filepath = uriParser_.getFilePath(); std::string filepath = uriParser_.getFullPath();
ResourceType resourceType = getResourceType(filepath); ResourceType resourceType = getResourceType(filepath);
switch (resourceType) switch (resourceType)
@ -78,9 +75,9 @@ std::unique_ptr<HttpResponse> FileHandler::getResponse() const
case FILE: return handleFile(filepath); case FILE: return handleFile(filepath);
case DIRECTORY_AUTOINDEX: case DIRECTORY_AUTOINDEX:
case DIRECTORY_INDEX: return handleDirectory(filepath, resourceType); case DIRECTORY_INDEX: return handleDirectory(filepath, resourceType);
case NOT_FOUND: return ErrorHandler::getErrorResponse(Http::StatusCode::NOT_FOUND, location_); case NOT_FOUND: return ErrorHandler::getErrorResponse(Http::StatusCode::NOT_FOUND, config_);
} }
return ErrorHandler::getErrorResponse(Http::StatusCode::NOT_FOUND, location_); return ErrorHandler::getErrorResponse(Http::StatusCode::NOT_FOUND, config_);
} }
FileHandler::ResourceType FileHandler::getResourceType(const std::string &path) const FileHandler::ResourceType FileHandler::getResourceType(const std::string &path) const
@ -94,11 +91,11 @@ FileHandler::ResourceType FileHandler::getResourceType(const std::string &path)
} }
if (uriParser_.isDirectory()) if (uriParser_.isDirectory())
{ {
if (location_->get<std::vector<std::string>>("index").has_value()) if (config_->get<std::vector<std::string>>("index").has_value())
{ {
return DIRECTORY_INDEX; return DIRECTORY_INDEX;
} }
if (location_->get<bool>("autoindex").value_or(false)) if (config_->get<bool>("autoindex").value_or(false))
{ {
return DIRECTORY_AUTOINDEX; return DIRECTORY_AUTOINDEX;
} }

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "webserv/config/AConfig.hpp"
#include <webserv/config/LocationConfig.hpp> #include <webserv/config/LocationConfig.hpp>
#include <webserv/handler/URIParser.hpp> #include <webserv/handler/URIParser.hpp>
#include <webserv/http/HttpResponse.hpp> // for HttpResponse #include <webserv/http/HttpResponse.hpp> // for HttpResponse
@ -8,18 +10,18 @@
#include <memory> // for unique_ptr #include <memory> // for unique_ptr
#include <string> // for string #include <string> // for string
class LocationConfig; class AConfig;
class URIParser; class URIParser;
class FileHandler class FileHandler
{ {
public: public:
FileHandler(const LocationConfig *location, const URIParser &uriParser); FileHandler(const AConfig *config, const URIParser &uriParser);
[[nodiscard]] std::unique_ptr<HttpResponse> getResponse() const; [[nodiscard]] std::unique_ptr<HttpResponse> getResponse() const;
private: private:
const LocationConfig *location_; const AConfig *config_;
const URIParser &uriParser_; const URIParser &uriParser_;
enum ResourceType : uint8_t enum ResourceType : uint8_t

View File

@ -1,96 +1,155 @@
#include <webserv/handler/URIParser.hpp> #include "webserv/config/AConfig.hpp"
#include "webserv/utils/FileUtils.hpp"
#include "webserv/utils/utils.hpp"
#include <webserv/config/LocationConfig.hpp> // for LocationConfig #include <webserv/config/LocationConfig.hpp> // for LocationConfig
#include <webserv/config/ServerConfig.hpp> // for ServerConfig #include <webserv/config/ServerConfig.hpp> // for ServerConfig
#include <webserv/handler/URIParser.hpp>
#include <optional> // for optional #include <optional> // for optional
#include <stddef.h> // for size_t #include <stddef.h> // for size_t
#include <sys/stat.h> // for stat, S_ISDIR, S_ISREG #include <sys/stat.h> // for stat, S_ISDIR, S_ISREG
URIParser::URIParser(const std::string &uri, const ServerConfig &serverConfig) : _locationConfig(nullptr) URIParser::URIParser(const std::string &uri, const ServerConfig &serverConfig)
: uriTrimmed_(utils::trim(uri, "/")), config_(matchConfig(uriTrimmed_, serverConfig))
{
parseUri(uri);
parseFullpath();
}
const AConfig *URIParser::matchConfig(const std::string &uri, const ServerConfig &serverConfig)
{ {
const auto &locations = serverConfig.getLocationPaths(); const auto &locations = serverConfig.getLocationPaths();
size_t maxMatchLength = 0; const AConfig *bestMatch = &serverConfig;
size_t maxMatchLength = 0;
for (const auto &locationPath : locations) for (const auto &locationPath : locations)
{ {
if (uri.starts_with((locationPath == "/") ? locationPath : locationPath + "/")) if (uri.empty() && locationPath == "/")
{ // TODO HMHMMz why does it need to end on a /? {
return serverConfig.getLocation(locationPath);
}
if (uri.starts_with(utils::trim(locationPath, "/")))
{
if (locationPath.length() > maxMatchLength) if (locationPath.length() > maxMatchLength)
{ {
maxMatchLength = locationPath.length(); maxMatchLength = locationPath.length();
_locationConfig = serverConfig.getLocation(locationPath); bestMatch = serverConfig.getLocation(locationPath);
} }
} }
} }
return bestMatch;
}
root_ = _locationConfig != nullptr ? _locationConfig->get<std::string>("root").value_or("") : ""; void URIParser::parseUri(const std::string &uri)
if (!root_.empty() && root_.back() == '/') {
if (config_->getType() == "server")
{ {
root_.pop_back(); // Remove trailing slash to avoid double slashes in path fullPath_ = FileUtils::joinPath(config_->get<std::string>("root").value_or(""), uriTrimmed_);
}
else
{
auto const *locConfig = dynamic_cast<LocationConfig const *>(config_);
std::string locTrimmed = utils::trim(locConfig->getPath(), "/");
std::string uriSub = uri.substr(locTrimmed.length());
fullPath_ = FileUtils::joinPath(locConfig->get<std::string>("root").value_or(""), uriSub);
} }
relativePath_ = uri.substr(maxMatchLength); size_t fragmentPos = fullPath_.find_first_of('#');
if (relativePath_.empty() || relativePath_[0] != '/') if (fragmentPos != std::string::npos)
{ {
relativePath_ = "/" + relativePath_; fragment_ = fullPath_.substr(fragmentPos + 1);
fullPath_ = fullPath_.substr(0, fragmentPos);
}
size_t queryPos = fullPath_.find_first_of('?');
if (queryPos != std::string::npos)
{
query_ = fullPath_.substr(queryPos + 1);
fullPath_ = fullPath_.substr(0, queryPos);
} }
} }
std::string URIParser::getFilePath() const void URIParser::parseFullpath()
{ {
return root_ + relativePath_; auto uriSegments = utils::split(fullPath_, '/');
}
std::string URIParser::getFilename() const for (const auto &segment : uriSegments)
{
size_t lastSlash = relativePath_.find_last_of('/');
if (lastSlash == std::string::npos)
{ {
return relativePath_; // No slashes, return the whole path std::string curDir = FileUtils::joinPath(dir_, segment);
if (segment.empty())
{
continue;
}
if (FileUtils::isFile(curDir) && baseName_.empty())
{
baseName_ = segment;
}
else if (FileUtils::isDirectory(curDir))
{
dir_ = FileUtils::joinPath(dir_, segment);
}
else if (!baseName_.empty()) // not file or dir, but we have a baseName already
{
pathInfo_ = FileUtils::joinPath(pathInfo_, baseName_);
}
} }
return relativePath_.substr(lastSlash + 1); fullPath_ = FileUtils::joinPath(dir_, baseName_);
} }
std::string URIParser::getExtension() const const AConfig *URIParser::getConfig() const
{ {
std::string filename = getFilename(); return config_;
size_t lastDot = filename.find_last_of('.');
if (lastDot == std::string::npos || lastDot == 0 || lastDot == filename.length() - 1)
{
return ""; // No extension found or dot is at start/end
}
return filename.substr(lastDot + 1);
}
LocationConfig const *URIParser::getLocation() const
{
return _locationConfig;
} }
bool URIParser::isFile() const bool URIParser::isFile() const
{ {
struct stat pathStat{}; return !baseName_.empty();
if (stat(getFilePath().c_str(), &pathStat) != 0)
{
return false;
}
return S_ISREG(pathStat.st_mode);
} }
bool URIParser::isDirectory() const bool URIParser::isDirectory() const
{ {
struct stat pathStat{}; return baseName_.empty();
if (stat(getFilePath().c_str(), &pathStat) != 0)
{
return false;
}
return S_ISDIR(pathStat.st_mode);
} }
bool URIParser::isValid() const bool URIParser::isValid() const
{ {
struct stat pathStat{}; return FileUtils::isValidPath(fullPath_);
return stat(getFilePath().c_str(), &pathStat) == 0; }
const std::string &URIParser::getBaseName() const
{
return baseName_;
}
std::string URIParser::getExtension() const
{
return FileUtils::getExtension(baseName_);
}
const std::string &URIParser::getFullPath() const
{
return fullPath_;
}
const std::string &URIParser::getDir() const
{
return dir_;
}
const std::string &URIParser::getPathInfo() const
{
return pathInfo_;
}
const std::string &URIParser::getQuery() const
{
return query_;
}
const std::string &URIParser::getFragment() const
{
return fragment_;
} }

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include "webserv/config/AConfig.hpp"
#include <webserv/config/LocationConfig.hpp> #include <webserv/config/LocationConfig.hpp>
#include <webserv/config/ServerConfig.hpp> #include <webserv/config/ServerConfig.hpp>
#include <webserv/server/Server.hpp> #include <webserv/server/Server.hpp>
@ -14,19 +16,31 @@ class URIParser
public: public:
URIParser(const std::string &uri, const ServerConfig &serverConfig); URIParser(const std::string &uri, const ServerConfig &serverConfig);
[[nodiscard]] std::string getFilePath() const;
[[nodiscard]] std::string getFilename() const;
[[nodiscard]] std::string getExtension() const;
[[nodiscard]] const LocationConfig *getLocation() const;
[[nodiscard]] bool isFile() const; [[nodiscard]] bool isFile() const;
[[nodiscard]] bool isDirectory() const; [[nodiscard]] bool isDirectory() const;
[[nodiscard]] bool isValid() const; [[nodiscard]] bool isValid() const;
[[nodiscard]] std::string getExtension() const;
[[nodiscard]] const AConfig *getConfig() const;
[[nodiscard]] const std::string &getBaseName() const;
[[nodiscard]] const std::string &getFullPath() const;
[[nodiscard]] const std::string &getDir() const;
[[nodiscard]] const std::string &getPathInfo() const;
[[nodiscard]] const std::string &getQuery() const;
[[nodiscard]] const std::string &getFragment() const;
private: private:
const LocationConfig *_locationConfig; void parseUri(const std::string &uri);
std::string relativePath_; void parseFullpath();
std::string root_;
std::string uriTrimmed_;
const AConfig *config_;
std::string fullPath_; // dir_ + baseName_ + pathInfo_
std::string baseName_;
std::string dir_;
std::string pathInfo_;
std::string query_;
std::string fragment_;
static const AConfig *matchConfig(const std::string &uri, const ServerConfig &serverConfig);
}; };

View File

@ -1,14 +1,14 @@
#include <webserv/router/Router.hpp> #include "webserv/config/AConfig.hpp"
#include <webserv/config/directive/ADirective.hpp>
#include <webserv/config/ConfigManager.hpp> // for ConfigManager #include <webserv/config/ConfigManager.hpp> // for ConfigManager
#include <webserv/config/ServerConfig.hpp> // for ServerConfig #include <webserv/config/ServerConfig.hpp> // for ServerConfig
#include <webserv/config/directive/ADirective.hpp>
#include <webserv/handler/ErrorHandler.hpp> // for ErrorHandler #include <webserv/handler/ErrorHandler.hpp> // for ErrorHandler
#include <webserv/handler/FileHandler.hpp> // for FileHandler #include <webserv/handler/FileHandler.hpp> // for FileHandler
#include <webserv/handler/URIParser.hpp> // for URIParser #include <webserv/handler/URIParser.hpp> // for URIParser
#include <webserv/http/HttpHeaders.hpp> // for HttpHeaders #include <webserv/http/HttpHeaders.hpp> // for HttpHeaders
#include <webserv/log/Log.hpp> // for LOCATION, Log #include <webserv/log/Log.hpp> // for LOCATION, Log
#include <webserv/router/Router.hpp>
#include <algorithm> #include <algorithm>
#include <memory> // for unique_ptr #include <memory> // for unique_ptr
@ -18,11 +18,9 @@
class LocationConfig; class LocationConfig;
Router::Router() {} bool Router::isMethodSupported(const std::string &method, const AConfig &config)
bool Router::isMethodSupported(const std::string &method, const LocationConfig &location)
{ {
const ADirective *allowedMethods = location.getDirective("allowed_methods"); const ADirective *allowedMethods = config.getDirective("allowed_methods");
if (allowedMethods == nullptr || !allowedMethods->getValue().try_get<std::vector<std::string>>().has_value()) if (allowedMethods == nullptr || !allowedMethods->getValue().try_get<std::vector<std::string>>().has_value())
{ {
return true; return true;
@ -35,28 +33,24 @@ std::unique_ptr<HttpResponse> Router::handleRequest(const HttpRequest &request)
{ {
Log::trace(LOCATION); Log::trace(LOCATION);
ServerConfig *config = ServerConfig *serverConfig =
ConfigManager::getInstance().getMatchingServerConfig(request.getHeaders().getHost().value_or("")); ConfigManager::getInstance().getMatchingServerConfig(request.getHeaders().getHost().value_or(""));
if (config == nullptr) if (serverConfig == nullptr)
{ {
return ErrorHandler::getErrorResponse(400); return ErrorHandler::getErrorResponse(400);
} }
URIParser uriParser{request.getTarget(), *config}; URIParser uriParser{request.getTarget(), *serverConfig};
const std::string &target = request.getTarget(); const std::string &target = request.getTarget();
static_cast<void>(target); // Suppress unused variable warning static_cast<void>(target); // Suppress unused variable warning
const std::string &method = request.getMethod(); const std::string &method = request.getMethod();
const LocationConfig *location = uriParser.getLocation(); const AConfig *config = uriParser.getConfig();
if (location == nullptr) if (!isMethodSupported(method, *config))
{
return ErrorHandler::getErrorResponse(404, config);
}
if (!isMethodSupported(method, *location))
{ {
return ErrorHandler::getErrorResponse(405, config); return ErrorHandler::getErrorResponse(405, config);
} }
FileHandler fileHandler(location, uriParser); FileHandler fileHandler(config, uriParser);
return fileHandler.getResponse(); return fileHandler.getResponse();
} }

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "webserv/config/AConfig.hpp"
#include <webserv/config/LocationConfig.hpp> #include <webserv/config/LocationConfig.hpp>
#include <webserv/http/HttpRequest.hpp> // for HttpRequest #include <webserv/http/HttpRequest.hpp> // for HttpRequest
#include <webserv/http/HttpResponse.hpp> // for HttpResponse #include <webserv/http/HttpResponse.hpp> // for HttpResponse
@ -13,11 +14,8 @@ class ServerConfig;
class Router class Router
{ {
public: public:
Router();
[[nodiscard]] static std::unique_ptr<HttpResponse> handleRequest(const HttpRequest &request); [[nodiscard]] static std::unique_ptr<HttpResponse> handleRequest(const HttpRequest &request);
private: private:
[[nodiscard]] const LocationConfig *getLocation(const std::string &path, const ServerConfig &serverConfig) const; [[nodiscard]] static bool isMethodSupported(const std::string &method, const AConfig &config);
[[nodiscard]] static bool isMethodSupported(const std::string &method, const LocationConfig &location);
}; };

View File

@ -1,6 +1,5 @@
#include <webserv/utils/FileUtils.hpp>
#include <webserv/log/Log.hpp> // for Log, LOCATION #include <webserv/log/Log.hpp> // for Log, LOCATION
#include <webserv/utils/FileUtils.hpp>
#include <cstring> // for size_t #include <cstring> // for size_t
#include <fstream> // for basic_ifstream, basic_ios, basic_istream, ios, ifstream, operator|, basic_istream::read, basic_istream::seekg, basic_istream::tellg, streamsize #include <fstream> // for basic_ifstream, basic_ios, basic_istream, ios, ifstream, operator|, basic_istream::read, basic_istream::seekg, basic_istream::tellg, streamsize
@ -31,6 +30,12 @@ bool isFile(const std::string &path)
return S_ISREG(pathStat.st_mode); return S_ISREG(pathStat.st_mode);
} }
bool isValidPath(const std::string &path)
{
struct stat pathStat{};
return stat(path.c_str(), &pathStat) == 0;
}
std::string getExtension(const std::string &filename) std::string getExtension(const std::string &filename)
{ {
size_t dotPos = filename.find_last_of('.'); size_t dotPos = filename.find_last_of('.');

View File

@ -7,6 +7,7 @@ namespace FileUtils
{ {
bool isDirectory(const std::string &path); bool isDirectory(const std::string &path);
bool isFile(const std::string &path); bool isFile(const std::string &path);
bool isValidPath(const std::string &path);
std::string getExtension(const std::string &filename); std::string getExtension(const std::string &filename);
std::string joinPath(const std::string &base, const std::string &addition); std::string joinPath(const std::string &base, const std::string &addition);

View File

@ -23,10 +23,10 @@ size_t stoul(const std::string &str, int base)
return value; return value;
} }
std::string trim(const std::string &str) std::string trim(const std::string &str, const std::string &charset)
{ {
size_t first = str.find_first_not_of(" \t\n\r"); size_t first = str.find_first_not_of(charset);
size_t last = str.find_last_not_of(" \t\n\r"); size_t last = str.find_last_not_of(charset);
if (first == std::string::npos || last == std::string::npos) if (first == std::string::npos || last == std::string::npos)
{ {
return ""; return "";
@ -82,7 +82,7 @@ void removeEmptyLines(std::string &str)
{ {
if (!utils::trim(line).empty()) if (!utils::trim(line).empty())
{ {
result += utils::trimSemi(utils::trim(line)) + '\n'; result += utils::trim(line, " \t\n\r;") + '\n';
} }
} }
str = result; str = result;

View File

@ -7,8 +7,8 @@
namespace utils namespace utils
{ {
size_t stoul(const std::string &str, int base = 10); size_t stoul(const std::string &str, int base = 10);
std::string trimSemi(const std::string &str); // std::string trimSemi(const std::string &str);
std::string trim(const std::string &str); std::string trim(const std::string &str, const std::string &charset = " \t\n\r");
size_t findCorrespondingClosingBrace(const std::string &str, size_t openPos); size_t findCorrespondingClosingBrace(const std::string &str, size_t openPos);
void removeEmptyLines(std::string &str); void removeEmptyLines(std::string &str);
void removeComments(std::string &str); void removeComments(std::string &str);