diff --git a/Makefile b/Makefile index a775cd8..ad045bd 100644 --- a/Makefile +++ b/Makefile @@ -11,10 +11,14 @@ CXXFLAGS += -MMD -MP #header dependencie VPATH = $(SRCS_D) -HEADERS_D = srcs +HEADERS_D = srcs \ + srcs/webserv \ + srcs/config SRCS_D = srcs \ - srcs/webserv + srcs/webserv \ + srcs/config + SRCS = main.cpp \ base.cpp init.cpp close.cpp epoll_update.cpp signal.cpp \ accept.cpp request.cpp response.cpp \ diff --git a/default.config b/default.config index a3de0b3..024d45c 100644 --- a/default.config +++ b/default.config @@ -12,6 +12,12 @@ server { index index.html; # this is another comment root ./www/; +# If not explicitly set, ConfigParser need to genererate a location block +# like this for path "/" (based on field "root" and "index" of the server) + location / { + root ./www/; + index index.html; + } + allow_methods GET; } - diff --git a/memo.txt b/memo.txt index 6f8b3ab..dd03cec 100644 --- a/memo.txt +++ b/memo.txt @@ -1,7 +1,21 @@ -- un thread par serveur présent dans le fichier de config ? ----------------- +- do correct handling of special character in url (/rfc2119_files/errata.js.t%C3%A9l%C3%A9chargement -> /rfc2119_files/errata.js.téléchargement) +- handle redirection +- maybe add a "last_action_time" in Client for timeout handling + little global timeout on epoll, like 100ms, then find client that actualy need to timeout + if (actual_time - client.last_action_time > 10000ms){timeout(client)} +- add headers "Date" and "Last-Modified" to response +- change "std::string" to reference "std::string &" in most functions +and add "const" if apropriate. +- http_method en mode binary flags. "std::vector allow_methods" -> "unsigned int allow_methods;" +- Dans le parsing, trier les "locations" par ordre de precision. +Compter les "/" dans le chemin, les locations avec le plus de "/" seront en premier dans le vector. +- Il faut vérifier le path de la requête, voir si le serveur est bien censé délivrer cette ressource et si le client y a accès, avant d'appeler le CGI. ----------------- + +__________________________ +-------------------------- + +----Discord 42------------ Un truc cool et surtout bien utile ici c'est d'utiliser un proxy entre ton navigateur et ton serveur pour vérifier ce qui est envoyé en raw. Les navigateurs peuvent avoir des comportements différents. Vous avez des modules sur vos navigateur ou des logiciels externe. C'est assez rapide et gratuit. \ No newline at end of file diff --git a/srcs/Client.cpp b/srcs/Client.cpp index e6c6f39..f994d57 100644 --- a/srcs/Client.cpp +++ b/srcs/Client.cpp @@ -1,11 +1,13 @@ #include "Client.hpp" +char Client::buf[MAX_FILESIZE+1]; + /********************************************* * COPLIENS *********************************************/ -Client::Client( ) { +Client::Client() : fd(0), body_size(0), status(0) { return; } @@ -35,7 +37,7 @@ void Client::parse_request() std::vector list; size_t pos; - pos = (raw_request).find("\r\n\r\n"); + pos = (raw_request).find(CRLF CRLF); sub = (raw_request).substr(0, pos); list = split(sub, '\n'); // request_line @@ -45,15 +47,33 @@ void Client::parse_request() _parse_request_headers(list); //body- message _parse_request_body(pos + 4); + + // add "raw_request.clear()" after parsing ? for little less memory usage ? } +void Client::clear() +{ + clear_request(); + raw_request.clear(); + response.clear(); + body_size = 0; + status = 0; +} +void Client::clear_request() +{ + _request.method = UNKNOWN; + _request.path.clear(); + _request.version.clear(); + _request.headers.clear(); + _request.body.clear(); +} -std::string Client::get_method() { return _request.method; } -std::string Client::get_path() { return _request.path; } -std::string Client::get_version() { return _request.version; } -std::string Client::get_body() { return _request.body; } -std::string Client::get_headers(std::string key) { return _request.headers[key]; } +http_method Client::get_method() { return _request.method; } +std::string &Client::get_path() { return _request.path; } +std::string &Client::get_version() { return _request.version; } +std::string &Client::get_body() { return _request.body; } +std::string &Client::get_headers(const std::string &key) { return _request.headers[key]; } /********************************************* * PRIVATE MEMBER FUNCTIONS @@ -73,7 +93,7 @@ void Client::_parse_request_line( std::string rline ) // method tmp = ::trim(sline[0], ' '); tmp = ::trim(tmp, '\r'); - _request.method = tmp; + _request.method = str_to_http_method(tmp); // TODO uri in request_line // https://www.rfc-editor.org/rfc/rfc7230#section-5.3 // https://stackoverflow.com/questions/40311306/when-is-absoluteuri-used-from-the-http-request-specs @@ -113,4 +133,15 @@ void Client::_parse_request_body( size_t pos ) _request.body = body; } +/********************************************* + * OVERLOAD + *********************************************/ +bool operator==(const Client& lhs, const Client& rhs) + { return lhs.fd == rhs.fd; } + +bool operator==(const Client& lhs, int fd) + { return lhs.fd == fd; } + +bool operator==(int fd, const Client& rhs) + { return fd == rhs.fd; } diff --git a/srcs/Client.hpp b/srcs/Client.hpp index 002f1c5..5a67601 100644 --- a/srcs/Client.hpp +++ b/srcs/Client.hpp @@ -10,13 +10,14 @@ struct Request { - std::map headers; - std::string method; + http_method method; std::string path; std::string version; + std::map headers; std::string body; }; +# define MAX_FILESIZE 1000000 // (1Mo) class Client { public: @@ -28,15 +29,21 @@ class Client int fd; std::string raw_request; std::string response; + static char buf[MAX_FILESIZE+1]; + size_t body_size; unsigned int status; + listen_socket *lsocket; - std::string get_method(); - std::string get_path(); - std::string get_version(); - std::string get_body(); - std::string get_headers(std::string key); + // const functions ? + http_method get_method(); + std::string &get_path(); + std::string &get_version(); + std::string &get_body(); + std::string &get_headers(const std::string &key); void parse_request(); + void clear(); + void clear_request(); private: struct Request _request; diff --git a/srcs/MethodType.hpp b/srcs/MethodType.hpp deleted file mode 100644 index 9c815f5..0000000 --- a/srcs/MethodType.hpp +++ /dev/null @@ -1,15 +0,0 @@ - - -#ifndef METHODTYPE_HPP -# define METHODTYPE_HPP - -enum MethodType -{ - GET, - POST, - DELETE, - INVALID, -}; - - -#endif diff --git a/srcs/Server.hpp b/srcs/Server.hpp deleted file mode 100644 index 7fcb4d9..0000000 --- a/srcs/Server.hpp +++ /dev/null @@ -1,21 +0,0 @@ - -#ifndef SERVER_HPP -# define SERVER_HPP - -# include -# include - -class Server -{ - public: - // Server(Placeholder); - // Server(); - // Server(Server const &src); - // ~Server(); - // Server &operator=(Server const &rhs); - - private: - -}; - -#endif diff --git a/srcs/Webserv_hugo.cpp b/srcs/Webserv_hugo.cpp deleted file mode 100644 index e373ffa..0000000 --- a/srcs/Webserv_hugo.cpp +++ /dev/null @@ -1,447 +0,0 @@ - -#include "Webserv.hpp" - -int g_last_signal; -bool g_run; -void signal_handler(int signum) -{ - g_last_signal = signum; -} - -Webserv::Webserv() -{ - std::cerr << "Server init\n"; - - _epfd = ::epoll_create1(0); // (EPOLL_CLOEXEC) for CGI fork ? - if (_epfd == -1) - { - std::perror("err epoll_create1()"); - throw std::runtime_error("Epoll init"); - } - - std::signal(SIGPIPE, signal_handler); - std::signal(SIGINT, signal_handler); -} - -/* Webserv::Webserv(Webserv const &src) -{ - -} */ - -Webserv::~Webserv() -{ - close(_socket_fd); - close(_epfd); - _close_all_clients(); - std::cerr << "Server destroyed\n"; -} - -/* Webserv & Webserv::operator=(Webserv const &rhs) -{ - -} */ - - /////////////// - // Functions // - -void Webserv::init_virtual_servers() // ADD config param -{ - _socket_fd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); // (SOCK_CLOEXEC) for CGI fork ? - if (_socket_fd == -1) - { - std::perror("err socket()"); - throw std::runtime_error("Socket init"); - } - // HUGO ADD - // allow socket descriptor to be reuseable - int on = 1; - if (setsockopt(_socket_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0) - { - ::perror("setsockopt() failed"); - throw std::runtime_error("Socket init"); - } - // HUGO ADD END - - _bind(_socket_fd, 4040); - _listen(_socket_fd, 512); // 512 arbitrary - - if (_epoll_update(_socket_fd, EPOLLIN, EPOLL_CTL_ADD) == -1) - throw std::runtime_error("Socket init"); -} - -void Webserv::start() -{ - std::cerr << "Server started\n"; - struct epoll_event events[MAX_EVENTS]; - int nfds; - int i; - int count_loop = 0; - - g_run = true; - while (g_run) - { - std::cerr << ++count_loop << "----loop epoll()\n"; - nfds = ::epoll_wait(_epfd, events, MAX_EVENTS, TIMEOUT); - if (nfds == -1) - { - std::perror("err epoll_wait(): "); - throw std::runtime_error("Epoll wait"); - } - else if (nfds == 0) - { - if (!_clients.empty()) - { - std::cerr << "Timeout " << TIMEOUT << "ms\n"; - _close_all_clients(); - } - } - i = 0; - while (i < nfds) - { - // if ((events[i].data.u32 == SERVER_FD) && (events[i].events & EPOLLIN)) // Dont work, see "SERVER_FD" define - if ((events[i].data.fd == _socket_fd) && (events[i].events & EPOLLIN)) - _accept_connection(events[i].data.fd); - else if (events[i].events & EPOLLIN) - _read_request(static_cast(events[i].data.ptr)); - else if (events[i].events & EPOLLOUT) - _send_response(static_cast(events[i].data.ptr)); - ++i; - _actual_client = NULL; - } - } -} - - - /////////////////////// - // Private Functions // - -void Webserv::_accept_connection(int fd) -{ - struct sockaddr_in addr; - socklen_t addr_len; - int accepted_fd; - - std::cerr << "accept()\n"; - addr_len = sizeof addr; - accepted_fd = ::accept(fd, (sockaddr*)&addr, &addr_len); - if (accepted_fd == -1) - { - std::perror("err accept(): "); - return ; - } - ::fcntl(accepted_fd, F_SETFL, O_NONBLOCK); - - _clients.push_back(Client()); - _clients.back().fd = accepted_fd; - - _epoll_update(accepted_fd, EPOLLIN, EPOLL_CTL_ADD, &_clients.back()); -} - - ////////// - // READ // -void Webserv::_read_request(Client *client) -{ - char buf[BUFSIZE+1]; - ssize_t ret; - _actual_client = client; - - std::cerr << "recv()\n"; - ret = ::recv(client->fd, buf, BUFSIZE, 0); - if (ret == -1) - { - std::perror("err recv(): "); - // if (g_last_signal) - // _handle_last_signal(); - // else - // _close_client(client->fd); - - std::cerr << "client ptr =" << client << "\n"; // DEBUG - std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG - return ; - } - /* - if (ret == BUFSIZE) - // send error like "request too long" to client - */ - - buf[ret] = '\0'; - client->raw_request.append(buf); - - // HUGO TMP - // - _parse_request(client); - // - // HUGO TMP END - - _epoll_update(client->fd, EPOLLOUT, EPOLL_CTL_MOD, client); -} - - /////////// - // WRITE // -void Webserv::_send_response(Client *client) -{ - ssize_t ret; - _actual_client = client; - - std::cerr << "send()\n"; - std::cerr << "RAW_REQUEST\n|\n" << client->raw_request << "|\n"; - - - // TMP HUGO test cgi - // - // if "POST" found in _buf, execve a cgi - if (client->raw_request.find("POST") != std::string::npos) - _exec_cgi_script(client); - // if "index.html" found in _buf, send the page - if (client->raw_request.find("index.html") != std::string::npos) - _serve_file(client, "index.html"); - // - // TMP HUGO end test cgi - - - ret = ::send(client->fd, MSG_TEST, sizeof MSG_TEST - 1, 0); - if (ret == -1) - { - std::perror("err send(): "); - if (g_last_signal) - _handle_last_signal(); - // else - // _close_client(client->fd); - return ; - } - - _close_client(client->fd); - // if (client->raw_request.find("Connection: keep-alive") == std::string::npos) - // _close_client(client->fd); - // else - // _epoll_update(client->fd, EPOLLIN, EPOLL_CTL_MOD, client); - - client->raw_request.clear(); -} - - - //////////////////// - // Misc functions // - -int Webserv::_epoll_update(int fd, uint32_t events, int op) -{ - struct epoll_event ev; - std::memset(&ev, 0, sizeof ev); - ev.events = events; - ev.data.fd = fd; - if (::epoll_ctl(_epfd, op, fd, &ev) == -1) - { - std::perror("err _epoll_update(): "); - return (-1); - } - return (0); -} - -// TMP HUGO -// -void _parse_request(Client *client) -{ - std::string request = client->raw_request; - std::map field = client->request; -// size_t size = request.size(); - size_t begin = 0; - size_t end; - size_t len; - -// std::string str ("test un: deux\ntest deux: trois quatre\ntest :trois quatre cinq"); -// std::string sub; - - std::cout << str << "\n\n"; - - int i = 0; - while (end != std::string::npos) - { - // find first portion, before ':' - end = str.find(':', begin); - len = end - begin; - if (end == std::string::npos) - len = end; - sub = str.substr(begin, len); - std::cout << i << "|" << sub << "\n"; - // std::cout << "[begin:" << begin << " - end:" << end << " - len:" << len << "] " << sub << "\n"; - begin = end + 1; - - // find second portion, until '\n' - end = str.find('\n', begin); - len = end - begin; - if (end == std::string::npos) - len = end; - sub = str.substr(begin, len); - std::cout << i << "|" << sub << "\n"; - begin = end + 1; - i++; - } - -// for (size_t i = 0; i < size; i++) -// { -// field.insert(request); -// } - -// GET /home.html HTTP/1.1 -// Host: developer.mozilla.org -// User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0 -// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 -// Accept-Language: en-US,en;q=0.5 -// Accept-Encoding: gzip, deflate, br -// Referer: https://developer.mozilla.org/testpage.html -// Connection: keep-alive -// Upgrade-Insecure-Requests: 1 -// If-Modified-Since: Mon, 18 Jul 2016 02:36:04 GMT -// If-None-Match: "c561c68d0ba92bbeb8b0fff2a9199f722e3a621a" -// Cache-Control: max-age=0 - -} -void Webserv::_serve_file(Client *client, std::string page) -{ - int page_fd; - std::string to_send; - std::string end_header = "\r\n\r\n"; - std::string body; - std::stringstream strs; - char buffer[1]; - - to_send = "HTTP/1.1 200 OK\n"; - to_send += "Content-Type: text/html;\n"; - to_send += "Content-Length: "; - page_fd = open(page.c_str(), O_RDONLY); - for (int ret = 1; ret > 0;) - { - ret = read(page_fd, buffer, 1); - body += buffer; - } - strs << body.size(); - to_send += strs.str(); - to_send += end_header; - to_send += body; - if (::send(client->fd, to_send.c_str(), to_send.size(), 0) == -1) - std::perror("err send()"); -} -void Webserv::_exec_cgi_script(Client *client) -{ - int save_stdout; - char** env = new char*[5]; - char * const * nll = NULL; - - // set env - env[0] = strdup("PATH_INFO=/no"); - env[1] = strdup("REQUEST_METHOD=POST"); - env[2] = strdup("SERVER_PROTOCOL=HTTP/1.1"); - env[3] = strdup("CONTENT_LENGTH=665"); - env[4] = NULL; - // save STDOUT - save_stdout = dup(STDOUT_FILENO); - // inside child process - if (fork() == 0) - { - dup2(client->fd, STDOUT_FILENO); -// execve("./srcs/cgi-bin/cgi_cpp.cgi", nll, env); - execve("./srcs/cgi-bin/php-cgi", nll, env); - } - // inside parent process - else - waitpid(-1, NULL, 0); - // restore stdout - dup2(save_stdout, STDOUT_FILENO); -} -// -// END TMP HUGO - -int Webserv::_epoll_update(int fd, uint32_t events, int op, void *ptr) -{ - struct epoll_event ev; - std::memset(&ev, 0, sizeof ev); - ev.events = events; - ev.data.ptr = ptr; - if (::epoll_ctl(_epfd, op, fd, &ev) == -1) - { - std::perror("err _epoll_update(): "); - return (-1); - } - return (0); -} - -void Webserv::_handle_last_signal() -{ - if (g_last_signal == SIGPIPE) - { - std::cerr << "SIGPIPE\n"; - if (_actual_client) - { - _close_client(_actual_client->fd); - _actual_client = NULL; - } - } - else if (g_last_signal == SIGINT) - { - g_run = false; - } - g_last_signal = 0; -} - -void Webserv::_close_client(int fd) -{ - std::vector::iterator it = _clients.begin(); - while (it != _clients.end()) - { - if (it->fd == fd) - { - // _epoll_update(fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG - if (::close(fd) == -1) - std::perror("err close(): "); - else - std::cerr << "close fd " << fd << "\n"; - _clients.erase(it); - break; - } - ++it; - } -} - -void Webserv::_close_all_clients() -{ - while (!_clients.empty()) - { - // _epoll_update(_clients.back().fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG - if (::close(_clients.back().fd) == -1) - std::perror("err close(): "); - else - std::cerr << "close fd " << _clients.back().fd << "\n"; - _clients.pop_back(); - } -} - - - //////////////////// - // Init functions // - -void Webserv::_bind(int socket_fd, in_port_t port) -{ - // cast invalid ? how to ? - // const struct sockaddr* cast_test = static_cast(addr); - - struct sockaddr_in addr; - std::memset(&addr, 0, sizeof addr); - addr.sin_family = AF_INET; - addr.sin_port = ::htons(port); - addr.sin_addr.s_addr = ::htonl(INADDR_ANY); // htonl useless with 0 value (INADDR_ANY) ? - - if (::bind(socket_fd, (const sockaddr*)&addr, sizeof addr) == -1) - { - std::perror("err bind(): "); - throw std::runtime_error("Socket bind"); - } -} - -void Webserv::_listen(int socket_fd, unsigned int max_connections) -{ - if (::listen(socket_fd, max_connections) == -1) - { - std::perror("err listen(): "); - throw std::runtime_error("Socket listen"); - } -} diff --git a/srcs/ConfigParser.cpp b/srcs/config/ConfigParser.cpp similarity index 98% rename from srcs/ConfigParser.cpp rename to srcs/config/ConfigParser.cpp index ad9257b..d754ec6 100644 --- a/srcs/ConfigParser.cpp +++ b/srcs/config/ConfigParser.cpp @@ -6,7 +6,7 @@ /* By: lperrey +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2022/07/13 22:11:17 by me #+# #+# */ -/* Updated: 2022/07/31 13:18:14 by simplonco ### ########.fr */ +/* Updated: 2022/08/03 17:51:35 by lperrey ### ########.fr */ /* */ /* ************************************************************************** */ @@ -262,8 +262,8 @@ void ConfigParser::_set_server_values(ServerConfig *server, \ { for (unsigned long i = 0; i != tmp_val.size(); i++) { - MethodType m = _str_to_method_type(tmp_val[i]); - if (m == 3) + http_method m = ::str_to_http_method(tmp_val[i]); + if (m == UNKNOWN) throw std::invalid_argument("not a valid method"); server->allow_methods.push_back(m); } @@ -347,8 +347,8 @@ void ConfigParser::_set_location_values(LocationConfig *location, \ { for (unsigned long i = 0; i != tmp_val.size(); i++) { - MethodType m = _str_to_method_type(tmp_val[i]); - if (m == 3) + http_method m = ::str_to_http_method(tmp_val[i]); + if (m == UNKNOWN) throw std::invalid_argument("not a valid method"); location->allow_methods.push_back(m); } diff --git a/srcs/ConfigParser.hpp b/srcs/config/ConfigParser.hpp similarity index 92% rename from srcs/ConfigParser.hpp rename to srcs/config/ConfigParser.hpp index 9ae1577..c5e29f3 100644 --- a/srcs/ConfigParser.hpp +++ b/srcs/config/ConfigParser.hpp @@ -3,10 +3,10 @@ /* ::: :::::::: */ /* ConfigParser.hpp :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: me +#+ +:+ +#+ */ +/* By: lperrey +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2022/07/11 23:01:41 by me #+# #+# */ -/* Updated: 2022/07/27 19:27:57 by me ### ########.fr */ +/* Updated: 2022/08/03 17:32:33 by lperrey ### ########.fr */ /* */ /* ************************************************************************** */ @@ -15,7 +15,6 @@ # include "ServerConfig.hpp" # include "LocationConfig.hpp" -# include "MethodType.hpp" # include "utils.hpp" # include @@ -76,8 +75,6 @@ private: std::string _get_rest_of_line(size_t *curr); // const? - // why static? it's an enum... - static MethodType _str_to_method_type(std::string str); diff --git a/srcs/ConfigParserPost.cpp b/srcs/config/ConfigParserPost.cpp similarity index 100% rename from srcs/ConfigParserPost.cpp rename to srcs/config/ConfigParserPost.cpp diff --git a/srcs/ConfigParserUtils.cpp b/srcs/config/ConfigParserUtils.cpp similarity index 92% rename from srcs/ConfigParserUtils.cpp rename to srcs/config/ConfigParserUtils.cpp index 861ae83..ee5ef4e 100644 --- a/srcs/ConfigParserUtils.cpp +++ b/srcs/config/ConfigParserUtils.cpp @@ -82,20 +82,6 @@ std::string ConfigParser::_get_rest_of_line(size_t *curr) return (values); } - -MethodType ConfigParser::_str_to_method_type(std::string str) -{ - if (str == "GET") - return GET; - else if (str == "POST") - return POST; - else if (str == "DELETE") - return DELETE; - return INVALID; -} - - - void ConfigParser::_print_content() const { std::cout << _content; diff --git a/srcs/LocationConfig.hpp b/srcs/config/LocationConfig.hpp similarity index 87% rename from srcs/LocationConfig.hpp rename to srcs/config/LocationConfig.hpp index 2a077f6..6db5d58 100644 --- a/srcs/LocationConfig.hpp +++ b/srcs/config/LocationConfig.hpp @@ -3,18 +3,16 @@ /* ::: :::::::: */ /* LocationConfig.hpp :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: me +#+ +:+ +#+ */ +/* By: lperrey +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2022/07/23 16:08:00 by me #+# #+# */ -/* Updated: 2022/07/25 20:09:48 by me ### ########.fr */ +/* Updated: 2022/08/02 14:06:07 by lperrey ### ########.fr */ /* */ /* ************************************************************************** */ #ifndef LOCATIONCONFIG_HPP # define LOCATIONCONFIG_HPP -# include "MethodType.hpp" - # include # include # include @@ -31,7 +29,7 @@ public: int client_body_limit; std::string root; std::vector index; - std::vector allow_methods; + std::vector allow_methods; std::map cgi_info; // wait if i can call several times, shouldn't it be a map? diff --git a/srcs/ServerConfig.hpp b/srcs/config/ServerConfig.hpp similarity index 95% rename from srcs/ServerConfig.hpp rename to srcs/config/ServerConfig.hpp index ce49255..1875592 100644 --- a/srcs/ServerConfig.hpp +++ b/srcs/config/ServerConfig.hpp @@ -2,7 +2,7 @@ #ifndef SERVERCONFIG_HPP # define SERVERCONFIG_HPP -# include "MethodType.hpp" +# include "utils.hpp" # include "LocationConfig.hpp" # include @@ -30,7 +30,7 @@ public: // there can only be one. std::string root; - int client_body_limit; // set to default max if none set + unsigned int client_body_limit; // set to default max if none set // might be the only one we let slide if bad input... bool autoindex; @@ -42,7 +42,7 @@ public: // i'm tempted to do something diff for storing method types... // fuck it, you can only call allow_methods once in Server // once more in each location. - std::vector allow_methods; + std::vector allow_methods; std::vector locations; diff --git a/srcs/ft_itoa.cpp b/srcs/ft_itoa.cpp deleted file mode 100644 index 90f6ff7..0000000 --- a/srcs/ft_itoa.cpp +++ /dev/null @@ -1,13 +0,0 @@ - -# include -# include - -char* itoa(int n) -{ - std::stringstream strs; - char * str; - - strs << n; - str = (char*)(strs.str().c_str()); - return (str); -} diff --git a/srcs/utils.cpp b/srcs/utils.cpp index 48af746..2144a51 100644 --- a/srcs/utils.cpp +++ b/srcs/utils.cpp @@ -52,3 +52,56 @@ bool isNumeric_btw(int low, int high, std::string str) return true; } +http_method str_to_http_method(std::string &str) +{ + if (str == "GET") + return GET; + else if (str == "POST") + return POST; + else if (str == "DELETE") + return DELETE; + return UNKNOWN; +} + +std::string http_methods_to_str(unsigned int methods) +{ + std::string str; + + if (methods & GET) + str.append("GET"); + if (methods & POST) + { + if (!str.empty()) + str.append(", "); + str.append("POST"); + } + if (methods & DELETE) + { + if (!str.empty()) + str.append(", "); + str.append("DELETE"); + } + + return (str); +} + +void replace_all_substr(std::string &str, const std::string &ori_substr, const std::string &new_substr) +{ + if (ori_substr.empty()) + return; + size_t pos = 0; + while (1) + { + pos = str.find(ori_substr, pos); + if (pos == std::string::npos) + break; + str.replace(pos, ori_substr.size(), new_substr); + pos += new_substr.size(); + } +} + +bool operator==(const listen_socket& lhs, int fd) + { return lhs.fd == fd; } + +bool operator==(int fd, const listen_socket& rhs) + { return fd == rhs.fd; } diff --git a/srcs/utils.hpp b/srcs/utils.hpp index a2e2409..6ea657c 100644 --- a/srcs/utils.hpp +++ b/srcs/utils.hpp @@ -7,10 +7,44 @@ # include # include // atoi -std::vector split(std::string input, char delimiter); -bool isNumeric(std::string str); -bool isNumeric_btw(int low, int high, std::string str); +# define CR "\r" +# define LF "\n" +# define CRLF CR LF + +// enum http_method +// { +// UNKNOWN = 0b00000000, +// GET = 0b00000001, +// POST = 0b00000010, +// DELETE = 0b00000100, +// ANY_METHODS = 0b11111111, +// }; + +enum http_method +{ + UNKNOWN = 0b0, + GET = 1 << 0, + POST = 1 << 1, + DELETE = 1 << 2, + ANY_METHODS = 0b11111111, +}; + +struct listen_socket +{ + int fd; + std::string host; + std::string port; +}; +bool operator==(const listen_socket& lhs, int fd); +bool operator==(int fd, const listen_socket& rhs); + +std::vector split(std::string input, char delimiter); +bool isNumeric(std::string str); +bool isNumeric_btw(int low, int high, std::string str); std::string itos(int n); std::string trim(std::string str, char c); +http_method str_to_http_method(std::string &str); +std::string http_methods_to_str(unsigned int methods); +void replace_all_substr(std::string &str, const std::string &ori_substr, const std::string &new_substr); #endif diff --git a/srcs/Webserv.hpp b/srcs/webserv/Webserv.hpp similarity index 64% rename from srcs/Webserv.hpp rename to srcs/webserv/Webserv.hpp index 6b858a4..c5d6409 100644 --- a/srcs/Webserv.hpp +++ b/srcs/webserv/Webserv.hpp @@ -22,37 +22,18 @@ // # include // usefull for what ? -> 'man (7) ip' says it's a superset of 'netinet/in.h' # include // find # include // string -# include // perror +# include // perror, remove # include // atoi (athough it's already cover by ) # include "Client.hpp" # include "ServerConfig.hpp" # include "utils.hpp" - -// TODO: A virer -//# include "ConfigParser.hpp" -//# include "LocationConfig.hpp" -//# include "MethodType.hpp" -//# include "utils.hpp" -// TODO: A virer +# include "http_status.hpp" extern bool g_run; extern int g_last_signal; void signal_handler(int signum); -/* enum // WIP test -{ - SERVER_FD = 1, - CLIENT_FD -}; - -struct s // WIP test -{ - int fd; - Client *ptr; -}; - */ - // these might only be TMP # define FAILURE -1 # define SUCCESS 1 @@ -77,21 +58,39 @@ class Webserv private: int _epfd; - std::vector _listen_sockets; + std::vector _listen_sockets; std::vector _servers; std::vector _clients; + std::map _http_status; + std::map _mime_types; // accept.cpp - void _accept_connection(int fd); + void _accept_connection(listen_socket &lsocket); // request.cpp void _request(Client *client); void _read_request(Client *client); // response.cpp void _response(Client *client); - void _send_response(Client *client); - void _construct_response(Client *client); + void _send_response(Client *client, ServerConfig &server); + + void _append_base_headers(Client *client); + void _construct_response(Client *client, ServerConfig &server); + void _process_method(Client *client, ServerConfig &server, LocationConfig &location); void _insert_status_line(Client *client); - void _get_ressource(Client *client); + void _error_html_response(Client *client, ServerConfig &server); + void _append_body(Client *client, const char *body, size_t body_size, const std::string &file_extension = ""); + + void _get(Client *client, ServerConfig &server, LocationConfig &location); + void _get_file(Client *client, const std::string &path); + + void _post(Client *client, ServerConfig &server, LocationConfig &location); + void _post_file(Client *client, const std::string &path); + + void _delete(Client *client, ServerConfig &server, LocationConfig &location); + void _delete_file(Client *client, const std::string &path); + + ServerConfig &_determine_process_server(Client *client); + LocationConfig &_determine_location(ServerConfig &server, std::string &path); // cgi_script.cpp bool _is_cgi(Client *client); void _exec_cgi(Client *client); @@ -107,9 +106,12 @@ class Webserv // close.cpp void _close_client(int fd); void _close_all_clients(); + void _close_all_listen_sockets(); // init.cpp void _bind(int socket_fd, in_port_t port, std::string host); void _listen(int socket_fd, unsigned int max_connections); + void _init_http_status_map(); + void _init_mime_types_map(); }; #endif diff --git a/srcs/webserv/accept.cpp b/srcs/webserv/accept.cpp index f6db17b..ac620db 100644 --- a/srcs/webserv/accept.cpp +++ b/srcs/webserv/accept.cpp @@ -1,7 +1,7 @@ #include "Webserv.hpp" -void Webserv::_accept_connection(int fd) +void Webserv::_accept_connection(listen_socket &lsocket) { struct sockaddr_in addr; socklen_t addr_len; @@ -9,7 +9,7 @@ void Webserv::_accept_connection(int fd) std::cerr << "accept()\n"; addr_len = sizeof addr; - accepted_fd = ::accept(fd, (sockaddr*)&addr, &addr_len); + accepted_fd = ::accept(lsocket.fd, (sockaddr*)&addr, &addr_len); if (accepted_fd == -1) { std::perror("err accept()"); @@ -21,6 +21,7 @@ void Webserv::_accept_connection(int fd) _clients.push_back(Client()); _clients.back().fd = accepted_fd; + _clients.back().lsocket = &lsocket; _epoll_update(accepted_fd, EPOLLIN, EPOLL_CTL_ADD); } diff --git a/srcs/webserv/base.cpp b/srcs/webserv/base.cpp index 6267ec9..ee59bbd 100644 --- a/srcs/webserv/base.cpp +++ b/srcs/webserv/base.cpp @@ -12,6 +12,9 @@ Webserv::Webserv() throw std::runtime_error("Epoll init"); } + _init_http_status_map(); + _init_mime_types_map(); + std::signal(SIGPIPE, signal_handler); std::signal(SIGINT, signal_handler); } @@ -42,10 +45,9 @@ Webserv::Webserv(std::vector* servers) Webserv::~Webserv() { - //close(_socket_fd); - // TODO : CLOSE ALL _listen_sockets close(_epfd); _close_all_clients(); + _close_all_listen_sockets(); std::cerr << "Server destroyed\n"; } diff --git a/srcs/webserv/cgi_script.cpp b/srcs/webserv/cgi_script.cpp index ee1319e..636303d 100644 --- a/srcs/webserv/cgi_script.cpp +++ b/srcs/webserv/cgi_script.cpp @@ -40,7 +40,7 @@ char** Webserv::_set_env(Client *client) env[8] = _dup_env("REMOTE_HOST", client->get_headers("Host")); // just test env[9] = _dup_env("REMOTE_IDENT"); env[10] = _dup_env("REMOTE_USER"); - env[11] = _dup_env("REQUEST_METHOD", client->get_method()); + env[11] = _dup_env("REQUEST_METHOD", ::http_methods_to_str(client->get_method())); env[12] = _dup_env("SCRIPT_NAME"); env[13] = _dup_env("SERVER_NAME"); env[14] = _dup_env("SERVER_PORT"); diff --git a/srcs/webserv/close.cpp b/srcs/webserv/close.cpp index f4bc0b9..9b28f6b 100644 --- a/srcs/webserv/close.cpp +++ b/srcs/webserv/close.cpp @@ -6,7 +6,7 @@ void Webserv::_close_client(int fd) std::vector::iterator it = _clients.begin(); while (it != _clients.end()) { - if (it->fd == fd) + if (*it == fd) { // _epoll_update(fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG if (::close(fd) == -1) @@ -32,3 +32,16 @@ void Webserv::_close_all_clients() _clients.pop_back(); } } + +void Webserv::_close_all_listen_sockets() +{ + while (!_listen_sockets.empty()) + { + // _epoll_update(_listen_sockets.back().fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG + if (::close(_listen_sockets.back().fd) == -1) + std::perror("err close()"); + else + std::cerr << "close fd " << _listen_sockets.back().fd << "\n"; + _listen_sockets.pop_back(); + } +} diff --git a/srcs/webserv/http_status.hpp b/srcs/webserv/http_status.hpp new file mode 100644 index 0000000..d6017f0 --- /dev/null +++ b/srcs/webserv/http_status.hpp @@ -0,0 +1,42 @@ + +#ifndef HTTP_STATUS_HPP +# define HTTP_STATUS_HPP + +// https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + +/* + First version of macro HTML_ERROR(STATUS) dont work with call like this : + client->response.append( HTML_ERROR( _http_status[client->status].c_str() ) ); + so I made the other version with dynamic replacement of STATUS . +*/ +// # define HTML_ERROR(STATUS) "\r\n"STATUS"

"STATUS"


Le Webserv/0.1

" + +# define STATUS_PLACEHOLDER "$STATUS" +# define HTML_ERROR \ +""\ +""\ +""\ + "" STATUS_PLACEHOLDER ""\ +""\ +""\ + "

" STATUS_PLACEHOLDER "

"\ + "
"\ + "

Le Webserv/0.1

"\ +""\ +"" + +// When new status added, need to update _init_http_status_map() +# define S200 "200 OK" +# define S201 "201 Created" +# define S204 "204 No Content" + +# define S400 "400 Bad Request" +# define S403 "403 Forbidden" +# define S404 "404 Not Found" +# define S405 "405 Method Not Allowed" +# define S413 "413 Content Too Large" + +# define S500 "500 Internal Server Error" +# define S501 "501 Not Implemented" + +#endif diff --git a/srcs/webserv/init.cpp b/srcs/webserv/init.cpp index 4c5956e..3a33ed1 100644 --- a/srcs/webserv/init.cpp +++ b/srcs/webserv/init.cpp @@ -4,7 +4,9 @@ void Webserv::init_virtual_servers(std::vector* servers) { int ret; - std::vector _open_ports; + std::vector open_sockets; + struct listen_socket new_socket; + std::string host_port; _servers = *servers; _listen_sockets.clear(); @@ -12,7 +14,11 @@ void Webserv::init_virtual_servers(std::vector* servers) while (it != _servers.end()) { - if ( std::find(_open_ports.begin(), _open_ports.end(), std::atoi(it->port.data()) ) != _open_ports.end() ) + host_port.clear(); + host_port.append(it->host); + host_port.append(":"); + host_port.append(it->port); + if ( std::find(open_sockets.begin(), open_sockets.end(), host_port) != open_sockets.end() ) { ++it; continue; @@ -24,27 +30,31 @@ void Webserv::init_virtual_servers(std::vector* servers) std::perror("err socket()"); throw std::runtime_error("Socket init"); } + new_socket.fd = ret; + new_socket.host = it->host; + new_socket.port = it->port; + _listen_sockets.push_back(new_socket); + // HUGO ADD // // allow socket descriptor to be reuseable // I just copied it from https://www.ibm.com/docs/en/i/7.2?topic=designs-example-nonblocking-io-select int on = 1; - if (setsockopt(ret, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0) + if (setsockopt(new_socket.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { ::perror("err setsockopt()"); throw std::runtime_error("Socket init"); } // // HUGO ADD END - _listen_sockets.push_back(ret); - _bind(_listen_sockets.back(), std::atoi(it->port.data()), it->host); - _listen(_listen_sockets.back(), 512); // 512 arbitrary + _bind(new_socket.fd, std::atoi(it->port.c_str()), it->host); + _listen(new_socket.fd, 512); // 512 arbitrary - if (_epoll_update(_listen_sockets.back(), EPOLLIN, EPOLL_CTL_ADD) == -1) + if (_epoll_update(new_socket.fd, EPOLLIN, EPOLL_CTL_ADD) == -1) throw std::runtime_error("Socket init"); - _open_ports.push_back(std::atoi(it->port.data())); + open_sockets.push_back(host_port); ++it; } } @@ -76,3 +86,135 @@ void Webserv::_listen(int socket_fd, unsigned int max_connections) throw std::runtime_error("Socket listen"); } } + +void Webserv::_init_http_status_map() +{ + _http_status[200] = S200; + _http_status[201] = S201; + _http_status[204] = S204; + + _http_status[400] = S400; + _http_status[403] = S403; + _http_status[404] = S404; + _http_status[405] = S405; + _http_status[413] = S413; + + _http_status[500] = S500; + _http_status[501] = S501; +} + +void Webserv::_init_mime_types_map() +{ + _mime_types[""] = "application/octet-stream"; + + _mime_types["html"] = "text/html"; + _mime_types["htm"] = "text/html"; + _mime_types["shtml"] = "text/html"; + _mime_types["css"] = "text/css"; + _mime_types["xml"] = "text/xml"; + _mime_types["gif"] = "image/gif"; + _mime_types["jpeg"] = "image/jpeg"; + _mime_types["jpg"] = "image/jpeg"; + _mime_types["js"] = "application/javascript"; + _mime_types["atom"] = "application/atom+xml"; + _mime_types["rss"] = "application/rss+xml"; + + _mime_types["mml"] = "text/mathml"; + _mime_types["txt"] = "text/plain"; + _mime_types["jad"] = "text/vnd.sun.j2me.app-descriptor"; + _mime_types["wml"] = "text/vnd.wap.wml"; + _mime_types["htc"] = "text/x-component"; + + _mime_types["png"] = "image/png"; + _mime_types["tif"] = "image/tiff"; + _mime_types["tiff"] = "image/tiff"; + _mime_types["wbmp"] = "image/vnd.wap.wbmp"; + _mime_types["ico"] = "image/x-icon"; + _mime_types["jng"] = "image/x-jng"; + _mime_types["bmp"] = "image/x-ms-bmp"; + _mime_types["svg"] = "image/svg+xml"; + _mime_types["svgz"] = "image/svg+xml"; + _mime_types["webp"] = "image/webp"; + + _mime_types["woff"] = "application/font-woff"; + _mime_types["jar"] = "application/java-archive"; + _mime_types["war"] = "application/java-archive"; + _mime_types["ear"] = "application/java-archive"; + _mime_types["json"] = "application/json"; + _mime_types["hqx"] = "application/mac-binhex40"; + _mime_types["doc"] = "application/msword"; + _mime_types["pdf"] = "application/pdf"; + _mime_types["ps"] = "application/postscript"; + _mime_types["eps"] = "application/postscript"; + _mime_types["ai"] = "application/postscript"; + _mime_types["rtf"] = "application/rtf"; + _mime_types["m3u8"] = "application/vnd.apple.mpegurl"; + _mime_types["xls"] = "application/vnd.ms-excel"; + _mime_types["eot"] = "application/vnd.ms-fontobject"; + _mime_types["ppt"] = "application/vnd.ms-powerpoint"; + _mime_types["wmlc"] = "application/vnd.wap.wmlc"; + _mime_types["kml"] = "application/vnd.google-earth.kml+xml"; + _mime_types["kmz"] = "application/vnd.google-earth.kmz"; + _mime_types["7z"] = "application/x-7z-compressed"; + _mime_types["cco"] = "application/x-cocoa"; + _mime_types["jardiff"] = "application/x-java-archive-diff"; + _mime_types["jnlp"] = "application/x-java-jnlp-file"; + _mime_types["run"] = "application/x-makeself"; + _mime_types["pl"] = "application/x-perl"; + _mime_types["pm"] = "application/x-perl"; + _mime_types["prc"] = "application/x-pilot"; + _mime_types["pdb"] = "application/x-pilot"; + _mime_types["rar"] = "application/x-rar-compressed"; + _mime_types["rpm"] = "application/x-redhat-package-manager"; + _mime_types["sea"] = "application/x-sea"; + _mime_types["swf"] = "application/x-shockwave-flash"; + _mime_types["sit"] = "application/x-stuffit"; + _mime_types["tcl"] = "application/x-tcl"; + _mime_types["tk"] = "application/x-tcl"; + _mime_types["der"] = "application/x-x509-ca-cert"; + _mime_types["pem"] = "application/x-x509-ca-cert"; + _mime_types["crt"] = "application/x-x509-ca-cert"; + _mime_types["xpi"] = "application/x-xpinstall"; + _mime_types["xhtml"] = "application/xhtml+xml"; + _mime_types["xspf"] = "application/xspf+xml"; + _mime_types["zip"] = "application/zip"; + + _mime_types["bin"] = "application/octet-stream"; + _mime_types["exe"] = "application/octet-stream"; + _mime_types["dll"] = "application/octet-stream"; + _mime_types["deb"] = "application/octet-stream"; + _mime_types["dmg"] = "application/octet-stream"; + _mime_types["iso"] = "application/octet-stream"; + _mime_types["img"] = "application/octet-stream"; + _mime_types["msi"] = "application/octet-stream"; + _mime_types["msp"] = "application/octet-stream"; + _mime_types["msm"] = "application/octet-stream"; + + _mime_types["docx"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + _mime_types["xlsx"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + _mime_types["pptx"] = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; + + _mime_types["mid"] = "audio/midi"; + _mime_types["midi"] = "audio/midi"; + _mime_types["kar"] = "audio/midi"; + _mime_types["mp3"] = "audio/mpeg"; + _mime_types["ogg"] = "audio/ogg"; + _mime_types["m4a"] = "audio/x-m4a"; + _mime_types["ra"] = "audio/x-realaudio"; + + _mime_types["3gpp"] = "video/3gpp"; + _mime_types["3gp"] = "video/3gpp"; + _mime_types["ts"] = "video/mp2t"; + _mime_types["mp4"] = "video/mp4"; + _mime_types["mpeg"] = "video/mpeg"; + _mime_types["mpg"] = "video/mpeg"; + _mime_types["mov"] = "video/quicktime"; + _mime_types["webm"] = "video/webm"; + _mime_types["flv"] = "video/x-flv"; + _mime_types["m4v"] = "video/x-m4v"; + _mime_types["mng"] = "video/x-mng"; + _mime_types["asx"] = "video/x-ms-asf"; + _mime_types["asf"] = "video/x-ms-asf"; + _mime_types["wmv"] = "video/x-ms-wmv"; + _mime_types["avi"] = "video/x-msvideo"; +} diff --git a/srcs/webserv/response.cpp b/srcs/webserv/response.cpp index da818e7..134dcff 100644 --- a/srcs/webserv/response.cpp +++ b/srcs/webserv/response.cpp @@ -3,95 +3,145 @@ void Webserv::_response(Client *client) { - _send_response(client); + client->status = 200; // default value + + ServerConfig &server = _determine_process_server(client); + _send_response(client, server); if (g_last_signal) _handle_last_signal(); } -void Webserv::_send_response(Client *client) +void Webserv::_send_response(Client *client, ServerConfig &server) { ssize_t ret; std::cerr << "send()\n"; - std::cerr << "RAW_REQUEST\n|\n" << client->raw_request << "|\n"; // DEBUG - _construct_response(client); + _append_base_headers(client); + _construct_response(client, server); + _insert_status_line(client); + if (client->status >= 400) + _error_html_response(client, server); - ret = ::send(client->fd, client->response.data(), client->response.size(), 0); + ret = ::send(client->fd, client->response.c_str(), client->response.size(), 0); if (ret == -1) { std::perror("err send()"); - std::cerr << "client ptr =" << client << "\n"; // DEBUG std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG _close_client(client->fd); return ; } - if (client->raw_request.find("Connection: close") != std::string::npos) + // Body send (WIP for working binary files) + if (client->body_size) + { + ret = ::send(client->fd, client->buf, client->body_size, 0); + if (ret == -1) + { + std::perror("err send()"); + std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG + _close_client(client->fd); + return ; + } + } + + if (client->get_headers("Connection") == "close") _close_client(client->fd); else { _epoll_update(client->fd, EPOLLIN, EPOLL_CTL_MOD); - client->raw_request.clear(); - client->response.clear(); + client->clear(); } } -void Webserv::_construct_response(Client *client) +void Webserv::_append_base_headers(Client *client) { - client->status = 200; - client->response.append("Server: Webserv/0.1\r\n"); + client->response.append("Server: Webserv/0.1" CRLF); - if (client->raw_request.find("Connection: close") != std::string::npos) - client->response.append("Connection: close\r\n"); + if (client->get_headers("Connection") == "close") + client->response.append("Connection: close" CRLF); else - client->response.append("Connection: keep-alive\r\n"); - - _get_ressource(client); - - _insert_status_line(client); + client->response.append("Connection: keep-alive" CRLF); +} + +void Webserv::_construct_response(Client *client, ServerConfig &server) +{ + // TODO : Move this in read(), stop read if content too large + if (client->get_body().size() > server.client_body_limit) + { + client->status = 413; + return; + } + LocationConfig &location = _determine_location(server, client->get_path()); + _process_method(client, server, location); +} + +void Webserv::_process_method(Client *client, ServerConfig &server, LocationConfig &location) +{ + unsigned int allow_methods = ANY_METHODS; // TEMP VARIABLE + // after update in ConfigParser, use the "allow_methods" of location. + // TODO in ConfigParser : by default if no field in config file, "allow_methods" must be set to ANY_METHODS + + if (client->get_method() == UNKNOWN) + { + client->status = 501; + } + else if (allow_methods & client->get_method()) + { + switch (client->get_method()) + { + case (GET): + _get(client, server, location); break; + case (POST): + _post(client, server, location); break; + case (DELETE): + _delete(client, server, location); break; + default: + break; + } + } + else + { + client->status = 405; + client->response.append("Allow: "); + client->response.append(::http_methods_to_str(allow_methods)); + client->response.append(CRLF); + } } -#define E400 "\r\n400 Bad Request

400 Bad Request


Le Webserv/0.1

" -#define E404 "\r\n404 Not Found

404 Not Found


Le Webserv/0.1

" -#define E500 "\r\n500 Internal Server Error

500 Internal Server Error


Le Webserv/0.1

" void Webserv::_insert_status_line(Client *client) { std::string status_line; status_line.append("HTTP/1.1 "); - // WIP, maybe make a map for status response - switch (client->status) - { - case (200): - status_line.append("200 OK"); - break; - case (400): - status_line.append("400 Not Found"); - client->response.append(E400); - break; - case (404): - status_line.append("404 Not Found"); - client->response.append(E404); - break; - case (500): - status_line.append("500 Internal Server Error"); - client->response.append(E500); - break; - } - status_line.append("\r\n"); - + status_line.append(_http_status[client->status]); + status_line.append(CRLF); client->response.insert(0, status_line); } -#define ROOT "website" -#define INDEX "index.html" -#define MAX_FILESIZE 1000000 // (1Mo) -void Webserv::_get_ressource(Client *client) +void Webserv::_error_html_response(Client *client, ServerConfig &server) { - std::ifstream ifd; // For chunk, ifstream directly in struct CLient for multiples read without close() ? - char buf[MAX_FILESIZE+1]; - std::string tmp; + if (server.error_pages[client->status].empty()) + { + std::string html_page = HTML_ERROR; + ::replace_all_substr(html_page, STATUS_PLACEHOLDER, _http_status[client->status]); + _append_body(client, html_page.c_str(), html_page.size(), "html"); + } + else + _get_file(client, server.error_pages[client->status]); +} + +#define INDEX "index.html" // temp wip +void Webserv::_get(Client *client, ServerConfig &server, LocationConfig &location) +{ + (void)server; // To remove from arg if we determine its useless + std::string path = client->get_path(); + + if (path == "/") // TODO : index and autoindex + path.append(INDEX); + path.insert(0, location.root); + + std::cerr << "path = " << path << "\n"; // TMP HUGO // @@ -103,41 +153,39 @@ void Webserv::_get_ressource(Client *client) // // END TMP HUGO - // Mini parsing à l'arrache du PATH - std::string path; - try - { - path = client->raw_request.substr(0, client->raw_request.find("\r\n")); - path = path.substr(0, path.rfind(" ")); - path = path.substr(path.find("/")); - if (path == "/") - path.append(INDEX); - path.insert(0, ROOT); - } - catch (std::out_of_range& e) - { - std::cout << e.what() << '\n'; - client->status = 400; - return ; - } + _get_file(client, path); +} - if (access(path.data(), R_OK) == -1) +void Webserv::_get_file(Client *client, const std::string &path) +{ + std::ifstream ifd; // For chunk, ifstream directly in struct CLient for multiples read without close() ? + // char buf[MAX_FILESIZE+1]; + + if (access(path.c_str(), F_OK) == -1) { std::perror("err access()"); client->status = 404; return ; } - ifd.open(path.data(), std::ios::binary | std::ios::ate); // std::ios::binary (binary for files like images ?) + if (access(path.c_str(), R_OK) == -1) + { + std::perror("err access()"); + client->status = 403; + return ; + } + + ifd.open(path.c_str(), std::ios::binary | std::ios::ate); // std::ios::binary (binary for files like images ?) if (!ifd) { - std::cerr << path << ": open fail" << '\n'; + std::cerr << path << ": ifd.open fail" << '\n'; client->status = 500; } else { - // WIP : Chunk or not chunk (if filesize too big) std::streampos size = ifd.tellg(); + + // WIP : Chunk or not chunk (if filesize too big) if (size > MAX_FILESIZE) { // Then chunk @@ -148,22 +196,211 @@ void Webserv::_get_ressource(Client *client) } ifd.seekg(0, std::ios::beg); - ifd.read(buf, size); - buf[ifd.gcount()] = '\0'; + ifd.read(client->buf, size); + if (!ifd) + { + std::cerr << path << ": ifd.read fail" << '\n'; + client->status = 500; + } + else + { + client->status = 200; + client->buf[ifd.gcount()] = '\0'; - client->response.append("Content-Type: text/html; charset=UTF-8\r\n"); + std::string file_ext = ""; + size_t dot_pos = path.rfind("."); + std::cerr << "dot_pos = " << dot_pos << "\n"; + if (dot_pos != std::string::npos && dot_pos + 1 < path.size()) + file_ext = path.substr(dot_pos + 1); + std::cerr << "file_ext = " << file_ext << "\n"; - client->response.append("Content-Length: "); - - tmp = ::itos(ifd.gcount()); - client->response.append(tmp.c_str()); - client->response.append("\r\n"); - - // Body - client->response.append("\r\n"); - client->response.append(buf); + client->body_size = ifd.gcount(); + // WIP, pass empty body argument because append to string mess up binary file + _append_body(client, "", client->body_size, file_ext); + } ifd.close(); } } +void Webserv::_append_body(Client *client, const char *body, size_t body_size, const std::string &file_extension) +{ +/* + TODO : determine Content-Type + how ? read the body ? + or before in other way (like based and file extension) and pass here as argument ? + http://nginx.org/en/docs/http/ngx_http_core_module.html#types + Need to look "conf/mime.types" of nginx. Maybe make a map<> based on that. +*/ + const std::string &mime_type = _mime_types[file_extension]; + client->response.append("Content-Type: "); + client->response.append(mime_type); + if (mime_type.find("text/") != std::string::npos) + client->response.append("; charset=UTF-8"); + client->response.append(CRLF); + + client->response.append("Content-Length: "); + std::string tmp = ::itos(body_size); + client->response.append(tmp); + client->response.append(CRLF); + + client->response.append(CRLF); + client->response.append(body); +} + +void Webserv::_post(Client *client, ServerConfig &server, LocationConfig &location) +{ + (void)server; // To remove from arg if we determine its useless +/* + WIP + https://www.rfc-editor.org/rfc/rfc9110.html#name-post +*/ + std::string path = client->get_path(); + path.insert(0, location.root); + + /* CGI Here ? */ + + _post_file(client, path); +} + +void Webserv::_post_file(Client *client, const std::string &path) +{ + std::ofstream ofd; + + bool file_existed; + if (access(path.c_str(), F_OK) == -1) + file_existed = false; + else + file_existed = true; + + // How to determine status 403 for file that dont already exist ? + if (file_existed && access(path.c_str(), W_OK) == -1) + { + std::perror("err access()"); + client->status = 403; + return ; + } + + ofd.open(path.c_str(), std::ios::binary | std::ios::trunc); + if (!ofd) + { + std::cerr << path << ": ofd.open fail" << '\n'; + client->status = 500; + } + else + { + // Used body.size() so Content-Length useless at this point ? + // Maybe usefull in _read_request() for rejecting too big content. + // Need to _determine_process_server() as soon as possible, + // like in _read_request() for stopping read if body is too big ? + ofd.write(client->get_body().c_str(), client->get_body().size()); + if (!ofd) + { + std::cerr << path << ": ofd.write fail" << '\n'; + client->status = 500; + } + else if (file_existed) + { + client->status = 200; + // WIP https://www.rfc-editor.org/rfc/rfc9110.html#name-200-ok + } + else + { + client->status = 201; + // WIP https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.3-4 + } + + ofd.close(); + } +} + +void Webserv::_delete(Client *client, ServerConfig &server, LocationConfig &location) +{ + (void)server; // To remove from arg if we determine its useless +/* + WIP + https://www.rfc-editor.org/rfc/rfc9110.html#name-delete +*/ + std::string path = client->get_path(); + path.insert(0, location.root); + + /* CGI Here ? */ + + _delete_file(client, path); +} + +void Webserv::_delete_file(Client *client, const std::string &path) +{ + if (access(path.c_str(), F_OK) == -1) + { + std::perror("err access()"); + client->status = 404; + return ; + } + + if (access(path.c_str(), W_OK) == -1) + { + std::perror("err access()"); + client->status = 403; + return ; + } + + if (remove(path.c_str()) == -1) + { + std::perror("err remove()"); + client->status = 500; + return ; + } +} + +ServerConfig &Webserv::_determine_process_server(Client *client) +{ +/* + http://nginx.org/en/docs/http/request_processing.html + _determine_process_server() should be complete. + TODO : test it +*/ + + std::string &server_name = client->get_headers("Host"); + std::vector::iterator it = _servers.begin(); + std::vector::iterator default_server = _servers.end(); + + while (it != _servers.end()) + { + if (it->host == client->lsocket->host + && it->port == client->lsocket->port) + { + if ( std::find(it->server_name.begin(), it->server_name.end(), server_name) != it->server_name.end() ) + break; + else if (default_server == _servers.end()) + default_server = it; + } + ++it; + } + if (it != _servers.end()) + return (*it); + else + return (*default_server); +} + +LocationConfig &Webserv::_determine_location(ServerConfig &server, std::string &path) +{ +/* + Assume there is at least one location in vector for path "/" + TODO in ConfigParser : + If no location block in config file, one need to be generated + for path "/", and filled with fields "root" and "index" based on parent server block +*/ + + std::vector::iterator it = server.locations.begin(); + while (it != server.locations.end()) + { + if (it->path.compare(0, path.size(), path)) + break; + ++it; + } + if (it != server.locations.end()) + return (*it); + else + return (server.locations.front()); +} diff --git a/srcs/webserv/run_loop.cpp b/srcs/webserv/run_loop.cpp index 215e4f9..ec9165d 100644 --- a/srcs/webserv/run_loop.cpp +++ b/srcs/webserv/run_loop.cpp @@ -4,14 +4,6 @@ #define MAX_EVENTS 42 // arbitrary #define TIMEOUT 3000 -// Temp. To move in other file -bool operator==(const Client& lhs, const Client& rhs) - { return lhs.fd == rhs.fd; } -bool operator==(const Client& lhs, int fd) - { return lhs.fd == fd; } -bool operator==(int fd, const Client& rhs) - { return fd == rhs.fd; } - void Webserv::run() { std::cerr << "Server started\n"; @@ -19,6 +11,7 @@ void Webserv::run() int nfds; int i; int count_loop = 0; + std::vector::iterator it_socket; g_run = true; while (g_run) @@ -42,9 +35,9 @@ void Webserv::run() while (i < nfds) { // TODO : handle EPOLLERR and EPOLLHUP - if ((std::find(_listen_sockets.begin(), _listen_sockets.end(), events[i].data.fd) != _listen_sockets.end()) - && (events[i].events & EPOLLIN)) - _accept_connection(events[i].data.fd); + it_socket = std::find(_listen_sockets.begin(), _listen_sockets.end(), events[i].data.fd); + if (it_socket != _listen_sockets.end() && events[i].events & EPOLLIN) + _accept_connection(*it_socket); else if (events[i].events & EPOLLIN) _request( &(*std::find(_clients.begin(), _clients.end(), events[i].data.fd)) ); else if (events[i].events & EPOLLOUT) diff --git a/website/index.html b/website/index.html deleted file mode 100644 index bc65643..0000000 --- a/website/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Le Webserv - - -

Le index (˘ ͜ʖ˘)

-
-

(˚3˚)

- - \ No newline at end of file diff --git a/website/rfc2119_no_link.html b/website/rfc2119_no_link.html deleted file mode 100644 index eec8fe5..0000000 --- a/website/rfc2119_no_link.html +++ /dev/null @@ -1,300 +0,0 @@ - - - - rfc2119 - - -
-This is a purely informative rendering of an RFC that includes verified errata. This rendering may not be used as a reference. -
-
-The following 'Verified' errata have been incorporated in this document: - EID 493, EID 494, EID 495, EID 496, EID 498, EID 499, EID 500, EID 5101 -
- -
Network Working Group                                         S. Bradner
-Request for Comments: 2119                            Harvard University
-BCP: 14                                                       March 1997
-Category: Best Current Practice
-
-
-        Key words for use in RFCs to Indicate Requirement Levels
-
-Status of this Memo
-
-   This document specifies an Internet Best Current Practices for the
-   Internet Community, and requests discussion and suggestions for
-   improvements.  Distribution of this memo is unlimited.
-
-Abstract
-
-   In many standards track documents several words are used to signify
-   the requirements in the specification.  These words are often
-   capitalized.  This document defines these words as they should be
-   interpreted in IETF documents.  Authors who follow these guidelines
-   should incorporate this phrase near the beginning of their document:
-
-             The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL 
-       NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT 
-       RECOMMENDED",  "MAY", and "OPTIONAL" in this document are to 
-       be interpreted as described in RFC 2119.
-
-
EID 499 (Verified) is as follows:
-
-Section: Abstract
-
-Original Text:
-
-       The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL
-       NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED",  "MAY", and
-       "OPTIONAL" in this document are to be interpreted as described in
-       RFC 2119.
-
-Corrected Text:
-
-       The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL
-       NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT 
-       RECOMMENDED",  "MAY", and "OPTIONAL" in this document are to 
-       be interpreted as described in RFC 2119.
-
-Notes:
-The phrase "NOT RECOMMENDED" is missing from this sentence. -
-
- Note that the force of these words is modified by the requirement - level of the document in which they are used. - -1. MUST This word, or the terms "REQUIRED" or "SHALL", means that the - definition is an absolute requirement of the specification. -
-
EID 493 (Verified) is as follows:
-
-Section: 1
-
-Original Text:
-
-2. MUST NOT   This phrase, or the phrase "SHALL NOT", mean that the
-   definition is an absolute prohibition of the specification.
-
-Corrected Text:
-
-2. MUST NOT   This phrase, or the phrase "SHALL NOT", means that the
-   definition is an absolute prohibition of the specification.
-
-Notes:
- -
-
-
EID 498 (Verified) is as follows:
-
-Section: 1
-
-Original Text:
-
-4. SHOULD NOT   This phrase, or the phrase "NOT RECOMMENDED" mean that
-   there may exist valid reasons in particular circumstances when the
-   particular behavior is acceptable or even useful, but the full
-   implications should be understood and the case carefully weighed
-   before implementing any behavior described with this label.
-
-Corrected Text:
-
-4. SHOULD NOT   This phrase, or the phrase "NOT RECOMMENDED", means that
-   there may exist valid reasons in particular circumstances when the
-   particular behavior is acceptable or even useful, but the full
-   implications should be understood and the case carefully weighed
-   before implementing any behavior described with this label.
-
-Notes:
- -
-
-
EID 500 (Verified) is as follows:
-
-Section: 1
-
-Original Text:
-
-3. SHOULD   This word, or the adjective "RECOMMENDED", mean that there
-   may exist valid reasons in particular circumstances to ignore a
-   particular item, but the full implications must be understood and
-   carefully weighed before choosing a different course.
-
-Corrected Text:
-
-3. SHOULD   This word, or the adjective "RECOMMENDED", means that there
-   may exist valid reasons in particular circumstances to ignore a
-   particular item, but the full implications must be understood and
-   carefully weighed before choosing a different course.
-
-Notes:
- -
-
-
-
EID 495 (Verified) is as follows:
-
-Section: 1
-
-Original Text:
-
-1. MUST   This word, or the terms "REQUIRED" or "SHALL", mean that the
-   definition is an absolute requirement of the specification.
-
-
-Corrected Text:
-
-1. MUST   This word, or the terms "REQUIRED" or "SHALL", means that the
-   definition is an absolute requirement of the specification.
-
-Notes:
- -
-
-2. MUST NOT This phrase, or the phrase "SHALL NOT", mean that the - definition is an absolute prohibition of the specification. - -3. SHOULD This word, or the adjective "RECOMMENDED", mean that there - may exist valid reasons in particular circumstances to ignore a - particular item, but the full implications must be understood and - carefully weighed before choosing a different course. - -4. SHOULD NOT This phrase, or the phrase "NOT RECOMMENDED" mean that - there may exist valid reasons in particular circumstances when the - particular behavior is acceptable or even useful, but the full - implications should be understood and the case carefully weighed - before implementing any behavior described with this label. - -5. MAY This word, or the adjective "OPTIONAL", mean that an item is - truly optional. One vendor may choose to include the item because a - particular marketplace requires it or because the vendor feels that - it enhances the product while another vendor may omit the same item. - An implementation which does not include a particular option MUST be - prepared to interoperate with another implementation which does - include the option, though perhaps with reduced functionality. In the - same vein an implementation which does include a particular option - MUST be prepared to interoperate with another implementation which - does not include the option (except, of course, for the feature the - option provides). -
-
EID 5101 (Verified) is as follows:
-
-Section: 5
-
-Original Text:
-
-5. MAY   This word, or the adjective "OPTIONAL", mean that an item is
-   truly optional.  One vendor may choose to include the item because a
-   particular marketplace requires it or because the vendor feels that
-   it enhances the product while another vendor may omit the same item.
-   An implementation which does not include a particular option MUST be
-   prepared to interoperate with another implementation which does
-   include the option, though perhaps with reduced functionality. In the
-   same vein an implementation which does include a particular option
-   MUST be prepared to interoperate with another implementation which
-   does not include the option (except, of course, for the feature the
-   option provides.)
-
-Corrected Text:
-
-5. MAY   This word, or the adjective "OPTIONAL", mean that an item is
-   truly optional.  One vendor may choose to include the item because a
-   particular marketplace requires it or because the vendor feels that
-   it enhances the product while another vendor may omit the same item.
-   An implementation which does not include a particular option MUST be
-   prepared to interoperate with another implementation which does
-   include the option, though perhaps with reduced functionality. In the
-   same vein an implementation which does include a particular option
-   MUST be prepared to interoperate with another implementation which
-   does not include the option (except, of course, for the feature the
-   option provides).
-
-Notes:
-Full stop should appear outside the parentheses in the last sentence. -
-
-6. Guidance in the use of these Imperatives - - Imperatives of the type defined in this memo must be used with care - and sparingly. In particular, they MUST only be used where it is - actually required for interoperation or to limit behavior which has - potential for causing harm (e.g., limiting retransmissions) For -
-
EID 494 (Verified) is as follows:
-
-Section: 6
-
-Original Text:
-
-(e.g., limiting retransmisssions)
-
-Corrected Text:
-
-(e.g., limiting retransmissions)
-
-Notes:
- -
-
-
EID 496 (Verified) is as follows:
-
-Section: 6
-
-Original Text:
-
-   In particular, they MUST only be used where it is actually required
-   for interoperation or to limit behavior which has potential for
-   causing harm (e.g., limiting retransmisssions)  For example, they
-   must not be used to try to impose a particular method on
-   implementors where the method is not required for interoperability.   
-
-Corrected Text:
-
-   In particular, they MUST only be used where it is actually required
-   for interoperation or to limit behavior which has potential for
-   causing harm (e.g., limiting retransmissions).  For example, they
-   must not be used to try to impose a particular method on
-   implementors where the method is not required for interoperability.
-
-Notes:
- -
-
example, they must not be used to try to impose a particular method - on implementors where the method is not required for - interoperability. - -7. Security Considerations - - These terms are frequently used to specify behavior with security - implications. The effects on security of not implementing a MUST or - SHOULD, or doing something the specification says MUST NOT or SHOULD - NOT be done may be very subtle. Document authors should take the time - to elaborate the security implications of not following - recommendations or requirements as most implementors will not have - had the benefit of the experience and discussion that produced the - specification. - -8. Acknowledgments - - The definitions of these terms are an amalgam of definitions taken - from a number of RFCs. In addition, suggestions have been - incorporated from a number of people including Robert Ullmann, Thomas - Narten, Neal McBurnett, and Robert Elz. - -9. Author's Address - - Scott Bradner - Harvard University - 1350 Mass. Ave. - Cambridge, MA 02138 - - phone - +1 617 495 3864 - - email - sob@harvard.edu - - - - - - -
\ No newline at end of file diff --git a/www/drill.jpg b/www/drill.jpg new file mode 100644 index 0000000..1812dbd Binary files /dev/null and b/www/drill.jpg differ diff --git a/www/favicon.ico b/www/favicon.ico new file mode 100644 index 0000000..8be776b Binary files /dev/null and b/www/favicon.ico differ diff --git a/www/index.html b/www/index.html index 7de0cd1..bc65643 100644 --- a/www/index.html +++ b/www/index.html @@ -1,16 +1,11 @@ - - - + Le Webserv - -

My First Heading

-

My first paragraph.

- +

Le index (˘ ͜ʖ˘)

+
+

(˚3˚)

- - - + \ No newline at end of file diff --git a/www/kermit.ico b/www/kermit.ico new file mode 100644 index 0000000..8be776b Binary files /dev/null and b/www/kermit.ico differ diff --git a/website/rfc2119_sigabrt.html b/www/rfc2119.html similarity index 99% rename from website/rfc2119_sigabrt.html rename to www/rfc2119.html index b8b389b..42168fa 100644 --- a/website/rfc2119_sigabrt.html +++ b/www/rfc2119.html @@ -13,7 +13,7 @@ - +
@@ -309,4 +309,4 @@ Full stop should appear outside the parentheses in the last sentence. - + \ No newline at end of file diff --git a/www/rfc2119_files/errata-base.css b/www/rfc2119_files/errata-base.css new file mode 100644 index 0000000..c34a536 --- /dev/null +++ b/www/rfc2119_files/errata-base.css @@ -0,0 +1,139 @@ + diff --git a/www/rfc2119_files/errata-color.css b/www/rfc2119_files/errata-color.css new file mode 100644 index 0000000..74428de --- /dev/null +++ b/www/rfc2119_files/errata-color.css @@ -0,0 +1,43 @@ + diff --git a/www/rfc2119_files/errata-monochrome.css b/www/rfc2119_files/errata-monochrome.css new file mode 100644 index 0000000..be74384 --- /dev/null +++ b/www/rfc2119_files/errata-monochrome.css @@ -0,0 +1,34 @@ + diff --git a/www/rfc2119_files/errata-printer.css b/www/rfc2119_files/errata-printer.css new file mode 100644 index 0000000..4a3dab3 --- /dev/null +++ b/www/rfc2119_files/errata-printer.css @@ -0,0 +1,43 @@ + diff --git a/www/rfc2119_files/errata.js b/www/rfc2119_files/errata.js new file mode 100644 index 0000000..4e29587 --- /dev/null +++ b/www/rfc2119_files/errata.js @@ -0,0 +1,4 @@ +function hideFunction(nodeId) { + var ul = document.getElementById(nodeId) + ul.className = (ul.className=="nodeOpenClass") ? "nodeCloseClass" : "nodeOpenClass" +} diff --git a/www/rfc2119_files/errata.js.téléchargement b/www/rfc2119_files/errata.js.téléchargement new file mode 100644 index 0000000..4e29587 --- /dev/null +++ b/www/rfc2119_files/errata.js.téléchargement @@ -0,0 +1,4 @@ +function hideFunction(nodeId) { + var ul = document.getElementById(nodeId) + ul.className = (ul.className=="nodeOpenClass") ? "nodeCloseClass" : "nodeOpenClass" +} diff --git a/www/root.png b/www/root.png new file mode 100644 index 0000000..ebd1548 Binary files /dev/null and b/www/root.png differ