From e7d498caba999cba34e9d325b0146b687e8b1bf1 Mon Sep 17 00:00:00 2001 From: Taanviir <66136914+Taanviir@users.noreply.github.com> Date: Fri, 7 Jun 2024 02:37:16 +0400 Subject: [PATCH] feat: improved GET request --- sources/http/handler/GetRequestHandler.cpp | 82 ++++++++++++--------- sources/http/handler/PostRequestHandler.cpp | 52 +++++++++---- sources/http/handler/PostRequestHandler.hpp | 1 + sources/parser/ConfigParser.cpp | 26 +++++-- sources/parser/ServerConfig.hpp | 4 + 5 files changed, 111 insertions(+), 54 deletions(-) diff --git a/sources/http/handler/GetRequestHandler.cpp b/sources/http/handler/GetRequestHandler.cpp index 204c2ab..60d27e8 100644 --- a/sources/http/handler/GetRequestHandler.cpp +++ b/sources/http/handler/GetRequestHandler.cpp @@ -15,57 +15,71 @@ const vector GetRequestHandler::get_resource(const Request& r) { status = r.get_status(); - const string& resource = r.get_resource(); - const string& resourcePath = r.get_resource_path(); + const string& resource = r.get_resource(); + const string& resourcePath = r.get_resource_path(); + const string& locationMatch = r.get_location_match(); - // return list_directory(resourcePath, resource); //! dir listing + string defaultPath = cfg.root + "/"; + string index = defaultPath + cfg.indexFile; + bool autoindex = cfg.autoindex; - // default path - if (!r.get_location_match().empty()) { - // found matching location + if (!locationMatch.empty()) { + const Location& location = cfg.locations.at(locationMatch); + + autoindex = location.autoindex; + defaultPath = location.root + "/"; + index = defaultPath + location.indexFile; + cp.set_index_page(index); + // TODO handle redirection } - else if (resourcePath == (cfg.root + "/")) { - // TODO if the index file does not exist + autoindex is on, list dir. - cout << "default path" << endl; - Page& p = cp.get_page("index"); - _add_header("Content-Type", p.contentType); - _add_header("Content-Length", ws_itoa(p.contentLength)); - return p.data; + vector body; + struct stat fileInfo; + stat(resourcePath.c_str(), &fileInfo); + if (S_ISDIR(fileInfo.st_mode)) { + ifstream indexFile(index.c_str(), std::ios_base::binary); + if (indexFile.good()) { + Page& p = cp.get_page(index); + _add_header("Content-Type", p.contentType); + _add_header("Content-Length", ws_itoa(p.contentLength)); + return p.data; + } + else if (autoindex) { + body = list_directory(resourcePath, resource); + _add_header("Content-Type", "text/html"); + _add_header("Content-Length", ws_itoa(body.size())); + return body; + } + return make_error_body(NOT_FOUND); } - ifstream resource_file(resourcePath.c_str(), std::ios_base::binary); - if (resource_file.fail()) - status = NOT_FOUND; //! need more error handling, eg: forbidden, not allowed, etc + ifstream resourceFile(resourcePath.c_str(), std::ios_base::binary); + if (resourceFile.fail()) + return make_error_body(NOT_FOUND); + else if (access(resourcePath.c_str(), R_OK) == -1) + return make_error_body(FORBIDDEN); - // errors - if (status >= 400) - return (make_error_body(status)); + string resource_type = find_resource_type(resource); + if (resource_type.length() == 0) + return make_error_body(UNSUPPORTED_MEDIA_TYPE); - vector body; - string resource_type = find_resource_type(resource); //! doesn't make sense - if (resource_type.length() != 0) //! and what if it is zero? - _add_header("Content-Type", resource_type); - if (resource.find("/cgi-bin") != string::npos) { + _add_header("Content-Type", resource_type); + + if (locationMatch == "/cgi-bin" && resource.find("/cgi-bin") != string::npos) { CGI cgi(r, cfg, cp); - body = cgi.execute(r.cgiStatus, r.cgiReadFd, - r.cgiChild); // ! r.fd set reference is kinda idk + body = cgi.execute(r.cgiStatus, r.cgiReadFd, r.cgiChild); _add_header("Content-Length", ws_itoa(body.size())); } else { - body = vector((std::istreambuf_iterator(resource_file)), + body = vector((std::istreambuf_iterator(resourceFile)), std::istreambuf_iterator()); _add_header("Content-Length", ws_itoa(body.size())); } - /* authentication function goes here for the requested resource */ - // DEBUG_MSG("Resource '" + resource + "' : [" + ws_itoa(resource_size) + "]", W); - - // TODO caching control */ - + // TODO authentication function goes here for the requested resource + // TODO caching control // TODO compression/encoding - - // TODO support range requests, usefull for large files + // TODO support range requests, useful for large files return body; } diff --git a/sources/http/handler/PostRequestHandler.cpp b/sources/http/handler/PostRequestHandler.cpp index eacd152..7fffba8 100644 --- a/sources/http/handler/PostRequestHandler.cpp +++ b/sources/http/handler/PostRequestHandler.cpp @@ -16,33 +16,55 @@ Response PostRequestHandler::handle_request(const Request& r) vector PostRequestHandler::process_data(const Request& r) { - const string& resource = r.get_resource(); - const string& requestBody = r.get_body(); - vector responseBody; + // const string& resource = r.get_resource(); + const string& resourcePath = r.get_resource_path(); + const string& requestBody = r.get_body(); + const string& locationMatch = r.get_location_match(); + + vector responseBody; // for uploads, check if the location block allows uploads + if (!locationMatch.empty() && locationMatch == "/upload") { + handle_upload(); + } if (requestBody.empty()) // no data to write return make_error_body(BAD_REQUEST); - // append to file - ofstream outputFile(resource.c_str(), std::ios_base::app); - if (!outputFile.is_open()) // failed to open file + ofstream outputFile(resourcePath.c_str(), std::ios_base::app); + if (outputFile.fail()) // failed to open file return make_error_body(INTERNAL_SERVER_ERROR); // need to check if file is too big (return 413 if so) // need to check if the file type is allowed (return 415 if not) - // need to handle CGI POST requests - //? need to parse the request body - outputFile << requestBody; + if (!parse_request_body(requestBody)) + return make_error_body(BAD_REQUEST); + outputFile.close(); status = OK; - //! build response body - string successMsg = "POST request was successful."; - responseBody.assign(successMsg.begin(), successMsg.end()); - _add_header("Content-Length", ws_itoa(responseBody.size())); - _add_header("Content-Type", "text/html;"); - return responseBody; } + +bool PostRequestHandler::parse_request_body(const string& body) +{ + map form; + istringstream iss(body); + string keyValuePair; + while (std::getline(iss, keyValuePair, '&')) { + istringstream keyValueStream(keyValuePair); + string key, value; + if (std::getline(std::getline(keyValueStream, key, '='), value)) { + if (!key.empty() && !value.empty()) + form.insert(make_pair(key, value)); + else + return false; + } + } + return true; +} + +void handle_upload() +{ + // ... +} diff --git a/sources/http/handler/PostRequestHandler.hpp b/sources/http/handler/PostRequestHandler.hpp index 1650def..a46d9c6 100644 --- a/sources/http/handler/PostRequestHandler.hpp +++ b/sources/http/handler/PostRequestHandler.hpp @@ -17,6 +17,7 @@ class PostRequestHandler: public RequestHandlerBase { private: vector process_data(const Request& r); + bool parse_request_body(const string& body); }; #endif // POST_REQUEST_HANDLER_HPP diff --git a/sources/parser/ConfigParser.cpp b/sources/parser/ConfigParser.cpp index 07e3eb8..4908927 100644 --- a/sources/parser/ConfigParser.cpp +++ b/sources/parser/ConfigParser.cpp @@ -137,7 +137,7 @@ void ConfigParser::parse_index(string& indexFile, const string& root) if (is_keyword(*_itr) || _itr->find("/") != string::npos) THROW_EXCEPTION_WITH_INFO(ERR_INDEX); - indexFile = root + "/" + *_itr; + indexFile = *_itr; check_semicolon(); } @@ -287,9 +287,25 @@ vector ConfigParser::parse_allow_methods(void) return methods; } -void ConfigParser::parse_http_redirection() +void ConfigParser::parse_http_redirection(RedirectionMap& redirMap) { - // redirect [uri] [other uri] + ++_itr; // move to the URI being redirected + string redirectFrom = *_itr; + if (redirectFrom[0] != '/') // uri should begin with / + THROW_EXCEPTION_WITH_INFO(ERR_REDIRECT_SLASH); + else if (redirectFrom.find_first_of("/") != redirectFrom.find_last_of("/")) + THROW_EXCEPTION_WITH_INFO(ERR_REDIRECT_DUP_SLASH); + + ++_itr; // move to the target URI + string redirectTo = *_itr; + if (redirectTo[0] != '/') // uri should begin with / + THROW_EXCEPTION_WITH_INFO(ERR_REDIRECT_SLASH); + else if (redirectTo.find_first_of("/") != redirectTo.find_last_of("/")) + THROW_EXCEPTION_WITH_INFO(ERR_REDIRECT_DUP_SLASH); + + redirMap[redirectFrom] = redirectTo; + + check_semicolon(); } void ConfigParser::parse_location_context(ServerConfig& server) @@ -329,8 +345,8 @@ void ConfigParser::parse_location_context(ServerConfig& server) parse_index(location.indexFile, location.root + uri); else if (*_itr == "autoindex") location.autoindex = parse_autoindex(); - // else if (*itr == "redirect") - // location. + else if (*_itr == "redirect") + parse_http_redirection(location.redirections); else if (*_itr == ";" || *_itr == "{") ++_itr; else diff --git a/sources/parser/ServerConfig.hpp b/sources/parser/ServerConfig.hpp index 25374e5..cac52c2 100644 --- a/sources/parser/ServerConfig.hpp +++ b/sources/parser/ServerConfig.hpp @@ -4,12 +4,16 @@ #ifndef CONFIG_HPP #define CONFIG_HPP +typedef map RedirectionMap; + struct Location { string root; string indexFile; bool autoindex; size_t maxBodySize; vector methods; + RedirectionMap redirections; + Location() : indexFile("index.html"), autoindex(false), maxBodySize(1000000) {} };