diff --git a/.vscode/settings.json b/.vscode/settings.json index 8646d0e..71fae9b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -84,6 +84,9 @@ "random": "cpp", "type_traits": "cpp", "utility": "cpp", - "numbers": "cpp" + "numbers": "cpp", + "ranges": "cpp", + "span": "cpp", + "cinttypes": "cpp" } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f089dc8..f1119be 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -19,10 +19,13 @@ "${workspaceFolder}/src/server/Server.cpp", "${workspaceFolder}/src/server/HttpRequest.cpp", "${workspaceFolder}/src/server/HttpResponse.cpp", + "${workspaceFolder}/src/server/ErrorResponse.cpp", + "${workspaceFolder}/src/server/Context.cpp", "${workspaceFolder}/src/server/RequestHandler.cpp", "${workspaceFolder}/src/server/StaticFileHandler.cpp", "${workspaceFolder}/src/util/Location.cpp", "${workspaceFolder}/src/util/Config.cpp", + "${workspaceFolder}/src/util/Util.cpp", "-o", "${workspaceFolder}/webserv_test" ], diff --git a/Makefile b/Makefile index 1a92bae..49df307 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,8 @@ SRC_NAME = ./src/main.cpp \ ./src/server/Context.cpp \ ./src/server/StaticFileHandler.cpp \ ./src/util/Config.cpp \ - ./src/util/Location.cpp + ./src/util/Location.cpp \ + ./src/util/Util.cpp # SRC_NAME = $(shell find ./src -iname "*.cpp") OBJ_NAME = $(SRC_NAME:.cpp=.o) diff --git a/include/Context.hpp b/include/Context.hpp index bb815f5..30bfffd 100644 --- a/include/Context.hpp +++ b/include/Context.hpp @@ -5,32 +5,31 @@ #include "Location.hpp" #include "HttpRequest.hpp" -std::string getMatchedLocation(std::string path, const std::map& locations); +std::string getMatchedLocation(const std::string& path, const std::map& locations); std::string getParentPath(const std::string& path); +std::string normalisePath(const std::string& path); class Context { public: - Context(ServerConfig& config, HttpRequest& request); - Context(const Context& other); - ~Context(); - Context& operator=(const Context& other); - - const ServerConfig& getServer() const; - const Location& getLocation() const; + Context(ServerConfig& config, HttpRequest& request); + Context(const Context& other); + ~Context(); + Context& operator=(const Context& other); +; + const ServerConfig& getServer() const; + const Location& getLocation() const; const HttpRequest& getRequest() const; - void setRequest(HttpRequest& request); - void setServer(ServerConfig& config); - void setLocation(Location& location); - - bool isInitialized() const; + void setRequest(HttpRequest& request); + void setServer(ServerConfig& config); + void setLocation(Location& location); private: - ServerConfig& _serverConfig; + ServerConfig& _serverConfig; HttpRequest& _request; - Location& _location; + Location& _location; - Location& _findLocation(Context& context) const; + Location& _findLocation(Context& context) const; }; #endif // CONTEXT_H \ No newline at end of file diff --git a/include/HttpRequest.hpp b/include/HttpRequest.hpp index dc60aa3..1a97a5d 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/26 12:30:28 by minakim ### ########.fr */ +/* Updated: 2024/10/23 11:19:30 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ @@ -17,51 +17,82 @@ # include # include +# include "Util.hpp" + class HttpResponse; + # define WHITESPACE " \t\r\n" +# define NOT_SET 0 -struct t_readed_parts { +struct ReadedLines +{ std::string request; - std::vector headers; - std::string body; - bool iscomplete; - t_readed_parts() : iscomplete(false) {} + std::vector headers; + std::string bodyLines; }; + class HttpRequest { - public: - HttpRequest(); - ~HttpRequest(); - - bool parse(const std::string& requestData); - - std::string getMethod() const; - std::string getUri() const; - std::string getVersion() const; +public: + enum e_body_type + { + RAW, + CHUNKED, + FORM_DATA, + NONE + }; - std::map getHeaders() const; - std::string getBody() const; +public: - void setUri(std::string uri); + HttpRequest(); + HttpRequest(std::string& data); + ~HttpRequest(); + bool parse(const std::string& requestData); + + std::string getMethod() const; + std::string getUri() const; + std::string getVersion() const; + std::map getHeaders() const; + std::string getBody() const; + + size_t getContentLength() const; + + void setUri(const std::string& uri); + void setMethod(const std::string& method); + void setVersion(const std::string& version); + void setHeaders(const std::map& headers); + void setBody(const std::vector& bodyLines, e_body_type type); + void setBody(const std::string& bodyLines, e_body_type type); + void setContentLength(const ssize_t& contentLength); + + bool hasBody() const; - bool isConnectionClose() const; - static std::string trim(const std::string& str); - private: - std::string _method; - std::string _uri; - std::string _version; - std::map _headers; - std::string _body; + bool isConnectionClose() const; + static std::string trim(const std::string& str); + +private: + std::string _method; // GET, POST, DELETE + std::string _uri; // + std::string _version; // HTTP/1.1 + std::map _headers; // key: value + std::string _body; // raw, chunked, formdata + e_body_type _type; // type of body @see e_body_type + std::pair _content; // from Headers["Content-Length"], if not found NOT_SET -1 - 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 _convertPartToHeaders(std::istringstream& iss); - std::string _convertPartToBody(std::istringstream& iss); + + ReadedLines _splitRequestData(const std::string& requestData); + + bool _processRequestBody(const std::string& bodyLines); + bool _parseRequestLine(const std::string& requestLine); + bool _parseHeaders(const std::vector& headerLines); + + std::vector _convertPartToHeaders(std::istringstream& iss); + std ::string _convertPartToBodyLines(std::istringstream& iss); }; +// TODO: implement "<< operator" for HttpRequest +// std::ostream& operator<<(std::ostream& os, const HttpRequest& request); #endif \ No newline at end of file diff --git a/include/HttpResponse.hpp b/include/HttpResponse.hpp index 86777d3..b7ce7a8 100644 --- a/include/HttpResponse.hpp +++ b/include/HttpResponse.hpp @@ -6,7 +6,7 @@ /* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:00 by sanghupa #+# #+# */ -/* Updated: 2024/08/08 21:47:33 by minakim ### ########.fr */ +/* Updated: 2024/10/22 23:10:03 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ @@ -17,6 +17,7 @@ # include # include "Config.hpp" +# include "Util.hpp" class HttpRequest; class Config; @@ -30,6 +31,14 @@ struct t_page_detail bool isValid; }; +enum e_status { + STATUS_INVALID = -1, + STATUS_INFORMATIONAL, + STATUS_SUCCESS, + STATUS_REDIRECTION, + STATUS_ERROR +}; + bool isFile(const std::string path); bool isDir(const std::string path); // std::string generateHtmlBody(int code, const std::string& message); @@ -37,18 +46,11 @@ bool isDir(const std::string path); class HttpResponse { public: - // HttpResponse(); - // HttpResponse(const std::string& filePath); - // HttpResponse(const ServerConfig& config); - // HttpResponse(const ServerConfig& config, const Location& location); HttpResponse(const Context& context); HttpResponse(const Context& context, 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); @@ -60,25 +62,12 @@ class HttpResponse std::string getBody(); size_t getBodyLength(); std::string getResponseLine() const; - std::string toString() const; + std::string generateResponseToString() 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(); - - static HttpResponse _createErrorResponse(int code, const Context& context); + void initializefromFile(const Context& context, const std::string& filePath); + + static HttpResponse createErrorResponse(int code, const Context& context); static HttpResponse badRequest_400(const Context& context); static HttpResponse forbidden_403(const Context& context); static HttpResponse notFound_404(const Context& context); @@ -91,6 +80,8 @@ class HttpResponse static HttpResponse success_200(const Context& context); + static e_status checkStatusRange(int code); + private: int _statusCode; std::string _statusMessage; @@ -100,18 +91,21 @@ class HttpResponse std::string _getStatusLine() const; std::string _getHeadersString() const; - void _fileToBody(const std::string& filePath); + void _fileToBody( const Context& context, const std::string& filePath); std::string _generateHtmlBody(); void _setDefaultHeadersImpl(); - static const std::map& _StaticInitStatusMap(); + static const std::map& _staticInitStatusMap(); protected: Context& _context; + HttpResponse createErrorResponse(int code); HttpResponse _createSimpleHttpResponse(int code); - HttpResponse _createErrorResponse(int code); t_page_detail _constructPageDetail(const std::string& path); }; + +// TODO: implement "<< operator" for HttpResponse +// std::ostream& operator<<(std::ostream& os, const HttpResponse& response); #endif \ No newline at end of file diff --git a/include/RequestHandler.hpp b/include/RequestHandler.hpp index 283e52d..ed382b3 100644 --- a/include/RequestHandler.hpp +++ b/include/RequestHandler.hpp @@ -6,7 +6,7 @@ /* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:00 by sanghupa #+# #+# */ -/* Updated: 2024/08/08 20:47:32 by minakim ### ########.fr */ +/* Updated: 2024/10/22 19:51:30 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ @@ -15,7 +15,6 @@ class HttpResponse; class HttpRequest; -class ServerConfig; class Location; class Context; @@ -35,11 +34,14 @@ class RequestHandler private: StaticFileHandler _staticFileHandler; - HttpResponse _processRequest(const Context& context); + HttpResponse _processStandardMethods(const Context& context); bool _isAllowedMethod(const Context& context) const; HttpResponse _handleGet(const Context& context); HttpResponse _handlePost(const Context& context); HttpResponse _handleDelete(const Context& context); + + bool _isCGIReqeust(const Context& context) const; + HttpResponse _handleCGIRequest(const Context& context); }; #endif \ No newline at end of file diff --git a/include/RequestParser.backup b/include/RequestParser.backup new file mode 100644 index 0000000..4fe3964 --- /dev/null +++ b/include/RequestParser.backup @@ -0,0 +1,143 @@ + +#ifndef REQUESTPARSER_HPP +#define REQUESTPARSER_HPP + +#include +#include +#include +#include "Util.hpp" + +//////////////////////////////////////////////////////////////////////////////// +/// Raw +//////////////////////////////////////////////////////////////////////////////// + +struct Raw { + std::string _data; + + Raw(); + Raw(const std::string& rawData); + + void setData(const std::string& rawData); + std::string getData() const; +}; + +//////////////////////////////////////////////////////////////////////////////// +/// Chunked +//////////////////////////////////////////////////////////////////////////////// + +struct ChunkSegment { + std::string _data; + size_t _size; + bool _isEOF; + + ChunkSegment(); + ChunkSegment(const std::string& chunkData, const size_t chunkSize); + + std::string getData() const; + size_t getSize() const; + void setSize(size_t chunkSize); + void setData(const std::string& chunkData); + size_t parseSize(const std::string& line); + std::string parseData(const std::string& line); +}; + +struct Chunked { + std::vector _datas; + bool _isComplete; + + Chunked(); + Chunked(const ChunkSegment& segment); + + void addChunkSegment(const ChunkSegment& segment); + void setComplete(); + std::string assembleBody() const; +}; + +//////////////////////////////////////////////////////////////////////////////// +/// FormData +//////////////////////////////////////////////////////////////////////////////// + +struct FormDataEntry { + std::string _name; + std::string _filename; + std::string _contentType; + std::string _data; + bool _isProcessed; + + FormDataEntry(); + FormDataEntry(const std::string& partName, const std::string& partData, + const std::string& partFilename, const std::string& partContentType); + FormDataEntry(const FormDataEntry& entry); + + std::string getName() const; + std::string getFilename() const; + std::string getContentType() const; + std::string getData() const; + bool getIsProcessed() const; + + void setFilename(const std::string& partFilename); + void setContentType(const std::string& partContentType); + void setName(const std::string& partName); + void setData(const std::string& partData); + void setProcessed(); + + void handleContentDisposition(const std::string& line); + void handleContentType(const std::string& line); + // void handleContentTransferEncoding(const std::string& line); +}; + + +struct FormData { + + std::vector _datas; + std::string _boundary; + + FormData(); + FormData(const FormDataEntry& entry); + + std::string getBoundary() const; + void setBoundary(const std::string& partBoundary); + void addFormDataEntry(const FormDataEntry& entry); +}; + + +//////////////////////////////////////////////////////////////////////////////// +/// Body +//////////////////////////////////////////////////////////////////////////////// + +enum Type { + RAW, + CHUNKED, + FORM_DATA, + NONE +}; + +struct Body +{ + Type bodyType; + + union { + Raw* raw; + Chunked* chunked; + FormData* formData; + }; + + Body(); + ~Body(); + + void clear(); + bool empty() const; + + void initRaw(); + void initRaw(const std::string& rawData); + void initChunked(); + void initFormData(); + void initFormData(const std::string& boundary); + + Raw getRaw() const; + Chunked getChunked() const; + FormData getFormData() const; + +}; + +#endif // REQUESTPARSER_HPP \ No newline at end of file diff --git a/include/StaticFileHandler.hpp b/include/StaticFileHandler.hpp index fba1e53..dc3d35b 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/08/08 21:54:57 by minakim ### ########.fr */ +/* Updated: 2024/10/22 19:59:31 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ @@ -15,8 +15,8 @@ # include # include +# include # include - class HttpResponse; class HttpRequest; class Location; @@ -25,7 +25,6 @@ class Context; # define LOCATION_PATH "./www/static" # define INDEX_HTML "index.html" -std::string genDirListingHtml(const std::string& path); class StaticFileHandler { @@ -35,7 +34,9 @@ class StaticFileHandler StaticFileHandler(const StaticFileHandler& other); StaticFileHandler& operator=(const StaticFileHandler& other); - HttpResponse handleRequest(const Context& context); + HttpResponse handleget(const Context& context); + HttpResponse handlepost(const Context& context); + std::string getFullPath() const; std::string resolveMimeType(const std::string path) const; @@ -45,7 +46,14 @@ class StaticFileHandler std::map _mimeTypes; - void _initMimeTypes(const Context& context); + void _initMimeTypes(); + + int _verifyHeaders(const Context& context) const; + int _validateGetHeaders(const std::map& headers) const; + int _validatePostHeaders(const Context& context, const std::map& headers) const; + int _validateDeleteHeaders(const std::map& headers) const; + bool _hasTargetHeader(const std::string& target, const std::map& headers) const; + HttpResponse _handleDirListing(const Context& context); HttpResponse _handleDirRequest(const Context& context); @@ -53,8 +61,10 @@ class StaticFileHandler HttpResponse _createResponseForFile(const Context& context) const; + HttpResponse _createDirListingResponse(const Context& context) const; - + std::string _genDirListingHtml(const std::string& path) const; + std::string _genListing(const std::string& path) const; HttpResponse _handleRoot(const Context& context); HttpResponse _handleNotFound(const Context& context); @@ -62,7 +72,6 @@ class StaticFileHandler std::string _buildAbsolutePathWithRoot(const Context& context) const; std::string _buildAbsolutePathWithIndex(const Context& context) const; - void _setHandledPath(const std::string& fullPath); }; diff --git a/include/Util.hpp b/include/Util.hpp new file mode 100644 index 0000000..a50d081 --- /dev/null +++ b/include/Util.hpp @@ -0,0 +1,18 @@ +#ifndef UTIL_HPP +# define UTIL_HPP + +# include +# include +# include +# include + +std::string toString(const int value); +std::string toString(const size_t value); + +// FIXME: delete this function +std::string toString(const ssize_t value); +std::string toString(const std::vector& values); + +size_t toSizeT(const std::string& value); + +#endif \ No newline at end of file diff --git a/include/webserv.hpp b/include/webserv.hpp index f26f5f6..0669d2d 100644 --- a/include/webserv.hpp +++ b/include/webserv.hpp @@ -3,10 +3,10 @@ /* ::: :::::::: */ /* webserv.hpp :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: sanghupa +#+ +:+ +#+ */ +/* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/22 21:04:39 by sanghupa #+# #+# */ -/* Updated: 2024/07/23 00:01:43 by sanghupa ### ########.fr */ +/* Updated: 2024/10/23 10:15:54 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ @@ -59,4 +59,15 @@ extern volatile bool g_sigint; +#define RESET "\033[0m" + +// Regular Colors +#define RED "\033[1;31m" +#define GREEN "\033[1;32m" +#define YELLOW "\033[1;33m" +#define BLUE "\033[1;34m" +#define MAGENTA "\033[1;35m" +#define CYAN "\033[1;36m" +#define WHITE "\033[1;37m" + #endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 1d78761..3734724 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,10 +3,10 @@ /* ::: :::::::: */ /* main.cpp :+: :+: :+: */ /* +:+ +:+ +:+ */ -/* By: sanghupa +#+ +:+ +#+ */ +/* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/22 21:05:14 by sanghupa #+# #+# */ -/* Updated: 2024/07/22 23:45:33 by sanghupa ### ########.fr */ +/* Updated: 2024/10/22 19:20:23 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ diff --git a/src/server/Context.cpp b/src/server/Context.cpp index a4d3229..738df4b 100644 --- a/src/server/Context.cpp +++ b/src/server/Context.cpp @@ -12,8 +12,6 @@ Context::Context(ServerConfig& config, HttpRequest& request) Context::Context(const Context& other) : _serverConfig(other._serverConfig), _request(other._request), _location(other._location) { - if (!this->isInitialized()) - throw std::invalid_argument("Context not initialized"); } Context& Context::operator=(const Context& other) @@ -31,9 +29,8 @@ Context::~Context() { } - //////////////////////////////////////////////////////////////////////////////// -/// Public Methods +/// Public Methods: Getters //////////////////////////////////////////////////////////////////////////////// const ServerConfig& Context::getServer() const @@ -51,6 +48,10 @@ const HttpRequest& Context::getRequest() const return (_request); } +//////////////////////////////////////////////////////////////////////////////// +/// Public Methods: setters +//////////////////////////////////////////////////////////////////////////////// + void Context::setRequest(HttpRequest& request) { _request = request; @@ -66,29 +67,47 @@ void Context::setLocation(Location& location) _location = location; } -bool Context::isInitialized() const -{ - if (&_serverConfig != NULL && &_location != NULL && &_request != NULL) - return (true); - return (false); -} //////////////////////////////////////////////////////////////////////////////// /// Private Methods //////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/// Prototype to test TODO: Remove +void test_different_path(const std::map& locations); +//////////////////////////////////////////////////////////////////////////////// + + +/// @brief finds the location object that matches the request URI. +/// @param context +/// @return if the location is found, returns the location object. +/// Otherwise, returns the default location object. +/// +/// @example +/// if location has = "/", "/path/", "/path/to/" +/// and uri = +/// - "/" => returns the default root location object. +/// - "/path" => returns the location object for "/". +/// - "/path/" => returns the location object for "/path/". +/// - "/path//////to/dir/" => returns the location object for "/path/to/". +/// - "/path/to//////////// => returns the location object for "/path/to/". Location& Context::_findLocation(Context& context) const { - const std::string& uri = context.getRequest().getUri(); - + const std::string& uri = context.getRequest().getUri(); std::map locations = _serverConfig.map_locationObjs; + + + //// Test function + // test_different_path(locations); + std::string matchedLocation = getMatchedLocation(uri, locations); - if (!matchedLocation.empty()) + if (!matchedLocation.empty() && locations.find(matchedLocation) != locations.end()) return (*locations.at(matchedLocation)); - throw std::invalid_argument("No match found for path: " + uri); + if (locations.find("/") == locations.end()) + throw std::runtime_error("No matching location found and root location is not defined."); + return (*locations.at("/")); } - /// @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 @@ -98,42 +117,94 @@ Location& Context::_findLocation(Context& context) const /// @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 getMatchedLocation(const std::string& path, const std::map& locations) { std::string matched; std::string parentPath; if (path.empty()) throw std::invalid_argument("Path cannot be empty"); - if (path == "/") - { - std::map::const_iterator it = locations.find(path); - if (it != locations.end()) - 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()) + if (locations.empty()) + throw std::invalid_argument("Locations map cannot be empty"); + std::string::size_type lastSlashPos = path.find_last_of('/'); + if (lastSlashPos == std::string::npos) + return (""); + std::string normalisedPath = normalisePath(path); + lastSlashPos = normalisedPath.find_last_of('/'); + if (lastSlashPos == 0) + return ("/"); + while (!normalisedPath.empty()) { - std::map::const_iterator it = locations.find(path); + std::map::const_iterator it = locations.find(normalisedPath); if (it != locations.end()) { - matched = path; + matched = normalisedPath; break; } - path = getParentPath(path); - if (path == "/") + normalisedPath = getParentPath(normalisedPath); + if (normalisedPath == "/") break; } return (matched); } +/// @brief Extracts the parent path from a given path. +/// @param path +/// @return return the parent path of the given path. std::string getParentPath(const std::string& path) { + if (path == "/") + return path; - ssize_t lastSlashPos = path.find_last_of('/'); + std::string trimmedPath = path; + + if (trimmedPath.length() > 1 && trimmedPath[trimmedPath.length() - 1] == '/') // while? if? + trimmedPath.erase(trimmedPath.length() - 1); + std::cout << "trimmedPath: " << trimmedPath << std::endl; + + std::string::size_type lastSlashPos = trimmedPath.find_last_of('/'); + if (lastSlashPos == std::string::npos) + return (""); if (lastSlashPos == 0) return ("/"); - return (path.substr(0, lastSlashPos)); + return (trimmedPath.substr(0, lastSlashPos + 1)); +} + +std::string normalisePath(const std::string& path) +{ + std::string normalisedPath = path; + + while (normalisedPath.find("//") != std::string::npos) + normalisedPath.replace(normalisedPath.find("//"), 2, "/"); + return (normalisedPath); +} + +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +/// Test Functions for the Context class | TODO: Remove +//////////////////////////////////////////////////////////////////////////////// +void test_path_unit(const std::string& path, const std::map& locations) +{ + std::cout << "----------------------------------------------------------------------" << std::endl; + std::cout << "TEST | path: " << path << std::endl; + std::string matchedLocation = getMatchedLocation(path, locations); + std::cout << "matchedLocation: " << matchedLocation << std::endl; +} + +void test_different_path(const std::map& locations) +{ + test_path_unit("/", locations); + test_path_unit("///////////////", locations); + test_path_unit("/image/", locations); + test_path_unit("/images/", locations); + test_path_unit("/images//////////////////", locations); + test_path_unit("/images/not_exist/", locations); + test_path_unit("/images/not_exist/////////////////////also_not_exist/", locations); + test_path_unit("/images/test_dir/", locations); + test_path_unit("/images/test_dir////////////////////", locations); + test_path_unit("/images///////////////test_dir////////////////////", locations); + test_path_unit("/images/test_dir/ho/", locations); + std::cout << "--------------test unit done------------------------------------------" << std::endl << std::endl << std::endl; + } \ No newline at end of file diff --git a/src/server/ErrorResponse.cpp b/src/server/ErrorResponse.cpp index a4a1308..9f2e87b 100644 --- a/src/server/ErrorResponse.cpp +++ b/src/server/ErrorResponse.cpp @@ -17,14 +17,13 @@ ErrorResponse::~ErrorResponse() //////////////////////////////////////////////////////////////////////////////// HttpResponse ErrorResponse::generateErrorResponse(int code) { - if (code < 400 || code > 599) - throw std::runtime_error("Invalid error code"); + if (checkStatusRange(code) != STATUS_ERROR) + throw std::runtime_error("Invalid error code: " + toString(code)); t_page_detail pageData = _fetchPageData(code); if (pageData.path.empty() || !pageData.isValid) return (_createSimpleHttpResponse(code)); - HttpResponse resp(_context, pageData.path); resp.setStatusCode(code); return (resp); diff --git a/src/server/HttpRequest.cpp b/src/server/HttpRequest.cpp index 2ae4fc3..15e615c 100644 --- a/src/server/HttpRequest.cpp +++ b/src/server/HttpRequest.cpp @@ -6,16 +6,27 @@ /* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:00 by sanghupa #+# #+# */ -/* Updated: 2024/07/25 16:51:55 by minakim ### ########.fr */ +/* Updated: 2024/10/23 11:29:10 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ #include "webserv.hpp" #include "HttpRequest.hpp" - HttpRequest::HttpRequest() -{} + : _body(""), _type(NONE), _content(false, NOT_SET) +{ +} + +HttpRequest::HttpRequest(std::string& data) + : _body(""), _type(NONE), _content(false, NOT_SET) +{ + /// FIXME: check logic + if (parse(data)) + std::cout << "TEST | HttpRequest | parse success" << std::endl; + else + std::cout << "TEST | HttpRequest | parse failed" << std::endl; +} HttpRequest::~HttpRequest() {} @@ -32,39 +43,92 @@ HttpRequest::~HttpRequest() /// @return bool bool HttpRequest::parse(const std::string& requestData) { - t_readed_parts separatedData = _splitRequestData(requestData); - if (!separatedData.iscomplete) + ReadedLines splitedRequestData = _splitRequestData(requestData); + if (!_parseRequestLine(splitedRequestData.request)) return (false); - if (!_parseRequestLine(separatedData.request)) + if (!_parseHeaders(splitedRequestData.headers)) return (false); - if (!_parseHeaders(separatedData.headers)) + if (!_processRequestBody(splitedRequestData.bodyLines)) return (false); - if (_method == "POST" && !_parseBody(separatedData.body)) + return (true); +} + +bool HttpRequest::_processRequestBody(const std::string& bodyLines) +{ + if (!hasBody()) + return (true); + if (getContentLength() <= 0) return (false); + if (_headers["Content-Type"] == "application/json" || _headers["Content-Type"] == "text/plain") + setBody(bodyLines, RAW); + else if (_headers["Transfer-Encoding"] == "chunked") + setBody(bodyLines, CHUNKED); + else if (_headers["Content-Type"].find("multipart/form-data") != std::string::npos) + setBody(bodyLines, FORM_DATA); return (true); } +//////////////////////////////////////////////////////////////////////////////// + +/* +/// @example POST request with chunked body +POST /upload HTTP/1.1 +Host: example.com +Transfer-Encoding: chunked +Content-Type: text/plain + +7\r\n +Mozilla\r\n +9\r\n +Developer\r\n +7\r\n +Network\r\n +0\r\n +\r\n +*/ + +//////////////////////////////////////////////////////////////////////////////// + +/* +/// @example POST request with multipart/form-data +POST /upload HTTP/1.1 +Host: example.com +Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW + +------WebKitFormBoundary7MA4YWxkTrZu0gW +Content-Disposition: form-data; name="text" + +Hello, World! +------WebKitFormBoundary7MA4YWxkTrZu0gW +Content-Disposition: form-data; name="file"; filename="example.txt" +Content-Type: text/plain + +(File content here) +------WebKitFormBoundary7MA4YWxkTrZu0gW-- +*/ + + //////////////////////////////////////////////////////////////////////////////// /// @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_readed_parts HttpRequest::_splitRequestData(const std::string& requestData) +ReadedLines HttpRequest::_splitRequestData(const std::string& requestData) { - t_readed_parts data; + ReadedLines data; std::istringstream iss(requestData); std::string readline; std::string drafts; - if (requestData.empty()) + if (requestData.empty()) return (data); + if (!std::getline(iss, readline)) return (data); data.request = readline; data.headers = _convertPartToHeaders(iss); if (data.headers.empty()) return (data); - data.body = _convertPartToBody(iss); - data.iscomplete = true; + data.bodyLines = _convertPartToBodyLines(iss); return (data); } @@ -78,15 +142,14 @@ std::vector HttpRequest::_convertPartToHeaders(std::istringstream& return (res); } -std::string HttpRequest::_convertPartToBody(std::istringstream& iss) +std::string HttpRequest::_convertPartToBodyLines(std::istringstream& iss) { - std::string readline; - std::string drafts; + std::string readline; + // std::vector drafts; + std::string drafts; while (std::getline(iss, readline)) - drafts += readline; - if (drafts.empty()) - return (""); + drafts += readline + "\n"; return (drafts); } @@ -94,7 +157,7 @@ std::string HttpRequest::_convertPartToBody(std::istringstream& iss) /// @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 -bool HttpRequest::_parseRequestLine(const std::string requestLine) +bool HttpRequest::_parseRequestLine(const std::string& requestLine) { std::string trimmedLine = trim(requestLine); std::istringstream iss(trimmedLine); @@ -110,10 +173,10 @@ bool HttpRequest::_parseRequestLine(const std::string requestLine) /// @brief Parses the header lines and extracts the headers. /// @param headerLines key:value pairs separated by `\r\n` /// @return bool -bool HttpRequest::_parseHeaders(const std::vector headerLines) +bool HttpRequest::_parseHeaders(const std::vector &headerLines) { if (headerLines.empty()) - return false; + return (false); for (std::vector::const_iterator it = headerLines.begin(); it != headerLines.end(); ++it) @@ -132,20 +195,12 @@ bool HttpRequest::_parseHeaders(const std::vector headerLines) return (false); _headers.insert(std::make_pair(key, value)); } + if (_headers.find("Content-Length") != _headers.end()) + _content = std::make_pair(true, toSizeT(_headers["Content-Length"])); return (true); } -/// @brief Parses the body and extracts the body. -/// @param bodylines exameple key1=value1&key2=value2 -/// @return bool -bool HttpRequest::_parseBody(const std::string bodylines) -{ - if (bodylines.empty()) - return (false); - _body = bodylines; - return (true); -} - +//////////////////////////////////////////////////////////////////////////////// /// @brief Trims the string by removing leading and trailing whitespace. /// @details `WHITESPACE`: Whitespace includes: space, tab, carriage return, and newline. /// @param str `const std::string&`, The string to be trimmed. @@ -161,9 +216,10 @@ std::string HttpRequest::trim(const std::string& str) return (str.substr(first, last - first + 1)); } +//////////////////////////////////////////////////////////////////////////////// +/// Checker functions //////////////////////////////////////////////////////////////////////////////// -// TODO: check for necessary initialization bool HttpRequest::isConnectionClose() const { std::map::const_iterator it = _headers.find("Connection"); @@ -198,15 +254,73 @@ std::map HttpRequest::getHeaders() const std::string HttpRequest::getBody() const { - if (_body.empty()) - return (""); return (_body); } + +size_t HttpRequest::getContentLength() const +{ + return (_content.second); +} + + //////////////////////////////////////////////////////////////////////////////// /// Setters //////////////////////////////////////////////////////////////////////////////// -void HttpRequest::setUri(std::string uri) +void HttpRequest::setUri(const std::string& uri) { _uri = uri; } + +void HttpRequest::setMethod(const std::string& method) +{ + _method = method; +} + +void HttpRequest::setVersion(const std::string& version) +{ + _version = version; +} + +void HttpRequest::setHeaders(const std::map& headers) +{ + _headers = headers; +} + +bool HttpRequest::hasBody() const +{ + return (_content.first); +} + +void HttpRequest::setBody(const std::vector& bodyLines, e_body_type type) +{ + if (!hasBody() || getContentLength() == 0) + return; + std::string bodyLinesToString = toString(bodyLines); + if (bodyLinesToString.length() != getContentLength()) + throw std::runtime_error( + "HTTP method [" + getMethod() + "] at URI [" + getUri() + "] encountered a body length mismatch: " + "Expected Content-Length = " + toString(getContentLength()) + + ", but received body length = " + toString(bodyLinesToString.length()) + "."); + _body = bodyLinesToString; + _type = type; +} + +void HttpRequest::setBody(const std::string& bodyLines, e_body_type type) +{ + if (!hasBody() || getContentLength() == 0) + return; + if (bodyLines.length() != getContentLength()) + throw std::runtime_error( + "HTTP method [" + getMethod() + "] at URI [" + getUri() + "] encountered a body length mismatch: " + "Expected Content-Length = " + toString(getContentLength()) + + ", but received body length = " + toString(bodyLines.length()) + "."); + _body = bodyLines; + _type = type; +} + +void HttpRequest::setContentLength(const ssize_t& contentLength) +{ + if (hasBody()) + _content.second = contentLength; +} \ No newline at end of file diff --git a/src/server/HttpResponse.cpp b/src/server/HttpResponse.cpp index ea5a5f2..bdab49f 100644 --- a/src/server/HttpResponse.cpp +++ b/src/server/HttpResponse.cpp @@ -6,7 +6,7 @@ /* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:00 by sanghupa #+# #+# */ -/* Updated: 2024/08/08 21:47:19 by minakim ### ########.fr */ +/* Updated: 2024/10/22 14:12:58 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ @@ -17,44 +17,12 @@ #include "Context.hpp" //////////////////////////////////////////////////////////////////////////////// +/// @brief 42 pdf /// 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"), _location(NULL) -// { -// } - -// /// @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"), _location(NULL) -// { -// initializefromFile(filePath); -// } - -/// @brief Default constructor for the HttpResponse class. -/// Initializes the status code to 200 and the status message to "OK". -// HttpResponse::HttpResponse(const ServerConfig& config) -// : _statusCode(200), _statusMessage("OK"), _serverConfig(&config) -// { -// } - -// /// @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 ServerConfig& config, const std::string& filePath) -// :_statusCode(200), _statusMessage("OK"), _serverConfig(&config) -// { -// initializefromFile(filePath); -// } - HttpResponse::HttpResponse(const Context& context) : _statusCode(200), _statusMessage("OK"), _context(const_cast(context)) { @@ -63,7 +31,7 @@ HttpResponse::HttpResponse(const Context& context) HttpResponse::HttpResponse(const Context& context, const std::string& filePath) : _statusCode(200), _statusMessage("OK"), _context(const_cast(context)) { - initializefromFile(filePath); + initializefromFile(context, filePath); } /// @brief Copy constructor for the HttpResponse class. @@ -97,15 +65,24 @@ HttpResponse::~HttpResponse() } //////////////////////////////////////////////////////////////////////////////// - +/// Public member functions: toString //////////////////////////////////////////////////////////////////////////////// -std::string HttpResponse::toString() const +std::string HttpResponse::generateResponseToString() const { return (getResponseLine() + _getHeadersString() + "\r\n\r\n" + _body); } //////////////////////////////////////////////////////////////////////////////// +/// Public member functions: set default headers +/// @fn setDefaultHeadersImpl: +/// Basic implementation of setting the default headers for an HttpResponse object. +/// @fn setDefaultHeaders(HttpResponse& resp): Static function +/// Use in static function to set the default headers for an HttpResponse object. +/// @fn setDefaultHeaders(): +/// Read the Object's body and set the default headers for the response. +//////////////////////////////////////////////////////////////////////////////// + /// @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. @@ -131,39 +108,46 @@ void HttpResponse::_setDefaultHeadersImpl() setHeader("Connection", "close"); } +//////////////////////////////////////////////////////////////////////////////// +/// Public member functions: initializefromFile //////////////////////////////////////////////////////////////////////////////// /// @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. /// @warning If the file cannot be opened, the response point a 404 error(). -void HttpResponse::initializefromFile(const std::string& filePath) +void HttpResponse::initializefromFile(const Context& context, const std::string& filePath) { - _fileToBody(filePath); - if (_body.empty() || _bodyLength <= 0) + _fileToBody(context, filePath); + if (_body.empty()) // FIXME:if file is empty, what shuld I do? return ; if (_statusCode == 200) setDefaultHeaders(); } - + +//////////////////////////////////////////////////////////////////////////////// +/// Private member functions +//////////////////////////////////////////////////////////////////////////////// /// @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) +void HttpResponse::_fileToBody(const Context& context, const std::string& filePath) { std::ifstream file(filePath.c_str(), std::ios::binary | std::ios::ate); - std::string body; + std::string body = ""; std::streamsize fileLength; + // FIXME: unused parameter. why? + (void)context; if (!file.is_open()) { *this = notFound_404(_context); return ; } fileLength = file.tellg(); - if (fileLength <= 0) + if (fileLength < 0) { *this = internalServerError_500(_context); return ; @@ -184,8 +168,8 @@ void HttpResponse::_fileToBody(const std::string& filePath) } //////////////////////////////////////////////////////////////////////////////// -/// @brief Returns the headers as a string. -/// @return std::string, The headers as a string. +/// @brief returns the headers as a string. +/// @return `std::string`, The headers as a string. std::string HttpResponse::_getHeadersString() const { std::string headers; @@ -199,7 +183,7 @@ std::string HttpResponse::_getHeadersString() const } /// @brief returns the status line as a string. -/// @return std::string, The status line as a string. +/// @return `std::string`, The status line as a string. std::string HttpResponse::_getStatusLine() const { std::stringstream statusLine; @@ -209,9 +193,9 @@ 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. +/// @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() +const std::map& HttpResponse::_staticInitStatusMap() { static std::map statusMap; @@ -245,17 +229,17 @@ const std::map& HttpResponse::_StaticInitStatusMap() /// 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. +/// @throws `std::runtime_error` if the status code is not found in the map. void HttpResponse::setStatusCode(int code) { _statusCode = code; - const std::map& statusMap = _StaticInitStatusMap(); + 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())); + throw std::runtime_error("Unknown status code: " + toString(getStatusCode())); } //////////////////////////////////////////////////////////////////////////////// @@ -271,6 +255,8 @@ HttpResponse HttpResponse::_createSimpleHttpResponse(int code) return (resp); } +/// @brief generates a simple HTML response body for a given HTTP status code. +/// @return `std::string`, The generated HTML response body. std::string HttpResponse::_generateHtmlBody() { if (toString(getStatusCode()).empty() || getStatusMessage().empty()) @@ -284,6 +270,10 @@ std::string HttpResponse::_generateHtmlBody() return (body); } +/// @brief constructs a page detail object for a given file path. +/// `t_page_detail` is a struct that contains the path of the file and a boolean flag indicating if the file is valid. +/// @param path +/// @return `t_page_detail` t_page_detail HttpResponse::_constructPageDetail(const std::string& path) { t_page_detail page; @@ -291,74 +281,87 @@ t_page_detail HttpResponse::_constructPageDetail(const std::string& path) page.isValid = isFile(path); return (page); } + //////////////////////////////////////////////////////////////////////////////// -/// error responses. +/// Protected member funtion: createErrorResponse(), error responses //////////////////////////////////////////////////////////////////////////////// -HttpResponse HttpResponse::_createErrorResponse(int code) + +/// @brief use it to create an error response with status code. +/// @param code status code. +/// @return reutrn `ErrorResponse` object. +HttpResponse HttpResponse::createErrorResponse(int code) { - if (code < 400 || code > 599) - throw std::runtime_error("Invalid error code"); - if (&_context == NULL) - throw std::runtime_error("Location is not set"); + if (checkStatusRange(code) != STATUS_ERROR) + throw std::runtime_error("Invalid error code: " + toString(code)); ErrorResponse errorResp(_context); return (errorResp.generateErrorResponse(code)); } //////////////////////////////////////////////////////////////////////////////// -/// static error responses. +/// Public member function: createErrorResponse(), static error responses. //////////////////////////////////////////////////////////////////////////////// -HttpResponse HttpResponse::_createErrorResponse(int code, const Context& context) + +/// @brief Static, use it to create an error response +/// @param code status code. +/// @param context reference to the context object. +/// @return reutrn `ErrorResponse` object. +HttpResponse HttpResponse::createErrorResponse(int code, const Context& context) { - if (code < 400 || code > 599) - throw std::runtime_error("Invalid error code"); - ErrorResponse errorResp(context); - return (errorResp.generateErrorResponse(code)); + if (checkStatusRange(code) != STATUS_ERROR) + throw std::runtime_error("Invalid error code: " + toString(code)); + + ErrorResponse errorResp(context); + return (errorResp.generateErrorResponse(code)); } +//////////////////////////////////////////////////////////////////////////////// +/// Public member functions: static error responses. +//////////////////////////////////////////////////////////////////////////////// + HttpResponse HttpResponse::badRequest_400(const Context& context) { - return (_createErrorResponse(400, context)); + return (createErrorResponse(400, context)); } HttpResponse HttpResponse::forbidden_403(const Context& context) { - return (_createErrorResponse(403, context)); + return (createErrorResponse(403, context)); } HttpResponse HttpResponse::notFound_404(const Context& context) { - return ( _createErrorResponse(404, context)); + return ( createErrorResponse(404, context)); } HttpResponse HttpResponse::methodNotAllowed_405(const Context& context) { - return (_createErrorResponse(405, context)); + return (createErrorResponse(405, context)); } HttpResponse HttpResponse::requestTimeout_408(const Context& context) { - return (_createErrorResponse(408, context)); + return (createErrorResponse(408, context)); } HttpResponse HttpResponse::requestEntityTooLarge_413(const Context& context) { - return (_createErrorResponse(413, context)); + return (createErrorResponse(413, context)); } HttpResponse HttpResponse::imaTeapot_418(const Context& context) { - return (_createErrorResponse(418, context)); + return (createErrorResponse(418, context)); } HttpResponse HttpResponse::internalServerError_500(const Context& context) { - return (_createErrorResponse(500, context)); + return (createErrorResponse(500, context)); } HttpResponse HttpResponse::notImplemented_501(const Context& context) { - return (_createErrorResponse(501, context)); + return (createErrorResponse(501, context)); } /// @brief Creates a successful HTTP response with status code 200. @@ -372,6 +375,25 @@ HttpResponse HttpResponse::success_200(const Context& context) return (resp); } +/// @brief Checks if the status code is within the valid range. +/// @param code status code. +/// @return return the status range, `e_status`. +/// @enum e_statue. +e_status HttpResponse::checkStatusRange(int code) +{ + if (code < 100 || code > 599) + return (STATUS_INVALID); + if (code < 200) + return (STATUS_INFORMATIONAL); + if (code < 300) + return (STATUS_SUCCESS); + if (code < 400) + return (STATUS_REDIRECTION); + if (code < 600) + return (STATUS_ERROR); + return (STATUS_INVALID); +} + //////////////////////////////////////////////////////////////////////////////// /// Setters //////////////////////////////////////////////////////////////////////////////// @@ -424,20 +446,6 @@ std::string HttpResponse::getResponseLine() const 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 //////////////////////////////////////////////////////////////////////////////// diff --git a/src/server/RequestHandler.cpp b/src/server/RequestHandler.cpp index 76cab10..ac722c2 100644 --- a/src/server/RequestHandler.cpp +++ b/src/server/RequestHandler.cpp @@ -6,7 +6,7 @@ /* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:00 by sanghupa #+# #+# */ -/* Updated: 2024/08/08 20:51:35 by minakim ### ########.fr */ +/* Updated: 2024/10/23 11:32:44 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ @@ -42,20 +42,52 @@ RequestHandler::~RequestHandler() /// 3. or the result of `_processRequest` if the method is valid and the location is found. HttpResponse RequestHandler::handleRequest(const Context& context) { - // // FIXME: change to Logger + // std::cout << "\r" << request.getMethod() << " | " << request.getUri() << " | " << // request.getVersion() << std::endl; - HttpResponse reps(context); - if (!_isAllowedMethod(context)) - return (reps.methodNotAllowed_405(context)); - return (_processRequest(context)); + if (_isCGIReqeust(context)) + return (_handleCGIRequest(context)); + else if (_isAllowedMethod(context)) + return (_processStandardMethods(context)); + return (HttpResponse::methodNotAllowed_405(context)); } //////////////////////////////////////////////////////////////////////////////// /// Private Methods //////////////////////////////////////////////////////////////////////////////// +/// @brief Checks if the request is a CGI request at the current location. +/// @param context +/// @return bool +bool RequestHandler::_isCGIReqeust(const Context& context) const +{ + + + std::map cgiMap = context.getLocation().getCgi(); + std::cout << YELLOW << "TEST | location.getCgi()\n" << RESET << std::endl; + for (std::map::const_iterator it = cgiMap.begin(); it != cgiMap.end(); ++it) + { + std::cout << YELLOW << " key: " << it->first << " -> value: " << it->second << RESET << std::endl; + } + std::cout << YELLOW << " --- cgi map end" << RESET << std::endl; + + + if (context.getLocation().getCgi().empty()) + return (false); + return (true); +} + +/// @brief Not implemented. +/// @param context +/// @return +HttpResponse RequestHandler::_handleCGIRequest(const Context& context) +{ + // env, fork... + return (HttpResponse::notImplemented_501(context)); +} + +//////////////////////////////////////////////////////////////////////////////// /// @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"}. @@ -81,9 +113,10 @@ bool RequestHandler::_isAllowedMethod(const Context& context) const /// @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 Context& context) +HttpResponse RequestHandler::_processStandardMethods(const Context& context) { const HttpRequest& request = context.getRequest(); + if (request.getMethod() == "GET") return (_handleGet(context)); else if (request.getMethod() == "POST") @@ -96,12 +129,12 @@ HttpResponse RequestHandler::_processRequest(const Context& context) HttpResponse RequestHandler::_handleGet(const Context& context) { - return (_staticFileHandler.handleRequest(context)); + return (_staticFileHandler.handleget(context)); } HttpResponse RequestHandler::_handlePost(const Context& context) { - return (HttpResponse::notImplemented_501(context)); + return (_staticFileHandler.handlepost(context)); } HttpResponse RequestHandler::_handleDelete(const Context& context) @@ -109,6 +142,7 @@ HttpResponse RequestHandler::_handleDelete(const Context& context) return (HttpResponse::notImplemented_501(context)); } + //////////////////////////////////////////////////////////////////////////////// // TODO: Redirections diff --git a/src/server/RequestParser.backup b/src/server/RequestParser.backup new file mode 100644 index 0000000..fef454b --- /dev/null +++ b/src/server/RequestParser.backup @@ -0,0 +1,377 @@ +#include "RequestParser.hpp" + +//////////////////////////////////////////////////////////////////////////////// +/// BodyCOnatiner +//////////////////////////////////////////////////////////////////////////////// + +BodyContainer::BodyContainer() + : body(NULL), bodyType(Body::NONE) {} + +BodyContainer::~BodyContainer() +{ + delete body; +} + +void BodyContainer::initRaw() +{ + clear(); + body = new Raw(); + bodyType = Body::RAW; +} + +void BodyContainer::initRaw(const std::string& rawData) { + clear(); + body = new Raw(rawData); + bodyType = Body::RAW; +} +void BodyContainer::initChunked() +{ + clear(); + body = new Chunked(); + bodyType = Body::CHUNKED; +} + +void BodyContainer::initFormData() +{ + clear(); + body = new FormData(); + bodyType = Body::FORM_DATA; +} + +void BodyContainer::initFormData(const std::string& boundary) +{ + clear(); + FormData* formData = new FormData(); + formData->setBoundary(boundary); + body = formData; + bodyType = Body::FORM_DATA; +} + +void BodyContainer::clear() +{ + delete body; + body = NULL; + bodyType = Body::NONE; +} + +bool BodyContainer::empty() const +{ + return (body == NULL); +} + +Body::Type BodyContainer::getType() const +{ + return (bodyType); +} + +Raw* BodyContainer::getRaw() const +{ + if (bodyType == Body::RAW) + return (static_cast(body)); + return (NULL); +} + +Chunked* BodyContainer::getChunked() const +{ + if (bodyType == Body::CHUNKED) + return (static_cast(body)); + return (NULL); +} + +FormData* BodyContainer::getFormData() const +{ + if (bodyType == Body::FORM_DATA) + return (static_cast(body)); + return (NULL); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Raw +//////////////////////////////////////////////////////////////////////////////// + +Raw::Raw() + : _data("") +{ +} + +Raw::Raw(const std::string& data) + : _data(data) +{ +} + +void Raw::setData(const std::string& data) +{ + _data = data; +} + +std::string Raw::getData() const +{ + return (_data); +} + +std::string Raw::toString() const +{ + return (_data); +} + +Body::Type Raw::getType() const +{ + return( Body::RAW); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Chunked +//////////////////////////////////////////////////////////////////////////////// + +ChunkSegment::ChunkSegment() + : _data(""), _size(0), _isEOF(false) +{ +} + +ChunkSegment::ChunkSegment(const std::string& chunkData, const size_t chunkSize) + : _data(chunkData), _size(chunkSize), _isEOF(false) +{ +} + +size_t ChunkSegment::parseSize(const std::string& line) +{ + size_t readedSize; + std::stringstream ss; + + ss << std::hex << removeCRLF(line); + ss >> readedSize; + return (readedSize); +} + +std::string ChunkSegment::parseData(const std::string& line) +{ + return (removeCRLF(line)); +} + +std::string ChunkSegment::getData() const +{ + return (_data); +} + +size_t ChunkSegment::getSize() const +{ + return (_size); +} + +bool ChunkSegment::getIsEOF() const +{ + return (_isEOF); +} + +void ChunkSegment::setData(const std::string& chunkData) +{ + _data = chunkData; +} + +void ChunkSegment::setSize(size_t chunkSize) +{ + _size = chunkSize; +} + +void ChunkSegment::setEOF() +{ + _isEOF = true; +} + +//////////////////////////////////////////////////////////////////////////////// + +Chunked::Chunked() + : _isComplete(false) +{ +} + +Chunked::Chunked(const ChunkSegment& chunk) + : _isComplete(false) +{ + addChunkSegment(chunk); +} + + +void Chunked::addChunkSegment(const ChunkSegment& chunk) +{ + _datas.push_back(chunk); +} + +void Chunked::setComplete() +{ + _isComplete = true; +} + +std::string Chunked::assembleBody() const +{ + std::string body; + + for (std::vector::const_iterator it = _datas.begin(); it != _datas.end(); ++it) + body += it->getData(); + return (body); +} + +std::string Chunked::toString() const +{ + return (assembleBody()); +} + +Body::Type Chunked::getType() const +{ + return (Body::CHUNKED); +} + +//////////////////////////////////////////////////////////////////////////////// +/// FormDataPart +//////////////////////////////////////////////////////////////////////////////// + +FormDataEntry::FormDataEntry() + : _name(""), _filename(""), _contentType(""), _data(""), _isProcessed(false) +{ +} + +FormDataEntry::FormDataEntry(const std::string& partName, const std::string& partData, + const std::string& partFilename, const std::string& partContentType) + : _name(partName), _filename(partFilename), _contentType(partContentType), _data(partData), _isProcessed(false) +{ +} + +FormDataEntry::FormDataEntry(const FormDataEntry& formData) + : _name(formData._name), _filename(formData._filename), _contentType(formData._contentType), + _data(formData._data), _isProcessed(formData._isProcessed) +{ +} + +//////////////////////////////////////////////////////////////////////////////// + +void FormDataEntry::handleContentDisposition(const std::string& line) +{ + std::string::size_type pos = line.find("Content-Disposition: "); + if (pos != std::string::npos) + { + std::string contentDisposition = line.substr(pos + 21); + std::string::size_type namePos = contentDisposition.find("name=\""); + std::string::size_type filenamePos = contentDisposition.find("filename=\""); + std::string::size_type endPos = contentDisposition.find("\"", namePos + 6); + + if (namePos != std::string::npos) + _name = contentDisposition.substr(namePos + 6, endPos - namePos - 6); + if (filenamePos != std::string::npos) + { + std::string::size_type endPos = contentDisposition.find("\"", filenamePos + 10); + _filename = contentDisposition.substr(filenamePos + 10, endPos - filenamePos - 10); + } + } +} + +void FormDataEntry::handleContentType(const std::string& line) +{ + std::string::size_type pos = line.find("Content-Type: "); + if (pos != std::string::npos) + { + std::string contentType = line.substr(pos + 14); + setContentType(contentType); + } +} + +//////////////////////////////////////////////////////////////////////////////// + + +std::string FormDataEntry::getName() const +{ + return (_name); +} + +std::string FormDataEntry::getFilename() const +{ + return (_filename); +} + +std::string FormDataEntry::getContentType() const +{ + return (_contentType); +} + +std::string FormDataEntry::getData() const +{ + return (_data); +} + +bool FormDataEntry::getIsProcessed() const +{ + return (_isProcessed); +} + +//////////////////////////////////////////////////////////////////////////////// + +void FormDataEntry::setFilename(const std::string& partFilename) +{ + _filename = partFilename; +} + +void FormDataEntry::setContentType(const std::string& partContentType) +{ + _contentType = partContentType; +} + +void FormDataEntry::setName(const std::string& partName) +{ + _name = partName; +} + +void FormDataEntry::setData(const std::string& partData) +{ + _data = partData; +} + +void FormDataEntry::setProcessed() +{ + _isProcessed = true; +} + +//////////////////////////////////////////////////////////////////////////////// + +FormData::FormData() + : _datas() +{ +} + +FormData::FormData(const FormDataEntry& formData) +{ + _datas.push_back(formData); +} + +void FormData::addFormDataEntry(const FormDataEntry& formData) +{ + _datas.push_back(formData); +} + +void FormData::setBoundary(const std::string& partBoundary) +{ + _boundary = partBoundary; +} + +std::string FormData::getBoundary() const +{ + return (_boundary); +} + +std::string FormData::toString() const // FIXME: sample code need to fix (auto, logic ... ) +{ + std::stringstream ss; + for (const auto& entry : _datas) + { + ss << "Name: " << entry.getName() << "\n"; + ss << "Filename: " << entry.getFilename() << "\n"; + ss << "ContentType: " << entry.getContentType() << "\n"; + ss << "Data: " << entry.getData() << "\n"; + ss << "------\n"; + } + return (ss.str()); +} + +Body::Type FormData::getType() const +{ + return (Body::FORM_DATA); +} \ No newline at end of file diff --git a/src/server/Server.cpp b/src/server/Server.cpp index 688a73c..a564330 100644 --- a/src/server/Server.cpp +++ b/src/server/Server.cpp @@ -6,7 +6,7 @@ /* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:46 by sanghupa #+# #+# */ -/* Updated: 2024/08/08 21:35:43 by minakim ### ########.fr */ +/* Updated: 2024/10/23 11:36:18 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ @@ -136,8 +136,10 @@ void Server::start() } else { - // Data received on an existing client connection - char buffer[1024]; + ServerConfig& serverConfig = _fetchConfig(target); // 여기로 + + // Data received on an existing client connection + char buffer[serverConfig.max_body_size + 1]; ssize_t count = read(target, buffer, sizeof(buffer)); if (count <= 0) { @@ -150,22 +152,31 @@ void Server::start() } // buffer[count] = '\0'; + //////////////////////////////////////////////////////////////////////////////////////// + // FIXME: need to handle bigger size of data, not only 1024 + std::string requestData(buffer, count); - HttpRequest request; + std::cout << YELLOW << "TEST | requestData (server.cpp, buffer to std::string)" << std::endl; + std::cout << "buffer: " << count << ", string size: "<< requestData.size() << std::endl; + std::cout << RESET << "\nrequestData\n\n" << YELLOW << requestData << "\n\n" << RESET << std::endl; + + + // @author minakim + // FIXME: @sanghupa please update this part - if (!request.parse(requestData)) - return ; // throw? + // ServerConfig& serverConfig = _fetchConfig(target); + HttpRequest request(requestData); - ServerConfig& serverConfig = _fetchConfig(target); + // test line: POST, bad request + // request.setMethod("POST"); + // request.setContentLength(10); + Context contextFromTarget(serverConfig, request); HttpResponse response = _requestHandler.handleRequest(contextFromTarget); - std::string responseData = response.toString(); + std::string responseData = response.generateResponseToString(); // std::string responseData = handle_request(requestData); - - - std::cout << "TEST | start: handleClientData_2" << std::endl; - std::cout << "TEST | " << requestData << std::endl; + //////////////////////////////////////////////////////////////////////////////////////// // Send the response write(target, responseData.c_str(), responseData.size()); @@ -189,6 +200,8 @@ void Server::start() } } +/// @author minakim +/// FIXME: @sanghupa please change or delete this method ServerConfig& Server::_fetchConfig(int target) { ServerConfig& serverConfig = *_config.getServerByListen(_clients[target].listen); @@ -307,19 +320,18 @@ void Server::_handleClientData(int clientSocket, size_t idx) } std::string requestData(buffer, count); - - HttpRequest request; + HttpRequest request(requestData); - if (!request.parse(requestData)) - return ; // throw? + // if (!request.parse(requestData)) + // return ; // throw? // HttpResponse response = _requestHandler.handleRequest(request, _fetchConfig(clientSocket)); // std::string responseData = response.toString(); ServerConfig& serverConfig = _fetchConfig(clientSocket); Context contextFromTarget(serverConfig, request); HttpResponse response = _requestHandler.handleRequest(contextFromTarget); - std::string responseData = response.toString(); + std::string responseData = response.generateResponseToString(); std::cout << "TEST | start: handleClientData_2" << std::endl; std::cout << "TEST | " << requestData << std::endl; diff --git a/src/server/StaticFileHandler.cpp b/src/server/StaticFileHandler.cpp index 39d9f91..2f5e908 100644 --- a/src/server/StaticFileHandler.cpp +++ b/src/server/StaticFileHandler.cpp @@ -6,7 +6,7 @@ /* By: minakim +#+ +:+ +#+ */ /* +#+#+#+#+#+ +#+ */ /* Created: 2024/06/30 16:23:00 by sanghupa #+# #+# */ -/* Updated: 2024/08/08 22:00:37 by minakim ### ########.fr */ +/* Updated: 2024/10/23 10:29:02 by minakim ### ########.fr */ /* */ /* ************************************************************************** */ @@ -41,32 +41,100 @@ StaticFileHandler::~StaticFileHandler() {} //////////////////////////////////////////////////////////////////////////////// -/// Public methods: handleRequest +/// Public methods: handleget //////////////////////////////////////////////////////////////////////////////// -/// @brief handleRequest, public method to handle the request -/// This method is called by the RequestHandler to handle the request. +/// @brief handleget, public method to handle the request +/// This method is called by the handleget 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 Context& context) +HttpResponse StaticFileHandler::handleget(const Context& context) { - this->_initMimeTypes(context); + _initMimeTypes(); + int status = _verifyHeaders(context); + if (HttpResponse::checkStatusRange(status) != STATUS_SUCCESS) + return (HttpResponse::createErrorResponse(status, context)); + if (context.getRequest().getUri() == "/") return (_handleRoot(context)); _setHandledPath(_buildPathWithUri(context)); if (isDir(_handledPath)) { - // if (/* && location.location.isListDir() */) - // return (_handleDirListing(request, location)); + if (context.getLocation().isListdir()) + return (_handleDirListing(context)); return (_handleDirRequest(context)); } - if (isFile(_handledPath)) + else if (isFile(_handledPath)) return (_handleFileRequest(context)); return (_handleNotFound(context)); } +//////////////////////////////////////////////////////////////////////////////// +/// Public methods: handlepost +//////////////////////////////////////////////////////////////////////////////// + +HttpResponse StaticFileHandler::handlepost(const Context& context) +{ + _initMimeTypes(); + int status = _verifyHeaders(context); + if (HttpResponse::checkStatusRange(status) != STATUS_SUCCESS) + return (HttpResponse::createErrorResponse(status, context)); + HttpResponse resp(context); + return (resp); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Private methods: verify headers +//////////////////////////////////////////////////////////////////////////////// + +int StaticFileHandler::_verifyHeaders(const Context& context) const +{ + const std::string& method = context.getRequest().getMethod(); + const std::map& headers = context.getRequest().getHeaders(); + + if (method == "GET") + return (_validateGetHeaders(headers)); + else if (method == "POST") + return (_validatePostHeaders(context, headers)); + else if (method == "DELETE") + return (_validateDeleteHeaders(headers)); + else + throw std::runtime_error("Wrong method checked during verify headers. " + method); +} + +int StaticFileHandler::_validateGetHeaders(const std::map& headers) const +{ + if (!_hasTargetHeader("Host", headers)) + return (400); + return (200); +} + +int StaticFileHandler::_validatePostHeaders(const Context& context, const std::map& headers) const +{ + if (context.getRequest().getBody().empty()) + return (400); + if (!_hasTargetHeader("Content-Length", headers)) + return (411); + if (!_hasTargetHeader("Content-Type", headers)) + return (400); + return (200); +} + +int StaticFileHandler::_validateDeleteHeaders(const std::map& headers) const +{ + (void)headers; + return (false); +} + +bool StaticFileHandler::_hasTargetHeader(const std::string& target, const std::map& headers) const +{ + if (headers.find(target) == headers.end()) + return (false); + return (true); +} + //////////////////////////////////////////////////////////////////////////////// /// Private methods @@ -83,7 +151,7 @@ HttpResponse StaticFileHandler::_handleDirRequest(const Context& context) Context& moditiedContext = const_cast(context); _setHandledPath(_buildAbsolutePathWithIndex(context)); - std::cout << "TEST | absolute path that with index is built: " << _handledPath << std::endl; + std::cout << " absolute path that with index is built: " << _handledPath << std::endl; if (!isFile(_handledPath)) return (_handleNotFound(context)); modifiedRequest.setUri(getFullPath()); @@ -110,13 +178,12 @@ HttpResponse StaticFileHandler::_handleDirListing(const Context& context) return (_createDirListingResponse(context)); } -// 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 Context& context) const { HttpResponse resp(context); - resp.setBody(genDirListingHtml(getFullPath())); + resp.setBody(_genDirListingHtml(getFullPath())); if (resp.getBody().empty() || resp.getBodyLength() <= 0) return (HttpResponse::internalServerError_500(context)); @@ -126,8 +193,8 @@ HttpResponse StaticFileHandler::_createDirListingResponse(const Context& context /// @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) +/// @return Create a list of directories and files in HTML and export it to `std::string`. +std::string StaticFileHandler::_genDirListingHtml(const std::string& path) const { std::stringstream html; std::string body; @@ -136,13 +203,40 @@ std::string genDirListingHtml(const std::string& path) html << "Directory Listing"; html << "

Directory Listing for " << path << "

    "; - // TODO: Implement directory listing + html << _genListing(path); html << "
"; body = html.str(); return (body); } +std::string StaticFileHandler::_genListing(const std::string& path) const +{ + DIR* dir = opendir(path.c_str()); + struct dirent* entry; + std::string listing; + + if (dir == NULL) + return ("
  • Error opening directory
  • "); + else + { + while ((entry = readdir(dir)) != NULL) + { + // TODO: implement: sort the list + std::string name = entry->d_name; + if (name != "." && name != "..") + { + if (entry->d_type == DT_DIR) + listing += "
  • " + name + "
  • "; + else + listing += "
  • " + name + "
  • "; + } + } + closedir(dir); + return (listing); + } +} + //////////////////////////////////////////////////////////////////////////////// HttpResponse StaticFileHandler::_handleFileRequest(const Context& context) { @@ -177,10 +271,9 @@ std::string StaticFileHandler::_buildAbsolutePathWithRoot(const Context& context if (locationRoot.empty()) throw std::runtime_error("Root path is empty"); - if (defaultFile.empty()) // if can not read from cong, give default value: hard code + if (defaultFile.empty()) defaultFile = INDEX_HTML; std::string fullPath = "." + serverRoot + locationRoot + "/" + defaultFile; - std::cout << "TEST | absolute path that with index is built: " << _handledPath << std::endl; return (fullPath); } @@ -193,7 +286,7 @@ std::string StaticFileHandler::_buildPathWithUri(const Context& context) const throw std::runtime_error("Root path is empty"); std::string fullPath = "." + serverRoot + locationRoot + context.getRequest().getUri(); - std::cout << "TEST | first path that is built: " << _handledPath << std::endl; + std::cout << "TEST | absolute path that is built: " << fullPath << std::endl; return(fullPath); } @@ -225,18 +318,17 @@ std::string StaticFileHandler::getFullPath() const //////////////////////////////////////////////////////////////////////////////// /// Mime types //////////////////////////////////////////////////////////////////////////////// - -// FIXME: logic changed, need to updat: "context" parameter /// @brief Initializes the MIME types map from the configuration. /// If the configuration does not provide MIME types, default values are used. -void StaticFileHandler::_initMimeTypes(const Context& context) +void StaticFileHandler::_initMimeTypes() { - (void)context; - // ServerConfig& serverConfig = const_cast(context.getServer()); - // if (serverConfig.getMimeTypes().size() > 0) - // _staticMimeTypes = serverConfig.getMimeTypes(); - // else - // { + Config& config = Config::getInstance(); + + config.getMimeTypeMap(); + if (config.getMimeTypeMap().size() > 0) + _mimeTypes = config.getMimeTypeMap(); + else + { _mimeTypes.insert(std::make_pair("html", "text/html")); _mimeTypes.insert(std::make_pair("css", "text/css")); _mimeTypes.insert(std::make_pair("js", "application/javascript")); @@ -244,7 +336,7 @@ void StaticFileHandler::_initMimeTypes(const Context& context) _mimeTypes.insert(std::make_pair("jpg", "image/jpeg")); _mimeTypes.insert(std::make_pair("gif", "image/gif")); _mimeTypes.insert(std::make_pair("txt", "text/plain")); - // } + } } /// @brief Returns the MIME type of a given file based on its file extension. diff --git a/src/util/Util.cpp b/src/util/Util.cpp new file mode 100644 index 0000000..697c729 --- /dev/null +++ b/src/util/Util.cpp @@ -0,0 +1,42 @@ +#include "Util.hpp" +#include + + + +// FIXME: error handling or copy from cpp06 +std::string toString(const int value) +{ + std::ostringstream oss; + oss << value; + return (oss.str()); +} + +std::string toString(const size_t value) +{ + std::ostringstream oss; + oss << value; + return (oss.str()); +} + +std::string toString(const ssize_t value) +{ + std::ostringstream oss; + oss << value; + return (oss.str()); +} + +std::string toString(const std::vector& values) +{ + std::string result = ""; + for (std::vector::const_iterator it = values.begin(); it != values.end(); ++it) + result += *it; + return (result); +} + +size_t toSizeT(const std::string& value) +{ + std::istringstream iss(value); + size_t result; + iss >> result; + return (result); +} \ No newline at end of file diff --git a/webserv_test b/webserv_test new file mode 100755 index 0000000..5ee5d30 Binary files /dev/null and b/webserv_test differ diff --git a/www/data/images/test_dir/test1 b/www/data/images/test_dir/test1 new file mode 100644 index 0000000..e69de29 diff --git a/www/data/images/test_dir/test2.html b/www/data/images/test_dir/test2.html new file mode 100644 index 0000000..6c70af2 --- /dev/null +++ b/www/data/images/test_dir/test2.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/www/data/images/test_dir/test3.html b/www/data/images/test_dir/test3.html new file mode 100644 index 0000000..e69de29 diff --git a/www/data/images/test_file_no_mimeype b/www/data/images/test_file_no_mimeype new file mode 100644 index 0000000..e69de29 diff --git a/www/data/images/test_html_empty.html b/www/data/images/test_html_empty.html new file mode 100644 index 0000000..e69de29 diff --git a/www/data/images/test_html_type.html b/www/data/images/test_html_type.html new file mode 100644 index 0000000..6c70af2 --- /dev/null +++ b/www/data/images/test_html_type.html @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/www/static/test.html b/www/static/test.html new file mode 100644 index 0000000..e4ef362 --- /dev/null +++ b/www/static/test.html @@ -0,0 +1,33 @@ + + + + + + File Upload + + +

    File Upload

    +
    + + +
    +
    + +
    + +

    Files currently stored on the server

    + + +


    +
    + + +        +        +       + +
    +
    + + + \ No newline at end of file