diff --git a/.vscode/launch.json b/.vscode/launch.json index 2853e67..5c53baf 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,7 @@ "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/webserv_test", - "args": ["abc"], + "args": ["webserv.conf"], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], diff --git a/.vscode/tasks.json b/.vscode/tasks.json index e5b2e94..f089dc8 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -21,6 +21,8 @@ "${workspaceFolder}/src/server/HttpResponse.cpp", "${workspaceFolder}/src/server/RequestHandler.cpp", "${workspaceFolder}/src/server/StaticFileHandler.cpp", + "${workspaceFolder}/src/util/Location.cpp", + "${workspaceFolder}/src/util/Config.cpp", "-o", "${workspaceFolder}/webserv_test" ], diff --git a/include/HttpRequest.hpp b/include/HttpRequest.hpp index 83224f6..dc60aa3 100644 --- a/include/HttpRequest.hpp +++ b/include/HttpRequest.hpp @@ -6,7 +6,7 @@ /* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:00 by sanghupa #+# #+# */ -/* Updated: 2024/07/14 14:49:37 by minakim ### ########.fr */ +/* Updated: 2024/07/26 12:30:28 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ @@ -15,16 +15,18 @@ # include # include +# include -# define WHITESPACE " \t\r\n" +class HttpResponse; +# define WHITESPACE " \t\r\n" -struct t_read_request { +struct t_readed_parts { std::string request; std::vector headers; - std::string body; + std::string body; bool iscomplete; - t_read_request() : iscomplete(false) {} + t_readed_parts() : iscomplete(false) {} }; class HttpRequest @@ -42,6 +44,8 @@ class HttpRequest std::map getHeaders() const; std::string getBody() const; + void setUri(std::string uri); + bool isConnectionClose() const; static std::string trim(const std::string& str); private: @@ -51,12 +55,12 @@ class HttpRequest std::map _headers; std::string _body; - t_read_request _splitRequestData(const std::string& requestData); + t_readed_parts _splitRequestData(const std::string& requestData); bool _parseRequestLine(const std::string requestLine); bool _parseHeaders(const std::vector headerLines); bool _parseBody(const std::string bodylines); - std::vector _dataToHeaders(std::istringstream& iss); - std::string _dataToBody(std::istringstream& iss); + std::vector _convertPartToHeaders(std::istringstream& iss); + std::string _convertPartToBody(std::istringstream& iss); }; diff --git a/include/HttpResponse.hpp b/include/HttpResponse.hpp index 1a8ec1d..44ecc0b 100644 --- a/include/HttpResponse.hpp +++ b/include/HttpResponse.hpp @@ -3,10 +3,10 @@ /* ::: :::::::: */ /* HttpResponse.hpp :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: sanghupa +#+ +:+ +#+ */ +/* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:00 by sanghupa #+# #+# */ -/* Updated: 2024/07/14 22:26:03 by sanghupa ### ########.fr */ +/* Updated: 2024/07/25 16:46:01 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ @@ -16,50 +16,80 @@ # include # include -std::string getErrorPagePath(int pageCode); +# include "Config.hpp" + +# define LOCATION_ROOT_PATH "/www" + +class HttpRequest; +class Config; + +struct t_page_detail +{ + std::string path; + bool isValid; +}; + +std::string getFullErrorPath(const std::string& path); +bool isFile(const std::string path); +bool isDir(const std::string path); +t_page_detail constructPageDetail(const std::string& path); + class HttpResponse { public: HttpResponse(); - HttpResponse(const std::string filePath); + HttpResponse(const std::string& filePath); HttpResponse(const HttpResponse& other); HttpResponse& operator=(const HttpResponse& other); ~HttpResponse(); - void setStatusCode(int code); - void setStatusCode(int code, const std::string statusMessage); - void setHeader(const std::string key, const std::string value); - void setBody(const std::string bodyContent); - - std::string getBody(); - std::string toString() const; - - void fromFile(const std::string filePath); - // static HttpResponse fromFile(const std::string filePath); - static HttpResponse badRequest_400(); - static HttpResponse forbidden_403(); - static HttpResponse notFound_404(); - static HttpResponse methodNotAllowed_405(); - static HttpResponse requestTimeout_408(); - static HttpResponse requestEntityTooLarge_413(); - static HttpResponse imaTeapot_418(); - static HttpResponse internalServerError_500(); - static HttpResponse success_200(); - static HttpResponse notImplemented_501(); + void setStatusCode(int code); + void setStatusCode(int code, const std::string statusMessage); + void setHeader(const std::string key, const std::string value); + void setBody(const std::string bodyContent); + void setDefaultHeaders(); + static void setDefaultHeaders(HttpResponse& resp); + + std::string getBody(); + size_t getBodyLength(); + std::string getResponseLine() const; + std::string toString() const; + int getStatusCode() const; + std::string getStatusMessage() const; + std::string toString(int value) const; + std::string toString(size_t value) const; + void initializefromFile(const std::string& filePath); + + static HttpResponse badRequest_400(); + static HttpResponse forbidden_403(); + static HttpResponse notFound_404(); + static HttpResponse methodNotAllowed_405(); + static HttpResponse requestTimeout_408(); + static HttpResponse requestEntityTooLarge_413(); + static HttpResponse imaTeapot_418(); + static HttpResponse internalServerError_500(); + static HttpResponse success_200(); + static HttpResponse notImplemented_501(); private: int _statusCode; std::string _statusMessage; std::string _body; std::map _headers; + size_t _bodyLength; std::string _getStatusLine() const; std::string _getHeadersString() const; - std::string _getResponseLine() const; + void _fileToBody(const std::string& filePath); + + static HttpResponse _createErrorResponse(int code); + static HttpResponse _createSimpleHttpResponse(int code); + + std::string _generateHtmlBody(); + void _setDefaultHeadersImpl(); - static const std::map& _getStatusMessages(); - static HttpResponse _errorResponse(int code); + static const std::map& _StaticInitStatusMap(); }; #endif \ No newline at end of file diff --git a/include/RequestHandler.hpp b/include/RequestHandler.hpp index 8a30ad8..c254926 100644 --- a/include/RequestHandler.hpp +++ b/include/RequestHandler.hpp @@ -6,27 +6,42 @@ /* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:00 by sanghupa #+# #+# */ -/* Updated: 2024/07/12 11:12:44 by minakim ### ########.fr */ +/* Updated: 2024/07/23 22:24:17 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ #ifndef REQUESTHANDLER_HPP # define REQUESTHANDLER_HPP -# include "HttpRequest.hpp" -# include "HttpResponse.hpp" +class HttpResponse; +class HttpRequest; +class Location; + +# include # include "StaticFileHandler.hpp" +# include "webserv.hpp" +std::string _extractPathFromUri(const std::string& uri); +std::string _getMatchedLocation(std::string path, + const std::map& locations); class RequestHandler { public: RequestHandler(); ~RequestHandler(); - HttpResponse handleRequest(const HttpRequest request); + HttpResponse handleRequest(const HttpRequest& request); private: StaticFileHandler _staticFileHandler; + Location* _findLocation(const std::string& uri); + HttpResponse _processRequest(const HttpRequest& request, const Location& location); + + bool _isAllowedMethod(const std::string &target, std::vector list) const; + HttpResponse _handleGet(const HttpRequest& request, const Location& location); + HttpResponse _handlePost(const HttpRequest& request, const Location& location); + HttpResponse _handleDelete(const HttpRequest& request, const Location& location); }; #endif \ No newline at end of file diff --git a/include/StaticFileHandler.hpp b/include/StaticFileHandler.hpp index be9e587..55cb3a4 100644 --- a/include/StaticFileHandler.hpp +++ b/include/StaticFileHandler.hpp @@ -6,7 +6,7 @@ /* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:00 by sanghupa #+# #+# */ -/* Updated: 2024/07/14 16:44:21 by minakim ### ########.fr */ +/* Updated: 2024/07/24 00:35:43 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ @@ -15,30 +15,53 @@ # include # include -# include "HttpRequest.hpp" -# include "HttpResponse.hpp" +# include -// FIXME: Delete when correct logic is applied in the future -# define LOCATION_FIXME "./www/static" -# define LOOT_DIR "./www/static" +class HttpResponse; +class HttpRequest; +class Location; + +# define LOCATION_PATH "./www/static" +# define INDEX_HTML "index.html" + +std::string genDirListingHtml(const std::string& path); class StaticFileHandler { public: StaticFileHandler(); ~StaticFileHandler(); + StaticFileHandler(const StaticFileHandler& other); + StaticFileHandler& operator=(const StaticFileHandler& other); + + HttpResponse handleRequest(const HttpRequest& request, const Location& location); - HttpResponse handleRequest(const HttpRequest request); + std::string getFullPath() const; + std::string resolveMimeType(const std::string path) const; private: static std::map _staticMimeTypes; + std::string _handledPath; + + static void _staticInitMimeTypes(); + + HttpResponse _handleDirListing(const HttpRequest& request, const Location& location); + HttpResponse _handleDirRequest(const HttpRequest& request, const Location& location); + HttpResponse _handleFileRequest(const HttpRequest& request, const Location& location); + + + HttpResponse _createResponseForFile() const; + HttpResponse _createDirListingResponse() const; + + HttpResponse _handleRoot(const Location& location); + HttpResponse _handleNotFound(); + + std::string _buildPathWithUri(const HttpRequest& request, const Location& location) const; + std::string _buildAbsolutePathWithRoot(const Location& location) const; + std::string _buildAbsolutePathWithIndex(const Location& location) const; + - std::string _getMimeType(const std::string path) const; - bool _fileExists(const std::string path) const; - static void _staticInitializeMimeTypes(); - HttpResponse _handleRoot(); - std::string _getFilePath(const std::string& uri) const; - HttpResponse _handleFileNotFound(); + void _setHandledPath(const std::string& fullPath); }; #endif diff --git a/src/server/HttpRequest.cpp b/src/server/HttpRequest.cpp index d58e991..2ae4fc3 100644 --- a/src/server/HttpRequest.cpp +++ b/src/server/HttpRequest.cpp @@ -6,87 +6,25 @@ /* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:00 by sanghupa #+# #+# */ -/* Updated: 2024/07/14 15:39:21 by minakim ### ########.fr */ +/* Updated: 2024/07/25 16:51:55 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ #include "webserv.hpp" #include "HttpRequest.hpp" -/// TODO check for necessary initialization + HttpRequest::HttpRequest() {} HttpRequest::~HttpRequest() {} -//////////////////////////////////////////////////////////////////////////////// - -std::string HttpRequest::getMethod() const -{ - return (_method); -} - -std::string HttpRequest::getUri() const -{ - return (_uri); -} - -std::string HttpRequest::getVersion() const -{ - return (_version); -} - -std::map HttpRequest::getHeaders() const -{ - return (_headers); -} - -std::string HttpRequest::getBody() const -{ - if (_body.empty()) - return (""); - return (_body); -} - -// TODO: check for necessary initialization -bool HttpRequest::isConnectionClose() const -{ - std::map::const_iterator it = _headers.find("Connection"); - if (it != _headers.end() && it->second == "close") - return (true); - return (false); -} - -//////////////////////////////////////////////////////////////////////////////// - -std::vector HttpRequest::_dataToHeaders(std::istringstream& iss) -{ - std::string readline; - std::vector res; - - while (std::getline(iss, readline) && readline != "\r") - res.push_back(readline); - return (res); -} - -std::string HttpRequest::_dataToBody(std::istringstream& iss) -{ - std::string readline; - std::string drafts; - - while (std::getline(iss, readline)) - drafts += readline; - if (drafts.empty()) - return (""); - return (drafts); -} - //////////////////////////////////////////////////////////////////////////////// /// The current parse logic for my request is to first split the syntax into /// request, header, and body lines, and then parse the split syntax separately. /// I've separated the 'split' and 'parse' parts so that one function does one job. - +//////////////////////////////////////////////////////////////////////////////// /// @brief This function parses the request data and extracts /// the method, path, version, headers, and body. @@ -94,7 +32,7 @@ std::string HttpRequest::_dataToBody(std::istringstream& iss) /// @return bool bool HttpRequest::parse(const std::string& requestData) { - t_read_request separatedData = _splitRequestData(requestData); + t_readed_parts separatedData = _splitRequestData(requestData); if (!separatedData.iscomplete) return (false); if (!_parseRequestLine(separatedData.request)) @@ -106,29 +44,53 @@ bool HttpRequest::parse(const std::string& requestData) return (true); } - +//////////////////////////////////////////////////////////////////////////////// /// @brief Separate the request data into request line, headers, and body. /// @param requestData The request data to be separated. /// @return A struct containing the separated request data. -t_read_request HttpRequest::_splitRequestData(const std::string& requestData) +t_readed_parts HttpRequest::_splitRequestData(const std::string& requestData) { - t_read_request data; + t_readed_parts data; std::istringstream iss(requestData); std::string readline; std::string drafts; + if (requestData.empty()) return (data); if (!std::getline(iss, readline)) return (data); data.request = readline; - data.headers = _dataToHeaders(iss); + data.headers = _convertPartToHeaders(iss); if (data.headers.empty()) return (data); - data.body = _dataToBody(iss); + data.body = _convertPartToBody(iss); data.iscomplete = true; return (data); } +std::vector HttpRequest::_convertPartToHeaders(std::istringstream& iss) +{ + std::string readline; + std::vector res; + + while (std::getline(iss, readline) && readline != "\r") + res.push_back(readline); + return (res); +} + +std::string HttpRequest::_convertPartToBody(std::istringstream& iss) +{ + std::string readline; + std::string drafts; + + while (std::getline(iss, readline)) + drafts += readline; + if (drafts.empty()) + return (""); + return (drafts); +} + +//////////////////////////////////////////////////////////////////////////////// /// @brief Parses the request line and extracts the method, path, and version. /// @param requestLine `_method` `_uri` `_version`, example: GET /path/resource HTTP/1.1 /// @return bool @@ -197,4 +159,54 @@ std::string HttpRequest::trim(const std::string& str) if (last == std::string::npos) return (""); return (str.substr(first, last - first + 1)); -} \ No newline at end of file +} + +//////////////////////////////////////////////////////////////////////////////// + +// TODO: check for necessary initialization +bool HttpRequest::isConnectionClose() const +{ + std::map::const_iterator it = _headers.find("Connection"); + if (it != _headers.end() && it->second == "close") + return (true); + return (false); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Getters +//////////////////////////////////////////////////////////////////////////////// + +std::string HttpRequest::getMethod() const +{ + return (_method); +} + +std::string HttpRequest::getUri() const +{ + return (_uri); +} + +std::string HttpRequest::getVersion() const +{ + return (_version); +} + +std::map HttpRequest::getHeaders() const +{ + return (_headers); +} + +std::string HttpRequest::getBody() const +{ + if (_body.empty()) + return (""); + return (_body); +} +//////////////////////////////////////////////////////////////////////////////// +/// Setters +//////////////////////////////////////////////////////////////////////////////// + +void HttpRequest::setUri(std::string uri) +{ + _uri = uri; +} diff --git a/src/server/HttpResponse.cpp b/src/server/HttpResponse.cpp index f787e0f..1a41444 100644 --- a/src/server/HttpResponse.cpp +++ b/src/server/HttpResponse.cpp @@ -3,128 +3,155 @@ /* ::: :::::::: */ /* HttpResponse.cpp :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: sanghupa +#+ +:+ +#+ */ +/* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:00 by sanghupa #+# #+# */ -/* Updated: 2024/07/14 22:34:17 by sanghupa ### ########.fr */ +/* Updated: 2024/07/25 16:50:17 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ #include "webserv.hpp" #include "HttpResponse.hpp" -// TODO add doc +//////////////////////////////////////////////////////////////////////////////// +/// Your HTTP response status codes must be accurate. +/// You server must have default error pages if none are provided. +/// Limit client body size. +//////////////////////////////////////////////////////////////////////////////// + +/// @brief Default constructor for the HttpResponse class. +/// Initializes the status code to 200 and the status message to "OK". HttpResponse::HttpResponse() : _statusCode(200), _statusMessage("OK") { } -HttpResponse::HttpResponse(const std::string filePath) +/// @brief Constructor for the HttpResponse class with file path. +/// Initializes the status code to 200 and the status message to "OK". +/// @param filePath +HttpResponse::HttpResponse(const std::string& filePath) :_statusCode(200), _statusMessage("OK") { - fromFile(filePath); + initializefromFile(filePath); } +/// @brief Copy constructor for the HttpResponse class. +/// @param other HttpResponse::HttpResponse(const HttpResponse& other) { _statusCode = other._statusCode; - _statusMessage = other._statusMessage; + _statusMessage = other._statusMessage; _headers = other._headers; _body = other._body; } +/// @brief Assignment operator for the HttpResponse class. +/// @param other HttpResponse& HttpResponse::operator=(const HttpResponse& other) { - if (this != &other) + if (this != &other) { - _statusCode = other._statusCode; - _statusMessage = other._statusMessage; - _headers = other._headers; - _body = other._body; - } - return (*this); + _statusCode = other._statusCode; + _statusMessage = other._statusMessage; + _headers = other._headers; + _body = other._body; + } + return (*this); } - +/// @brief Destructor for the HttpResponse class. HttpResponse::~HttpResponse() { } //////////////////////////////////////////////////////////////////////////////// -void HttpResponse::setStatusCode(int code, const std::string statusMessage) -{ - _statusCode = code; - _statusMessage = statusMessage; -} - -void HttpResponse::setHeader(const std::string key, const std::string value) -{ - _headers.insert(std::pair(key, value)); -} - -void HttpResponse::setBody(const std::string bodyContent) +std::string HttpResponse::toString() const { - _body = bodyContent; + return (getResponseLine() + _getHeadersString() + "\r\n\r\n" + _body); } -std::string HttpResponse::getBody() +//////////////////////////////////////////////////////////////////////////////// +/// @brief Use in static function to set the default headers for an HttpResponse object. +/// @param resp The object to set the headers for. +/// @param bodyContent The content to be included in the response body. +void HttpResponse::setDefaultHeaders(HttpResponse& resp) { - return (_body); + resp._setDefaultHeadersImpl(); } -std::string HttpResponse::_getResponseLine() const +/// @brief Read the Object's body and set the default headers for the response. +/// @param bodyContent The content to be included in the response body. +void HttpResponse::setDefaultHeaders() { - std::ostringstream oss; - oss << "HTTP/1.1 " << _statusCode << " " << _statusMessage << "\r\n"; - return (oss.str()); + this->_setDefaultHeadersImpl(); } -std::string HttpResponse::toString() const +/// @brief Basic implementation of setting the default headers for an HttpResponse object. +void HttpResponse::_setDefaultHeadersImpl() { - return (_getResponseLine() + _getHeadersString() + "\r\n\r\n" + _body); + if (_bodyLength <= 0) + *this = HttpResponse::internalServerError_500(); + setHeader("Content-Length", toString(_bodyLength)); + setHeader("Content-Type", "text/html"); + setHeader("Connection", "close"); } //////////////////////////////////////////////////////////////////////////////// - /// @brief Creates an Static HttpResponse object by reading the contents of a file. /// @param filePath The path to the file to be read. /// @return HttpResponse The created HttpResponse object. -void HttpResponse::fromFile(const std::string filePath) +/// @warning If the file cannot be opened, the response point a 404 error(). +void HttpResponse::initializefromFile(const std::string& filePath) { - std::ifstream file(filePath.data()); + _fileToBody(filePath); + if (_body.empty() || _bodyLength <= 0) + return ; + if (_statusCode == 200) + setDefaultHeaders(); +} + +/// @brief Reads the contents of a file into a string. +/// If the file cannot be opened, the response point a 404 error(). +/// If the file is empty, the response point a 500 error(). +/// @param filePath The path to the file to be read. +/// @return Return the file content as a string. +/// If there is any error, return an empty string. +void HttpResponse::_fileToBody(const std::string& filePath) +{ + Config& config = Config::getInstance(); + std::ifstream file(filePath.c_str(), std::ios::binary | std::ios::ate); + std::string body; + std::streamsize fileLength; if (!file.is_open()) { *this = notFound_404(); - return; + return ; } - file.seekg(0, std::ios::end); - std::streamsize fileSize = file.tellg(); - file.seekg(0, std::ios::beg); - if (fileSize <= 0) + fileLength = file.tellg(); + if (fileLength <= 0) { *this = internalServerError_500(); - return; + return ; } - std::string fileContents(fileSize, '\0'); - if (!file.read(&fileContents[0], fileSize)) + file.seekg(0, std::ios::beg); + if (fileLength > config.getInt("max_body_size")) + { + *this = requestEntityTooLarge_413(); + return ; + } + body.resize(fileLength); + if (!file.read(&body[0], fileLength)) { - file.close(); *this = internalServerError_500(); - return; + return ; } - file.close(); - std::stringstream ss; - ss << fileSize; - this->setBody(fileContents); - this->setHeader("Content-Length", ss.str()); - this->setHeader("Connection", "close"); + setBody(body); } //////////////////////////////////////////////////////////////////////////////// - /// @brief Returns the headers as a string. /// @return std::string, The headers as a string. std::string HttpResponse::_getHeadersString() const @@ -149,153 +176,306 @@ std::string HttpResponse::_getStatusLine() const } //////////////////////////////////////////////////////////////////////////////// +/// @brief Initializes the map of HTTP status codes to status messages. +/// @var statusMap Static, The map of status codes to status messages. +/// @return Return the map of status codes and status messages. +const std::map& HttpResponse::_StaticInitStatusMap() +{ + static std::map statusMap; + if (statusMap.empty()) + { + statusMap[200] = "OK"; + statusMap[201] = "Created"; + statusMap[204] = "No Content"; + statusMap[301] = "Moved Permanently"; + statusMap[302] = "Found"; + statusMap[400] = "Bad Request"; + statusMap[401] = "Unauthorized"; + statusMap[403] = "Forbidden"; + statusMap[404] = "Not Found"; + statusMap[405] = "Method Not Allowed"; + statusMap[408] = "Request Timeout"; + statusMap[413] = "Request Entity Too Large"; + statusMap[418] = "I'm a Teapot"; + statusMap[500] = "Internal Server Error"; + statusMap[501] = "Not Implemented"; + } + return (statusMap); +} -std::string getErrorPagePath(int pageCode) +/// @brief Sets the HTTP status code and corresponding message for the response. +/// +/// This function sets the `_statusCode` member variable and updates the `_statusMessage` +/// based on a predefined map of status codes. The map is initialized once and reused, +/// ensuring a consistent set of status codes and messages across the application. +/// +/// If the provided status code is not found in the map, an exception is thrown. +/// +/// @param code The HTTP status code to set for the response. +/// @throws std::runtime_error if the status code is not found in the map. +void HttpResponse::setStatusCode(int code) { - std::ostringstream oss; - oss << "./www/error_pages/" << pageCode << ".html"; - return (oss.str()); + _statusCode = code; + const std::map& statusMap = _StaticInitStatusMap(); + + std::map::const_iterator it = statusMap.find(code); + if (it != statusMap.end()) + _statusMessage = it->second; + else + throw std::runtime_error("Unknown status code :" + toString(getStatusCode())); } -const std::map& HttpResponse::_getStatusMessages() +//////////////////////////////////////////////////////////////////////////////// +/// @brief Retrieves error page data for a given HTTP status code. +/// +/// This function returns the error page data, including the full path and validity of the page file. +/// It first checks if the status code is within the valid range (400-599). If not, it throws an exception. +/// If the data for the given code is not already cached, it fetches the page path from the configuration, +/// constructs the full path, checks if the file exists, and caches the result. +/// +/// @param code The HTTP status code for which to fetch the error page data. +/// @return The `t_page_detail` containing the full path and validity of the error page file. +/// @throws std::runtime_error if the status code is not in the valid range. +t_page_detail fetchPageData(int code) { - static std::map statusMessages; + static std::map pageMap; + + if (code < 400 || code > 599) + throw std::runtime_error("Invalid error code"); - if (statusMessages.empty()) + std::map::iterator it = pageMap.find(code); + if (it == pageMap.end()) { - statusMessages[200] = "OK"; - // statusMessages[201] = "Created"; - // statusMessages[204] = "No Content"; - // statusMessages[301] = "Moved Permanently"; - // statusMessages[302] = "Found"; - statusMessages[400] = "Bad Request"; - // statusMessages[401] = "Unauthorized"; - statusMessages[403] = "Forbidden"; - statusMessages[404] = "Not Found"; - statusMessages[405] = "Method Not Allowed"; - statusMessages[408] = "Request Timeout"; - statusMessages[413] = "Request Entity Too Large"; - statusMessages[418] = "I'm a Teapot"; - statusMessages[500] = "Internal Server Error"; - statusMessages[501] = "Not Implemented"; + Config& config = Config::getInstance(); + std::string fullPath = getFullErrorPath(config.getErrorPage(code)); + pageMap[code] = constructPageDetail(fullPath); } - return (statusMessages); + return (pageMap[code]); +} + +t_page_detail constructPageDetail(const std::string& path) +{ + t_page_detail page; + page.path = path; + page.isValid = isFile(path); + return (page); } -void HttpResponse::setStatusCode(int code) +std::string getFullErrorPath(const std::string& path) { - _statusCode = code; - const std::map& statusMessages = _getStatusMessages(); - std::map::const_iterator it = statusMessages.find(code); - if (it != statusMessages.end()) - _statusMessage = it->second; - else - throw std::runtime_error("Unknown status code"); + if (path.empty()) + return (""); + return (LOCATION_ROOT_PATH + path); } //////////////////////////////////////////////////////////////////////////////// -/// @brief static functions to create HttpResponse objects -/// with specific status codes. -/// @return HttpResponse The created HttpResponse object. -/// -// TODO think about seperate functions to another class. +/// Static functions: Helper functions to create common HTTP responses. //////////////////////////////////////////////////////////////////////////////// -/// @brief Handle error responses. -/// @param code -/// @return HttpResponse -HttpResponse HttpResponse::_errorResponse(int code) +/// @brief Creates an HTTP response object for a specified error code. +/// +/// This function constructs an `HttpResponse` object based on the given error code. It first attempts +/// to fetch error page data using the `fetchPageData` function. If a valid error page file is found, it +/// uses that file to create the response. If the error page is missing or invalid, it generates a simple +/// HTML response indicating an error. +/// +/// @param code The HTTP error code for which to create the response. It should be in the range 400-599. +/// @return An `HttpResponse` object configured with either the error page content or a default error message. +/// @throws std::runtime_error if the provided error code is out of the valid range (400-599). +HttpResponse HttpResponse::_createErrorResponse(int code) +{ + if (code < 400 || code > 599) + throw std::runtime_error("Invalid error code"); + + t_page_detail pageData = fetchPageData(code); + if (pageData.path.empty() || !pageData.isValid) + return (_createSimpleHttpResponse(code)); + + HttpResponse resp(pageData.path); + resp.setStatusCode(code); + return (resp); +} + +/// @brief Generates a simple HTML response for a given HTTP status code. +/// @param code The HTTP status code for which to generate the response. +/// @return Return the generated HTML response. +HttpResponse HttpResponse::_createSimpleHttpResponse(int code) { - HttpResponse resp(getErrorPagePath(code)); - resp.setStatusCode(code); + HttpResponse resp; + resp.setStatusCode(code); + resp.setBody(resp._generateHtmlBody()); + setDefaultHeaders(resp); return (resp); } +/// @brief Generates a simple HTML response body for a given HTTP status code. +/// @return Return the generated HTML body as a string. +std::string HttpResponse::_generateHtmlBody() +{ + if (toString(getStatusCode()).empty() || getStatusMessage().empty()) + return (""); + std::string body = + "" + "" + "

" + toString(getStatusCode()) + " " + getStatusMessage() + "

" + "" + ""; + return (body); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Static functions: Common HTTP responses. +//////////////////////////////////////////////////////////////////////////////// + HttpResponse HttpResponse::badRequest_400() { - return (_errorResponse(400)); + return (_createErrorResponse(400)); } HttpResponse HttpResponse::forbidden_403() { - return (_errorResponse(403)); + return (_createErrorResponse(403)); } -/// @brief Creates an HttpResponse object for a 404 Not Found error. -/// -/// This function attempts to read a default 404 error page from the -/// filesystem. If the page is not found or cannot be read, it falls -/// back to a simple HTML message indicating a 404 error. -/// -/// @return HttpResponse The generated HttpResponse object for the 404 error. HttpResponse HttpResponse::notFound_404() { - HttpResponse resp; - HttpResponse from404File(_errorResponse(404)); - - if (from404File.getBody().empty()) - resp.setBody("

404 Not Found

"); - else - resp.setBody(from404File.getBody()); - return (resp); + return ( _createErrorResponse(404)); } HttpResponse HttpResponse::methodNotAllowed_405() { - return (_errorResponse(405)); - + return (_createErrorResponse(405)); } HttpResponse HttpResponse::requestTimeout_408() { - return (_errorResponse(408)); + return (_createErrorResponse(408)); } HttpResponse HttpResponse::requestEntityTooLarge_413() { - return (_errorResponse(413)); + return (_createErrorResponse(413)); } HttpResponse HttpResponse::imaTeapot_418() { - return (_errorResponse(418)); + return (_createErrorResponse(418)); } HttpResponse HttpResponse::internalServerError_500() { - return (_errorResponse(500)); + return (_createErrorResponse(500)); +} + +HttpResponse HttpResponse::notImplemented_501() +{ + return (_createErrorResponse(501)); } -// TODO add doc, make new mtethid to set header & body +/// @brief Creates a successful HTTP response with status code 200. +/// @details All `Response` object constructors initialize the status code to 200 and the status message to "OK". +/// @return Return the generated HTML response. HttpResponse HttpResponse::success_200() { HttpResponse resp; - resp.setStatusCode(200, "OK"); - std::string body = "

Success

"; - resp.setBody(body); - std::ostringstream oss; - oss << body.size(); - resp.setHeader("Content-Length",oss.str()); - resp.setHeader("Content-Type", "text/html"); - resp.setHeader("Connection", "close"); + resp.setBody(resp._generateHtmlBody()); + setDefaultHeaders(resp); return (resp); } -HttpResponse HttpResponse::notImplemented_501() +//////////////////////////////////////////////////////////////////////////////// +/// Setters +//////////////////////////////////////////////////////////////////////////////// + +void HttpResponse::setStatusCode(int code, const std::string statusMessage) { - HttpResponse resp; - resp.setStatusCode(501, "Not Implemented"); + _statusCode = code; + _statusMessage = statusMessage; +} + +void HttpResponse::setHeader(const std::string key, const std::string value) +{ + _headers.insert(std::pair(key, value)); +} - std::string body = "

501 Not Implemented

"; - resp.setBody(body); +void HttpResponse::setBody(const std::string bodyContent) +{ + _body = bodyContent; + _bodyLength = static_cast(_body.size()); +} +//////////////////////////////////////////////////////////////////////////////// +/// Getters +//////////////////////////////////////////////////////////////////////////////// + +std::string HttpResponse::getBody() +{ + return (_body); +} + +size_t HttpResponse::getBodyLength() +{ + return (_bodyLength); +} + +int HttpResponse::getStatusCode() const +{ + return (_statusCode); +} + +std::string HttpResponse::getStatusMessage() const +{ + return (_statusMessage); +} + +std::string HttpResponse::getResponseLine() const +{ std::ostringstream oss; - oss << body.size(); - resp.setHeader("Content-Length",oss.str()); - resp.setHeader("Content-Type", "text/html"); - resp.setHeader("Connection", "close"); - return (resp); + oss << "HTTP/1.1 " << _statusCode << " " << _statusMessage << "\r\n"; + return (oss.str()); +} + +std::string HttpResponse::toString(int value) const +{ + std::ostringstream oss; + oss << value; + return (oss.str()); +} + +std::string HttpResponse::toString(size_t value) const +{ + std::ostringstream oss; + oss << value; + return (oss.str()); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Utility functions +//////////////////////////////////////////////////////////////////////////////// + +/// @brief Checks if a file exists. +/// @param path The path of the file. +bool isFile(const std::string path) +{ + struct stat buffer; + if (stat(path.c_str(), &buffer) != 0) + return (false); + if (S_ISREG(buffer.st_mode)) + return (true); + return (false); +} + +/// @brief Checks if a file exists. +/// @param path The path of the file. +bool isDir(const std::string path) +{ + struct stat buffer; + if (stat(path.c_str(), &buffer) != 0) + return (false); + if (S_ISDIR(buffer.st_mode)) + return (true); + return (false); } \ No newline at end of file diff --git a/src/server/RequestHandler.cpp b/src/server/RequestHandler.cpp index 60075c8..b479b23 100644 --- a/src/server/RequestHandler.cpp +++ b/src/server/RequestHandler.cpp @@ -6,14 +6,14 @@ /* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:00 by sanghupa #+# #+# */ -/* Updated: 2024/07/12 20:48:17 by minakim ### ########.fr */ +/* Updated: 2024/07/25 16:52:51 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ -#include "webserv.hpp" #include "RequestHandler.hpp" -#include "StaticFileHandler.hpp" #include "HttpResponse.hpp" +#include "HttpRequest.hpp" +#include "Location.hpp" RequestHandler::RequestHandler() {} @@ -21,22 +21,207 @@ RequestHandler::RequestHandler() RequestHandler::~RequestHandler() {} -/// @brief -/// @param request HttpRequest object -/// @return 1. if request is `GET`, 2. if request is `POST`, -/// 3. if request is `DELETE`, 4. if request is `not implemented`. -/// @note `501 Not Implemented`: temp response, the server does not support -/// the functionality required to fulfill the request. -HttpResponse RequestHandler::handleRequest(const HttpRequest request) +//////////////////////////////////////////////////////////////////////////////// +/// Public Methods +//////////////////////////////////////////////////////////////////////////////// + +/// @brief Processes the incoming HTTP request and returns an appropriate response. +/// +/// This function handles an incoming HTTP request by first logging the request details. It then +/// locates the corresponding `Location` object based on the request URI. If no matching location is +/// found, it returns a `404 Not Found` response. If the HTTP method is not allowed for the located +/// `Location`, it returns a `405 Method Not Allowed` response. Otherwise, it delegates the request +/// processing to the appropriate handler based on the method (`GET`, `POST`, `DELETE`, etc.). +/// +/// @param request The `HttpRequest` object representing the incoming request. +/// @return An `HttpResponse` object containing the result of processing the request. +/// 1. Returns `404 Not Found` if no location is found. +/// 2. `405 Method Not Allowed` if the method is not allowed. +/// 3. or the result of `_processRequest` if the method is valid and the location is found. +HttpResponse RequestHandler::handleRequest(const HttpRequest& request) { + // FIXME: change to Logger std::cout << "\r" << request.getMethod() << " | " << request.getUri() << " | " << request.getVersion() << std::endl; + + HttpResponse reps; + Location* location = _findLocation(request.getUri()); + + if (location == NULL) + return (HttpResponse::notFound_404()); + if (!_isAllowedMethod(request.getMethod(), (*location).getAllowedMethods())) + return (HttpResponse::methodNotAllowed_405()); + reps = _processRequest(request, (*location)); + return (reps); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Private Methods +//////////////////////////////////////////////////////////////////////////////// + +/// @brief Finds the `Location` object that matches the request URI. +/// +/// This function extracts the path from the given URI and searches for the most specific +/// location in the `Config` instance that matches the extracted path. It uses the location map +/// from the `Config` to find the best matching `Location`. If a match is found, the corresponding +/// `Location` object is returned. If no match is found, `NULL` is returned. +/// +/// @param uri The request URI to extract the path from and match against the location map. +/// @return A pointer to the matching `Location` object if a match is found; otherwise, `NULL`. +Location* RequestHandler::_findLocation(const std::string& uri) +{ + Config& config = Config::getInstance(); + std::map locations = config.getLocationMap(); + + std::string matchedLocation = _getMatchedLocation(uri, locations); + if (!matchedLocation.empty()) + return (locations.at(matchedLocation)); + return (NULL); +} + +// TODO: check logic *** +std::string getParentPath(const std::string& path) { + + ssize_t lastSlashPos = path.find_last_of('/'); + if (lastSlashPos == 0) + return "/"; + return path.substr(0, lastSlashPos); +} + + +/// @brief Finds the longest location path that matches the request URI. +/// +/// This function extracts the path from the URI and searches for the most specific location +/// that matches this path. It starts with the full path and progressively checks shorter +/// paths up to the root, using the provided locations map from the `Config` instance. +/// +/// @param path The path extracted from the URI to match against the locations. +/// @param locations A map of location paths to `Location` objects from the `Config` instance. +/// @return The longest matching `location path`. If no match is found, returns an `empty string`. +std::string _getMatchedLocation(std::string path, const std::map& locations) +{ + std::string matched; + std::string parentPath; +// TODO: 1. check logic *** 2. clean up 3. modularize + + if (path.empty()) + throw std::invalid_argument("Path cannot be empty"); + if (path == "/") // if root + { + std::map::const_iterator it = locations.find(path); + if (it != locations.end()) + { + std::cout << "Match found: " << it->first << std::endl; + return (path); + } + throw std::invalid_argument("No match found for path: " + path); + } + + ssize_t lastSlashPos = path.find_last_of('/'); + path = path.substr(0, lastSlashPos + 1); + while (!path.empty()) + { + std::cout << "Checking path: [" << path << "]" << std::endl; + std::map::const_iterator it = locations.find(path); + if (it != locations.end()) + { + std::cout << "Match found: " << it->first << std::endl; + matched = path; + break; + } + std::cout << "No match for: [" << path << "]" << std::endl; + path = getParentPath(path); + if (path == "/") + break; + } + return (matched); + +} + +/// @brief Extracts and normalizes the path from a given URI. +/// +/// This function removes the trailing slash from the URI path if it exists. +/// The URI is processed to return the normalized path, which does not end with a slash. +/// @param uri +/// @return extracted path +std::string _extractPathFromUri(const std::string& uri) +{ + std::string path = uri; + + if (path.empty()) + throw std::invalid_argument("URI cannot be empty"); + if (path == "/") + return (path); + if (path.size() > 1 && path[path.size() - 1] == '/') + path.erase(path.size() - 1); + return (path); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief This function checks if the request method is in the list of allowed methods. +/// When the Location object is created, it is initialized with a list of allowed methods. +/// if user did not specify the allowed methods at `.conf file`, it is initialized with {"GET", "POST", "DELETE"}. +bool RequestHandler::_isAllowedMethod(const std::string &target, std::vector list) const +{ + if (!list.empty() && std::find(list.begin(), list.end(), target) != list.end()) + return (true); + return (false); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief Processes the HTTP request based on its method. +/// +/// This function handles an HTTP request by dispatching it to the appropriate method handler +/// (`GET`, `POST`, or `DELETE`). If the method is not supported, it returns a `501 Not Implemented` +/// response. +/// +/// @param request The `HttpRequest` object representing the incoming HTTP request. +/// @param location The `Location` object associated with the request's URI. +/// @return An `HttpResponse` object containing the result of processing the request based on the method. +/// If the method is not supported, returns a `501 Not Implemented` response. +HttpResponse RequestHandler::_processRequest(const HttpRequest& request, + const Location& location) +{ if (request.getMethod() == "GET") - return (_staticFileHandler.handleRequest(request)); + return (_handleGet(request, location)); else if (request.getMethod() == "POST") - return (HttpResponse::notImplemented_501()); + return (_handlePost(request, location)); else if (request.getMethod() == "DELETE") - return (HttpResponse::notImplemented_501()); + return (_handleDelete(request, location)); else return (HttpResponse::notImplemented_501()); } + +HttpResponse RequestHandler::_handleGet(const HttpRequest& request, + const Location& location) +{ + + return (_staticFileHandler.handleRequest(request, location)); +} + +HttpResponse RequestHandler::_handlePost(const HttpRequest& request, + const Location& location) +{ + (void) request; + (void) location; + return (HttpResponse::notImplemented_501()); +} + +HttpResponse RequestHandler::_handleDelete(const HttpRequest& request, + const Location& location) +{ + (void) request; + (void) location; + return (HttpResponse::notImplemented_501()); +} + +//////////////////////////////////////////////////////////////////////////////// + +// TODO: Redirections +// Redirections https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections +// request | get -> response -> 301 -> request | get -> response +/* + + + +*/ diff --git a/src/server/Server.cpp b/src/server/Server.cpp index b5eadc4..3197f9c 100644 --- a/src/server/Server.cpp +++ b/src/server/Server.cpp @@ -3,15 +3,17 @@ /* ::: :::::::: */ /* Server.cpp :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: sanghupa +#+ +:+ +#+ */ +/* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:46 by sanghupa #+# #+# */ -/* Updated: 2024/07/23 00:18:15 by sanghupa ### ########.fr */ +/* Updated: 2024/07/23 22:31:12 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ #include "webserv.hpp" #include "Server.hpp" +#include "HttpRequest.hpp" +#include "HttpResponse.hpp" // ---------------------------------------------------- diff --git a/src/server/StaticFileHandler.cpp b/src/server/StaticFileHandler.cpp index 09caa47..921357c 100644 --- a/src/server/StaticFileHandler.cpp +++ b/src/server/StaticFileHandler.cpp @@ -3,93 +3,268 @@ /* ::: :::::::: */ /* StaticFileHandler.cpp :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: sanghupa +#+ +:+ +#+ */ +/* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:00 by sanghupa #+# #+# */ -/* Updated: 2024/07/14 22:23:57 by sanghupa ### ########.fr */ +/* Updated: 2024/07/30 14:09:23 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ -#include "webserv.hpp" #include "StaticFileHandler.hpp" +#include "HttpResponse.hpp" +#include "HttpRequest.hpp" +#include "Location.hpp" +//////////////////////////////////////////////////////////////////////////////// +/// @brief Static member variable to store the MIME types. +/// Calls the `_staticInitMimeTypes` method to initialize the map +/// before the first object of this class is created. std::map StaticFileHandler::_staticMimeTypes; +//////////////////////////////////////////////////////////////////////////////// StaticFileHandler::StaticFileHandler() -{ +{ if (_staticMimeTypes.empty()) - _staticInitializeMimeTypes(); + _staticInitMimeTypes(); +} + +StaticFileHandler::StaticFileHandler(const StaticFileHandler& other) +{ + _handledPath = other._handledPath; +} + +StaticFileHandler& StaticFileHandler::operator=(const StaticFileHandler& other) +{ + if (this != &other) + { + _handledPath = other._handledPath; + } + return (*this); } StaticFileHandler::~StaticFileHandler() {} -HttpResponse StaticFileHandler::handleRequest(const HttpRequest request) +//////////////////////////////////////////////////////////////////////////////// +/// Public methods: handleRequest +//////////////////////////////////////////////////////////////////////////////// + +/// @brief handleRequest, public method to handle the request +/// This method is called by the RequestHandler to handle the request. +/// It checks if the requested URI is a directory or a file and calls the +/// appropriate method to handle the request. +/// @param request HttpRequest object as reference +/// @param location Location object as reference +HttpResponse StaticFileHandler::handleRequest(const HttpRequest& request, const Location& location) { if (request.getUri() == "/") - return (_handleRoot()); + return (_handleRoot(location)); + _setHandledPath(_buildPathWithUri(request, location)); + if (isDir(_handledPath)) + { + // if (/* && location.location.isListDir() */) + // return (_handleDirListing(request, location)); + return (_handleDirRequest(request, location)); + } + if (isFile(_handledPath)) + return (_handleFileRequest(request, location)); + return (_handleNotFound()); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Private methods +//////////////////////////////////////////////////////////////////////////////// + +/// @brief _handleDirRequest, private method to handle the directory request +/// This method is called when the directory listing is not `true`, +/// but the `uri` in the request is a directory. +/// the method modifies the request to append the default file name. +/// @warning this method depends on `_handleFileRequest` method +HttpResponse StaticFileHandler::_handleDirRequest(const HttpRequest& request, const Location& location) +{ + HttpRequest modifiedRequest = request; + + std::string absolutePath = _buildAbsolutePathWithIndex(location); + _setHandledPath(absolutePath); + + std::cout << "TEST | absolute path that with index is built: " << _handledPath << std::endl; + + if (!isFile(_handledPath)) + return (_handleNotFound()); + modifiedRequest.setUri(getFullPath()); + return (_handleFileRequest(modifiedRequest, location)); +} + +std::string StaticFileHandler::_buildAbsolutePathWithIndex(const Location& location) const +{ + std::string index = INDEX_HTML; + if (!location.getIndex().empty()) + index = location.getIndex(); + return (_handledPath + index); +} + +//////////////////////////////////////////////////////////////////////////////// +/// @brief _handleDirListing, private method to handle the directory listing +/// This method is called when the directory listing is `true`, +/// and the `uri` in the request is a directory. +/// @warning not implemented yet +HttpResponse StaticFileHandler::_handleDirListing(const HttpRequest& request, const Location& location) +{ + (void) request; + (void) location; + return (_createDirListingResponse()); +} + +// TODO: update this method to return a response object with the directory listing +/// @brief Create a `response` object for a directory listing. +/// @return `HttpResponse` +HttpResponse StaticFileHandler::_createDirListingResponse() const +{ + HttpResponse resp; + resp.setBody(genDirListingHtml(getFullPath())); + + if (resp.getBody().empty() || resp.getBodyLength() <= 0) + return (HttpResponse::internalServerError_500()); + resp.setDefaultHeaders(); + return (resp); +} + +/// @brief Generates an HTML page with a directory listing. +/// @param path +/// @return Create a list of directories and files in HTML and export it to std::stringstream. +std::string genDirListingHtml(const std::string& path) +{ + std::stringstream html; + std::string body; + + html.clear(); + html << "Directory Listing"; + html << "

Directory Listing for " << path << "

    "; + + // TODO: Implement directory listing - std::string filePath = _getFilePath(request.getUri()); - if (!_fileExists(filePath)) - return (_handleFileNotFound()); - std::string mimeType = _getMimeType(filePath); - HttpResponse resp(filePath); - // HttpResponse resp = HttpResponse::fromFile(filePath); - resp.setHeader(_getMimeType(filePath), mimeType); + html << "
"; + body = html.str(); + return (body); +} + +//////////////////////////////////////////////////////////////////////////////// +HttpResponse StaticFileHandler::_handleFileRequest(const HttpRequest& request, const Location& location) +{ + (void)request; + (void)location; + return (_createResponseForFile()); +} +HttpResponse StaticFileHandler::_createResponseForFile() const +{ + HttpResponse resp(_handledPath); + resp.setHeader("Content-Type", resolveMimeType(_handledPath)); return (resp); } -HttpResponse StaticFileHandler::_handleRoot() +//////////////////////////////////////////////////////////////////////////////// +/// @brief _handleRoot, private method to handle the root request +/// This method is called when the requested URI is the root directory. +/// It builds the full path of the default file and checks if the file exists. +HttpResponse StaticFileHandler::_handleRoot(const Location& location) { - return (HttpResponse::success_200()); + _setHandledPath(_buildAbsolutePathWithRoot(location)); + if (!isFile(_handledPath)) + return (_handleNotFound()); + return (_createResponseForFile()); } -std::string StaticFileHandler::_getFilePath(const std::string& uri) const +//////////////////////////////////////////////////////////////////////////////// +std::string StaticFileHandler::_buildAbsolutePathWithRoot(const Location& location) const { - return( LOCATION_FIXME + uri); + std::string root = location.getRootPath(); + std::string defaultFile = location.getIndex(); + std::string fullPath; + + if (root.empty()) + throw std::runtime_error("Root path is empty"); + if (defaultFile.empty()) // if can not read from cong, give default value: hard code + defaultFile = INDEX_HTML; + fullPath = "." + root + "/" + defaultFile; + std::cout << "TEST | absolute path that with index is built: " << _handledPath << std::endl; + return (fullPath); } -HttpResponse StaticFileHandler::_handleFileNotFound() +std::string StaticFileHandler::_buildPathWithUri(const HttpRequest& request, const Location& location) const +{ + std::string root = location.getRootPath(); + std::string fullPath; + if (root.empty()) + throw std::runtime_error("Root path is empty"); + fullPath = "." + root + request.getUri(); + + std::cout << "TEST | first path that is built: " << _handledPath << std::endl; + + return(fullPath); +} + +HttpResponse StaticFileHandler::_handleNotFound() { return (HttpResponse::notFound_404()); } + +//////////////////////////////////////////////////////////////////////////////// +/// Setters +//////////////////////////////////////////////////////////////////////////////// + +void StaticFileHandler::_setHandledPath(const std::string& fullPath) +{ + _handledPath = fullPath; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Getters +//////////////////////////////////////////////////////////////////////////////// + +std::string StaticFileHandler::getFullPath() const +{ + return (_handledPath); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Mime types +//////////////////////////////////////////////////////////////////////////////// + +/// @brief Initializes the MIME types map from the configuration. +/// If the configuration does not provide MIME types, default values are used. +void StaticFileHandler::_staticInitMimeTypes() +{ + Config& config = Config::getInstance(); + + if (config.getMimeTypeMap().size() > 0) + _staticMimeTypes = config.getMimeTypeMap(); + else + { + _staticMimeTypes.insert(std::make_pair("html", "text/html")); + _staticMimeTypes.insert(std::make_pair("css", "text/css")); + _staticMimeTypes.insert(std::make_pair("js", "application/javascript")); + _staticMimeTypes.insert(std::make_pair("png", "image/png")); + _staticMimeTypes.insert(std::make_pair("jpg", "image/jpeg")); + _staticMimeTypes.insert(std::make_pair("gif", "image/gif")); + _staticMimeTypes.insert(std::make_pair("txt", "text/plain")); + } +} + +//////////////////////////////////////////////////////////////////////////////// /// @brief Returns the MIME type of a given file based on its file extension. +/// @example resolveMimeType("index.html") ext = "html", and returns "text/html" /// @param path The path of the file. /// @return The MIME type of the file. -std::string StaticFileHandler::_getMimeType(const std::string path) const +std::string StaticFileHandler::resolveMimeType(const std::string path) const { - std::string::size_type dotPosition = path.find_last_of("."); - if (dotPosition == std::string::npos) + std::string::size_type dotPos = path.find_last_of("."); + if (dotPos == std::string::npos) return ("text/plain"); - std::string ext = path.substr(dotPosition); + std::string ext = path.substr(dotPos + 1); std::map::const_iterator it = _staticMimeTypes.find(ext); if (it != _staticMimeTypes.end()) return (it->second); return ("application/octet-stream"); -} - -/// @brief Checks if a file exists. -/// @param path The path of the file. -bool StaticFileHandler::_fileExists(const std::string path) const -{ - struct stat buffer; - if (stat(path.c_str(), &buffer) != 0) - return (false); - if (S_ISREG(buffer.st_mode)) - return (true); - return (false); -} - -/// @brief Initializes the mimeTypes map with the default mime types. -void StaticFileHandler::_staticInitializeMimeTypes() -{ - _staticMimeTypes.insert(std::make_pair(".html", "text/html")); - _staticMimeTypes.insert(std::make_pair(".css", "text/css")); - _staticMimeTypes.insert(std::make_pair(".js", "application/javascript")); - _staticMimeTypes.insert(std::make_pair(".png", "image/png")); - _staticMimeTypes.insert(std::make_pair(".jpg", "image/jpeg")); - _staticMimeTypes.insert(std::make_pair(".gif", "image/gif")); - _staticMimeTypes.insert(std::make_pair(".txt", "text/plain")); -} +} \ No newline at end of file