diff --git a/Makefile b/Makefile index f191630..4aef912 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ # By: adjoly +#+ +:+ +#+ # # +#+#+#+#+#+ +#+ # # Created: 2024/10/25 16:09:27 by adjoly #+# #+# # +# Updated: 2025/04/10 11:52:13 by mmoussou ### ########.fr # # Updated: 2025/03/25 18:13:53 by adjoly ### ########.fr # # # # **************************************************************************** # @@ -57,6 +58,7 @@ fclean: clean @rm -Rf $(OBJSDIR) @printf "$(RED)「🗑️」 fclean($(NAME)): program deleted\n" -re: fclean all +re: fclean + @$(MAKE) -s all .PHONY: clean fclean all re diff --git a/includes/requests/Errors.hpp b/includes/requests/Errors.hpp new file mode 100644 index 0000000..c57a05d --- /dev/null +++ b/includes/requests/Errors.hpp @@ -0,0 +1,47 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Errors.hpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: mmoussou +#include + +#include + +namespace webserv { +namespace http { + +/* + * DOES NOT WORK + * still need to do uh things but base is done at least :D +*/ + +class Errors { +public: + //static http::Response &getRequest(int error_code); + static std::string getResponseBody(int error_code); + static void setEntry(const std::string &key, int value); + + static std::map message; +private: + static std::map populateMessages(); + + static std::map set_error_pages; + +}; + +} // -namespace http +} // -namespace webserv + +#endif // __WEBSERV_REQUESTS_ERRORS_HPP__ diff --git a/includes/requests/HttpIMessage.hpp b/includes/requests/HttpIMessage.hpp index e72e361..9ed38b2 100644 --- a/includes/requests/HttpIMessage.hpp +++ b/includes/requests/HttpIMessage.hpp @@ -6,7 +6,7 @@ /* By: mmoussou getHeaders(void) const; + virtual std::map getHeaders(void) const; virtual std::string getBody(void) const; - virtual void setHeaders(std::multimap const headers); + virtual void setHeaders(std::map const headers); virtual void setBody(std::string const body); virtual void addHeader(std::string const key, std::string const value); @@ -34,9 +34,11 @@ public: virtual std::string str(void) const = 0; protected: - std::multimap _headers; + std::map _headers; std::string _body; + static const std::map _response_status_codes; + }; } // -namespace http diff --git a/includes/requests/HttpRequest.hpp b/includes/requests/HttpRequest.hpp index 830bfaa..8670da5 100644 --- a/includes/requests/HttpRequest.hpp +++ b/includes/requests/HttpRequest.hpp @@ -6,7 +6,7 @@ /* By: mmoussou #include #include #include diff --git a/includes/requests/HttpResponse.hpp b/includes/requests/HttpResponse.hpp index c9c7304..1c4283b 100644 --- a/includes/requests/HttpResponse.hpp +++ b/includes/requests/HttpResponse.hpp @@ -6,7 +6,7 @@ /* By: mmoussou +typedef unsigned int uint; + namespace webserv { namespace http { @@ -26,18 +28,17 @@ public: Response(void); std::string getProtocol(void) const; - size_t getStatusCode(void) const; + uint getStatusCode(void) const; std::string getStatusText(void) const; void setProtocol(std::string const protocol); - void setStatusCode(size_t const status_code); - void setStatusText(std::string const status_text); + void setStatusCode(uint const status_code); std::string str(void) const; private: std::string _protocol; - size_t _status_code; + uint _status_code; std::string _status_text; }; diff --git a/includes/requests/default.hpp b/includes/requests/default.hpp index 3c8763d..82636b6 100644 --- a/includes/requests/default.hpp +++ b/includes/requests/default.hpp @@ -6,7 +6,7 @@ /* By: mmoussou #include #include diff --git a/src/main.cpp b/src/main.cpp index 7af2e1d..232db99 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ /* By: mmoussou -int main(int, char **) { - webserv::config::Server serverConf("exemples/test.toml"); - return 0; +#define PORT 8080 +#define BUFFER_SIZE 4096 + +int server_socket; +int client_socket; + +void close_socket(int signal) +{ + std::cerr << std::endl << "closing..." << std::endl; + close(client_socket); + close(server_socket); + exit(signal); +} + +std::string getMethod(std::string &data) +{ + return (data.substr(0, data.substr(0, 4).find_last_not_of(" ") + 1)); +} + +int main() +{ + // handle ctrl-C to close server socket + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR || signal(SIGINT, close_socket) == SIG_ERR || signal(SIGQUIT, close_socket) == SIG_ERR) + { + std::cerr << "Error registering signal handlers!" << std::endl; + return 1; + } + + // create a socket + server_socket = socket(AF_INET, SOCK_STREAM, 0); + if (server_socket == -1) + { + std::cerr << "Failed to create socket" << std::endl; + return 1; + } + + // prepare the server address + sockaddr_in server_address; + std::memset(&server_address, 0, sizeof(server_address)); + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = INADDR_ANY; + server_address.sin_port = htons(PORT); + + if (bind(server_socket, (sockaddr*)&server_address, sizeof(server_address)) == -1) + { + std::cerr << "Failed to bind socket" << std::endl; + return 1; + } + if (listen(server_socket, 5) == -1) + { + std::cerr << "Failed to listen on socket" << std::endl; + return 1; + } + + std::cout << "Server is listening on port " << PORT << std::endl; + + while (true) + { + // accept an incoming connection + sockaddr_in client_address; + socklen_t client_address_len = sizeof(client_address); + //int client_socket = accept(server_socket, (sockaddr*)&client_address, &client_address_len); + client_socket = accept(server_socket, (sockaddr*)&client_address, &client_address_len); + if (client_socket == -1) { + std::cerr << "Failed to accept connection" << std::endl; + continue; + } + + // receive the HTTP request + std::string received_data; + char buffer[BUFFER_SIZE]; + ssize_t bytes_received; + do + { + std::memset(buffer, 0, BUFFER_SIZE); + bytes_received = recv(client_socket, buffer, BUFFER_SIZE - 1, 0); + if (bytes_received == -1) + { + std::cerr << "Failed to receive request" << std::endl; + close(client_socket); + continue; + } + received_data += std::string(buffer, bytes_received); + } + while (buffer[bytes_received]); + + // parse the request + + // handle the request + std::string response; + + //std::cout << received_data << std::endl; + std::cout << getMethod(received_data) << std::endl; + + if (getMethod(received_data) == "GET") + { + std::cout << "------------ GET REQUEST ------------" << std::endl; + http::Get request(received_data); + + response = request.execute().str(); + } + else if (getMethod(received_data) == "POST") + { + std::cout << "------------ POST REQUEST ------------" << std::endl; + http::Post request(received_data); + + response = request.execute().str(); + //std::cout << "worked" << std::endl; + } + else + { + response = "HTTP/1.1 501 Not Implemented\r\nContent-Type: text/html\r\n\r\n

501 Not Implemented

"; + } + + send(client_socket, response.c_str(), response.length(), 0); + //close(client_socket); + } + + close(server_socket); + return 0; } diff --git a/src/requests_handling/Errors.cpp b/src/requests_handling/Errors.cpp new file mode 100644 index 0000000..8684d28 --- /dev/null +++ b/src/requests_handling/Errors.cpp @@ -0,0 +1,99 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ +/* Errors.cpp :+: :+: :+: */ +/* +:+ +:+ +:+ */ +/* By: mmoussou + +using namespace webserv; + +std::string http::Errors::getResponseBody(int error_code) +{ + std::string body; + + if (http::Errors::set_error_pages.find(error_code) != http::Errors::set_error_pages.end()) + return(Errors::set_error_pages[error_code]); + else + return("

" + http::Errors::message[error_code] + "

"); +} + +std::map http::Errors::message = Errors::populateMessages(); +std::map http::Errors::set_error_pages; + +std::map http::Errors::populateMessages() +{ + std::map m; + + m[100] = "Continue"; + m[101] = "Switching Protocols"; + m[102] = "Processing"; + m[103] = "Early Hints"; + m[200] = "OK"; + m[201] = "Created"; + m[202] = "Accepted"; + m[203] = "Non-Authoritative Information"; + m[204] = "No Content"; + m[205] = "Reset Content"; + m[206] = "Partial Content"; + m[207] = "Multi-Status"; + m[208] = "Already Reported"; + m[226] = "IM Used"; + m[300] = "Multiple Choices"; + m[301] = "Moved Permanently"; + m[302] = "Found"; + m[303] = "See Other"; + m[304] = "Not Modified"; + m[305] = "Use Proxy"; + m[306] = "Switch Proxy"; + m[307] = "Temporary Redirect"; + m[308] = "Permanent Redirect"; + m[400] = "Bad Request"; + m[401] = "Unauthorized"; + m[402] = "Payment Required"; + m[403] = "Forbidden"; + m[404] = "Not Found"; + m[405] = "Method Not Allowed"; + m[406] = "Not Acceptable"; + m[407] = "Proxy Authentication Required"; + m[408] = "Request Timeout"; + m[409] = "Conflict"; + m[410] = "Gone"; + m[411] = "Length Required"; + m[412] = "Precondition Failed"; + m[413] = "Payload Too Large"; + m[414] = "URI Too Long"; + m[415] = "Unsupported Media Type"; + m[416] = "Range Not Satisfiable"; + m[417] = "Expectation Failed"; + m[418] = "I'm a teapot"; + m[420] = "Method Failure"; + m[421] = "Misdirected Request"; + m[422] = "Unprocessable Entity"; + m[423] = "Locked"; + m[424] = "Failed Dependency"; + m[426] = "Upgrade Required"; + m[428] = "Precondition Required"; + m[429] = "Too Many Requests"; + m[431] = "Request Header Fields Too Large"; + m[451] = "Unavailable For Legal Reasons"; + m[500] = "Internal Server error"; + m[501] = "Not Implemented"; + m[502] = "Bad Gateway"; + m[503] = "Service Unavailable"; + m[504] = "gateway Timeout"; + m[505] = "Http version not supported"; + m[506] = "Varient Also negotiate"; + m[507] = "Insufficient Storage"; + m[508] = "Loop Detected"; + m[510] = "Not Extended"; + m[511] = "Network Authentication Required"; + + return m; +} diff --git a/src/requests_handling/HttpIMessage.cpp b/src/requests_handling/HttpIMessage.cpp index 0a1a85d..2b91323 100644 --- a/src/requests_handling/HttpIMessage.cpp +++ b/src/requests_handling/HttpIMessage.cpp @@ -6,7 +6,7 @@ /* By: mmoussou http::IMessage::getHeaders(void) const +std::map http::IMessage::getHeaders(void) const { return (this->_headers); } @@ -24,7 +24,7 @@ std::string http::IMessage::getBody(void) const return (this->_body); } -void http::IMessage::setHeaders(std::multimap const headers) +void http::IMessage::setHeaders(std::map const headers) { this->_headers = headers; } diff --git a/src/requests_handling/HttpRequests.cpp b/src/requests_handling/HttpRequests.cpp index 37ccdec..1ac8959 100644 --- a/src/requests_handling/HttpRequests.cpp +++ b/src/requests_handling/HttpRequests.cpp @@ -6,11 +6,17 @@ /* By: mmoussou +#include + #include +#include +#include +#include using namespace webserv; @@ -21,7 +27,7 @@ std::string http::IRequest::str(void) const response << this->_method << " " << this->_target << " " << this->_protocol; response << "\r\n"; - for (std::multimap::const_iterator it = this->_headers.begin(); it != this->_headers.end(); ++it) + for (std::map::const_iterator it = this->_headers.begin(); it != this->_headers.end(); ++it) response << it->first << ": " << it->second << "\r\n"; response << "\r\n"; @@ -103,6 +109,7 @@ void http::Get::parse(std::string const &data) this->_body = body_stream.str(); /* + std::cout << "-- start-line --" << std::endl; std::cout << "method: " << this->_method << std::endl; std::cout << "target: " << this->_target << std::endl; std::cout << "protocol: " << this->_protocol << std::endl; @@ -115,30 +122,289 @@ void http::Get::parse(std::string const &data) */ } +char isDirectory(const std::string& path) +{ + struct stat file_stat; + + if (stat(path.c_str(), &file_stat) != 0) + { + throw std::runtime_error("can't open file (non-existant ?)"); + } + return S_ISDIR(file_stat.st_mode); +} + http::Response http::Get::execute(void) { http::Response response; try { - std::ifstream file(this->_target.c_str(), std::ios::binary); - response.setProtocol(this->_protocol); - response.setStatusCode(200); - response.setStatusText("OK"); - response.addHeader("Content-Type", "text/html"); - response.setBody(std::string((std::istreambuf_iterator(file)), std::istreambuf_iterator())); + if (isDirectory(this->_target)) + { + DIR *dir; + struct dirent *entry; + struct stat file_stat; + std::vector files; + + if ((dir = opendir(this->_target.c_str())) == NULL) + throw; + while ((entry = readdir(dir)) != NULL) + { + std::string file_name = entry->d_name; + if (file_name == ".") + continue; + std::string file_path = this->_target + "/" + file_name; + if (stat(file_path.c_str(), &file_stat) == 0) + { + if (S_ISDIR(file_stat.st_mode)) + files.push_back(file_name + "/"); + else + files.push_back(file_name); + } + } + closedir(dir); + + std::sort(files.begin(), files.end()); + + std::string body = "
    \n"; + for (size_t i = 0; i < files.size(); i++) + body += "
  • " + files[i] + "
  • \n"; + body += "
"; + + response.setProtocol(this->_protocol); + response.setStatusCode(200); + std::stringstream length; + length << body.length(); + response.addHeader("Content-Length", length.str()); + response.addHeader("Content-Type", "text/html"); + response.setBody(body); + + } + else + { + std::ifstream file(this->_target.c_str(), std::ios::binary); + std::streampos file_start = file.tellg(); + response.setBody(std::string((std::istreambuf_iterator(file)), std::istreambuf_iterator())); + std::stringstream length; + length << (file.tellg() - file_start); + response.addHeader("Content-Length", length.str()); + + response.setProtocol(this->_protocol); + response.setStatusCode(200); + response.addHeader("Content-Type", "text/html"); // TODO: change it to check the file extension and set it to the corresponding MIME or text/plain if unkown. we will only implement the important MIME types in the Mozilla documentation because https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types/Common_types + + //std::ifstream file_end(this->_target.c_str(), std::ios::binary | std::ios::ate); + //std::stringstream length; + //length << (file_end.tellg() - file.tellg()); + //std::cout << length.str() << std::endl; + //response.addHeader("Content-Length", length.str()); + } } catch (...) { // TODO: replace with a predefined array of error pages response.setProtocol(this->_protocol); response.setStatusCode(404); - response.setStatusText("Not Found"); response.addHeader("Content-Type", "text/html"); - response.setBody("nuh uh, get 404'd >:D"); + response.setBody(http::Errors::getResponseBody(response.getStatusCode())); } return (response); } // ------------------------------------------------------------------ + +http::Delete::Delete(void) +{ +} + +http::Delete::Delete(std::string &data) +{ + this->parse(data); +} + +void http::Delete::parse(std::string const &data) +{ + std::istringstream stream(data); + std::string line; + + if (std::getline(stream, line)) + { + std::istringstream line_stream(line); + line_stream >> this->_method >> this->_target >> this->_protocol; + this->_target.insert(this->_target.begin(), '.'); + } + + while (std::getline(stream, line) && line != "\r") + { + size_t delimiter_index = line.find(':'); + if (delimiter_index != std::string::npos) + { + std::string key = line.substr(0, delimiter_index); + std::string value = line.substr(delimiter_index + 2); + this->_headers.insert(std::make_pair(key, value)); + } + } + + std::ostringstream body_stream; + while (std::getline(stream, line)) + body_stream << line << "\n"; + this->_body = body_stream.str(); + + /* + std::cout << "-- start-line --" << std::endl; + std::cout << "method: " << this->_method << std::endl; + std::cout << "target: " << this->_target << std::endl; + std::cout << "protocol: " << this->_protocol << std::endl; + std::cout << std::endl; + std::cout << "-- headers --" << std::endl; + for (std::map::const_iterator it = this->_headers.begin(); it != this->_headers.end(); ++it) + std::cout << it->first << ": " << it->second << std::endl; + std::cout << std::endl; + std::cout << "-- body --" << std::endl << this->_body << std::endl; + */ +} + +http::Response http::Delete::execute(void) +{ + http::Response response; + + try + { + if (std::remove(this->_target.c_str())) + throw std::runtime_error("can't remove file, FF"); + response.setProtocol(this->_protocol); + response.setStatusCode(204); + time_t now = std::time(NULL); + response.addHeader("Date", std::string(std::ctime(&now))); + } + catch (...) + { + // TODO: check errno value and get corresponding error page, check for corresponding error code : https://cdn.discordapp.com/attachments/784779058407014403/1350841524778307586/image.png?ex=67d8dd74&is=67d78bf4&hm=c030468d3862627d6402bf200960d1a15249ba2f8dac772af3283b368a77f2f5& + + response.setProtocol(this->_protocol); + response.setStatusCode(404); + response.addHeader("Content-Type", "text/html"); + response.setBody(http::Errors::getResponseBody(response.getStatusCode())); + } + + return (response); +} + +// ------------------------------------------------------------------ + +http::Post::Post(void) +{ +} + +http::Post::Post(std::string &data) +{ + this->parse(data); +} + +void http::Post::parse(std::string const &data) +{ + std::istringstream stream(data); + std::string line; + + if (std::getline(stream, line)) + { + std::istringstream line_stream(line); + line_stream >> this->_method >> this->_target >> this->_protocol; + this->_target.insert(this->_target.begin(), '.'); + } + + while (std::getline(stream, line) && line != "\r") + { + size_t delimiter_index = line.find(':'); + if (delimiter_index != std::string::npos) + { + std::string key = line.substr(0, delimiter_index); + std::string value = line.substr(delimiter_index + 2); + this->_headers.insert(std::make_pair(key, value)); + } + } + + std::ostringstream body_stream; + while (std::getline(stream, line)) + body_stream << line << "\n"; + this->_body = body_stream.str(); + + /* + std::cout << "-- start-line --" << std::endl; + std::cout << "method: " << this->_method << std::endl; + std::cout << "target: " << this->_target << std::endl; + std::cout << "protocol: " << this->_protocol << std::endl; + std::cout << std::endl; + std::cout << "-- headers --" << std::endl; + for (std::map::const_iterator it = this->_headers.begin(); it != this->_headers.end(); ++it) + std::cout << it->first << ": " << it->second << std::endl; + std::cout << std::endl; + //std::cout << "-- body --" << std::endl << this->_body << std::endl; + */ +} + +std::string extractFilename(const std::string &header) +{ + size_t start = header.find("filename=\"") + 10; + size_t end = header.find("\"", start); + return header.substr(start, end - start); +} + +void handleMultipartData(const std::string &body, const std::string &boundary) +{ + size_t i = 0; + std::string delim = "--" + boundary; + delim.erase(delim.size() - 1); + + while ((i = body.find(delim, i)) != std::string::npos) + { + size_t start = i + delim.length(); + size_t end = body.find("\r\n\r\n", start); + + if (end != std::string::npos) + { + std::string part_header = body.substr(start, end - start); + //std::cout << std::endl << std::endl << std::endl << std::endl; + std::string part_content = body.substr(end + 4, body.find(delim, end) - end - 4); + + std::ofstream outfile(extractFilename(part_header).c_str(), std::ios::binary); + if (outfile.is_open()) + { + outfile.write(part_content.c_str(), part_content.length()); + outfile.close(); + } + else + { + std::cerr << "open failed" << std::endl; + } + } + + i += delim.length(); + } +} + +http::Response http::Post::execute(void) +{ + http::Response response; + + try + { + handleMultipartData(this->_body, this->getHeaders()["Content-Type"].substr(this->getHeaders()["Content-Type"].find("=", this->getHeaders()["Content-Type"].find(";")) + 1)); + + response.setProtocol(this->_protocol); + response.setStatusCode(200); + response.addHeader("Content-Type", "text/html"); + response.setBody(http::Errors::getResponseBody(response.getStatusCode())); + } + catch (...) + { + response.setProtocol(this->_protocol); + response.setStatusCode(500); + response.addHeader("Content-Type", "text/html"); + response.setBody(http::Errors::getResponseBody(response.getStatusCode())); + } + return (response); +} + +// ------------------------------------------------------------------ diff --git a/src/requests_handling/HttpResponse.cpp b/src/requests_handling/HttpResponse.cpp index 5857f28..9fccdb4 100644 --- a/src/requests_handling/HttpResponse.cpp +++ b/src/requests_handling/HttpResponse.cpp @@ -6,17 +6,21 @@ /* By: mmoussou +#include /* - do a map of all the status_text and get it from here, not storing them - get error pages from an array of predefined response, that would be modified by the config */ +// tmp, need to be cleaned +#include + using namespace webserv; http::Response::Response(void) @@ -30,12 +34,13 @@ std::string http::Response::str(void) const response << this->_protocol << " " << this->_status_code << " " << this->_status_text; response << "\r\n"; - for (std::multimap::const_iterator it = this->_headers.begin(); it != this->_headers.end(); ++it) + for (std::map::const_iterator it = this->_headers.begin(); it != this->_headers.end(); ++it) response << it->first << ": " << it->second << "\r\n"; response << "\r\n"; response << this->_body; + //std::cout << "------------ RESPONSE -------------" << std::endl << response.str(); return (response.str()); } @@ -44,7 +49,7 @@ std::string http::Response::getProtocol(void) const return (this->_protocol); } -size_t http::Response::getStatusCode(void) const +uint http::Response::getStatusCode(void) const { return (this->_status_code); } @@ -59,12 +64,8 @@ void http::Response::setProtocol(std::string const protocol) this->_protocol = protocol; } -void http::Response::setStatusCode(size_t const status_code) +void http::Response::setStatusCode(uint const status_code) { this->_status_code = status_code; -} - -void http::Response::setStatusText(std::string const status_text) -{ - this->_status_text = status_text; + this->_status_text = Errors::message[status_code]; } diff --git a/upload.html b/upload.html new file mode 100644 index 0000000..a9e575f --- /dev/null +++ b/upload.html @@ -0,0 +1,90 @@ + + + + upload + + + + + +
+
+ +

+

+
+ + + + +