From 1994de27af70af44a3a38751bb13d988b6dc660d Mon Sep 17 00:00:00 2001 From: Quinten Date: Tue, 7 Oct 2025 16:40:50 +0200 Subject: [PATCH] feat: add validation rules for CGI extensions, folder existence, and host validation --- webserv/config/validation/ConfigValidator.cpp | 15 +++-- .../directive_rules/CgiExtValidationRule.cpp | 52 ++++++++++++++ .../directive_rules/CgiExtValidationRule.hpp | 12 ++++ .../directive_rules/FolderExistsRule.cpp | 28 ++++++++ .../directive_rules/FolderExistsRule.hpp | 13 ++++ .../directive_rules/HostValidationRule.cpp | 67 +++++++++++++++++++ .../directive_rules/HostValidationRule.hpp | 17 +++++ 7 files changed, 200 insertions(+), 4 deletions(-) create mode 100644 webserv/config/validation/directive_rules/CgiExtValidationRule.cpp create mode 100644 webserv/config/validation/directive_rules/CgiExtValidationRule.hpp create mode 100644 webserv/config/validation/directive_rules/FolderExistsRule.cpp create mode 100644 webserv/config/validation/directive_rules/FolderExistsRule.hpp create mode 100644 webserv/config/validation/directive_rules/HostValidationRule.cpp create mode 100644 webserv/config/validation/directive_rules/HostValidationRule.hpp diff --git a/webserv/config/validation/ConfigValidator.cpp b/webserv/config/validation/ConfigValidator.cpp index 80af27d..ac2da42 100644 --- a/webserv/config/validation/ConfigValidator.cpp +++ b/webserv/config/validation/ConfigValidator.cpp @@ -1,12 +1,15 @@ +#include "webserv/config/validation/directive_rules/CgiExtValidationRule.hpp" #include - -#include // for ValidationEngine -#include // for AValidationRule -#include // for AllowedValuesRule +#include // for ValidationEngine +#include // for AValidationRule +#include // for AllowedValuesRule +#include +#include #include // for PortValidationRule #include // for structural rules #include // for LOCATION, Log +#include #include // for basic_string, string ConfigValidator::ConfigValidator(const GlobalConfig *config) : engine_(std::make_unique(config)) @@ -22,10 +25,14 @@ ConfigValidator::ConfigValidator(const GlobalConfig *config) : engine_(std::make /*Server Directive Rules*/ engine_->addServerRule("listen", std::make_unique()); + engine_->addServerRule("host", std::make_unique()); + engine_->addServerRule("root", std::make_unique(true)); /*Location Directive Rules*/ engine_->addLocationRule("allowed_methods", std::make_unique(std::vector{"GET", "POST", "DELETE"})); + engine_->addLocationRule("root", std::make_unique(true)); + engine_->addLocationRule("cgi_ext", std::make_unique(true)); engine_->validate(); } diff --git a/webserv/config/validation/directive_rules/CgiExtValidationRule.cpp b/webserv/config/validation/directive_rules/CgiExtValidationRule.cpp new file mode 100644 index 0000000..7eb270b --- /dev/null +++ b/webserv/config/validation/directive_rules/CgiExtValidationRule.cpp @@ -0,0 +1,52 @@ +#include "webserv/config/AConfig.hpp" +#include "webserv/config/validation/ValidationResult.hpp" +#include "webserv/utils/FileUtils.hpp" + +#include + +#include +#include + +CgiExtValidationRule::CgiExtValidationRule(bool requiresValue) + : AValidationRule("CgiExt", "Ensure CGI extension is valid", requiresValue) +{ +} + +bool isAllowedCGIExtension(const std::string &extension) +{ + static const std::vector allowedExtensions = {".php", ".py"}; + return std::ranges::any_of(allowedExtensions, [&extension](const auto &it) { return extension == it; }); +} + +ValidationResult CgiExtValidationRule::validateValue(const AConfig *config, const std::string &directiveName) const +{ + const ADirective *directive = config->getDirective(directiveName); + if (!directive->getValue().holds>()) + { + return ValidationResult::error("Directive '" + directive->getName() + "' does not hold a string value"); + } + + auto cgiExt = directive->getValue().get>(); + if (cgiExt.size() != 2) + { + return ValidationResult::error("Directive '" + directive->getName() + + "' has invalid format, expected extension and path"); + } + auto extension = std::string(cgiExt[0]); + auto path = std::string(cgiExt[1]); + if (extension.empty() || extension[0] != '.') + { + return ValidationResult::error("Directive '" + directive->getName() + "' has invalid extension '" + extension + + "'"); + } + if (!isAllowedCGIExtension(extension)) + { + return ValidationResult::error("Directive '" + directive->getName() + "' has unsupported extension '" + + extension + "'"); + } + if (!FileUtils::isFile(path)) + { + return ValidationResult::error("Directive '" + directive->getName() + "' has invalid path '" + path + "'"); + } + return ValidationResult::success(); +} \ No newline at end of file diff --git a/webserv/config/validation/directive_rules/CgiExtValidationRule.hpp b/webserv/config/validation/directive_rules/CgiExtValidationRule.hpp new file mode 100644 index 0000000..6303a45 --- /dev/null +++ b/webserv/config/validation/directive_rules/CgiExtValidationRule.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "webserv/config/validation/directive_rules/AValidationRule.hpp" +#include // for ValidationResult + +class CgiExtValidationRule : public AValidationRule +{ + public: + CgiExtValidationRule(bool requiresValue); + + ValidationResult validateValue(const AConfig *config, const std::string &directiveName) const override; +}; \ No newline at end of file diff --git a/webserv/config/validation/directive_rules/FolderExistsRule.cpp b/webserv/config/validation/directive_rules/FolderExistsRule.cpp new file mode 100644 index 0000000..b88f2a3 --- /dev/null +++ b/webserv/config/validation/directive_rules/FolderExistsRule.cpp @@ -0,0 +1,28 @@ +#include "webserv/config/directive/ADirective.hpp" +#include "webserv/config/validation/ValidationResult.hpp" +#include "webserv/log/Log.hpp" +#include "webserv/utils/FileUtils.hpp" + +#include + +FolderExistsRule::FolderExistsRule(bool requiresValue) + : AValidationRule("FolderExists", "Ensures the specified folder exists", requiresValue) +{ +} + +ValidationResult FolderExistsRule::validateValue(const AConfig *config, const std::string &directiveName) const +{ + const ADirective *directive = config->getDirective(directiveName); + if (!directive->getValue().holds()) + { + return ValidationResult::error("Directive '" + directive->getName() + "' does not hold a string value"); + } + + auto folderPath = directive->getValue().get(); + Log::debug("Validating folder exists: " + folderPath); + if (!FileUtils::isDirectory(folderPath)) + { + return ValidationResult::error(folderPath + " is not a valid directory"); + } + return ValidationResult::success(); +} \ No newline at end of file diff --git a/webserv/config/validation/directive_rules/FolderExistsRule.hpp b/webserv/config/validation/directive_rules/FolderExistsRule.hpp new file mode 100644 index 0000000..3af0055 --- /dev/null +++ b/webserv/config/validation/directive_rules/FolderExistsRule.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "webserv/config/AConfig.hpp" +#include "webserv/config/validation/ValidationResult.hpp" +#include "webserv/config/validation/directive_rules/AValidationRule.hpp" + +class FolderExistsRule : public AValidationRule +{ + public: + FolderExistsRule(bool requiresValue); + + ValidationResult validateValue(const AConfig *config, const std::string &directiveName) const override; +}; diff --git a/webserv/config/validation/directive_rules/HostValidationRule.cpp b/webserv/config/validation/directive_rules/HostValidationRule.cpp new file mode 100644 index 0000000..fe196ec --- /dev/null +++ b/webserv/config/validation/directive_rules/HostValidationRule.cpp @@ -0,0 +1,67 @@ +#include "webserv/config/AConfig.hpp" +#include "webserv/config/directive/ADirective.hpp" +#include "webserv/config/validation/ValidationResult.hpp" +#include "webserv/utils/utils.hpp" + +#include + +#include +#include +#include + +HostValidationRule::HostValidationRule(bool requiresValue) + : AValidationRule("HostValidationRule", "Validates that the host is a valid domain name", requiresValue) +{ +} + +bool isValidIPv4(const std::string &ip) +{ + std::vector parts = utils::split(ip, '.'); + if (parts.size() != 4) + { + return false; + } + for (const std::string &part : parts) + { + if (part.empty() || part.size() > 3) + { + return false; + } + for (char c : part) + { + if (std::isdigit(c) < 0) + { + return false; + } + } + int num = std::stoi(part); + if (num < 0 || num > 255) + { + return false; + } + if (part.size() > 1 && part[0] == '0') + { + return false; + } + } + return true; +} + +ValidationResult HostValidationRule::validateValue(const AConfig *config, const std::string &directiveName) const +{ + const ADirective *directive = config->getDirective(directiveName); + + if (!directive->getValue().holds()) + { + return ValidationResult::error("Directive '" + directive->getName() + "' does not hold a string value"); + } + + auto host = directive->getValue().get(); + + if (!isValidIPv4(host)) + { + return ValidationResult::error("Host '" + host + "' is not a valid IPv4 address"); + } + + return ValidationResult::success(); +} \ No newline at end of file diff --git a/webserv/config/validation/directive_rules/HostValidationRule.hpp b/webserv/config/validation/directive_rules/HostValidationRule.hpp new file mode 100644 index 0000000..c6e10c2 --- /dev/null +++ b/webserv/config/validation/directive_rules/HostValidationRule.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include // for AValidationRule + +#include // for string + +class AConfig; + +class HostValidationRule : public AValidationRule +{ + public: + HostValidationRule(bool requiresValue = true); + + private: + [[nodiscard]] ValidationResult validateValue(const AConfig *config, + const std::string &directiveName) const override; +}; \ No newline at end of file