#include "Webserv.hpp" void Webserv::_response(Client *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, ServerConfig &server) { ssize_t ret; std::cerr << "send()\n"; _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.c_str(), client->response.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->clear(); } } void Webserv::_append_base_headers(Client *client) { client->response.append("Server: Webserv/0.1" CRLF); if (client->get_headers("Connection") == "close") client->response.append("Connection: close" CRLF); else client->response.append("Connection: keep-alive" CRLF); } void Webserv::_construct_response(Client *client, ServerConfig &server) { 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 = ALL_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 ALL_METHODS if (client->get_method() == UNKNOWN) { client->status = 400; } 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); } } void Webserv::_insert_status_line(Client *client) { std::string status_line; status_line.append("HTTP/1.1 "); status_line.append(_http_status[client->status]); status_line.append(CRLF); client->response.insert(0, status_line); } void Webserv::_error_html_response(Client *client, ServerConfig &server) { 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()); } 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 // if (_is_cgi(client)) { _exec_cgi(client); return; } // // END TMP HUGO _get_file(client, path); } #define MAX_FILESIZE 1000000 // (1Mo) 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 ; } 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 << ": ifd.open fail" << '\n'; client->status = 500; } else { std::streampos size = ifd.tellg(); // WIP : Chunk or not chunk (if filesize too big) if (size > MAX_FILESIZE) { // Then chunk client->status = 500; // WIP temp std::cerr << "File too large for non chunk body\n"; ifd.close(); return ; } ifd.seekg(0, std::ios::beg); ifd.read(buf, size); if (!ifd) { std::cerr << path << ": ifd.read fail" << '\n'; client->status = 500; } else { client->status = 200; buf[ifd.gcount()] = '\0'; _append_body(client, buf, ifd.gcount()); } ifd.close(); } } void Webserv::_append_body(Client *client, const char *body, size_t body_size) { /* 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. */ client->response.append("Content-Type: text/html; charset=UTF-8" 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) { /* TODO : determine virtual server based on ip_address::port and server_name Ior now its just based on server_name. (maybe with a map< string, std::vector > where the string is "ip_adress::port") http://nginx.org/en/docs/http/request_processing.html */ std::string &server_name = client->get_headers("Host"); std::vector::iterator it = _servers.begin(); while (it != _servers.end()) { if ( std::find(it->server_name.begin(), it->server_name.end(), server_name) != it->server_name.end() ) break; ++it; } if (it != _servers.end()) return (*it); else return (_servers.front()); } 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()); }