#include "Client.hpp" /********************************************* * COPLIENS *********************************************/ Client::Client() : status(0), header_complete(false), body_complete(false), request_complete(false), read_body_size(0), assigned_server(NULL), assigned_location(NULL), _fd(0), _port(""), _ip(""), _lsocket(NULL) { return; } Client::Client(int afd, listen_socket *lsocket, std::string aport, std::string aip) : status(0), header_complete(false), body_complete(false), request_complete(false), read_body_size(0), assigned_server(NULL), assigned_location(NULL), _fd(afd), _port(aport), _ip(aip), _lsocket(lsocket) { return; } Client::~Client() { return; } /* // WIP not sure fo what is more logic here Client::Client( Client const & src ) : status ( src.status ), header_complete ( src.header_complete ), read_body_size ( src.read_body_size ), assigned_server ( src.assigned_server ), assigned_location ( src.assigned_location ), _fd ( src._fd ), _port ( src._port ), _ip ( src._ip ), _lsocket ( src._lsocket ) { raw_request = src.raw_request; response = src.response; // buf = strdup(src.buf); // TODO: this doesn't work return; } */ /* // WIP placeholder because of const values Client & Client::operator=( Client const & rhs ) { if ( this != &rhs ) { // stuff } return *this; } */ /********************************************* * PUBLIC MEMBER FUNCTIONS *********************************************/ /* HTTP Headers : https://www.iana.org/assignments/http-fields/http-fields.xhtml https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers https://www.ibm.com/docs/en/cics-ts/5.3?topic=protocol-http-requests https://www.tutorialspoint.com/http/http_requests.htm */ void Client::parse_request_headers(std::vector &servers) { if (raw_request.find(CRLF CRLF) == NPOS) return ; header_complete = true; clear_request(); // not mandatory _parse_request_line(); if (status) return; _parse_request_fields(); // DEBUG // print_client("headers"); if (status) return; assigned_server = _determine_process_server(this, servers); assigned_location = _determine_location(*assigned_server, _request.abs_path); _check_request_errors(); if (status) return; _parse_port_hostname(get_rq_headers("Host")); // use getter for headers because it works case insensitive // DEBUG // std::cerr << get_rq_method_str() << " " << get_rq_target() << " " << get_rq_version() << "\n"; // dont clear raw_request, we need it for future reparsing of body // see call of parse_request() in _read_request() // raw_request.clear(); } void Client::parse_request_body() { std::cerr << "parse_request_body()\n"; size_t pos; pos = raw_request.find(CRLF CRLF); if (pos == NPOS) { std::cerr << "parse_request_body() bad call, header incomplete\n"; // QUESTION from hugo : don't we change the status here ? // RESPONSE from luke : C'est vrai. Peut-être mettre un 500, c'etait plus du debug à la base. // C'est seulement si on appelle la fonction au mauvais endroit, avant d'avoir un header complet, que ça arrive. return; } if (!get_rq_headers("Transfer-Encoding").empty() && get_rq_headers("Transfer-Encoding") == "chunked") { // Chunked decoding WIP. How to test this ? dont know how to send chunks with telnet. _parse_chunked_body(pos + CRLF_SIZE*2); } else if (raw_request.size() - pos >= std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10)) { if (get_rq_headers("Content-Type").find("multipart/form-data") != NPOS) _parse_multipart_body(pos); else _request.body = raw_request.substr(pos + CRLF_SIZE*2); body_complete = true; } // std::cerr << "Content-Length = " << std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10) << "\n"; // std::cerr << "raw_request.size() - pos = " << raw_request.size() - pos << "\n"; // _request.body = raw_request.substr(pos); // std::cerr << "_request.body.size() = " << _request.body.size() << "\n"; /////////////// // Body checks if (_request.body.size() > assigned_server->client_body_limit) status = 413; // HTTP Client Errors } void Client::_parse_chunked_body(size_t pos) { size_t chunk_size = 1; size_t chunk_field_end = 0; char *endptr = NULL; char *endptr_copy = NULL; /* TODO: verify if last chunk in raw_request (to avoid multiples complete parsing) but how ? with "raw_request.rfind("0" CRLF CRLF)", there no confirmation that we have found the last last-chunk OR just some data */ _request.body = raw_request.substr(pos); std::cerr << "______Chunked\n" << _request.body << "\n______\n"; pos = 0; while (chunk_size != 0) { /* if (pos > _request.body.size()) { std::cerr << "parse_request_body(), pos > size()\n"; // status = 400; return; } */ /* if (pos == _request.body.size()) { std::cerr << "parse_request_body(), will reread till last chunk\n"; return; } */ /* endptr_copy = endptr; */ (void)endptr_copy; chunk_size = std::strtoul(&_request.body[pos], &endptr, 16); if (errno == ERANGE) { status = 413; return ; } /* if (endptr == endptr_copy) { std::cerr << "parse_request_body(), no conversion possible\n"; return; } */ chunk_field_end = _request.body.find(CRLF, pos); if (chunk_field_end == NPOS) { std::cerr << "parse_request_body(), chunk_field no CRLF\n"; // status = 400; return; } chunk_field_end += CRLF_SIZE; _request.body.erase(pos, chunk_field_end); pos += chunk_size + CRLF_SIZE; } _request.headers.erase("Transfer-Encoding"); body_complete = true; } void Client::fill_script_path(std::string &path, size_t pos) { std::string tmp; if (path[0] == '.') { path.erase(0, 1); pos--; } _request.script.path = path.substr(0, pos); _request.script.info = path.substr(pos); } void Client::clear() { clear_request(); header_complete = false; body_complete = false; request_complete = false; read_body_size = 0; assigned_server = NULL; assigned_location = NULL; raw_request.clear(); response.clear(); status = 0; } void Client::clear_request() { clear_script(); _request.method = UNKNOWN; _request.target.clear(); _request.version.clear(); _request.headers.clear(); _request.body.clear(); _request.multi_bodys.clear(); _request.abs_path.clear(); _request.query.clear(); _request.port.clear(); _request.hostname.clear(); } void Client::clear_script() { _request.script.path.clear(); _request.script.info.clear(); } // debug void Client::print_client(std::string message) { std::map::iterator it; std::cout << "\n=== DEBUG PRINT CLIENT ===\n"; std::cout << message << ":\n----------\n\n" << "raw_request:\n__\n"; ::print_special(raw_request); std::cout << "\n__\n" << "get_cl_fd() : [" << get_cl_fd() << "]\n" << "get_cl_port() : [" << get_cl_port() << "]\n" << "get_cl_ip() : [" << get_cl_ip() << "]\n" << "get_rq_method_str() : [" << get_rq_method_str() << "]\n" << "get_rq_target() : [" << get_rq_target() << "]\n" << "get_rq_abs_path() : [" << get_rq_abs_path() << "]\n" << "get_rq_query() : [" << get_rq_query() << "]\n" << "get_rq_version() : [" << get_rq_version() << "]\n" << "get_rq_body() : [" << get_rq_body() << "]\n" << "get_rq_port() : [" << get_rq_port() << "]\n" << "get_rq_hostname() : [" << get_rq_hostname() << "]\n" << "get_rq_script_path() : [" << get_rq_script_path() << "]\n" << "get_rq_script_info() : [" << get_rq_script_info() << "]\n" << "headers :\n"; for (it = _request.headers.begin(); it != _request.headers.end(); it++) std::cout << " " << it->first << ": [" << it->second << "]\n"; std::cout << "\n=== END PRINT CLIENT ===\n\n"; } /********************************************* * GETTERS *********************************************/ // client side int Client::get_cl_fd() const { return _fd; } const std::string & Client::get_cl_ip() const { return _ip; } const std::string & Client::get_cl_port() const { return _port; } const listen_socket * Client::get_cl_lsocket() const { return _lsocket; } // requette http_method Client::get_rq_method() const { return _request.method; } std::string Client::get_rq_method_str() const { return ::http_methods_to_str(_request.method); } std::string Client::get_rq_target() const { return _request.target; } std::string Client::get_rq_abs_path() const { return _request.abs_path; } std::string Client::get_rq_query() const { return _request.query; } std::string Client::get_rq_version() const { return _request.version; } std::string Client::get_rq_body() const { return _request.body; } std::string Client::get_rq_port() const { return _request.port; } std::string Client::get_rq_hostname() const { return _request.hostname; } std::string Client::get_rq_script_path()const { return _request.script.path; } std::string Client::get_rq_script_info()const { return _request.script.info; } std::string Client::get_rq_headers(const std::string & key) const { std::map::const_iterator it; it = _request.headers.find(::str_tolower(key)); if (it == _request.headers.end()) return ""; return it->second; } /********************************************* * PRIVATE MEMBER FUNCTIONS *********************************************/ void Client::_parse_request_line() { std::vector line; std::string raw_line; raw_line = ::get_line(raw_request, 0, CRLF); line = ::split_trim(raw_line, " "); if (line.size() != 3) { std::cerr << "err _parse_first_line(): wrong number of elements (" << line.size() << " instead of 3)\n"; status = 400; // "bad request" } else { _request.method = str_to_http_method(line[0]); _request.target = line[1]; _parse_request_target(line[1]); _request.version = line[2]; } } void Client::_parse_request_target( std::string target ) { size_t pos; pos = target.find("?"); if (pos != NPOS) _request.query = target.substr(pos + 1); else _request.query = ""; _request.abs_path = target.substr(0, pos); if (_request.abs_path[_request.abs_path.size() - 1] == '/') _request.abs_path.erase(_request.abs_path.size() - 1); } void Client::_parse_request_fields() { std::string headers; size_t pos; int ret; headers = raw_request; // delete first line pos = headers.find(CRLF); if (pos != NPOS) headers.erase(0, pos + CRLF_SIZE); // delete body part pos = headers.find(CRLF CRLF); if (pos != NPOS) headers.erase(pos); else { std::cerr << "err _parse_request_fields(): request header doesn't end with empty line\n"; status = 400; // "bad request" } // copy result of parser into headers ret = ::parse_http_headers(headers, _request.headers); if (ret > 0) { std::cerr << "err _parse_request_fields(): " << ret << " fields are bad formated\n"; status = 400; // "bad request" } ::str_map_key_tolower(_request.headers); } // TODO : I think its now useless. Probably to delete. void Client::_parse_port_hostname(std::string host) { size_t pos; if (host == "") std::cerr << "no host\n"; pos = host.find(':'); // port : if (pos == NPOS) _request.port = "4040"; // TODO: make equal to default port in config else _request.port = host.substr(pos); if (_request.port == ":") _request.port = ""; // hostname : _request.hostname = host.substr(0, pos); } void Client::_check_request_errors() { std::cerr << "Content-Length=" << get_rq_headers("Content-Length") << "\n"; std::cerr << "strtoul=" << std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10) << "\n"; std::cerr << "client_body_limit=" << assigned_server->client_body_limit << "\n"; /////////////////////// // Request line checks if (_request.method == UNKNOWN) status = 501; // HTTP Client Errors else if (_request.version.compare(0, sizeof("HTTP/1") - 1, "HTTP/1") != 0) status = 505; // HTTP Client Errors else if (!(assigned_location->allow_methods & _request.method)) { status = 405; // HTTP Client Errors response.append("Allow: "); response.append(::http_methods_to_str(assigned_location->allow_methods)); response.append(CRLF); } else if (assigned_location->redirect_status) { // Weird behavior. Sometimes, the web browser seems to wait for a complete response until timeout. // (for codes 301, 302, 303, 307, and 308) status = assigned_location->redirect_status; response.append("Location: "); response.append(assigned_location->redirect_uri); response.append(CRLF CRLF); } ////////////////// // Headers checks else if (!get_rq_headers("Content-Length").empty() && std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10) > assigned_server->client_body_limit) status = 413; else if (!get_rq_headers("Transfer-Encoding").empty() && get_rq_headers("Transfer-Encoding") != "chunked" ) status = 501; else if (!get_rq_headers("Content-Encoding").empty()) { status = 415; response.append("Accept-Encoding:"); // empty, no encoding accepted response.append(CRLF); } return; } ServerConfig *Client::_determine_process_server(Client *client, std::vector &servers) { /* Behavior like this : http://nginx.org/en/docs/http/request_processing.html */ std::string server_name = client->get_rq_headers("Host"); std::cerr << "server_name = " << server_name << "\n"; size_t pos = server_name.rfind(':'); if (pos != NPOS) server_name.erase(pos); std::cerr << "server_name = " << server_name << "\n"; std::vector::iterator it = servers.begin(); std::vector::iterator default_server = servers.end(); while (it != servers.end()) { if (it->host == client->get_cl_lsocket()->host && it->port == client->get_cl_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)); } // const? const LocationConfig *Client::_determine_location(const ServerConfig &server, const std::string &path) { /* RULES *** If a path coresponds exactly to a location, use that one if no path coresponds then use the most correct one most correct means the most precise branch that is still above the point we are aiming for New Rule for location paths, they never end in / Sooo If we get a url that ends in / ignore the last / */ std::string uri = path; if (uri[uri.size() - 1] == '/' && uri.size() != 1) uri.erase(uri.size() - 1); for (std::vector::const_iterator it = server.locations.begin(); it != server.locations.end(); it++) { // std::cout << it->path << " -- "; if (it->path.size() > uri.size()) continue ; if (uri.compare(0, it->path.size(), it->path) == 0) { if (it->path.size() == uri.size()) return (&(*it)); else if (uri[it->path.size()] == '/') return (&(*it)); // this works cuz only ever looking for a / burried in a longer path } } return (&(server.locations.back())); // /test/mdr // /test/mdr/ // /test/mdrBST /////// More stuff to check this still works with // /test/test_ // /test/test_/ // /test/test_deeper // /test/test_deeper/ // /test/test_deepei // /test/test_deepei/ // /test/test_deeperi // /test/test_deeper/super_deep/ // /test/aaaaaaaaaaa/super_deep/ } /********************************************* * OVERLOAD *********************************************/ bool operator==(const Client& lhs, const Client& rhs) { return lhs.get_cl_fd() == rhs.get_cl_fd(); } bool operator==(const Client& lhs, int fd) { return lhs.get_cl_fd() == fd; } bool operator==(int fd, const Client& rhs) { return fd == rhs.get_cl_fd(); }