#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 ; } // 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->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) { // 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); } } 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(), "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) { /* RULES ** if path is a valid dir check if index is specified and serve that if no index and autoindex, server that if file, server that! WHere does cgi fit in in all this ??? THIS NEEDS WORK... */ (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); */ // that was actually a horrible idea... path.insert(0, location.root); std::cerr << "path = " << path << "\n"; // path = root + location.path // we will tack on an index if there is a valid one // or autoindex if allowed // or let _get_file sort out the error otherwise. if (path_is_valid(path) == 1) { std::cout << "path is valid\n"; if (path[path.size() - 1] != '/') path.push_back('/'); for (size_t i = 0; i < location.index.size(); i++) { std::cout << "location path: " << location.path << '\n'; std::cout << "location index: " << location.index[i] << '\n'; // std::cout << "path with index: " << path + location.index[i] << '\n'; if (path_is_valid(path + location.index[i]) == 2) { std::cout << "found a valid index\n"; path.append(location.index[i]); break ; // what if index and autoindex in a single location? // does this completely fail? // do this instead of break? //_get_file(client, path); } } if (location.autoindex == true) { _autoindex(client, location, path); return ; } } // else // _get_file(client, path); // what about cgi ??? // TMP HUGO // if (_is_cgi(client)) { _exec_cgi(client); return; } // // END TMP HUGO _get_file(client, path); } // i only sort of need &path... // def can improve but works for now... void Webserv::_autoindex(Client *client, LocationConfig &location, std::string &path) { std::cout << "made it to _autoindex\n"; std::string dir_list; DIR *dir; struct dirent *ent; std::cout << "location root: " << location.root << " location path: " \ << location.path << '\n'; // if ((dir = opendir (path.c_str())) != NULL) if ((dir = opendir ((location.root + location.path).c_str())) != NULL) { /* print all the files and directories within directory */ dir_list.append(AUTOINDEX_START); dir_list.append(location.path); dir_list.append(AUTOINDEX_MID1); dir_list.append(location.path); dir_list.append(AUTOINDEX_MID2); while ((ent = readdir (dir)) != NULL) { if (strcmp(".", ent->d_name) == 0) continue ; dir_list.append("d_name); dir_list.append("\">"); dir_list.append(ent->d_name); dir_list.append(""); dir_list.append("\r\n"); // is this right? } // nginx.org.
// index1.html // apparently this is more than good enough! // .. dir_list.append(AUTOINDEX_END); std::cout << "\n\n" << dir_list << '\n'; closedir (dir); _append_body(client, dir_list.c_str(), dir_list.size(), "html"); } else { // in theory not possible cuz we already checked... /* could not open directory */ // perror (""); std::cout << "could not open dir\n"; return ; } } 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]; std::cout << "made it to get_file\n"; 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(client->buf, size); if (!ifd) { std::cerr << path << ": ifd.read fail" << '\n'; client->status = 500; } else { client->status = 200; client->buf[ifd.gcount()] = '\0'; 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->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) { // std::cout << "determin location path sent: " << path << '\n'; std::vector::iterator it = server.locations.begin(); while (it != server.locations.end()) { // std::cout << it->path << " -- "; // if (it->path.compare(0, path.size(), path) == 0) if (it->path.compare(0, it->path.size(), path) == 0) break; // kinda gross i know but... have to deal with when there's a / at the end if (it->path[it->path.size() - 1] == '/' \ && it->path.compare(0, it->path.size() - 1, path) == 0) break; ++it; } if (it != server.locations.end()) return (*it); else return (server.locations.front()); }