#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://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(this->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 ? return; } pos += CRLF_SIZE*2; // Chunked decoding WIP. Dont work. if (!get_rq_headers("Transfer-Encoding").empty() && get_rq_headers("Transfer-Encoding") == "chunked") { 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 (chunk_size == LONG_MAX && errno == ERANGE) status = 413; */ /* 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; } else { 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"; if (raw_request.size() - pos >= std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10)) { _request.body = raw_request.substr(pos); body_complete = true; } /* Should be equivalent */ // _request.body = raw_request.substr(pos); // if (_request.body.size() >= std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10)) // body_complete = true; } /////////////// // Body checks if (_request.body.size() > assigned_server->client_body_limit) status = 413; // HTTP Client Errors } // TODO HUGO : faire la fonction, mdr. void Client::fill_script_path(const std::string &path, size_t pos) { (void)path; (void)pos; /* size_t pos; size_t len = path.size(); std::string path = this->get_rq_abs_path(); std::string tmp; pos = path.find(script); if (pos == 0) { tmp = path.substr(0, pos + len); _request.script.path = "./srcs" + tmp; // TODO: root path ? _request.script.path = "./srcs" + tmp; // TODO: root path ? _request.script.info = path.substr(pos + len); return true; } return false; */ } 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.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); } 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 + std::string(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); } 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() { /////////////////////// // 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 (!this->get_rq_headers("Content-Length").empty() && std::strtoul(this->get_rq_headers("Content-Length").c_str(), NULL, 10) > assigned_server->client_body_limit) status = 413; else if (!this->get_rq_headers("Transfer-Encoding").empty() && this->get_rq_headers("Transfer-Encoding") != "chunked" ) status = 501; else if (!this->get_rq_headers("Content-Encoding").empty()) { status = 415; response.append("Accept-Encoding:"); // empty, no encoding accepted response.append(CRLF); } return; } /********************************************* * 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(); }