From c9e74ca508dcf51d790750751e75f6394270ee2b Mon Sep 17 00:00:00 2001 From: Quinten Date: Thu, 25 Sep 2025 17:33:14 +0200 Subject: [PATCH] feat(directives): implement directive parsing and factory pattern for configuration --- config/default.conf | 3 +- docs/directives.md | 40 ++++++++++++ webserv/config/AConfig.hpp | 0 webserv/config/ConfigManager.cpp | 40 ++++++++---- webserv/config/ConfigManager.hpp | 5 +- webserv/config/directive/ADirective.cpp | 16 +++++ webserv/config/directive/ADirective.hpp | 62 ++++++++++++++++++ webserv/config/directive/BoolDirective.cpp | 30 +++++++++ webserv/config/directive/BoolDirective.hpp | 27 ++++++++ webserv/config/directive/DirectiveFactory.cpp | 64 +++++++++++++++++++ webserv/config/directive/DirectiveFactory.hpp | 43 +++++++++++++ webserv/config/directive/IntDirective.cpp | 13 ++++ webserv/config/directive/IntDirective.hpp | 27 ++++++++ webserv/config/directive/SizeDirective.cpp | 49 ++++++++++++++ webserv/config/directive/SizeDirective.hpp | 27 ++++++++ webserv/config/directive/StringDirective.cpp | 13 ++++ webserv/config/directive/StringDirective.hpp | 25 ++++++++ webserv/config/directive/VectorDirective.cpp | 23 +++++++ webserv/config/directive/VectorDirective.hpp | 28 ++++++++ webserv/main.cpp | 15 +++-- 20 files changed, 530 insertions(+), 20 deletions(-) create mode 100644 docs/directives.md create mode 100644 webserv/config/AConfig.hpp create mode 100644 webserv/config/directive/ADirective.cpp create mode 100644 webserv/config/directive/ADirective.hpp create mode 100644 webserv/config/directive/BoolDirective.cpp create mode 100644 webserv/config/directive/BoolDirective.hpp create mode 100644 webserv/config/directive/DirectiveFactory.cpp create mode 100644 webserv/config/directive/DirectiveFactory.hpp create mode 100644 webserv/config/directive/IntDirective.cpp create mode 100644 webserv/config/directive/IntDirective.hpp create mode 100644 webserv/config/directive/SizeDirective.cpp create mode 100644 webserv/config/directive/SizeDirective.hpp create mode 100644 webserv/config/directive/StringDirective.cpp create mode 100644 webserv/config/directive/StringDirective.hpp create mode 100644 webserv/config/directive/VectorDirective.cpp create mode 100644 webserv/config/directive/VectorDirective.hpp diff --git a/config/default.conf b/config/default.conf index 0de2a12..9678cb7 100644 --- a/config/default.conf +++ b/config/default.conf @@ -1,3 +1,5 @@ +autoindex on + server { listen 8080; host 0.0.0.0; @@ -39,7 +41,6 @@ server { cgi_pass /cgi-bin/; cgi_ext .py .php; } -asdfasdfasdf server { listen 8081; host 127.0.0.1; diff --git a/docs/directives.md b/docs/directives.md new file mode 100644 index 0000000..fd8c8f6 --- /dev/null +++ b/docs/directives.md @@ -0,0 +1,40 @@ +## Directives + +- listen INT {server} +- host STRING {server} +- server_name STRING {server} +- root STRING {server, location} +- index STRING[] {server, location} +- error_page INT STIRNG {server, location} +- client_max_body_size SIZE {server, location} +- autoindex BOOL {location} +- allowed_methods STRING[] {location} +- cgi_pass STRING {location} +- cgi_ext STRING[] {location} +- cgi_timout INT {location} +- upload_enabled BOOL {location} +- upload_store STRING {location} +- redirect INT STRING {location} + +struct Directives +{ + +} + + +LocationConfig lcocation; +location["index"] +Decl operator[](std::string const & key) -> ConfigValue &; +{ + declaration_map_t::iterator it = declarations.find(key); + if (it == declarations.end()) + serverconfig[key] = Declaration(key); +} + +IntDecl get() +StringDecl +BoolDecl +SizeDecl +StringArrayDecl + +auto decl = location[root].get(); diff --git a/webserv/config/AConfig.hpp b/webserv/config/AConfig.hpp new file mode 100644 index 0000000..e69de29 diff --git a/webserv/config/ConfigManager.cpp b/webserv/config/ConfigManager.cpp index c6c0764..c7b97fe 100644 --- a/webserv/config/ConfigManager.cpp +++ b/webserv/config/ConfigManager.cpp @@ -1,12 +1,14 @@ #include -#include // for ServerConfig -#include // for trim, findCorrespondingClosingBrace, trimSemi -#include // for Log +#include // for ServerConfig +#include // for DirectiveFactory +#include // for trim, findCorrespondingClosingBrace, trimSemi +#include // for Log #include // for size_t #include // for basic_ifstream, basic_istream, basic_filebuf, basic_ostream::operator<<, ifstream, istringstream, stringstream #include // for basic_stringstream, basic_istringstream #include // for runtime_error +#include ConfigManager::ConfigManager() : initialized_(false) {} @@ -82,7 +84,7 @@ void ConfigManager::parseConfigFile(const std::string &filePath) removeComments(content); std::string globalDeclarations; - + Log::trace("Content before parsing servers:\n" + content); size_t pos = 0; while (true) { @@ -96,6 +98,9 @@ void ConfigManager::parseConfigFile(const std::string &filePath) } // Add global declarations before this server block globalDeclarations += content.substr(pos, serverPos - pos); + Log::trace(LOCATION, {{"pos", std::to_string(pos)}, + {"serverPos", std::to_string(serverPos)}, + {"globalDeclarations", globalDeclarations}}); size_t closeBrace = utils::findCorrespondingClosingBrace(content, bracePos); if (closeBrace == std::string::npos) { @@ -107,14 +112,25 @@ void ConfigManager::parseConfigFile(const std::string &filePath) pos = closeBrace + 1; } - // parseGlobalDeclarations(globalDeclarations); // Implement this function to handle global config - Log::info("Global Declarations..."); + parseGlobalDeclarations(globalDeclarations); // Implement this function to handle global config file.close(); } -// void ConfigManager::parseGlobalDeclarations(const std::string &declarations) -// { -// // Placeholder for actual global declarations parsing logic -// std::cout << "Parsing global declarations:\n" << declarations << '\n'; -// // Implement the parsing logic here -// } +void ConfigManager::parseGlobalDeclarations(const std::string &declarations) +{ + Log::trace(LOCATION); + std::stringstream ss(declarations); + std::string line; + while (ss.good()) + { + std::getline(ss, line); + line = utils::trim(line); + if (line.empty()) + { + continue; + } + Log::info("Global Declaration: " + line); + auto directive = DirectiveFactory::createDirective(line); + globalDirectives_.push_back(std::move(directive)); + } +} diff --git a/webserv/config/ConfigManager.hpp b/webserv/config/ConfigManager.hpp index 4e59515..6033201 100644 --- a/webserv/config/ConfigManager.hpp +++ b/webserv/config/ConfigManager.hpp @@ -1,7 +1,9 @@ #pragma once +#include "webserv/config/directive/ADirective.hpp" #include +#include #include #include @@ -23,7 +25,8 @@ class ConfigManager ConfigManager(); ~ConfigManager(); std::vector serverConfigs_; + std::vector> globalDirectives_; void parseConfigFile(const std::string &filePath); - // void parseGlobalDeclarations(const std::string &declarations); + void parseGlobalDeclarations(const std::string &declarations); }; \ No newline at end of file diff --git a/webserv/config/directive/ADirective.cpp b/webserv/config/directive/ADirective.cpp new file mode 100644 index 0000000..9c2a8c8 --- /dev/null +++ b/webserv/config/directive/ADirective.cpp @@ -0,0 +1,16 @@ +#include // for ADirective + +DirectiveValue ADirective::get() const +{ + return {getValue()}; +} + +std::string ADirective::getName() const +{ + return name_; +} + +void ADirective::setName(const std::string &name) +{ + name_ = name; +} \ No newline at end of file diff --git a/webserv/config/directive/ADirective.hpp b/webserv/config/directive/ADirective.hpp new file mode 100644 index 0000000..c2350d2 --- /dev/null +++ b/webserv/config/directive/ADirective.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include + +class DirectiveValue +{ + public: + DirectiveValue(std::any value) : value_(value) {} + + template operator T() const { return std::any_cast(value_); } + + private: + std::any value_; +}; + +class ADirective +{ + public: + ADirective() = delete; + + ADirective(std::string name) : name_(std::move(name)) {} + + ADirective(const ADirective &other) = delete; + ADirective &operator=(const ADirective &other) = delete; + ADirective(ADirective &&other) noexcept = delete; + ADirective &operator=(ADirective &&other) noexcept = delete; + + virtual ~ADirective() {} + + virtual void parse(const std::string &value) = 0; + + [[nodiscard]] virtual std::any getValue() const = 0; + + [[nodiscard]] DirectiveValue get() const; + + [[nodiscard]] std::string getName() const; + + void setName(const std::string &name); + + protected: + std::string name_; +}; + +// Supported directives: + +// - listen INT {server} +// - host STRING {server} +// - server_name STRING {server} +// - root STRING {server, location} +// - index STRING[] {server, location} +// - error_page INT STIRNG {server, location} +// - client_max_body_size SIZE {server, location} +// - autoindex BOOL {location} +// - allowed_methods STRING[] {location} +// - cgi_pass STRING {location} +// - cgi_ext STRING[] {location} +// - cgi_timout INT {location} +// - upload_enabled BOOL {location} +// - upload_store STRING {location} +// - redirect INT STRING {location} \ No newline at end of file diff --git a/webserv/config/directive/BoolDirective.cpp b/webserv/config/directive/BoolDirective.cpp new file mode 100644 index 0000000..34f4b9a --- /dev/null +++ b/webserv/config/directive/BoolDirective.cpp @@ -0,0 +1,30 @@ +#include // for IntDirective +#include // for trim + +#include +#include +#include + +void BoolDirective::parse(const std::string &arg) +{ + std::string value = arg; + value = utils::trim(value); + std::ranges::transform(value, value.begin(), ::tolower); + if (value == "true" || value == "1" || value == "on" || value == "yes") + { + value_ = true; + } + else if (value == "false" || value == "0" || value == "off" || value == "no") + { + value_ = false; + } + else + { + throw std::invalid_argument("Invalid boolean value: " + value); + } +} + +std::any BoolDirective::getValue() const +{ + return value_; +} \ No newline at end of file diff --git a/webserv/config/directive/BoolDirective.hpp b/webserv/config/directive/BoolDirective.hpp new file mode 100644 index 0000000..baf2af3 --- /dev/null +++ b/webserv/config/directive/BoolDirective.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "ADirective.hpp" + +#include + +class BoolDirective : public ADirective +{ + public: + BoolDirective() = delete; + + BoolDirective(const std::string &name, const std::string &value) : ADirective(name) { parse(value); } + + BoolDirective(const BoolDirective &other) = delete; + BoolDirective &operator=(const BoolDirective &other) = delete; + BoolDirective(BoolDirective &&other) noexcept = delete; + BoolDirective &operator=(BoolDirective &&other) noexcept = delete; + + ~BoolDirective() override = default; + + void parse(const std::string &value) override; + + [[nodiscard]] std::any getValue() const override; + + private: + bool value_ = false; +}; \ No newline at end of file diff --git a/webserv/config/directive/DirectiveFactory.cpp b/webserv/config/directive/DirectiveFactory.cpp new file mode 100644 index 0000000..76df3e2 --- /dev/null +++ b/webserv/config/directive/DirectiveFactory.cpp @@ -0,0 +1,64 @@ +#include +#include +#include // for DirectiveFactory +#include +#include +#include +#include +#include // for trim, findCorrespondingClosingBrace, trimSemi +#include // for Log + +std::unique_ptr DirectiveFactory::createDirective(const std::string &line) +{ + std::stringstream ss(line); + std::string name; + ss >> name; + std::string arg; + std::getline(ss, arg); + + std::string_view type; + for (const auto &directive : supportedDirectives) + { + if (directive.name == name) + { + type = directive.type; + break; + } + } + + if (type.empty()) + { + throw std::invalid_argument("Unsupported directive: " + name); + } + + return create(type, name, arg); +} + +std::unique_ptr DirectiveFactory::create(std::string_view type, const std::string &name, + const std::string &arg) +{ + const auto &factories = getFactories(); + auto it = factories.find(type); + if (it == factories.end()) + { + throw std::invalid_argument("No factory found for directive type: " + std::string(type)); + } + return it->second(name, utils::trimSemi(utils::trim(arg))); +} + +const std::unordered_map &DirectiveFactory::getFactories() +{ + static const std::unordered_map factories = { + {"BoolDirective", + [](const std::string &name, const std::string &arg) { return std::make_unique(name, arg); }}, + {"IntDirective", + [](const std::string &name, const std::string &arg) { return std::make_unique(name, arg); }}, + {"SizeDirective", + [](const std::string &name, const std::string &arg) { return std::make_unique(name, arg); }}, + {"StringDirective", + [](const std::string &name, const std::string &arg) { return std::make_unique(name, arg); }}, + {"VectorDirective", + [](const std::string &name, const std::string &arg) { return std::make_unique(name, arg); }}, + }; + return factories; +} diff --git a/webserv/config/directive/DirectiveFactory.hpp b/webserv/config/directive/DirectiveFactory.hpp new file mode 100644 index 0000000..d7eed66 --- /dev/null +++ b/webserv/config/directive/DirectiveFactory.hpp @@ -0,0 +1,43 @@ +#include "webserv/config/directive/ADirective.hpp" + +#include +#include +#include +#include + +class DirectiveFactory +{ + public: + static std::unique_ptr createDirective(const std::string &line); + + private: + using CreatorFunc = std::function(const std::string &, const std::string &arg)>; + + static const std::unordered_map &getFactories(); + static std::unique_ptr create(std::string_view type, const std::string &name, const std::string &arg); + + struct DirectiveInfo + { + std::string_view name; + std::string_view type; + std::string_view context; + }; + + constexpr static std::array supportedDirectives = {{ + {.name = "listen", .type = "IntDirective", .context = "SL"}, + {.name = "host", .type = "StringDirective", .context = "SL"}, + {.name = "server_name", .type = "VectorDirective", .context = "SL"}, + {.name = "root", .type = "StringDirective", .context = "SL"}, + {.name = "index", .type = "VectorDirective", .context = "SL"}, + {.name = "error_page", .type = "VectorDirective", .context = "SL"}, + {.name = "client_max_body_size", .type = "SizeDirective", .context = "SL"}, + {.name = "autoindex", .type = "BoolDirective", .context = "L"}, + {.name = "allowed_methods", .type = "VectorDirective", .context = "L"}, + {.name = "cgi_pass", .type = "StringDirective", .context = "L"}, + {.name = "cgi_ext", .type = "VectorDirective", .context = "L"}, + {.name = "cgi_timeout", .type = "IntDirective", .context = "L"}, + {.name = "upload_enabled", .type = "BoolDirective", .context = "L"}, + {.name = "upload_store", .type = "StringDirective", .context = "L"}, + {.name = "redirect", .type = "VectorDirective", .context = "L"}, + }}; +}; \ No newline at end of file diff --git a/webserv/config/directive/IntDirective.cpp b/webserv/config/directive/IntDirective.cpp new file mode 100644 index 0000000..87fefb2 --- /dev/null +++ b/webserv/config/directive/IntDirective.cpp @@ -0,0 +1,13 @@ +#include // for IntDirective + +#include + +void IntDirective::parse(const std::string &value) +{ + value_ = std::stoi(value); // TODO: check parsing +} + +std::any IntDirective::getValue() const +{ + return value_; +} \ No newline at end of file diff --git a/webserv/config/directive/IntDirective.hpp b/webserv/config/directive/IntDirective.hpp new file mode 100644 index 0000000..38a1598 --- /dev/null +++ b/webserv/config/directive/IntDirective.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "ADirective.hpp" + +#include + +class IntDirective : public ADirective +{ + public: + IntDirective() = delete; + + IntDirective(const std::string &name, const std::string &value) : ADirective(name) { parse(value); } + + IntDirective(const IntDirective &other) = delete; + IntDirective &operator=(const IntDirective &other) = delete; + IntDirective(IntDirective &&other) noexcept = delete; + IntDirective &operator=(IntDirective &&other) noexcept = delete; + + ~IntDirective() override = default; + + void parse(const std::string &value) override; + + [[nodiscard]] std::any getValue() const override; + + private: + int value_ = 0; +}; \ No newline at end of file diff --git a/webserv/config/directive/SizeDirective.cpp b/webserv/config/directive/SizeDirective.cpp new file mode 100644 index 0000000..cc29303 --- /dev/null +++ b/webserv/config/directive/SizeDirective.cpp @@ -0,0 +1,49 @@ +#include // for SizeDirective +#include // for trim + +#include +#include +#include +#include + +void SizeDirective::parse(const std::string &value) +{ + size_t multiplier = 1; + size_t idx = 0; + std::string number = value; + std::ranges::transform(number, number.begin(), ::tolower); + number = utils::trim(number); + if (number.find_first_not_of("01234567890kmg") != std::string::npos) + { + throw std::invalid_argument("Invalid size directive: " + value); + } + value_ = std::stoul(number, &idx); + if (idx == number.size()) + { + return; + } + std::string suffix = number.substr(idx); + if (suffix == "k") + { + multiplier = 1024; + } + else if (suffix == "m") + { + multiplier = 1024 * 1024; + } + else if (suffix == "g") + { + multiplier = 1024 * 1024 * 1024; + } + else + { + throw std::invalid_argument("Invalid size directive: " + value); + } + + value_ *= multiplier; +} + +std::any SizeDirective::getValue() const +{ + return value_; +} \ No newline at end of file diff --git a/webserv/config/directive/SizeDirective.hpp b/webserv/config/directive/SizeDirective.hpp new file mode 100644 index 0000000..4ff77e8 --- /dev/null +++ b/webserv/config/directive/SizeDirective.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "ADirective.hpp" + +#include + +class SizeDirective : public ADirective +{ + public: + SizeDirective() = delete; + + SizeDirective(const std::string &name, const std::string &value) : ADirective(name) { parse(value); } + + SizeDirective(const SizeDirective &other) = delete; + SizeDirective &operator=(const SizeDirective &other) = delete; + SizeDirective(SizeDirective &&other) noexcept = delete; + SizeDirective &operator=(SizeDirective &&other) noexcept = delete; + + ~SizeDirective() override = default; + + void parse(const std::string &value) override; + + [[nodiscard]] std::any getValue() const override; + + private: + size_t value_ = 0; +}; \ No newline at end of file diff --git a/webserv/config/directive/StringDirective.cpp b/webserv/config/directive/StringDirective.cpp new file mode 100644 index 0000000..7d94f79 --- /dev/null +++ b/webserv/config/directive/StringDirective.cpp @@ -0,0 +1,13 @@ +#include // for IntDirective + +#include + +void StringDirective::parse(const std::string &value) +{ + value_ = value; +} + +std::any StringDirective::getValue() const +{ + return value_; +} \ No newline at end of file diff --git a/webserv/config/directive/StringDirective.hpp b/webserv/config/directive/StringDirective.hpp new file mode 100644 index 0000000..8a15684 --- /dev/null +++ b/webserv/config/directive/StringDirective.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "ADirective.hpp" + +class StringDirective : public ADirective +{ + public: + StringDirective() = delete; + + StringDirective(const std::string &name, const std::string &value) : ADirective(name) { parse(value); } + + StringDirective(const StringDirective &other) = delete; + StringDirective &operator=(const StringDirective &other) = delete; + StringDirective(StringDirective &&other) noexcept = delete; + StringDirective &operator=(StringDirective &&other) noexcept = delete; + + ~StringDirective() override = default; + + void parse(const std::string &value) override; + + [[nodiscard]] std::any getValue() const override; + + private: + std::string value_; +}; \ No newline at end of file diff --git a/webserv/config/directive/VectorDirective.cpp b/webserv/config/directive/VectorDirective.cpp new file mode 100644 index 0000000..13146a5 --- /dev/null +++ b/webserv/config/directive/VectorDirective.cpp @@ -0,0 +1,23 @@ +#include // for IntDirective + +#include +#include // for std::getline, std::basic_istream, std::char_traits, std::basic_stringbuf + +void VectorDirective::parse(const std::string &value) +{ + std::stringstream ss(value); + while(ss.good()) + { + std::string item; + std::getline(ss, item, ' ');// index indx.html + if (!item.empty()) + { + value_.push_back(item); + } + } +} + +std::any VectorDirective::getValue() const +{ + return value_; +} \ No newline at end of file diff --git a/webserv/config/directive/VectorDirective.hpp b/webserv/config/directive/VectorDirective.hpp new file mode 100644 index 0000000..2ddddc5 --- /dev/null +++ b/webserv/config/directive/VectorDirective.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "ADirective.hpp" + +#include +#include + +class VectorDirective : public ADirective +{ + public: + VectorDirective() = delete; + + VectorDirective(const std::string &name, const std::string &value) : ADirective(name) { parse(value); } + + VectorDirective(const VectorDirective &other) = delete; + VectorDirective &operator=(const VectorDirective &other) = delete; + VectorDirective(VectorDirective &&other) noexcept = delete; + VectorDirective &operator=(VectorDirective &&other) noexcept = delete; + + ~VectorDirective() override = default; + + void parse(const std::string &value) override; + + [[nodiscard]] std::any getValue() const override; + + private: + std::vector value_; +}; \ No newline at end of file diff --git a/webserv/main.cpp b/webserv/main.cpp index ca6d5b3..a4e5073 100644 --- a/webserv/main.cpp +++ b/webserv/main.cpp @@ -1,11 +1,14 @@ -#include // for ConfigManager -#include // for Log, LOCATION -#include // for Server +#include // for ConfigManager +#include // for ADirective +#include // for IntDirective +#include // for Log, LOCATION +#include // for Server #include // for basic_ostream, operator<<, cerr, ios_base #include // for map -#include // for basic_string, char_traits, allocator, operator+, operator<=> -#include // for pair +#include +#include // for basic_string, char_traits, allocator, operator+, operator<=> +#include // for pair int main(int argc, char **argv) { @@ -21,7 +24,7 @@ int main(int argc, char **argv) Log::warning("Testing context: " + LOCATION, {{"key1", "value1"}, {"key2", "value2"}}); ConfigManager::getInstance().init(argv[1]); // NOLINT Server server(ConfigManager::getInstance()); + server.start(); - return 0; } \ No newline at end of file