diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..7a2deec --- /dev/null +++ b/.clang-format @@ -0,0 +1,5 @@ +UseTab: Always +IndentWidth: 4 +TabWidth: 4 +AlignConsecutiveDeclarations: true +ConstructorInitializerIndentWidth: 4 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..228b417 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/tomlpp"] + path = lib/tomlpp + url = https://github.com/keyzox71/tomlpp diff --git a/Makefile b/Makefile index eb79aeb..4aef912 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ # +#+#+#+#+#+ +#+ # # Created: 2024/10/25 16:09:27 by adjoly #+# #+# # # Updated: 2025/04/10 11:52:13 by mmoussou ### ########.fr # +# Updated: 2025/03/25 18:13:53 by adjoly ### ########.fr # # # # **************************************************************************** # @@ -24,7 +25,7 @@ SRCS = $(shell find . -name '*.cpp') OBJS = $(addprefix $(OBJSDIR), $(SRCS:.cpp=.o)) -FLAGS = -Wall -Werror -Wextra -std=c++98 -MMD -MP +FLAGS = -Wall -Werror -Wextra -std=c++98 -MMD -MP -g RED = \033[0;31m GREEN = \033[0;32m @@ -45,7 +46,7 @@ $(NAME): $(OBJS) $(OBJSDIR)%.o: %.cpp @mkdir -p $(@D) - @$(CC) $(FLAGS) -I$(INCLUDES) -c $< -o $@ + @$(CC) $(FLAGS) -I$(INCLUDES) -Ilib/tomlpp/includes -c $< -o $@ @printf "$(DELETE)$(GREEN)「🔨」 build($<): object compiled\n" clean: diff --git a/exemples/test.toml b/exemples/test.toml new file mode 100644 index 0000000..777ed4b --- /dev/null +++ b/exemples/test.toml @@ -0,0 +1,24 @@ +[server] +server_names = { "localhost", "2B5.local" } +host = "localhost" +port = 8080 + +[server.error_pages] +404 = "not_found.html" +401 = "unauthorized.html" +402 = "uwu.html" + +[server.location./] +methods = { "GET" } +root = "/var/www/html" +dirlist = true +client_max_body_size = "10M" + +[server.location./api] +methods = { "GET", "POST" } +root = "/var/www/api" +upload_path = "/etc/webserv/up" +cgi.go = "/bin/go" + +[server.location./redir] +redirect = "https://kanel.ovh" diff --git a/includes/cgi.hpp b/includes/cgi.hpp new file mode 100644 index 0000000..90f9cce --- /dev/null +++ b/includes/cgi.hpp @@ -0,0 +1,23 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* cgi.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: adjoly +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2025/03/24 14:17:34 by adjoly #+# #+# */ +/* Updated: 2025/03/24 14:20:00 by adjoly ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#pragma once + +#include +class cgi { + public: + cgi(); + ~cgi(void); + protected: + private: + std::string _request; +}; diff --git a/includes/config/Route.hpp b/includes/config/Route.hpp new file mode 100644 index 0000000..a09e6ed --- /dev/null +++ b/includes/config/Route.hpp @@ -0,0 +1,102 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Route.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: adjoly +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2025/03/19 14:59:41 by adjoly #+# #+# */ +/* Updated: 2025/03/26 08:31:41 by adjoly ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#pragma once + +#include "cppeleven.hpp" +#include "log.hpp" +#include "node/default.hpp" +#include +#include +#include +#include +#include + +namespace webserv { +namespace config { + +class Route { + public: + Route(toml::ANode *, Logger *); + ~Route(void); + + protected: + private: + bool _dirlist; + bool _cookies; + bool _redirect; + + int32_t _max_body; + + std::string _root; + std::string _up_root; + std::string _index; + std::map *_cgi; + + Logger *_log; + + bool _methods[3]; ///> A methods boolean array which correspond to - 0: GET, + ///1: POST, 2: DELETE + toml::ANode *_table; + + /** + * @brief Can be used to parse a table of cgi + * + * @param The table to get cgi from + * + * @return A pointer to a map of cgi + */ + std::map * + _parseCGI(toml::ANode *); + + /** + * @brief Can be used to parse a table of error pages + * + * @param The table to get the error pages from + * + * @return A pointer to a map of error pages + */ + std::map * + _parseErrPages(std::map *); + + /** + * @brief Can be used to parse a array of methods + * + * @param The table to get the methods from + */ + void _parseMethods(std::vector *); + + /** + * @brief Can be used to sed err pages to the default error pages + */ + void _defaultErrPages(void); + + /** + * @brief Can be used to parse a string of a number with a size (ex. 10M) + * + * @param The input string + * + * @return The number in bytes + */ + int32_t _parseSize(std::string size) { + if (size[size.size()] == 'M') + return std::atoi(size.c_str()) * 1024 * 1024; + if (size[size.size()] == 'K') + return std::atoi(size.c_str()) * 1024; + if (isalpha(size[size.size()])) + return std::atoi(size.c_str()); + return -1; + } +}; + +} // namespace config +} // namespace webserv diff --git a/includes/config/Server.hpp b/includes/config/Server.hpp new file mode 100644 index 0000000..34c3964 --- /dev/null +++ b/includes/config/Server.hpp @@ -0,0 +1,93 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Server.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: adjoly +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2025/03/19 14:11:28 by adjoly #+# #+# */ +/* Updated: 2025/03/25 17:56:34 by adjoly ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#pragma once + +#include "config/default.hpp" +#include "cppeleven.hpp" +#include "node/ANode.hpp" + +namespace webserv { +namespace config { + +class Server { + public: + Server(std::string); + ~Server(); + + /** + * @brief Can be used to get the path of a specific error page or return + * an empty string + * + * @param The http error code for the page + */ + std::string getErrorPage(int page) { + if (_err_pages->find(page) != _err_pages->end()) + return (*_err_pages)[page]; + else + return ""; + } + + /** + * @brief Can be used to get a pointer to a specific route (or not_nullptr + * if not found) + */ + Route *getRoute(std::string route) { + if (_routes->find(route) != _routes->end()) + return (*_routes)[route]; + else + return not_nullptr; + } + + /** + * @brief Can be used to get the Logger pointer + */ + Logger *getLogger(void) { return _log; } + + // @brief Can be used to get a server name + std::vector *getServerNames(void) { return _server_names; } + // @brief Can be used to get the host specified in the config file + std::string getHost(void) { return _host; } + // @brief Can be used to get the port specified in the config file + int getPort(void) { return _port; } + + protected: + private: + std::map + *_routes; ///> A map of all the route present in the config file + std::map *_err_pages; ///> An error pages map to map error + /// specified in the config file + + std::vector + *_server_names; ///> A vector with all the server names + + std::string _host; ///> The host on which the server will be exposed + unsigned short _port; ///> The port on which the server will be exposed + + toml::ANode *_table; ///> The table used for the parsing (is deleted at the + /// end of the constructor) + Logger *_log; ///> A pointer to the logger class + + std::map * + _parseErrPages(std::map *table); + + /** + * @brief Can be used to get the [server] table in _table + * + * @return A pointer to the [server] table as an ANode + */ + toml::ANode *_getServerTable(void); +}; + +} // namespace config + +} // namespace webserv diff --git a/includes/config/default.hpp b/includes/config/default.hpp new file mode 100644 index 0000000..5ff94dc --- /dev/null +++ b/includes/config/default.hpp @@ -0,0 +1,58 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* default.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: adjoly +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2025/03/19 14:15:51 by adjoly #+# #+# */ +/* Updated: 2025/03/26 08:39:08 by adjoly ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#pragma once + +#include "Route.hpp" +#include "Server.hpp" +#include "cppeleven.hpp" +#include "node/Table.hpp" +#include "node/default.hpp" +#include + +namespace webserv { +namespace config { + +/** + * @brief Can be used to access a value in the _table(ANode *) of a specific + *type + * + * @param The name of the value to get + * @param The type of the value to get + * @param The table to search in + * @param A Logger class + * + * @return The value got or not_nullptr + */ +static inline void *accessValue(const std::string &name, toml::nodeType type, + toml::ANode *table, Logger *log) { + void *val; + bool found = false; + + if (table == not_nullptr) + return not_nullptr; + val = dynamic_cast(table)->access(name, type, found); + if (found == true && val != not_nullptr) { + return val; + } else { + if (found == false) { + return not_nullptr; + } else { + log->warn("found - " + name + " but is not " + + toml::nodeTypeToStr(type) + ", skipping..."); + return not_nullptr; + } + } +} + +}; // namespace config +}; // namespace webserv diff --git a/includes/log.hpp b/includes/log.hpp new file mode 100644 index 0000000..df5e305 --- /dev/null +++ b/includes/log.hpp @@ -0,0 +1,119 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* log.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: adjoly +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2025/03/20 09:28:27 by adjoly #+# #+# */ +/* Updated: 2025/03/25 17:50:45 by adjoly ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace webserv { +class Logger { + public: + Logger(const std::string &fileName) : _fileName(fileName) { + if (fileName.empty()) + _ttyOnly = true; + else { + _file.open(fileName.c_str(), std::ios::app); + _ttyOnly = false; + } + if (!_file.is_open() && !_ttyOnly) { + throw std::runtime_error( + "could not open fileeee"); // TODO change that shit but i dont + // know what to put other than a + // htrow + } + } + + Logger(const Logger &other) : _ttyOnly(other._ttyOnly) { + if (!other._ttyOnly) { + _file.open(other._fileName.c_str(), std::ios::app); + if (!_file.is_open()) { + throw std::runtime_error("Could not open file: " + + other._fileName); + } + } + } + + // Copy assignment operator + Logger &operator=(const Logger &other) { + if (this != &other) { + if (_file.is_open()) { + _file.close(); + } + _ttyOnly = other._ttyOnly; + if (!other._ttyOnly) { + _file.open(other._fileName.c_str(), std::ios::app); + if (!_file.is_open()) { + throw std::runtime_error("Could not open file: " + + other._fileName); + } + } + } + return *this; + } + ~Logger(void) { + if (_file.is_open()) + _file.close(); + } + + void info(const std::string &msg) { + std::string ss = printPogitMsg("✏️", "webserv", "info", msg); + std::cerr << ss << std::endl; + if (!_ttyOnly) { + _file << ss << std::endl; + } + } + void warn(const std::string &msg) { + std::string ss = printPogitMsg("🔨", "webserv", "warning", msg); + std::cerr << ss << std::endl; + if (!_ttyOnly) { + _file << ss << std::endl; + } + } + void error(const std::string &msg) { + std::string ss = printPogitMsg("🚧", "webserv", "error", msg); + std::cerr << ss << std::endl; + if (!_ttyOnly) { + _file << ss << std::endl; + } + } + + protected: + private: + std::string printPogitMsg(const std::string &emoji, + const std::string &type, + const std::string &what, + const std::string &msg) { + std::stringstream os; +#ifdef tty + if (what.empty()) + os << type << ":" << msg; + else + os << type << "(" << what << "):" << msg; +#else + if (what.empty()) + os << "「" << emoji << "」" << type << ":" << msg; + else + os << "「" << emoji << "」" << type << "(" << what << "):" << msg; +#endif + return os.str(); + } + + std::string _fileName; + bool _ttyOnly; + std::ofstream _file; +}; + +}; // namespace webserv diff --git a/lib/tomlpp b/lib/tomlpp new file mode 160000 index 0000000..d9e5070 --- /dev/null +++ b/lib/tomlpp @@ -0,0 +1 @@ +Subproject commit d9e507093a3b37d35d5115d766e6d81044cacb00 diff --git a/src/config/Route.cpp b/src/config/Route.cpp new file mode 100644 index 0000000..db52d41 --- /dev/null +++ b/src/config/Route.cpp @@ -0,0 +1,132 @@ +/* ::: :::::::: */ +/* Route.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: adjoly +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2025/03/21 20:37:02 by adjoly #+# #+# */ +/* Updated: 2025/03/26 08:19:25 by adjoly ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "cppeleven.hpp" +#include "log.hpp" +#include "node/default.hpp" +#include +#include +#include + +using namespace webserv::config; + +std::map *Route::_parseCGI(toml::ANode *table) { + std::map *cgi = + new std::map; + void *val; + + for (std::map::iterator it = + table->getTable()->begin(); + it != table->getTable()->end(); it++) { + val = accessValue(it->first, toml::STRING, table, _log); + if (val != not_nullptr) { + if (cgi->find(it->first) != cgi->end()) + continue; + else + (*cgi)[it->first] = *static_cast(val); + } + } + + return cgi; +} + +void Route::_parseMethods(std::vector *table) { + std::string val; + + _methods[0] = false; + _methods[1] = false; + _methods[2] = false; + for (std::vector::iterator it = table->begin(); + it != table->end(); it++) { + if ((*it)->type() == toml::STRING) { + val = *static_cast((*it)->getValue()); + if (val == "GET") + _methods[0] = true; + if (val == "POST") + _methods[1] = true; + if (val == "DELETE") + _methods[2] = true; + } + } +} + +Route::Route(toml::ANode *table, Logger *logger) + : _max_body(10485760), _log(logger) { + void *val; + bool found; + + _log = logger; + _table = table; + if (_table->type() != toml::TABLE) { + _log->warn("location need to be a table and not a :" + + toml::nodeTypeToStr(_table->type())); + return; + } + val = accessValue("redirect", toml::STRING, _table, _log); + if (val != not_nullptr) { + _root = *static_cast(val); + _redirect = true; + return; + } else + _redirect = false; + val = accessValue("dirlist", toml::BOOL, _table, _log); + if (val != not_nullptr) + _dirlist = *static_cast(val); + else + _dirlist = true; + val = accessValue("cookies", toml::BOOL, _table, _log); + if (val != not_nullptr) + _cookies = *static_cast(val); + else + _cookies = false; + val = accessValue("upload_path", toml::STRING, _table, _log); + if (val != not_nullptr) + _up_root = *static_cast(val); + else + _up_root = ""; + val = accessValue("index", toml::STRING, _table, _log); + if (val != not_nullptr) + _index = *static_cast(val); + else + _index = "index.html"; + val = accessValue("root", toml::STRING, _table, _log); + if (val != not_nullptr) + _root = *static_cast(val); + else +#ifdef PKGS + _root = "/var/www/html" +#else + _root = "./html"; +#endif + val = + accessValue("client_max_body_size", toml::STRING, _table, _log); + if (val != not_nullptr) + _max_body = _parseSize(*static_cast(val)); + std::map::iterator it = + _table->accessIt("cgi", toml::TABLE, found); + if (found == true && it != _table->getTable()->end()) + _cgi = _parseCGI(it->second); + else + _cgi = not_nullptr; + val = accessValue("methods", toml::ARRAY, _table, _log); + if (val != not_nullptr) + _parseMethods(static_cast *>(val)); + else { + _methods[0] = true; + _methods[1] = false; + _methods[2] = false; + } +} + +Route::~Route(void) { + if (_redirect == false) + if (_cgi != not_nullptr) + delete _cgi; +} diff --git a/src/config/Server.cpp b/src/config/Server.cpp new file mode 100644 index 0000000..06b4e77 --- /dev/null +++ b/src/config/Server.cpp @@ -0,0 +1,145 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Server.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: adjoly +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2025/03/24 15:10:07 by adjoly #+# #+# */ +/* Updated: 2025/03/26 08:47:50 by adjoly ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "config/Server.hpp" +#include "config/Route.hpp" +#include "cppeleven.hpp" +#include "log.hpp" +#include "node/ANode.hpp" +#include "node/Table.hpp" +#include "node/default.hpp" +#include "tomlpp.hpp" +#include +#include +#include +#include + +using namespace webserv::config; + +toml::ANode *Server::_getServerTable(void) { + toml::ANode *serverT; + + std::map::iterator table = + _table->getTable()->find("server"); + if (table == _table->getTable()->end()) + throw std::runtime_error( + "could not find any [server] table in config file :("); + else + serverT = table->second; + return serverT; +} + +Server::Server(std::string file_name) { + toml::Toml *tomlFile = new toml::Toml(file_name); + + try { + tomlFile->parse(); + } catch (std::runtime_error &e) { + throw e; + } + bool found; + + std::map *map; + _table = tomlFile->getParsedFile(); + + void *val = _table->access("log_file", toml::STRING, found); + std::string log_file = ""; + if (found == true && val != not_nullptr) { + std::string log_file = *static_cast(val); + } + _log = new Logger(log_file); + _table = _getServerTable(); + + // host and port parsing + void *host = accessValue("host", toml::STRING, _table, _log); + if (host != not_nullptr) { + _host = *static_cast(host); + } else { + throw std::runtime_error( + "no host specified - please specify one in server.host"); + } + void *port = accessValue("port", toml::INT, _table, _log); + if (host != not_nullptr) { + _port = *static_cast(port); + } else { + throw std::runtime_error( + "no port specified - please specify one in server.port"); + } + + // server_names parsing + std::map::iterator it = + _table->accessIt("server_names", toml::ARRAY, found); + if (found == true && it != _table->getTable()->end()) { + std::vector::iterator vecIt = + it->second->getArray()->begin(); + _server_names = new std::vector; + for (; vecIt != it->second->getArray()->end(); vecIt++) { + std::string str = *static_cast((*vecIt)->getValue()); + _server_names->push_back(str); + } + } else + _log->warn( + "no server_names all request will be accepted from any hostname"); + + // error_pages parsing + map = static_cast *>( + accessValue("error_pages", toml::TABLE, _table, _log)); + if (map != not_nullptr) { + _err_pages = _parseErrPages(map); + } else + _err_pages = not_nullptr; + + // location parsing + it = _table->accessIt("location", toml::TABLE, found); + if (found == true && it != _table->getTable()->end()) { + _routes = new std::map; + std::map *location_table = it->second->getTable(); + for (it = location_table->begin(); it != location_table->end(); it++) { + if (_routes->find(it->first) != _routes->end()) + continue; + (*_routes)[it->first] = new Route(it->second, _log); + } + } + delete tomlFile->getParsedFile(); + delete tomlFile; +} + +Server::~Server(void) { + std::map::iterator it = _routes->begin(); + for (; it != _routes->end(); it++) { + delete it->second; + } + delete _routes; + delete _err_pages; + delete _server_names; + delete _log; // to see if nessecary +} + +std::map * +Server::_parseErrPages(std::map *table) { + std::map *errPages = new std::map; + void *val; + int nb; + + for (std::map::iterator it = table->begin(); + it != table->end(); it++) { + val = accessValue(it->first, toml::STRING, _table, _log); + if (val != not_nullptr) { + nb = std::atoi(it->first.c_str()); + if (nb >= 400 && nb <= 599) + (*errPages)[nb] = *static_cast(val); + else + _log->warn("error page - " + it->first + " is not valid :("); + } + } + return errPages; +} diff --git a/src/main.cpp b/src/main.cpp index b8a6ace..232db99 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,11 +7,12 @@ /* +#+#+#+#+#+ +#+ */ /* Created: 2025/02/03 15:45:07 by mmoussou #+# #+# */ /* Updated: 2025/04/10 12:18:39 by mmoussou ### ########.fr */ +/* Updated: 2025/03/25 17:10:29 by adjoly ### ########.fr */ /* */ /* ************************************************************************** */ -#include -#include +#include "config/Server.hpp" +#include #define PORT 8080 #define BUFFER_SIZE 4096