diff --git a/.gitignore b/.gitignore index 2a54193..383646c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ obj/ .direnv/ compile_commands.json .cache +*.log diff --git a/Dockerfile b/Dockerfile index 509185a..d12e52d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,13 @@ -FROM alpine:3.21 +FROM alpine:3.21 -COPY ./ /build +COPY ./ /build -RUN apk add --no-cache clang make \ - && cd /build \ - && make \ - && chmod +x webserv \ - && cp webserv /bin/webserv +RUN apk add --no-cache clang make \ + && cd /build \ + && make \ + && chmod +x webserv \ + && cp webserv /bin/webserv -RUN [ "/bin/webserv", "$WEBSERV-CONF"] +STOPSIGNAL SIGINT + +RUN [ "/bin/webserv", "$WEBSERV-CONF"] diff --git a/Makefile b/Makefile index e2cedc7..971b463 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,7 @@ # By: adjoly +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # 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 # +# Updated: 2025/04/22 14:32:50 by adjoly ### ########.fr # # # # **************************************************************************** # @@ -15,7 +14,7 @@ SHELL = bash NAME = webserv -CC = c++ +CC = clang++ OBJSDIR = obj/ diff --git a/exemples/test.toml b/exemples/test.toml index 46b1b6d..60416b7 100644 --- a/exemples/test.toml +++ b/exemples/test.toml @@ -2,7 +2,7 @@ log_file = "test.log" [server] server_names = { "localhost", "2B5.local" } -host = "localhost" +host = "0.0.0.0" port = 8080 [server.error_pages] diff --git a/includes/cgi.hpp b/includes/cgi.hpp index 90f9cce..558c8d9 100644 --- a/includes/cgi.hpp +++ b/includes/cgi.hpp @@ -6,13 +6,14 @@ /* By: adjoly +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2025/03/24 14:17:34 by adjoly #+# #+# */ -/* Updated: 2025/03/24 14:20:00 by adjoly ### ########.fr */ +/* Updated: 2025/04/22 11:51:33 by mmoussou ### ########.fr */ /* */ /* ************************************************************************** */ #pragma once #include + class cgi { public: cgi(); diff --git a/includes/config/Config.hpp b/includes/config/Config.hpp new file mode 100644 index 0000000..e570ce7 --- /dev/null +++ b/includes/config/Config.hpp @@ -0,0 +1,35 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Config.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: adjoly +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2025/04/14 12:20:06 by adjoly #+# #+# */ +/* Updated: 2025/04/22 15:25:39 by adjoly ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#pragma once + +#include + +namespace webserv { +namespace config { + +class Config { + public: + Config(std::string &); + ~Config(); + + Logger *getLogger(void) { return _log; } + + std::vector getServers(void) { return _servers; } + Server *getServer(const std::string &); + + private: + std::vector _servers; +}; + +}; // namespace config +}; // namespace webserv diff --git a/includes/config/Route.hpp b/includes/config/Route.hpp index a09e6ed..7cded90 100644 --- a/includes/config/Route.hpp +++ b/includes/config/Route.hpp @@ -6,15 +6,15 @@ /* By: adjoly +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2025/03/19 14:59:41 by adjoly #+# #+# */ -/* Updated: 2025/03/26 08:31:41 by adjoly ### ########.fr */ +/* Updated: 2025/04/22 12:34:00 by adjoly ### ########.fr */ /* */ /* ************************************************************************** */ #pragma once -#include "cppeleven.hpp" -#include "log.hpp" -#include "node/default.hpp" +#include +#include +#include #include #include #include @@ -26,9 +26,22 @@ namespace config { class Route { public: - Route(toml::ANode *, Logger *); + Route(toml::ANode *); ~Route(void); + bool getDirList(void) { return _dirlist; } + bool getCookies(void) { return _cookies; } + bool getRedirect(void) { return _redirect; } + + int32_t getMaxBody(void) { return _max_body; } + + std::string getRootDir(void) { return _root; } + std::string getUpRoot(void) { return _up_root; } + std::string getIndex(void) { return _index; } + std::map *getCgi(void) { return _cgi; } + + bool *getMethods(void) { return _methods; } + protected: private: bool _dirlist; @@ -42,8 +55,6 @@ class Route { 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; diff --git a/includes/config/Server.hpp b/includes/config/Server.hpp index 34c3964..5dfb258 100644 --- a/includes/config/Server.hpp +++ b/includes/config/Server.hpp @@ -6,22 +6,25 @@ /* By: adjoly +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2025/03/19 14:11:28 by adjoly #+# #+# */ -/* Updated: 2025/03/25 17:56:34 by adjoly ### ########.fr */ +/* Updated: 2025/04/22 15:25:58 by adjoly ### ########.fr */ /* */ /* ************************************************************************** */ #pragma once -#include "config/default.hpp" -#include "cppeleven.hpp" -#include "node/ANode.hpp" +#include +#include +#include +#include +#include +#include namespace webserv { namespace config { class Server { public: - Server(std::string); + Server(toml::ANode *); ~Server(); /** @@ -48,11 +51,6 @@ class Server { 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 @@ -60,9 +58,14 @@ class Server { // @brief Can be used to get the port specified in the config file int getPort(void) { return _port; } + // @brief Can be used to check if a servername is present in this config + bool isServerName(const std::string &); + + Route *whatRoute(const URL &); + protected: private: - std::map + 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 @@ -75,17 +78,11 @@ class Server { 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 diff --git a/includes/config/URL.hpp b/includes/config/URL.hpp new file mode 100644 index 0000000..f5a7a50 --- /dev/null +++ b/includes/config/URL.hpp @@ -0,0 +1,75 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* URL.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: adjoly +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2025/04/22 12:17:48 by adjoly #+# #+# */ +/* Updated: 2025/04/22 12:42:21 by adjoly ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#pragma once + +#include +#include +#include +#include +#include + +class URL { + public: + URL(const std::string &url) : _full_url(url) { parse(); } + + bool operator==(const URL &other) const { + return comparePathSegments(other); + } + + bool operator<(const URL &other) const { return _full_url < other._full_url; } + + std::vector getSegments(void) { return _path_segments; } + std::string getFullUrl(void) { return _full_url; } + + private: + void parse() { + size_t scheme_pos = _full_url.find("://"); + size_t path_start = 0; + + if (scheme_pos != std::string::npos) { + path_start = _full_url.find('/', scheme_pos + 3); + } else { + path_start = 0; + } + + if (path_start != std::string::npos) { + std::string path = _full_url.substr(path_start); + splitPath(path, _path_segments); + } + } + + void splitPath(const std::string &path, + std::vector &segments) const { + std::stringstream ss(path); + std::string segment; + while (std::getline(ss, segment, '/')) { + if (!segment.empty()) { + segments.push_back(segment); + } + } + } + + bool comparePathSegments(const URL &other) const { + size_t min_size = + std::min(_path_segments.size(), other._path_segments.size()); + for (size_t i = 0; i < min_size; ++i) { + if (_path_segments[i] != other._path_segments[i]) { + return false; + } + } + return true; + } + + std::string _full_url; + std::vector _path_segments; +}; diff --git a/includes/config/default.hpp b/includes/config/default.hpp index 5ff94dc..3e77b19 100644 --- a/includes/config/default.hpp +++ b/includes/config/default.hpp @@ -6,17 +6,18 @@ /* By: adjoly +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2025/03/19 14:15:51 by adjoly #+# #+# */ -/* Updated: 2025/03/26 08:39:08 by adjoly ### ########.fr */ +/* Updated: 2025/04/22 15:28:31 by adjoly ### ########.fr */ /* */ /* ************************************************************************** */ #pragma once -#include "Route.hpp" -#include "Server.hpp" -#include "cppeleven.hpp" -#include "node/Table.hpp" -#include "node/default.hpp" +#include +#include +#include +#include +#include +#include #include namespace webserv { @@ -34,13 +35,13 @@ namespace config { * @return The value got or not_nullptr */ static inline void *accessValue(const std::string &name, toml::nodeType type, - toml::ANode *table, Logger *log) { + 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); + val = dynamic_cast(table)->access(name, type, found); if (found == true && val != not_nullptr) { return val; } else { diff --git a/includes/help.hpp b/includes/help.hpp index 442cfc1..900ba33 100644 --- a/includes/help.hpp +++ b/includes/help.hpp @@ -6,7 +6,7 @@ /* By: adjoly +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2025/04/10 13:43:54 by adjoly #+# #+# */ -/* Updated: 2025/04/10 13:58:52 by adjoly ### ########.fr */ +/* Updated: 2025/04/11 11:36:22 by adjoly ### ########.fr */ /* */ /* ************************************************************************** */ @@ -14,3 +14,5 @@ #define SAMPLE_CONF_PATH "./sample.conf" #define WEBSRV_VERSION "v0.1" + +bool help(int, char **); diff --git a/includes/log.hpp b/includes/log.hpp index 9d38ea3..16209e6 100644 --- a/includes/log.hpp +++ b/includes/log.hpp @@ -6,7 +6,7 @@ /* By: adjoly +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2025/03/20 09:28:27 by adjoly #+# #+# */ -/* Updated: 2025/04/10 14:21:46 by adjoly ### ########.fr */ +/* Updated: 2025/04/22 16:14:25 by adjoly ### ########.fr */ /* */ /* ************************************************************************** */ @@ -19,22 +19,41 @@ #include namespace webserv { + +/** + * @brief Used to log debug message + * + * @note Only work if VERBOSE mode is active + */ +static inline void log(std::string emoji, std::string who, std::string str) { +#ifdef VERBOSE + if (who.empty()) + std::cout << "「" << emoji << "」debug: " << str << std::endl; + else + std::cout << "「" << emoji << "」debug(" << who << "): " << str + << std::endl; +#else + (void)emoji, (void)who, (void)str; +#endif +} + class Logger { public: + Logger(void) : _ttyOnly(true) { + log("➕", "Logger", "default constructor called"); + } Logger(const std::string &fileName) : _fileName(fileName) { - if (fileName.empty()) - _ttyOnly = true; - else { - _file.open(fileName.c_str(), std::ios::app); - _ttyOnly = false; - } + log("➕", "Logger", "filename constructor called"); + _file.open(fileName.c_str(), std::ios::app); if (!_file.is_open() && !_ttyOnly) { _ttyOnly = true; warn("could not open log file, going tty only"); - } + } else + _ttyOnly = false; } Logger(const Logger &other) : _ttyOnly(other._ttyOnly) { + log("➕", "Logger", "copy constructor called"); if (!other._ttyOnly) { _file.open(other._fileName.c_str(), std::ios::app); if (!_file.is_open()) { @@ -46,6 +65,7 @@ class Logger { // Copy assignment operator Logger &operator=(const Logger &other) { + log("➕", "Logger", "copy assignment constructor called"); if (this != &other) { if (_file.is_open()) { _file.close(); @@ -62,6 +82,7 @@ class Logger { return *this; } ~Logger(void) { + log("➖", "Logger", "destructor called"); if (_file.is_open()) _file.close(); } @@ -88,12 +109,23 @@ class Logger { } } + void debug(const std::string &msg) { +#ifdef VERBOSE + std::string ss = printPogitMsg("🏗️", "webserv", "debug", msg); + std::cerr << ss << std::endl; + if (!_ttyOnly) { + _file << ss << std::endl; + } +#else + (void)msg; +#endif + + } + protected: private: - std::string printPogitMsg(const std::string &emoji, - const std::string &type, - const std::string &what, - const std::string &msg) { + std::string printPogitMsg(const std::string &emoji, const std::string &type, + const std::string &what, const std::string &msg) { std::stringstream os; #ifdef TTY (void)emoji; @@ -103,9 +135,9 @@ class Logger { os << type << "(" << what << "):" << msg; #else if (what.empty()) - os << "「" << emoji << "」" << type << ":" << msg; + os << "「" << emoji << "」" << type << ": " << msg; else - os << "「" << emoji << "」" << type << "(" << what << "):" << msg; + os << "「" << emoji << "」" << type << "(" << what << "): " << msg; #endif return os.str(); } @@ -115,4 +147,6 @@ class Logger { std::ofstream _file; }; +extern Logger *_log; + }; // namespace webserv diff --git a/includes/requests/Errors.hpp b/includes/requests/Errors.hpp index c57a05d..e2b2e46 100644 --- a/includes/requests/Errors.hpp +++ b/includes/requests/Errors.hpp @@ -6,13 +6,11 @@ /* By: mmoussou #include @@ -22,16 +20,10 @@ namespace webserv { namespace http { -/* - * DOES NOT WORK - * still need to do uh things but base is done at least :D -*/ - class Errors { public: - //static http::Response &getRequest(int error_code); static std::string getResponseBody(int error_code); - static void setEntry(const std::string &key, int value); + static void setEntries(const std::map); static std::map message; private: @@ -43,5 +35,3 @@ private: } // -namespace http } // -namespace webserv - -#endif // __WEBSERV_REQUESTS_ERRORS_HPP__ diff --git a/includes/requests/HttpIMessage.hpp b/includes/requests/HttpIMessage.hpp index 9ed38b2..60511ee 100644 --- a/includes/requests/HttpIMessage.hpp +++ b/includes/requests/HttpIMessage.hpp @@ -6,13 +6,11 @@ /* By: mmoussou #include @@ -25,6 +23,8 @@ public: virtual std::map getHeaders(void) const; virtual std::string getBody(void) const; + virtual ~IMessage() {} + virtual void setHeaders(std::map const headers); virtual void setBody(std::string const body); @@ -43,5 +43,3 @@ protected: } // -namespace http } // -namespace webserv - -#endif // __WEBSERV_REQUESTS_HTTP_IMESSAGE_HPP__ diff --git a/includes/requests/HttpRequest.hpp b/includes/requests/HttpRequest.hpp index 8670da5..f1d8b7e 100644 --- a/includes/requests/HttpRequest.hpp +++ b/includes/requests/HttpRequest.hpp @@ -6,13 +6,11 @@ /* By: mmoussou #include @@ -22,11 +20,15 @@ #include #include +#include + namespace webserv { namespace http { class IRequest: public http::IMessage { public: + virtual ~IRequest(void); + virtual void parse(std::string const &data) = 0; virtual http::Response execute(void) = 0; @@ -35,21 +37,25 @@ public: std::string getMethod(void) const; std::string getTarget(void) const; std::string getProtocol(void) const; + config::Server *getConfig(void) const; void setMethod(std::string const method); void setTarget(std::string const target); void setProtocol(std::string const protocol); + void setServer(std::string const protocol); protected: std::string _method; std::string _target; std::string _protocol; + config::Server *_conf; }; class Get: public http::IRequest { public: Get(void); + ~Get(void); Get(std::string &data); void parse(std::string const &data); @@ -61,6 +67,7 @@ public: class Post: public http::IRequest { public: Post(void); + ~Post(void); Post(std::string &data); void parse(std::string const &data); @@ -72,6 +79,7 @@ public: class Delete: public http::IRequest { public: Delete(void); + ~Delete(void); Delete(std::string &data); void parse(std::string const &data); @@ -82,5 +90,3 @@ public: } // -namespace http } // -namespace webserv - -#endif // __WEBSERV_REQUESTS_HTTP_REQUEST_HPP__ diff --git a/includes/requests/HttpResponse.hpp b/includes/requests/HttpResponse.hpp index 1c4283b..8ac7e33 100644 --- a/includes/requests/HttpResponse.hpp +++ b/includes/requests/HttpResponse.hpp @@ -6,13 +6,11 @@ /* By: mmoussou @@ -26,6 +24,7 @@ namespace http { class Response: public http::IMessage { public: Response(void); + ~Response(void); std::string getProtocol(void) const; uint getStatusCode(void) const; @@ -45,5 +44,3 @@ private: } // -namespace http } // -namespace webserv - -#endif // __WEBSERV_REQUESTS_HTTP_RESPONSE_HPP__ diff --git a/includes/requests/default.hpp b/includes/requests/default.hpp index 82636b6..6359f52 100644 --- a/includes/requests/default.hpp +++ b/includes/requests/default.hpp @@ -6,18 +6,14 @@ /* By: mmoussou #include #include using namespace webserv; - -#endif // __WEBSERV_REQUESTS_DEFAULT_HPP__ diff --git a/includes/server/Client.hpp b/includes/server/Client.hpp new file mode 100644 index 0000000..4d1ca6f --- /dev/null +++ b/includes/server/Client.hpp @@ -0,0 +1,42 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Client.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: adjoly +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2025/04/14 14:14:39 by adjoly #+# #+# */ +/* Updated: 2025/04/23 14:39:16 by mmoussou ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace webserv { +namespace server { + +class Client { + public: + Client(int, sockaddr_in, config::Config *); + virtual ~Client(void); + + void answer(void); + + private: + void _getRequest(std::string); + + int _fd; + struct sockaddr_in _client_addr; + http::IRequest *_request; + //http::Response *_response; + config::Server *_conf; +}; + +} // namespace server +} // namespace webserv diff --git a/includes/server/Server.hpp b/includes/server/Server.hpp new file mode 100644 index 0000000..2d13f61 --- /dev/null +++ b/includes/server/Server.hpp @@ -0,0 +1,92 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Server.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: adjoly +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2025/04/11 17:45:43 by adjoly #+# #+# */ +/* Updated: 2025/04/22 11:51:27 by mmoussou ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace webserv { + +struct client_data { + sockaddr_in sock_data; + pollfd poll_fd; +}; + +class Server { + public: + Server(config::Config *); + ~Server(void); + + protected: + private: + /** + * @brief Used to setup the webserver (primarly socket) + */ + void _setup(void); + /** + * @brief Used to run the webserver + */ + void _run(void); + + /** + * @brief Used to handle client request + * + * @param The fd of the client + */ + bool _handle_client(struct pollfd &, sockaddr_in *); + + /** + * @brief Can be used to fill the vector passed as parameters with all the + * port and host in the config + * @param The vector of host + * @param The vector of port + */ + int _fillHostsPorts(std::vector &, std::vector &); + + /** + * @brief Can be used to open a socket with a specific port and host + * @param The host + * @param The port + */ + int _createSocket(std::string, int); + + /** + * @brief Can be used to check if an fd is one of the socket or not + * + * @param the fd to check + */ + bool _isServerSocket(int fd) { + for (std::vector::iterator it = _fds_server.begin(); + it != _fds_server.end(); it++) { + if (fd == *it) { + return true; + } + } + return false; + } + + config::Config + *_conf; // Pointer to the configuration class (with all config in) + Logger *_log; // Pointer to the log class + std::vector _fds_server; // The fds of the sockets + std::vector _client_fds; // A vector of all the poll fd + std::vector + _client_data; // vector of all the client sockaddr_in +}; + +}; // namespace webserv diff --git a/includes/server/default.hpp b/includes/server/default.hpp index 1f956c6..76c1351 100644 --- a/includes/server/default.hpp +++ b/includes/server/default.hpp @@ -6,20 +6,19 @@ /* By: mmoussou +#include namespace webserv { -namespace server { -} // -namespace server + + } // -namespace webserv using namespace webserv; - -#endif // __WEBSERV_SERVER_DEFAULT_HPP__ diff --git a/includes/webserv.hpp b/includes/webserv.hpp index a519920..1462958 100644 --- a/includes/webserv.hpp +++ b/includes/webserv.hpp @@ -6,26 +6,40 @@ /* By: mmoussou -#include -#include +#define auto __auto_type + +#include #include #include +#include +#include #include -#include -#include #include +#include +#include +#include +#include +#include + + +#define range(x) \ + x.begin(); \ + it != x.end(); \ + it++ +#define prange(x) \ + x->begin(); \ + it != x->end(); \ + it++ + +#define BUFFER_SIZE 4096 namespace webserv { -} //-namespace webserv +} // namespace webserv -#endif // __WEBSERV_WEBSERV_HPP__ diff --git a/sample.conf b/sample.conf new file mode 100644 index 0000000..531a1af --- /dev/null +++ b/sample.conf @@ -0,0 +1,9 @@ +[server] +host = "localhost" +port = 8080 + +[server.location./] +methods = { "GET" } +root = "/var/www/html" +dirlist = true +client_max_body_size = "10M" diff --git a/src/config/Config.cpp b/src/config/Config.cpp new file mode 100644 index 0000000..02513d1 --- /dev/null +++ b/src/config/Config.cpp @@ -0,0 +1,60 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Config.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: adjoly +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2025/04/14 12:53:54 by adjoly #+# #+# */ +/* Updated: 2025/04/22 15:35:52 by adjoly ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include "node/default.hpp" +#include "webserv.hpp" +#include "cppeleven.hpp" +#include "node/ANode.hpp" +#include + +using namespace webserv::config; + +Config::Config(std::string &filename) { + toml::Toml *file = new toml::Toml(filename); + + file->parse(); + toml::ANode *table = file->getParsedFile(); + + bool found = false; + void *logFile = table->access("log_file", toml::STRING, found); + if (found == true && logFile != not_nullptr) { + _log = new Logger(*static_cast(logFile)); + } else { + _log = new Logger(); + } + + std::map *node = table->getTable(); + for (auto it = prange(node)) { + if (it->second->type() == toml::TABLE) { + _log->info("taking server from table : " + it->first); + Server *srv = new Server(it->second); + _servers.push_back(srv); + } + } + delete table; + delete file; +} + +Config::~Config(void) { + for (auto it = range(_servers)) { + delete *it; + } +} + +Server *Config::getServer(const std::string &server_name) { + for (auto it = range(_servers)) { + if ((*it)->isServerName(server_name)) { + return (*it); + } + } + return (not_nullptr); +} diff --git a/src/config/Route.cpp b/src/config/Route.cpp index db52d41..43e6d5b 100644 --- a/src/config/Route.cpp +++ b/src/config/Route.cpp @@ -9,12 +9,13 @@ /* ************************************************************************** */ #include "cppeleven.hpp" -#include "log.hpp" +#include #include "node/default.hpp" #include #include #include + using namespace webserv::config; std::map *Route::_parseCGI(toml::ANode *table) { @@ -57,12 +58,11 @@ void Route::_parseMethods(std::vector *table) { } } -Route::Route(toml::ANode *table, Logger *logger) - : _max_body(10485760), _log(logger) { +Route::Route(toml::ANode *table) + : _max_body(10485760) { void *val; bool found; - _log = logger; _table = table; if (_table->type() != toml::TABLE) { _log->warn("location need to be a table and not a :" + diff --git a/src/config/Server.cpp b/src/config/Server.cpp index 06b4e77..1e49c05 100644 --- a/src/config/Server.cpp +++ b/src/config/Server.cpp @@ -6,71 +6,37 @@ /* By: adjoly +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2025/03/24 15:10:07 by adjoly #+# #+# */ -/* Updated: 2025/03/26 08:47:50 by adjoly ### ########.fr */ +/* Updated: 2025/04/22 15:36:30 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 +#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; - } +Server::Server(toml::ANode *node) : _table(node) { bool found; - std::map *map; - _table = tomlFile->getParsedFile(); + if (_table == not_nullptr) + return; - 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 + // host parsing void *host = accessValue("host", toml::STRING, _table, _log); if (host != not_nullptr) { _host = *static_cast(host); } else { + delete _table; throw std::runtime_error( "no host specified - please specify one in server.host"); } + // port parsing void *port = accessValue("port", toml::INT, _table, _log); - if (host != not_nullptr) { + if (port != not_nullptr) { _port = *static_cast(port); } else { + delete _table; throw std::runtime_error( "no port specified - please specify one in server.port"); } @@ -86,13 +52,16 @@ Server::Server(std::string file_name) { std::string str = *static_cast((*vecIt)->getValue()); _server_names->push_back(str); } - } else + } else { _log->warn( "no server_names all request will be accepted from any hostname"); + _server_names = not_nullptr; + } // error_pages parsing - map = static_cast *>( - accessValue("error_pages", toml::TABLE, _table, _log)); + std::map *map = + static_cast *>( + accessValue("error_pages", toml::TABLE, _table, _log)); if (map != not_nullptr) { _err_pages = _parseErrPages(map); } else @@ -101,27 +70,26 @@ Server::Server(std::string file_name) { // 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(); + _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()) + if (_routes->find(URL(it->first)) != _routes->end()) continue; - (*_routes)[it->first] = new Route(it->second, _log); + (*_routes)[URL(it->first)] = new Route(it->second); } } - delete tomlFile->getParsedFile(); - delete tomlFile; + //delete _table; } Server::~Server(void) { - std::map::iterator it = _routes->begin(); - for (; it != _routes->end(); it++) { + for (auto it = prange(_routes)) { delete it->second; } delete _routes; delete _err_pages; - delete _server_names; - delete _log; // to see if nessecary + if (_server_names != not_nullptr) + delete _server_names; } std::map * @@ -143,3 +111,21 @@ Server::_parseErrPages(std::map *table) { } return errPages; } + +bool Server::isServerName(const std::string &server_name) { + for (auto it = prange(_server_names)) { + if (*it == server_name) { + return true; + } + } + return false; +} + +Route *Server::whatRoute(const URL &url) { + for (auto it = prange(_routes)) { + if (it->first == url) { + return it->second; + } + } + return not_nullptr; +} diff --git a/src/help.cpp b/src/help.cpp index 7bc3781..2982778 100644 --- a/src/help.cpp +++ b/src/help.cpp @@ -6,11 +6,11 @@ /* By: adjoly +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2025/04/10 13:08:36 by adjoly #+# #+# */ -/* Updated: 2025/04/10 14:20:36 by adjoly ### ########.fr */ +/* Updated: 2025/04/22 11:47:39 by mmoussou ### ########.fr */ /* */ /* ************************************************************************** */ -#include "log.hpp" +#include #include #include #include @@ -37,7 +37,7 @@ void _generateConf(void) { file << "[server]\nhost = \"localhost\"\nport = " "8080\n\n[server.location./]\nmethods = { \"GET\" }\nroot " "= \"/var/www/html\"\ndirlist = true\nclient_max_body_size " - "= \"10M\""; + "= \"10M\"\n"; file.close(); _log.info("config file successfully generated"); } else { @@ -50,16 +50,24 @@ void _printVersion(void) { std::cout << "You are running : Webserv " << WEBSRV_VERSION << std::endl; } -void help(int ac, char **av) { +bool help(int ac, char **av) { if (ac < 2) { _printHelp(); - return; + return true; } std::string option = av[1]; - if (option == "--help" || option == "-v") + if (option == "--help" || option == "-v") { _printHelp(); - else if (option == "--generate" || option == "-g") + return true; + } + else if (option == "--generate" || option == "-g") { _generateConf(); - else if (option == "--version" || option == "-v") + return true; + } + else if (option == "--version" || option == "-v") { _printVersion(); + return true; + } + else + return false; } diff --git a/src/main.cpp b/src/main.cpp index 5c7e217..37142c5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,134 +6,65 @@ /* By: mmoussou -#include -#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include -#define PORT 8080 -#define BUFFER_SIZE 4096 - -int server_socket; -int client_socket; - -void close_socket(int signal) -{ - std::cerr << std::endl << "closing..." << std::endl; - close(client_socket); - close(server_socket); - exit(signal); +namespace webserv { + Logger *_log = not_nullptr; } -std::string getMethod(std::string &data) -{ - return (data.substr(0, data.substr(0, 4).find_last_not_of(" ") + 1)); +int _sig = 0; + +void ft_sig(int sig) { + _sig = sig; + std::stringstream str; + str << "sig hitted = "; + str << sig; + _log->info(str.str()); } -int main() -{ - // handle ctrl-C to close server socket - if (signal(SIGPIPE, SIG_IGN) == SIG_ERR || signal(SIGINT, close_socket) == SIG_ERR || signal(SIGQUIT, close_socket) == SIG_ERR) - { - std::cerr << "Error registering signal handlers!" << std::endl; - return 1; - } +int main(int ac, char **av) { + if (help(ac, av)) { + return EXIT_SUCCESS; + } + std::cout << "Starting server..." << std::endl; + if (access(av[1], F_OK) < 0) { + std::cout << "File : " << av[1] << " could not be opened" << std::endl; + return EXIT_FAILURE; + } - // create a socket - server_socket = socket(AF_INET, SOCK_STREAM, 0); - if (server_socket == -1) - { - std::cerr << "Failed to create socket" << std::endl; - return 1; - } - // prepare the server address - sockaddr_in server_address; - std::memset(&server_address, 0, sizeof(server_address)); - server_address.sin_family = AF_INET; - server_address.sin_addr.s_addr = INADDR_ANY; - server_address.sin_port = htons(PORT); + config::Config *conf; + try { + std::string str = av[1]; + conf = new config::Config(str); + } catch (std::exception &e) { + std::cout << e.what() << std::endl; + return 1; + } + if (signal(SIGINT, &ft_sig) == SIG_ERR) { + conf->getLogger()->error("could not bind sigint :("); + return EXIT_FAILURE; + } - if (bind(server_socket, (sockaddr*)&server_address, sizeof(server_address)) == -1) - { - std::cerr << "Failed to bind socket" << std::endl; - return 1; - } - if (listen(server_socket, 5) == -1) - { - std::cerr << "Failed to listen on socket" << std::endl; - return 1; - } + webserv::Server *serv = new webserv::Server(conf); - std::cout << "Server is listening on port " << PORT << std::endl; - - while (true) - { - // accept an incoming connection - sockaddr_in client_address; - socklen_t client_address_len = sizeof(client_address); - //int client_socket = accept(server_socket, (sockaddr*)&client_address, &client_address_len); - client_socket = accept(server_socket, (sockaddr*)&client_address, &client_address_len); - if (client_socket == -1) { - std::cerr << "Failed to accept connection" << std::endl; - continue; - } - - // receive the HTTP request - std::string received_data; - char buffer[BUFFER_SIZE]; - ssize_t bytes_received; - do - { - std::memset(buffer, 0, BUFFER_SIZE); - bytes_received = recv(client_socket, buffer, BUFFER_SIZE - 1, 0); - if (bytes_received == -1) - { - std::cerr << "Failed to receive request" << std::endl; - close(client_socket); - continue; - } - received_data += std::string(buffer, bytes_received); - } - while (buffer[bytes_received]); - - // parse the request - - // handle the request - std::string response; - - //std::cout << received_data << std::endl; - std::cout << getMethod(received_data) << std::endl; - - if (getMethod(received_data) == "GET") - { - std::cout << "------------ GET REQUEST ------------" << std::endl; - http::Get request(received_data); - - response = request.execute().str(); - } - else if (getMethod(received_data) == "POST") - { - std::cout << "------------ POST REQUEST ------------" << std::endl; - http::Post request(received_data); - - response = request.execute().str(); - //std::cout << "worked" << std::endl; - } - else - { - response = "HTTP/1.1 501 Not Implemented\r\nContent-Type: text/html\r\n\r\n

501 Not Implemented

"; - } - - send(client_socket, response.c_str(), response.length(), 0); - //close(client_socket); - } - - close(server_socket); - return 0; + delete serv; + delete _log; + delete conf; } diff --git a/src/requests_handling/HttpRequests.cpp b/src/requests_handling/HttpRequests.cpp index 1ac8959..ba7e50f 100644 --- a/src/requests_handling/HttpRequests.cpp +++ b/src/requests_handling/HttpRequests.cpp @@ -6,7 +6,7 @@ /* By: mmoussou parse(data); @@ -218,6 +226,10 @@ http::Delete::Delete(void) { } +http::Delete::~Delete(void) +{ +} + http::Delete::Delete(std::string &data) { this->parse(data); @@ -297,6 +309,10 @@ http::Post::Post(void) { } +http::Post::~Post(void) +{ +} + http::Post::Post(std::string &data) { this->parse(data); diff --git a/src/requests_handling/HttpResponse.cpp b/src/requests_handling/HttpResponse.cpp index 9fccdb4..c38e4ec 100644 --- a/src/requests_handling/HttpResponse.cpp +++ b/src/requests_handling/HttpResponse.cpp @@ -6,7 +6,7 @@ /* By: mmoussou +#include + +using namespace server; + +Client::Client(int fd, sockaddr_in socket, config::Config *conf) + : _fd(fd), _client_addr(socket) { + std::string received_data; + char buffer[BUFFER_SIZE]; + ssize_t bytes_received; + do { + std::memset(buffer, 0, BUFFER_SIZE); + bytes_received = recv(fd, buffer, BUFFER_SIZE - 1, 0); + if (bytes_received == -1) { + _log->error("failed to receive request"); + throw std::runtime_error("failed to receive request"); + } + received_data += std::string(buffer, bytes_received); + } while (buffer[bytes_received]); + + this->_getRequest(received_data); + + this->_conf = conf->getServer(this->_request->getHeaders()["Host"]); + + // if (received_data.length > (get max_body_size from Route corresponding) ) + // throw error +} + +void Client::_getRequest(std::string request_str) { + std::string method = request_str.substr( + 0, request_str.substr(0, 4).find_last_not_of(" ") + 1); + + if (method == "GET") { + _log->info("get request received"); + this->_request = new http::Get(request_str); + } else if (method == "DELETE") { + _log->info("delete request received"); + this->_request = new http::Delete(request_str); + } else if (method == "POST") { + _log->info("post request received"); + this->_request = new http::Post(request_str); + } else { + _log->info("unsupported request received"); + this->_request = new http::Get(); + this->_request->setMethod("501"); + } +} + +void Client::answer(void) { + (void) _client_addr; + std::string response; + + if (this->_request->getMethod() == "GET" || + this->_request->getMethod() == "DELETE" || + this->_request->getMethod() == "POST") + response = this->_request->execute().str(); + else + response = "HTTP/1.1 501 Not Implemented\r\nContent-Type: text/html\r\n\r\n

501 Not Implemented

"; + send(this->_fd, response.c_str(), response.length(), 0); +} + +Client::~Client(void) { + delete (http::Get *)(this->_request); +} diff --git a/src/server/Server.cpp b/src/server/Server.cpp new file mode 100644 index 0000000..ec8f0cd --- /dev/null +++ b/src/server/Server.cpp @@ -0,0 +1,166 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Server.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: adjoly +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2025/04/11 16:11:40 by adjoly #+# #+# */ +/* Updated: 2025/04/23 16:22:22 by adjoly ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace webserv; + +extern int _sig; + +std::string convertIPToString(const struct in_addr *addr) { + unsigned int ip = ntohl(addr->s_addr); + std::stringstream ss; + ss << ((ip >> 24) & 0xFF) << "." << ((ip >> 16) & 0xFF) << "." + << ((ip >> 8) & 0xFF) << "." << (ip & 0xFF); + return ss.str(); +} + +std::string convertPortToString(const struct sockaddr_in *sa) { + int port = ntohs(sa->sin_port); + std::stringstream ss; + ss << port; + return ss.str(); +} + +std::string getMethod(std::string &data) { + return (data.substr(0, data.substr(0, 4).find_last_not_of(" ") + 1)); +} + +int Server::_fillHostsPorts(std::vector &hosts, + std::vector &ports) { + std::vector config = _conf->getServers(); + + for (auto it = range(config)) { + hosts.push_back((*it)->getHost()); + ports.push_back((*it)->getPort()); + } + return config.size(); +} + +void Server::_setup(void) { + std::vector hosts; + std::vector ports; + + int size = _fillHostsPorts(hosts, ports); + if (size < 1) + throw std::runtime_error("no server present in the config file"); + + auto itH = hosts.begin(); + for (auto it = range(ports), itH++) { + int fd = _createSocket(*itH, *it); + _fds_server.push_back(fd); + } +} + +short sigHandling(void) { + if (_sig == SIGINT) { + return 727; + } + return 0; +} + +void Server::_run(void) { + struct pollfd fd; + + for (std::vector::iterator it = _fds_server.begin(); + it != _fds_server.end(); it++) { + fd.fd = *it; + fd.events = POLLIN; + _client_fds.push_back(fd); + _client_data.push_back(NULL); + _log->debug("new socket in poll"); + } + + // to add signal instead of 727 + while (727 - sigHandling()) { + if (poll(_client_fds.data(), _client_fds.size(), -1) < 0) { + std::stringstream str; + str << "poll failed : "; + str << strerror(errno); + _log->error(str.str()); + continue; + } + + for (auto it = range(_fds_server)) { + struct sockaddr_in client_addr; + socklen_t addrlen = sizeof(client_addr); + int client_fd = + accept((*it), (struct sockaddr *)&client_addr, &addrlen); + + if (client_fd < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + continue; + std::stringstream str; + str << "Accept failed: "; + str << strerror(errno); + _log->error(str.str()); + continue; + } + + pollfd pfd; + pfd.fd = client_fd; + pfd.events = POLLIN | POLLOUT; + pfd.revents = 0; + _client_fds.push_back(pfd); + struct sockaddr_in *new_client_sock = new sockaddr_in(); + std::memmove(new_client_sock, &client_addr, + sizeof(struct sockaddr_in)); + _client_data.push_back(new_client_sock); + } + + for (size_t i = _fds_server.size(); i < _client_fds.size(); ++i) { + if (_client_fds[i].revents & POLLIN) { + if (_handle_client(_client_fds[i], _client_data[i])) { + close(_client_fds[i].fd); + _client_fds.erase(_client_fds.begin() + i); + delete _client_data[i]; + _client_data.erase(_client_data.begin() + i); + i--; + } + } + } + } +} + +Server::Server(config::Config *conf) : _conf(conf) { + log("➕", "Server::Server", "config constructor called"); + _log = conf->getLogger(); + try { + _setup(); + _run(); + } catch (std::exception &e) { + _log->error(e.what()); + } +} + +Server::~Server(void) { + log("➖", "Server::Server", "destructor called"); + for (std::vector::iterator it = _client_fds.begin(); + it != _client_fds.end(); it++) + close(it->fd); +} diff --git a/src/server/ServerUtils.cpp b/src/server/ServerUtils.cpp new file mode 100644 index 0000000..e2becb6 --- /dev/null +++ b/src/server/ServerUtils.cpp @@ -0,0 +1,93 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* ServerUtils.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: adjoly +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2025/04/17 11:58:42 by adjoly #+# #+# */ +/* Updated: 2025/04/23 16:00:14 by adjoly ### ########.fr */ +/* */ +/* ************************************************************************** */ + +#include +#include +#include +#include +#include +#include +#include + +using namespace webserv::server; + +bool convertStringToIP(const char *ip_str, struct in_addr *addr) { + // Split the IP string into four octets + unsigned int a, b, c, d; + if (sscanf(ip_str, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) { + return false; + } + + // Check if each octet is within the valid range + if (a > 255 || b > 255 || c > 255 || d > 255) { + return false; + } + + // Combine the octets into a single 32-bit address + addr->s_addr = htonl((a << 24) | (b << 16) | (c << 8) | d); + return true; +} + +int Server::_createSocket(std::string host, int port) { + int fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (fd == -1) { + std::ostringstream str; + str << port; + throw std::runtime_error("socket binding failed for : " + host + ":" + + str.str()); + return -1; + } + + int opt = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { + close(fd); + throw std::runtime_error("setsockopt failed"); + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + if (!convertStringToIP(host.c_str(), &addr.sin_addr)) { + throw std::runtime_error("ip is not of the valid format : " + host); + } + std::stringstream str; + str << port; + _log->debug("port : " + str.str()); + addr.sin_port = htons(port); + + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(fd); + std::ostringstream str; + str << port; + throw std::runtime_error("bind failed for : " + host + ":" + str.str()); + } + + if (listen(fd, SOMAXCONN) < 0) { + close(fd); + std::ostringstream str; + str << port; + throw std::runtime_error("listen failed for : " + host + ":" + str.str()); + } + + return (fd); +} + +bool Server::_handle_client(struct pollfd &pollfd, sockaddr_in *sock_data) { + try { + Client client(pollfd.fd, *sock_data, _conf); + client.answer(); + } catch (std::runtime_error &e) { + _log->error(e.what()); + return false; + } + + return true; +} diff --git a/upload.html b/upload.html deleted file mode 100644 index a9e575f..0000000 --- a/upload.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - upload - - - - - -
-
- -

-

-
- - - - -