diff --git a/Makefile b/Makefile index 03350d6..9bc7752 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ $(NAME): $(OBJS) $(CXX) $(OBJS) -o $(NAME) debug: - $(CXX) -I includes -Werror -Wextra -Wall -std=c++98 -g $(SRCS) -o $(NAME) -D DEBUG=1 SILENT=0 + $(CXX) -I includes -Werror -Wextra -Wall -std=c++98 -g $(SRCS) -o $(NAME) -D DEBUG=1 -D SILENT=0 verbose: $(CXX) -I includes -Werror -Wextra -Wall -std=c++98 -g $(SRCS) -o $(NAME) -D SILENT=0 diff --git a/README.md b/README.md index 305124d..b2ca137 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ ## ToDo/ToFix: -- [] compare host and server_name for route choice - [] organize answer structure - [] default html pages (404, 503 ) ?? - [] limit client body @@ -13,13 +12,13 @@ - [] hard test segmented request - [] verify upload of files - [] what to do with content ? -- [] CGI behavior - - [] based on files extensions - - [] - [] STRESS TEST!! Server must stay available!! - [] Server shout never indefinitly wait. - [] PUT non-text file may finish because of eof char. +## Notes: +- too low keep_alive amount badly close sockets and produce infinit client load. + ## Behavior: ### Configuration Parsing: diff --git a/default.json b/default.json index 461fcae..acfcb4c 100644 --- a/default.json +++ b/default.json @@ -6,13 +6,18 @@ "server_name": "localhost", "listens": ["localhost:8080"], "root": "public/", - "return": "301 https://$host$uri" + "return": [301, "http://webserv.doc/"] }, { "server_name": "webserv.doc", "listens": ["localhost"], "root": "html/", - "indexs": ["index.html"] + "indexs": ["index.html"], + "keepalive_requests": 20, + "error_pages": { + "/error3xx.html": [301, 302, 307, 308], + "/error4xx.html": [400, 403, 404, 405, 408, 413, 429] + } }, { "server_name": "localhost", @@ -24,11 +29,15 @@ "listens": ["192.168.62.61:8080", "localhost", "555"], "root": "public/html/", "indexs": ["basique.html"], + "error_pages": { + "/error404.html": [404] + }, "cgi": { ".php": "/usr/bin/php-cgi", ".py": "/usr/bin/python" }, "client_max_body_size": 10000, + "keepalive_time": 60, "locations": { "/docs/": { "root": "public/resources/", diff --git a/includes/Client.hpp b/includes/Client.hpp index 41e1e42..8fbc0ef 100644 --- a/includes/Client.hpp +++ b/includes/Client.hpp @@ -10,13 +10,17 @@ class Client { Env *_env; Route *_route; string _header, _body; + std::map _headers; string _method, _uri, _query, _host; int _len; bool _last_chunk; - std::map _headers; + bool _keepalive; + int _death_time; + time_t _requests_done; bool _finish; void init(void); + void debug(bool head); bool getBody(string paquet); bool parseHeader(Env *env); string header_pick(string key, size_t id); diff --git a/includes/Route.hpp b/includes/Route.hpp index ab6f6a3..9281f18 100644 --- a/includes/Route.hpp +++ b/includes/Route.hpp @@ -3,21 +3,27 @@ class Route { protected: - Server *_server; - string _location, _root, _ret; - bool _autoindex; + Server *_server; + string _location, _root; + bool _autoindex; public: - vec_string _indexs, _allowed_methods; - std::map _cgi; - int _client_max_body_size; + vec_string _indexs, _allowed_methods; + std::map _cgi; + int _client_max_body_size; + int _timeout; + int _max_requests; + int _ret_code; + string _ret_uri; + std::map _err_page; - Route(Server *server, string location, JSONNode *datas); - ~Route(void); - string getLocation(void); - string getRoot(void); - string getReturn(void); - Server *getServer(void); - string getIndex(string uri, string path); - string correctUri(string uri); + Route(Server *server, string location, JSONNode *datas); + ~Route(void); + string getLocation(void); + string get_custom_err(int error_code); + string getRoot(void); + string getReturn(void); + Server *getServer(void); + string getIndex(string uri, string path); + string correctUri(string uri); }; diff --git a/includes/webserv.hpp b/includes/webserv.hpp index b9bc590..e6c0190 100644 --- a/includes/webserv.hpp +++ b/includes/webserv.hpp @@ -3,10 +3,10 @@ #define DEBUG 0 #endif #ifndef SILENT - #define SILENT 1 + #define SILENT 1 #endif #ifndef MAX_CLIENTS - #define MAX_CLIENTS 5000 + #define MAX_CLIENTS 5000 #endif #include @@ -66,6 +66,7 @@ ip_port_t get_ip_port_t(string listen); ip_port_t get_ip_port_t(string ip, int port); string getMime(string path); string read_file(string path); +string file_answer(string path); // debug void debug_block(string name, string content); diff --git a/public/html/error403.html b/public/html/error403.html index 264c637..7f24385 100644 --- a/public/html/error403.html +++ b/public/html/error403.html @@ -7,10 +7,10 @@

403 ERROR

forbidden access

- + - \ No newline at end of file + diff --git a/public/html/error404.html b/public/html/error404.html index f30ec71..544dcb4 100644 --- a/public/html/error404.html +++ b/public/html/error404.html @@ -7,10 +7,10 @@

404 ERROR

there is no page here you monkey, go away

- + - \ No newline at end of file + diff --git a/public/html/error405.html b/public/html/error405.html index ccb2b76..11c0d0e 100644 --- a/public/html/error405.html +++ b/public/html/error405.html @@ -7,10 +7,10 @@

405 ERROR

method not allowed

- + - \ No newline at end of file + diff --git a/public/html/error414.html b/public/html/error414.html index af9347d..9033d59 100644 --- a/public/html/error414.html +++ b/public/html/error414.html @@ -7,10 +7,10 @@

414 ERROR

url too long

- + - \ No newline at end of file + diff --git a/public/html/error429.html b/public/html/error429.html index 5619224..2ff6691 100644 --- a/public/html/error429.html +++ b/public/html/error429.html @@ -7,10 +7,10 @@

429 ERROR

too many requests

- + - \ No newline at end of file + diff --git a/public/html/error503.html b/public/html/error503.html index 0c79b75..86dc205 100644 --- a/public/html/error503.html +++ b/public/html/error503.html @@ -7,10 +7,10 @@

503 SERVICE UNAVAILABLE

sorry for this desagrement, our best engineers are working to better your experience on our website

- + - \ No newline at end of file + diff --git a/public/html/error505.html b/public/html/error505.html index ae4024e..6f5b2a6 100644 --- a/public/html/error505.html +++ b/public/html/error505.html @@ -7,10 +7,10 @@

505 ERROR

http version not supported

- + - \ No newline at end of file + diff --git a/srcs/load/Env.cpp b/srcs/load/Env.cpp index 1677794..1469a7e 100644 --- a/srcs/load/Env.cpp +++ b/srcs/load/Env.cpp @@ -54,11 +54,11 @@ Env::~Env() { * - for each master socket, call his own post_poll method to check and handle the sockets flaged. */ void Env::cycle(void) { - if (!SILENT) cout << "|===||===| Waiting some HTTP request... |===||===|\n"; + //if (!SILENT) cout << "|===||===| Waiting some HTTP request... |===||===|\n"; int pollResult = poll(Master::_pollfds, Master::_poll_id_amount + 1, 5000); if ((pollResult < 0) && (errno != EINTR)) std::cerr << "Select: " << strerror(errno) << "\n"; if (pollResult > 0) { - if (!SILENT) cout << "==> Handle requests and answers:\n"; + //if (!SILENT) cout << "==> Handle requests and answers:\n"; for (std::vector::iterator it = this->_masters.begin(); it < this->_masters.end(); it++) try { (*it)->check_socket(); (*it)->check_childs(this); diff --git a/srcs/load/Route.cpp b/srcs/load/Route.cpp index 99764c4..d49ee5b 100644 --- a/srcs/load/Route.cpp +++ b/srcs/load/Route.cpp @@ -10,34 +10,61 @@ /** * @brief Constructor * - * A route is an object which define how to handle a request. Each Server is Route inherited and each location block - * lead to a new Route object. + * A route is an object which define how to handle a request. Each Server is + * Route inherited and each location block lead to a new Route object. * - * @param server The Server parent of the route. NULL if the object is the server. + * @param server The Server parent of the route. NULL if the object is the + * server. * @param location The uri associatied to the route. * @param datas The JSONNode giving configuration. */ -Route::Route(Server *server, string location, JSONNode *datas) : _server(server), _location(location) { - JSONObject object = datas->obj(); - JSONNode *tmp; - _autoindex = false; - _client_max_body_size = -1; - if ((tmp = object["root"])) _root = tmp->str(); - if ((tmp = object["return"])) _ret = tmp->str(); - if ((tmp = object["autoindex"])) _autoindex = tmp->boo(); - if ((tmp = object["indexs"])) { - JSONList indexs = tmp->lst(); - for (JSONList::iterator it = indexs.begin(); it < indexs.end(); it++) _indexs.push_back((*it)->str()); - } - if ((tmp = object["allowed_methods"])) { - JSONList headers = tmp->lst(); - for (JSONList::iterator it = headers.begin(); it < headers.end(); it++) _allowed_methods.push_back((*it)->str()); - } - if ((tmp = object["cgi"])) { - JSONObject cgis = tmp->obj(); - for (JSONObject::iterator it = cgis.begin(); it != cgis.end(); it++) _cgi[(*it).first] = (*it).second->str(); - } - if ((tmp = object["client_max_body_size"])) _client_max_body_size = tmp->nbr(); +Route::Route(Server *server, string location, JSONNode *datas) + : _server(server), _location(location) { + JSONObject object = datas->obj(); + JSONNode *tmp; + _autoindex = false; + _client_max_body_size = -1; + if ((tmp = object["root"])) + _root = tmp->str(); + if ((tmp = object["return"])) { + _ret_code = tmp->lst()[0]->nbr(); + _ret_uri = tmp->lst()[1]->str(); + } + if ((tmp = object["autoindex"])) + _autoindex = tmp->boo(); + if ((tmp = object["keepalive_time"])) + _timeout = tmp->nbr(); + else + _timeout = 0; + if ((tmp = object["keepalive_requests"])) + _max_requests = tmp->nbr(); + else + _max_requests = 0; + if ((tmp = object["error_pages"])) { + JSONObject pages = tmp->obj(); + for (JSONObject::iterator it = pages.begin(); it != pages.end(); it++) { + JSONList err = (*it).second->lst(); + for (JSONList::iterator it2 = err.begin(); it2 != err.end(); it2++) + _err_page[(*it2)->nbr()] = (*it).first; + } + } + if ((tmp = object["indexs"])) { + JSONList indexs = tmp->lst(); + for (JSONList::iterator it = indexs.begin(); it < indexs.end(); it++) + _indexs.push_back((*it)->str()); + } + if ((tmp = object["allowed_methods"])) { + JSONList headers = tmp->lst(); + for (JSONList::iterator it = headers.begin(); it < headers.end(); it++) + _allowed_methods.push_back((*it)->str()); + } + if ((tmp = object["cgi"])) { + JSONObject cgis = tmp->obj(); + for (JSONObject::iterator it = cgis.begin(); it != cgis.end(); it++) + _cgi[(*it).first] = (*it).second->str(); + } + if ((tmp = object["client_max_body_size"])) + _client_max_body_size = tmp->nbr(); } /// @brief Destructor @@ -46,7 +73,6 @@ Route::~Route(void) {} // Getters string Route::getLocation(void) { return _location; } string Route::getRoot(void) { return _root; } -string Route::getReturn(void) { return _ret; } /** * @brief Search for an index while generating autoindex @@ -54,35 +80,45 @@ string Route::getReturn(void) { return _ret; } * @param uri The uri requested by client. * @param path The correct path associated with uri. * - * @return The index content to give to client or an empty string if there is nothing for him. + * @return The index content to give to client or an empty string if there is + * nothing for him. */ string Route::getIndex(string uri, string path) { - std::stringstream body, ret; - DIR *dir; - struct dirent *entry; - struct stat info; - vec_string::iterator it; + std::stringstream body, ret; + DIR *dir; + struct dirent *entry; + struct stat info; + vec_string::iterator it; - if ((dir = opendir(path.c_str()))) { - if (DEBUG) cout << "get index(): path=" << path << "\n"; - body << "

" << path << " files :

\n
    \n"; - while ((entry = readdir(dir)) != NULL) { - if (entry->d_name[0] == '.') continue; - for (it = _indexs.begin(); it < _indexs.end(); it++) { - if (entry->d_name == *it) return (read_file(path + "/" + *it)); - } - body << "
  • d_name << "\">" << entry->d_name << "
  • \n"; - if (stat(path.c_str(), &info) != 0) std::cerr << "stat() error on " << path << ": " << strerror(errno) << "\n"; - } - body << "
"; - closedir(dir); - } - if (!dir || !_autoindex) return ""; - if (DEBUG) cout << "Getting autoindex\n"; - ret << "Content-type: text/html \r\n"; - ret << "Content-length: " << body.str().length() << "\r\n"; - ret << "\r\n" << body.str(); - return ret.str(); + if ((dir = opendir(path.c_str()))) { + if (DEBUG) + cout << "get index(): path=" << path << "\n"; + body << "

" << path + << " files :

\n
    \n"; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') + continue; + for (it = _indexs.begin(); it < _indexs.end(); it++) { + if (entry->d_name == *it) + return (file_answer(path + "/" + *it)); + } + body << "
  • d_name << "\">" + << entry->d_name << "
  • \n"; + if (stat(path.c_str(), &info) != 0) + std::cerr << "stat() error on " << path << ": " << strerror(errno) + << "\n"; + } + body << "
"; + closedir(dir); + } + if (!dir || !_autoindex) + return ""; + if (DEBUG) + cout << "Getting autoindex\n"; + ret << "Content-type: text/html \r\n"; + ret << "Content-length: " << body.str().length() << "\r\n"; + ret << "\r\n" << body.str(); + return ret.str(); } /** @@ -94,18 +130,22 @@ string Route::getIndex(string uri, string path) { * @deprecated Not used by nginx until config use rewrite keyword. */ string Route::correctUri(string uri) { - std::stringstream ret; - vec_string::iterator loc_word, uri_word; + std::stringstream ret; + vec_string::iterator loc_word, uri_word; - vec_string loc_words = split(_location, "/"); - vec_string uri_words = split(uri, "/"); - uri_word = uri_words.begin(); - for (loc_word = loc_words.begin(); loc_word < loc_words.end(); loc_word++) { - while (uri_word < uri_words.end() && *uri_word == "") uri_word++; - while (loc_word < loc_words.end() && *loc_word == "") loc_word++; - if (loc_word != loc_words.end()) uri_word++; - } - ret << "./" << _root; - while (uri_word < uri_words.end()) ret << "/" << *(uri_word++); - return ret.str(); + vec_string loc_words = split(_location, "/"); + vec_string uri_words = split(uri, "/"); + uri_word = uri_words.begin(); + for (loc_word = loc_words.begin(); loc_word < loc_words.end(); loc_word++) { + while (uri_word < uri_words.end() && *uri_word == "") + uri_word++; + while (loc_word < loc_words.end() && *loc_word == "") + loc_word++; + if (loc_word != loc_words.end()) + uri_word++; + } + ret << "./" << _root; + while (uri_word < uri_words.end()) + ret << "/" << *(uri_word++); + return ret.str(); } diff --git a/srcs/load/Server.cpp b/srcs/load/Server.cpp index 538d121..982cd70 100644 --- a/srcs/load/Server.cpp +++ b/srcs/load/Server.cpp @@ -87,9 +87,9 @@ Route *Server::choose_route(string uri) { loc_words = split((*loc_it).first, "/"); vec_string::iterator loc_word = loc_words.begin(); vec_string::iterator uri_word = uri_words.begin(); - while (*loc_word == *uri_word) { - while (*++loc_word == "") {} - while (*++uri_word == "") {} + while (loc_word != loc_words.end() && uri_word != uri_words.end() && *loc_word == *uri_word) { + while (++loc_word != loc_words.end() && *loc_word == "") {} + while (++uri_word != uri_words.end() && *uri_word == "") {} } if (loc_word == loc_words.end()) return ((*loc_it).second); } diff --git a/srcs/sock/Client.cpp b/srcs/sock/Client.cpp index 08ea4b1..55ccfcd 100644 --- a/srcs/sock/Client.cpp +++ b/srcs/sock/Client.cpp @@ -1,200 +1,312 @@ /** * @file Client.cpp - * @brief The client sockets class which keep keep clients information and handle answer to them. + * @brief The client sockets class which keep keep clients information and + * handle answer to them. * @author Narnaud * @version 0.1 * @date 2023-01-12 */ #include "webserv.hpp" +#include +#include inline string get_extension(string str) { - size_t pos = str.rfind('.'); - if (pos != string::npos) return str.substr(pos); - else return ""; + size_t pos = str.rfind('.'); + if (pos != string::npos) + return str.substr(pos); + else + return ""; } -Client::Client(int fd, ip_port_t ip_port, Master *parent) : _fd(fd), _ip_port(ip_port), _parent(parent) { - init(); - if (!SILENT) - cout << "New connection, socket fd is " << fd << ", ip is : " << _ip_port.ip << ", port : " << _ip_port.port - << "\n"; +Client::Client(int fd, ip_port_t ip_port, Master *parent) + : _fd(fd), _ip_port(ip_port), _parent(parent) { + _requests_done = 0; + _death_time = 0; + _finish = false; + _route = NULL; + _server = NULL; + init(); + if (!SILENT) + cout << "New connection, socket fd is " << fd << ", ip is : " << _ip_port.ip + << ", port : " << _ip_port.port << "\n"; } Client::~Client(void) { - close(_fd); + close(_fd); Master::_pollfds[_poll_id].fd = 0; - Master::_pollfds[_poll_id].events = 0; + Master::_pollfds[_poll_id].events = 0; Master::_pollfds[_poll_id].revents = 0; - Master::_poll_id_amount--; - _headers.clear(); - if (!SILENT) cout << "Host disconnected, ip " << _ip_port.ip << ", port " << _ip_port.port << "\n"; + Master::_poll_id_amount--; + _headers.clear(); + if (!SILENT) + cout << "Host disconnected, ip " << _ip_port.ip << ", port " + << _ip_port.port << "\n"; } void Client::init(void) { - _finish = false; - _server = NULL; - _route = NULL; - _method = _uri = _host = _header = _body = ""; - _len = 0; - _last_chunk = false; - _headers.clear(); + _requests_done++; + if (_route && _route->_max_requests > 0) { + if (_requests_done > _route->_max_requests) + _finish = true; + } else if (_server && _server->_max_requests > 0) { + if (_requests_done > _server->_max_requests) + _finish = true; + } + _method = _uri = _host = _header = _body = ""; + _len = 0; + _last_chunk = false; + _headers.clear(); +} +template void tab(T t, const int &width) { + std::cout << std::left << std::setw(width) << std::setfill(' ') << t; +} +void Client::debug(bool head) { + + if (head) { + std::cout << "Client " << _poll_id + << " debug ===================================\n"; + tab("Fd", 4); + tab("Ip", 12); + tab("Port", 6); + tab("Servername", 12); + tab("Route", 12); + tab("Method", 6); + tab("URI", 20); + tab("Query", 12); + tab("Host", 12); + tab("Len", 6); + tab("Keep", 4); + tab("Death", 6); + tab("Request", 6); + tab("Finish", 6); + std::cout << "\n"; + } + tab(_fd, 4); + tab(_ip_port.ip, 12); + tab(_ip_port.port, 6); + tab(_server->getName(), 12); + tab(_route->getLocation(), 12); + tab(_method, 6); + tab(_uri, 20); + tab(_query, 12); + tab(_host, 12); + tab(_len, 6); + tab(_keepalive, 4); + tab(_death_time, 6); + tab(_requests_done, 6); + tab(_finish, 6); + std::cout << "\n"; } bool Client::getRequest(Env *env, string paquet) { - if (paquet.length() < 1) send_error(403); - if (DEBUG) debug_block("Paquet: ", paquet); - if (header_pick("Method:", 0) != "") return getBody(paquet); - vec_string lines = split(paquet, "\r\n"); - for (vec_string::iterator it = lines.begin(); it < lines.end(); it++) { - size_t pos = paquet.find("\r\n"); - if (pos != string::npos) paquet.erase(0, pos + 2); - else paquet.clear(); - _header += *it + (it + 1 != lines.end() ? "\r\n" : ""); - if (_header.find("\r\n\r\n") != string::npos) - return !parseHeader(env) ? false : (_len != 0 ? getBody(paquet) : true); - } - return false; + if (DEBUG) + debug_block("Paquet: ", paquet); + if (header_pick("Method:", 0) != "") + return getBody(paquet); + vec_string lines = split(paquet, "\r\n"); + for (vec_string::iterator it = lines.begin(); it < lines.end(); it++) { + size_t pos = paquet.find("\r\n"); + if (pos != string::npos) + paquet.erase(0, pos + 2); + else + paquet.clear(); + _header += *it + (it + 1 != lines.end() ? "\r\n" : ""); + if (_header.find("\r\n\r\n") != string::npos) + return !parseHeader(env) ? false : (_len != 0 ? getBody(paquet) : true); + } + return false; } bool Client::getBody(string paquet) { - vec_string lines = split(paquet, "\r\n"); - vec_string::iterator it; - - for (it = lines.begin(); it < lines.end(); it++) { - if (DEBUG) cout << "Remaining length: " << _len << "\n"; - if ((*it).length() && _len <= 0 && header_pick("Transfer-Encoding:", 0) == "chunked") { - _len = std::strtol((*it).c_str(), 0, 16) + 2; - _last_chunk = _len == 2 ? true : false; - } else if (_len > 0 || it != lines.begin()) { - _body += *it + "\r\n"; - _len -= ((*it).length() + 2); - } - } - // if (_body.size()) - _body.resize(_body.length() - 2); - _len += 2; - return (_last_chunk && _len == 0) ? true : false; + vec_string lines = split(paquet, "\r\n"); + vec_string::iterator it; + + for (it = lines.begin(); it < lines.end(); it++) { + if (DEBUG) + cout << "Remaining length: " << _len << "\n"; + if ((*it).length() && _len <= 0 && + header_pick("Transfer-Encoding:", 0) == "chunked") { + _len = std::strtol((*it).c_str(), 0, 16) + 2; + _last_chunk = _len == 2 ? true : false; + } else if (_len > 0 || it != lines.begin()) { + _body += *it + "\r\n"; + _len -= ((*it).length() + 2); + } + } + // if (_body.size()) + _body.resize(_body.length() - 2); + _len += 2; + return (_last_chunk && _len == 0) ? true : false; } bool Client::parseHeader(Env *env) { - vec_string lines, method, line; - - if (DEBUG) cout << "Parsing header...\n"; - lines = split(_header, "\r\n"); - method = split(lines.at(0), " "); - _headers["Method:"] = method; - if (lines.size() > 0) { - for (vec_string::iterator it = lines.begin() + 1; it < lines.end(); it++) { - line = split(*it, " "); - _headers[line.at(0)] = vec_string(line.begin() + 1, line.end()); - } - } - _method = header_pick("Method:", 0); - if ((_method == "POST" || _method == "PUT") && header_pick("Content-Length:", 0) == "" && - header_pick("Transfer-Encoding:", 0) != "chunked") - return (send_error(400), false); - vec_string uri_split = split(header_pick("Method:", 1), "?"); - _uri = uri_split.at(0); - if (uri_split.size() > 1) _query = uri_split.at(1); - _host = header_pick("Host:", 0); - _env = env; - _server = _parent->choose_server(env, _host); - _route = _server->choose_route(_uri); - if (DEBUG) debug_header(); - string len = header_pick("Content-Length:", 0).c_str(); - if (len != "") { - _len = std::atoi(len.c_str()); - _last_chunk = true; - int max_len = _route->_client_max_body_size > 0 ? _route->_client_max_body_size - : _server->_client_max_body_size > 0 ? _server->_client_max_body_size - : INT_MAX; - if (_len > max_len) return (send_error(413), false); - } else _len = 0; - return true; -} - -string Client::header_pick(string key, size_t id) { return _headers[key].size() <= id ? "" : _headers[key].at(id); } + vec_string lines, method, line; + if (DEBUG) + cout << "Parsing header...\n"; + lines = split(_header, "\r\n"); + method = split(lines.at(0), " "); + _headers["Method:"] = method; + if (lines.size() > 0) { + for (vec_string::iterator it = lines.begin() + 1; it < lines.end(); it++) { + line = split(*it, " "); + _headers[line.at(0)] = vec_string(line.begin() + 1, line.end()); + } + } + _method = header_pick("Method:", 0); + if ((_method == "POST" || _method == "PUT") && + header_pick("Content-Length:", 0) == "" && + header_pick("Transfer-Encoding:", 0) != "chunked") + return (send_error(400), false); + vec_string uri_split = split(header_pick("Method:", 1), "?"); + _uri = uri_split.at(0); + if (uri_split.size() > 1) + _query = uri_split.at(1); + _host = header_pick("Host:", 0); + _keepalive = header_pick("Connection:", 0) == "keep-alive"; + _env = env; + struct timeval t; + gettimeofday(&t, NULL); + _server = _parent->choose_server(env, _host); + if (_server->_timeout) + _death_time = _server->_timeout + t.tv_sec; + _route = _server->choose_route(_uri); + if (_route->_timeout) + _death_time = _route->_timeout + t.tv_sec; + if (DEBUG) + debug_header(); + string len = header_pick("Content-Length:", 0).c_str(); + if (len != "") { + _len = std::atoi(len.c_str()); + _last_chunk = true; + int max_len = + _route->_client_max_body_size > 0 ? _route->_client_max_body_size + : _server->_client_max_body_size > 0 ? _server->_client_max_body_size + : INT_MAX; + if (_len > max_len) + return (send_error(413), false); + } else + _len = 0; + return true; +} + +string Client::header_pick(string key, size_t id) { + return _headers[key].size() <= id ? "" : _headers[key].at(id); +} bool Client::check_method(void) { - vec_string allowed; - if ((_route && (allowed = _route->_allowed_methods).size() > 0) || - (_server && (allowed = _server->_allowed_methods).size() > 0) || ((allowed = _env->_allowed_methods).size() > 0)) - return std::find(allowed.begin(), allowed.end(), _method) < allowed.end() ? true : false; - else if (_method == "GET" || _method == "POST" || _method == "DELETE" || _method == "PUT") return (true); - return (false); + vec_string allowed; + if ((_route && (allowed = _route->_allowed_methods).size() > 0) || + (_server && (allowed = _server->_allowed_methods).size() > 0) || + ((allowed = _env->_allowed_methods).size() > 0)) + return std::find(allowed.begin(), allowed.end(), _method) < allowed.end() + ? true + : false; + else if (_method == "GET" || _method == "POST" || _method == "DELETE" || + _method == "PUT") + return (true); + return (false); } void Client::handleRequest(void) { - if (DEBUG) { - debug_block("Header: ", _header); - debug_block("Body: ", _body); - } - string ret; - string req_path = _route->getRoot() + _uri; - if (!SILENT) std::cout << "||-> Request for " << req_path << " received <-||\n"; - string cgi_path = _route->_cgi.size() ? _route->_cgi[get_extension(req_path)] - : _server->_cgi.size() ? _server->_cgi[get_extension(req_path)] - : ""; - if (DEBUG) cout << "Path: " << req_path << "\n"; - if (!check_method()) send_error(405); - else { - if ((ret = _route->getIndex(_uri, req_path)) == "") ret = read_file(req_path); - if (ret == "404") { - if (_method == "POST" || _method == "PUT") create_file(req_path); - else send_error(404); - } else if (ret == "403") send_error(403); - else if (_method == "DELETE") std::remove(req_path.c_str()); - else if (cgi_path != "") cgi(cgi_path, req_path); - else send_answer("HTTP/1.1 200 OK\r\n" + ret); - } + if (DEBUG) { + debug_block("Header: ", _header); + debug_block("Body: ", _body); + } + if (_route->_ret_uri != "") + send_error(_route->_ret_code, _route->_ret_uri); + else if (_server->_ret_uri != "") + send_error(_server->_ret_code, _server->_ret_uri); + else { + string ret; + struct timeval t; + gettimeofday(&t, NULL); + if (_death_time && t.tv_sec > _death_time) { + send_error(408); + _finish = true; + return; + } + string req_path = _route->getRoot() + _uri; + if (!SILENT) + std::cout << "||-> Request for " << req_path << " received <-||\n"; + string cgi_path = + _route->_cgi.size() ? _route->_cgi[get_extension(req_path)] + : _server->_cgi.size() ? _server->_cgi[get_extension(req_path)] + : ""; + if (DEBUG) + cout << "Path: " << req_path << "\n"; + if (!check_method()) + send_error(405); + else { + if ((ret = _route->getIndex(_uri, req_path)) == "") + ret = file_answer(req_path); + if (ret == "404") { + if (_method == "POST" || _method == "PUT") + create_file(req_path); + else + send_error(404); + } else if (ret == "403") + send_error(403); + else if (_method == "DELETE") + std::remove(req_path.c_str()); + else if (cgi_path != "") + cgi(cgi_path, req_path); + else + send_answer("HTTP/1.1 200 OK\r\n" + ret); + } + } } void Client::create_file(string path) { - std::ofstream file(path.c_str()); - if (!file.good()) send_error(403); - else { - file << _body; - file.close(); - send_answer("HTTP/1.1 201 Accepted\r\n\r\n"); - } + std::ofstream file(path.c_str()); + if (!file.good()) + send_error(403); + else { + file << _body; + file.close(); + send_answer("HTTP/1.1 201 Accepted\r\n\r\n"); + } } /** * @brief Launch cgi binary to parse the file requested by the client. * - * @param cgi_path The cgi binary location specified in configuration file according to the file requested. + * @param cgi_path The cgi binary location specified in configuration file + * according to the file requested. * @param path The path to the file requested. */ void Client::cgi(string cgi_path, string path) { - int pipe_in[2]; - - send(_fd, "HTTP/1.1 200 OK\r\n", 17, MSG_NOSIGNAL); - if (!std::ifstream(cgi_path.c_str()).good()) return send_error(404); - if (DEBUG) std::cout << "Send cgi\n"; - if (fork() == 0) { - const char **args = new const char *[cgi_path.length() + 1]; - args[0] = cgi_path.c_str(); - args[1] = NULL; - string path_info = "PATH_INFO=" + _route->getRoot(); - string query = "QUERY_STRING=" + _query; - const char **env = new const char *[path_info.length() + query.length() + 2]; - env[0] = path_info.c_str(); - env[1] = query.c_str(); - env[2] = NULL; - pipe(pipe_in); - std::stringstream tmp; - tmp << std::ifstream(path.c_str()).rdbuf(); - string file = tmp.str(); - write(pipe_in[1], file.c_str(), file.size()); - close(pipe_in[1]); - dup2(pipe_in[0], STDIN_FILENO); - close(pipe_in[0]); - dup2(_fd, STDOUT_FILENO); - close(_fd); - execve(cgi_path.c_str(), (char **)args, (char **)env); - exit(1); - } - _finish = true; + int pipe_in[2]; + + send(_fd, "HTTP/1.1 200 OK\r\n", 17, MSG_NOSIGNAL); + if (!std::ifstream(cgi_path.c_str()).good()) + return send_error(404); + if (DEBUG) + std::cout << "Send cgi\n"; + if (fork() == 0) { + const char **args = new const char *[cgi_path.length() + 1]; + args[0] = cgi_path.c_str(); + args[1] = NULL; + string path_info = "PATH_INFO=" + _route->getRoot(); + string query = "QUERY_STRING=" + _query; + const char **env = + new const char *[path_info.length() + query.length() + 2]; + env[0] = path_info.c_str(); + env[1] = query.c_str(); + env[2] = NULL; + pipe(pipe_in); + std::stringstream tmp; + tmp << std::ifstream(path.c_str()).rdbuf(); + string file = tmp.str(); + write(pipe_in[1], file.c_str(), file.size()); + close(pipe_in[1]); + dup2(pipe_in[0], STDIN_FILENO); + close(pipe_in[0]); + dup2(_fd, STDOUT_FILENO); + close(_fd); + execve(cgi_path.c_str(), (char **)args, (char **)env); + exit(1); + } + _finish = true; } /** @@ -203,22 +315,50 @@ void Client::cgi(string cgi_path, string path) { * @param error_code The HTTP response code to send. */ void Client::send_error(int error_code, string opt) { - switch (error_code) { - case 301: - return send_answer("HTTP/1.1 301 Moved Permanently\r\nLocation: " + opt + "\r\n\r\n"); - case 400: - return send_answer("HTTP/1.1 400 Bad Request\r\n\r\n"); - case 403: - return send_answer("HTTP/1.1 403 Forbidden\r\n\r\n"); - case 404: - return send_answer("HTTP/1.1 404 Not Found\r\n\r\n"); - case 405: - return send_answer("HTTP/1.1 405 Method Not Allowed\r\nConnection: " - "close\r\n\r\n"); - case 413: - return send_answer("HTTP/1.1 413 Payload Too " - "Large\r\nConnection: close\r\n\r\n"); - } + string error_path, body; + error_path = _route->_err_page[error_code]; + if (error_path == "") { + error_path = _server->_err_page[error_code]; + if (error_path != "") + body = file_answer(_server->correctUri(error_path)); + } else + body = file_answer(_route->correctUri(error_path)); + + switch (error_code) { + case 301: + return send_answer("HTTP/1.1 301 Moved Permanently\r\nLocation: " + opt + + "\r\n" + body + "\r\n"); + case 302: + return send_answer("HTTP/1.1 302 Found\r\nLocation: " + opt + "\r\n" + + body + "\r\n"); + case 307: + return send_answer("HTTP/1.1 307 Temporary Redirect\r\nLocation: " + opt + + "\r\n" + body + "\r\n"); + case 308: + return send_answer("HTTP/1.1 308 Permanent Redirect\r\nLocation: " + opt + + "\r\n" + body + "\r\n"); + case 400: + return send_answer("HTTP/1.1 400 Bad Request\r\n" + body + "\r\n"); + case 403: + return send_answer("HTTP/1.1 403 Forbidden\r\n" + body + "\r\n"); + case 404: + return send_answer("HTTP/1.1 404 Not Found\r\n" + body + "\r\n"); + case 405: + return send_answer("HTTP/1.1 405 Method Not Allowed\r\nConnection: " + "close\r\n" + + body + "\r\n"); + case 408: + return send_answer("HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n" + + body + "\r\n"); + case 413: + return send_answer( + "HTTP/1.1 413 Payload Too Large\r\nConnection: close\r\n" + body + + "\r\n"); + case 429: + return send_answer( + "HTTP/1.1 429 Too Many Requests\r\nConnection: close\r\n" + body + + "\r\n"); + } } /** @@ -227,12 +367,14 @@ void Client::send_error(int error_code, string opt) { * @param msg The HTTP message to send. */ void Client::send_answer(string msg) { - if (DEBUG) debug_block("ANSWER: ", msg); + if (DEBUG) + debug_block("ANSWER: ", msg); #ifdef __linux__ - send(_fd, msg.c_str(), msg.length(), MSG_NOSIGNAL); + send(_fd, msg.c_str(), msg.length(), MSG_NOSIGNAL); #elif __APPLE__ - write(_fd, msg.c_str(), msg.length()); + write(_fd, msg.c_str(), msg.length()); #endif - init(); - _finish = true; + init(); + if (!_keepalive) + _finish = true; } diff --git a/srcs/sock/Master.cpp b/srcs/sock/Master.cpp index 21407f6..6a8b4d9 100644 --- a/srcs/sock/Master.cpp +++ b/srcs/sock/Master.cpp @@ -8,74 +8,85 @@ #include "webserv.hpp" /** - * @brief Destructor - * Close master socket descriptor. + * @brief Destructor Close master socket descriptor. */ Master::~Master(void) { - close(_fd); - if (DEBUG) cout << "Destroyed master socket\n"; + close(_fd); + if (DEBUG) + cout << "Destroyed master socket\n"; } /** * @brief Constructor * Try to create a socket listening to ip and port defined by input. - * If no exception if thrown, the creation success and the socket is then ready to listen for new clients. + * If no exception if thrown, the creation success and the socket is then ready + * to listen for new clients. * - * @param list An ip_port_t struct which contain the ip and the port the master listen. + * @param list An ip_port_t struct which contain the ip and the port the master + * listen. */ Master::Master(ip_port_t list) : _listen(list) { - int x = 1, port = _listen.port; - string ip = _listen.ip; + int x = 1, port = _listen.port; + string ip = _listen.ip; - _fd = socket(AF_INET, SOCK_STREAM, 0); - if (_fd == 0) throw std::runtime_error("socket() error" + string(strerror(errno))); - if (setsockopt(_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&x, sizeof(x)) < 0 && close(_fd) <= 0) - throw std::runtime_error("setsockopt() error: " + string(strerror(errno))); - _address.sin_family = AF_INET; - _address.sin_addr.s_addr = inet_addr(ip.c_str()); - _address.sin_port = htons(port); - if (bind(_fd, (struct sockaddr *)&_address, sizeof(_address)) && close(_fd) <= 0) - throw std::runtime_error("bind() error: " + string(strerror(errno))); - if (listen(_fd, 3) < 0 && close(_fd) <= 0) throw std::runtime_error("listen() error: " + string(strerror(errno))); + _fd = socket(AF_INET, SOCK_STREAM, 0); + if (_fd == 0) + throw std::runtime_error("socket() error" + string(strerror(errno))); + if (setsockopt(_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&x, sizeof(x)) < 0 && + close(_fd) <= 0) + throw std::runtime_error("setsockopt() error: " + string(strerror(errno))); + _address.sin_family = AF_INET; + _address.sin_addr.s_addr = inet_addr(ip.c_str()); + _address.sin_port = htons(port); + if (bind(_fd, (struct sockaddr *)&_address, sizeof(_address)) && + close(_fd) <= 0) + throw std::runtime_error("bind() error: " + string(strerror(errno))); + if (listen(_fd, 3) < 0 && close(_fd) <= 0) + throw std::runtime_error("listen() error: " + string(strerror(errno))); #ifdef __APPLE__ - fcntl(socket, F_SETFL, O_NONBLOCK); + fcntl(socket, F_SETFL, O_NONBLOCK); #endif - cout << "New master socket with fd " << _fd << " which listen " << ip << ":" << port << "\n"; - _pollfds[_poll_id_amount].fd = _fd; - _pollfds[_poll_id_amount].events = POLLIN | POLLPRI; - _poll_id = _poll_id_amount; - _poll_id_amount++; + cout << "New master socket with fd " << _fd << " which listen " << ip << ":" + << port << "\n"; + _pollfds[_poll_id_amount].fd = _fd; + _pollfds[_poll_id_amount].events = POLLIN | POLLPRI; + _poll_id = _poll_id_amount; + _poll_id_amount++; } /** * @brief Check master and his clients sockets after poll performed. */ void Master::check_socket(void) { - int addrlen = sizeof(_address); + int addrlen = sizeof(_address); - if (_pollfds[_poll_id].revents & POLLIN) { /// < incomming master request - int new_socket = accept(_fd, (struct sockaddr *)&_address, (socklen_t *)&addrlen); - if (new_socket < 0) throw std::runtime_error("accept() error:" + string(strerror(errno))); + if (_pollfds[_poll_id].revents & POLLIN) { /// < incomming master request + int new_socket = + accept(_fd, (struct sockaddr *)&_address, (socklen_t *)&addrlen); + if (new_socket < 0) + throw std::runtime_error("accept() error:" + string(strerror(errno))); #ifdef __APPLE__ - fcntl(new_socket, F_SETFL, O_NONBLOCK); + fcntl(new_socket, F_SETFL, O_NONBLOCK); #endif - ip_port_t cli_listen = get_ip_port_t(inet_ntoa(_address.sin_addr), ntohs(_address.sin_port)); - Client *new_cli = new Client(new_socket, cli_listen, this); - if (_poll_id_amount > MAX_CLIENTS) { - new_cli->send_error(503); - delete new_cli; - } else { - _childs.push_back(new_cli); - for (int i = _first_cli_id; i < MAX_CLIENTS; i++) { - if (_pollfds[i].fd != 0) continue; - _pollfds[i].fd = new_socket; - _pollfds[i].events = POLLIN | POLLPRI; - new_cli->_poll_id = i; - _poll_id_amount++; - break; - } - } - } + ip_port_t cli_listen = + get_ip_port_t(inet_ntoa(_address.sin_addr), ntohs(_address.sin_port)); + Client *new_cli = new Client(new_socket, cli_listen, this); + if (_poll_id_amount > MAX_CLIENTS) { + new_cli->send_error(503); + delete new_cli; + } else { + _childs.push_back(new_cli); + for (int i = _first_cli_id; i < MAX_CLIENTS; i++) { + if (_pollfds[i].fd != 0) + continue; + _pollfds[i].fd = new_socket; + _pollfds[i].events = POLLIN | POLLPRI; + new_cli->_poll_id = i; + _poll_id_amount++; + break; + } + } + } } /** @@ -83,45 +94,52 @@ void Master::check_socket(void) { * Loop along _childs * if poll even incomming on the child socket: * - give 1023 next sockets bits to Client methods to handle. - * - if Request is fully received, does the answer and flag the socket for outcomming event. + * - if Request is fully received, does the answer and flag the socket for + * outcomming event. * - if request isn't fully received, flag the socket for incomming event. * - if read returned 0, delete client. * @param env The environment object. */ void Master::check_childs(Env *env) { - int child_fd; - for (std::vector::iterator it = _childs.begin(); it < _childs.end(); it++) { - child_fd = (*it)->_fd; - int i = (*it)->_poll_id; - if (_pollfds[i].fd > 0 && _pollfds[i].revents & POLLIN) { - - char buffer[1024]; - int valread = read(child_fd, buffer, 1023); - buffer[valread] = '\0'; - if (valread == 0) { - delete (*it); - _childs.erase(it); - } else if ((*it)->getRequest(env, buffer)) { - (*it)->handleRequest(); - _pollfds[i].events = POLLOUT; - if ((*it)->_finish) { - delete (*it); - _childs.erase(it); - } - } else _pollfds[i].events = POLLIN | POLLPRI; - } - } + int child_fd; + for (std::vector::iterator it = _childs.begin(); it < _childs.end(); + it++) { + child_fd = (*it)->_fd; + int i = (*it)->_poll_id; + if (_pollfds[i].fd > 0 && _pollfds[i].revents & POLLIN) { + char buffer[1024]; + int valread = read(child_fd, buffer, 1023); + buffer[valread] = '\0'; + if (valread == 0) { + delete (*it); + _childs.erase(it); + } else if ((*it)->getRequest(env, buffer)) { + (*it)->debug(true); + (*it)->handleRequest(); + _pollfds[i].events = POLLIN | POLLPRI | POLLOUT; + (*it)->debug(false); + if ((*it)->_finish) { + delete (*it); + _childs.erase(it); + } + } else + _pollfds[i].events = POLLIN | POLLPRI; + } + } } /** * * @brief Choose the server which must handle a request - * Each server can lsiten multiple range_ip:port and each range_it:port can be listen by multiple servers. - * So for each request, we must look at the socket which given us the client to know how the client came. If multiple - * servers listen the range from where the client came, ones with exact correspondance are prefered. If there are - * multiples servers listening exactly the ip the client try to reach or which listen a range which contain it, the - * first one which have the same server_name as the host the client used to reach server is used, else it's the first - * one of exact correspondance or first one which have the ip requested in his listen range. + * Each server can lsiten multiple range_ip:port and each range_it:port can be + * listen by multiple servers. So for each request, we must look at the socket + * which given us the client to know how the client came. If multiple servers + * listen the range from where the client came, ones with exact correspondance + * are prefered. If there are multiples servers listening exactly the ip the + * client try to reach or which listen a range which contain it, the first one + * which have the same server_name as the host the client used to reach server + * is used, else it's the first one of exact correspondance or first one which + * have the ip requested in his listen range. * * @param env The environment object. * @param host The host the client used to reached the server. @@ -129,38 +147,48 @@ void Master::check_childs(Env *env) { * @return The server object choosen to handle the request. */ Server *Master::choose_server(Env *env, string host) { - std::vector exact, inrange; - vec_string ip_listen, ip_required; + std::vector exact, inrange; + vec_string ip_listen, ip_required; - ip_required = split(_listen.ip, "."); - for (std::vector::iterator server = env->_servers.begin(); server < env->_servers.end(); server++) { - std::vector serv_listens = (*server)->_listens; - for (std::vector::iterator it = serv_listens.begin(); it < serv_listens.end(); it++) { - if (_listen.port != (*it).port) continue; - if (_listen.ip == (*it).ip) { - exact.push_back(*server); - continue; - } - bool is_inrange = true; - ip_listen = split((*it).ip, "."); - vec_string::iterator r = ip_required.end(); - vec_string::iterator l = ip_listen.end(); - while (r > ip_required.begin()) { - if (*(--l) != *(--r) && *l != "0") is_inrange = false; - } - if (is_inrange) inrange.push_back(*server); - } - } - if (DEBUG) std::cout << "req: " << _listen.ip << ":" << _listen.port << "\n"; - if (exact.size() == 0) { - for (std::vector::iterator server = inrange.begin(); server < inrange.end(); server++) { - if (host == (*server)->getName()) return *server; - } - return inrange.front(); - } else { - for (std::vector::iterator server = exact.begin(); server < exact.end(); server++) { - if (host == (*server)->getName()) return *server; - } - return exact.front(); - } + ip_required = split(_listen.ip, "."); + for (std::vector::iterator server = env->_servers.begin(); + server < env->_servers.end(); server++) { + std::vector serv_listens = (*server)->_listens; + for (std::vector::iterator it = serv_listens.begin(); + it < serv_listens.end(); it++) { + if (_listen.port != (*it).port) + continue; + if (_listen.ip == (*it).ip) { + exact.push_back(*server); + continue; + } + bool is_inrange = true; + ip_listen = split((*it).ip, "."); + vec_string::iterator r = ip_required.end(); + vec_string::iterator l = ip_listen.end(); + while (r > ip_required.begin()) { + if (*(--l) != *(--r) && *l != "0") + is_inrange = false; + } + if (is_inrange) + inrange.push_back(*server); + } + } + if (DEBUG) + std::cout << "req: " << _listen.ip << ":" << _listen.port << "\n"; + if (exact.size() == 0) { + for (std::vector::iterator server = inrange.begin(); + server < inrange.end(); server++) { + if (host == (*server)->getName()) + return *server; + } + return inrange.front(); + } else { + for (std::vector::iterator server = exact.begin(); + server < exact.end(); server++) { + if (host == (*server)->getName()) + return *server; + } + return exact.front(); + } } diff --git a/srcs/tools.cpp b/srcs/tools.cpp index 43bd990..82e1815 100644 --- a/srcs/tools.cpp +++ b/srcs/tools.cpp @@ -56,6 +56,14 @@ ip_port_t get_ip_port_t(string ip, int port) { } string read_file(string path) { + std::ifstream file(path.c_str()); + if (!file.good()) return ""; + std::stringstream tmp; + tmp << file.rdbuf(); + return tmp.str(); +} + +string file_answer(string path){ struct stat info; if (stat(path.c_str(), &info) != 0) { std::cerr << "stat() error on " << path << ": " << strerror(errno) << "\n"; @@ -75,6 +83,7 @@ string read_file(string path) { << "\r\n" << body; return (ret.str()); + } string getMime(string path) {