diff --git a/conf/webserv_default.conf b/conf/webserv_default.conf index 2589d38b..a20130ee 100644 --- a/conf/webserv_default.conf +++ b/conf/webserv_default.conf @@ -7,7 +7,6 @@ server { upload_path /upload/; error_page 404 404.html; cgi_ext .cgi; - location /admin { return http://google.com; index admin.html; @@ -18,7 +17,6 @@ server { autoindex off; } } - server { listen 8080; server_name www.example.com; @@ -31,7 +29,6 @@ server { upload_path upload/; } } - server { listen 8081; server_name www.php_site.com; @@ -39,7 +36,6 @@ server { autoindex off; root var/; } - server { listen 8080; autoindex on; @@ -49,7 +45,6 @@ server { allow_methods GET POST DELETE; root var/; } - server { listen 8080; server_name www.python_site.com; @@ -59,6 +54,15 @@ server { root var/; } +server { + listen 8080; + server_name www.saladbook.xyz; + allow_methods GET POST DELETE; + autoindex on; + cgi_ext .cgi .py; + root var/; +} + server { listen 8080; server_name www.saladbook; diff --git a/database.json b/database.json new file mode 100644 index 00000000..2666c2b2 --- /dev/null +++ b/database.json @@ -0,0 +1,7 @@ +{ + "Leo": "Fruity", + "Stefano": "Carrots", + "Daniil": "with_beets", + "Jane": "Caesar", + "Janeddsads": "Caesar" +} \ No newline at end of file diff --git a/src/CGIHandler.cpp b/src/CGIHandler.cpp index e2a43cb8..ba5f2f82 100644 --- a/src/CGIHandler.cpp +++ b/src/CGIHandler.cpp @@ -39,7 +39,8 @@ void CGIHandler::handleRequest(HTTPRequest &request, HTTPResponse &response) Debug::log("CGIHandler::handleRequest", Debug::CGI); MetaVariables env; env.HTTPRequestToMetaVars(request, env); - if (!executeCGI(env, response)) + + if (!executeCGI(env, request.getBody(), response)) { response.setStatusCode(500, "Internal Server Error"); // TODO: it should be hardcoded @@ -69,6 +70,7 @@ std::vector CGIHandler::createArgvForExecve(const MetaVariables &en { argv.push_back(scriptPath); } + return argv; } @@ -102,26 +104,39 @@ void handleTimeout(int sig) Debug::log("CGIHandler: Timeout", Debug::CGI); } -bool CGIHandler::executeCGI(const MetaVariables &env, HTTPResponse &response) +bool CGIHandler::executeCGI(const MetaVariables &env, std::string body, HTTPResponse &response) { Debug::log("CGIHandler::executeCGI", Debug::CGI); std::string cgiOutput; std::vector argv = createArgvForExecve(env); std::vector envp = env.getForExecve(); + Debug::log("CGIHandler: executeCGI: body: " + body, Debug::NORMAL); + int pipeFD[2]; + int bodyPipeFD[2]; if (pipe(pipeFD) == -1) { perror("pipe failed"); return false; } + if (pipe(bodyPipeFD) == -1) + { + perror("body pipe failed"); + close(pipeFD[0]); + close(pipeFD[1]); + return false; + } + pid_t pid = fork(); if (pid == -1) { perror("fork failed"); close(pipeFD[0]); close(pipeFD[1]); + close(bodyPipeFD[0]); + close(bodyPipeFD[1]); return false; } else if (pid == 0) @@ -140,6 +155,10 @@ bool CGIHandler::executeCGI(const MetaVariables &env, HTTPResponse &response) dup2(pipeFD[1], STDOUT_FILENO); close(pipeFD[1]); + close(bodyPipeFD[1]); + dup2(bodyPipeFD[0], STDIN_FILENO); + close(bodyPipeFD[0]); + closeAllSocketFDs(); std::vector argvPointers = convertToCStringArray(argv); @@ -150,42 +169,45 @@ bool CGIHandler::executeCGI(const MetaVariables &env, HTTPResponse &response) Debug::log("CGIHandler: access failed", Debug::CGI); return false; _exit(EXIT_FAILURE); - // TODO: @leo I don't think we should exit here. We don't want to kill the whole server cause of a CGI - // error. No? } - // execve(argvPointers[0], argvPointers.data(), envpPointers.data()); if (execve(argvPointers[0], argvPointers.data(), envpPointers.data()) == -1) { Debug::log("CGIHandler: execve failed", Debug::CGI); return false; - // TODO: @leo We should check if execve failed and return an error response and not exti _exit(EXIT_FAILURE); } } - // This is executed if the CGI is started successfully - response.setIsCGI(true); - response.setCGIpipeFD(pipeFD); + else + { + close(pipeFD[1]); + close(bodyPipeFD[0]); + + write(bodyPipeFD[1], body.c_str(), body.size()); + close(bodyPipeFD[1]); - Debug::log("PIPE SAVED: " + toString(*response.getCGIpipeFD()), Debug::CGI); + response.setIsCGI(true); + response.setCGIpipeFD(pipeFD); - close(pipeFD[1]); - EventData data = {1, pid, pipeFD[0], pipeFD[1]}; // Assuming 1 is the event type for CGI started + close(pipeFD[1]); + EventData data = {1, pid, pipeFD[0], pipeFD[1]}; // Assuming 1 is the event type for CGI started - _eventManager.emit(data); // Emit event indicating a CGI process has started + _eventManager.emit(data); // Emit event indicating a CGI process has started - _connection.addCGI(pid); - Debug::log("CGIHandler: CGI PID: " + toString(pid), Debug::CGI); + _connection.addCGI(pid); + // std::cout << GREEN << _connection.getCGIPid() << RESET << std::endl; - // clang-format off - // std::vector > pipes = _eventManager.getPipeFDs(); - // for (std::vector >::const_iterator it = pipes.begin(); it != pipes.end(); ++it) - // { - // std::cout << GREEN << "CGIHandler: pipeFDs: " << (*it).first << RESET << std::endl; - // } - // clang-format on - Debug::log("CGIHandler: Waiting for CGI to finish", Debug::CGI); - return true; + // clang-format off + // std::vector > pipes = _eventManager.getPipeFDs(); + // for (std::vector >::const_iterator it = pipes.begin(); it != pipes.end(); ++it) + // { + // std::cout << GREEN << "CGIHandler: pipeFDs: " << (*it).first << RESET << std::endl; + // } + // clang-format on + // std::cout << RED << "Exiting CGIHandler::executeCGI with true" << RESET << std::endl; + return true; + } + return false; } void CGIHandler::setFDsRef(std::vector *FDsRef) diff --git a/src/CGIHandler.hpp b/src/CGIHandler.hpp index e06898f0..631e07d1 100644 --- a/src/CGIHandler.hpp +++ b/src/CGIHandler.hpp @@ -21,7 +21,7 @@ class CGIHandler : public AResponseHandler CGIHandler &operator=(const CGIHandler &other); virtual ~CGIHandler(); void handleRequest(HTTPRequest &request, HTTPResponse &response); - bool executeCGI(const MetaVariables &env, HTTPResponse &response); + bool executeCGI(const MetaVariables &env, std::string body, HTTPResponse &response); std::vector createArgvForExecve(const MetaVariables &env); std::vector convertToCStringArray(const std::vector &input); // void CGIStringToResponse(const std::string &cgiOutput, HTTPResponse &response); diff --git a/src/Debug.cpp b/src/Debug.cpp index 678626b2..9e6d9327 100644 --- a/src/Debug.cpp +++ b/src/Debug.cpp @@ -46,6 +46,7 @@ void Debug::log(const std::string &message, Debug::Level paramLevel) } if (debugLevel & paramLevel) { + (void)paramLevel; std::cout << message << std::endl; } } diff --git a/src/HTTPResponse.cpp b/src/HTTPResponse.cpp index d45f117d..3968df48 100644 --- a/src/HTTPResponse.cpp +++ b/src/HTTPResponse.cpp @@ -146,6 +146,7 @@ void HTTPResponse::setCGIpipeFD(int (&pipe)[2]) void HTTPResponse::CGIStringToResponse(const std::string &cgiOutput) { + // std::cout << YELLOW << cgiOutput << RESET << std::endl; std::size_t headerEndPos = cgiOutput.find("\r\n\r\n"); if (headerEndPos == std::string::npos) { @@ -153,6 +154,7 @@ void HTTPResponse::CGIStringToResponse(const std::string &cgiOutput) } std::string headersPart = cgiOutput.substr(0, headerEndPos); + // std::cout << "Headers: " << headersPart << std::endl; std::string bodyPart = cgiOutput.substr(headerEndPos); Debug::log("------------------CGIStringToResponse-------------------", Debug::CGI); @@ -172,13 +174,25 @@ void HTTPResponse::CGIStringToResponse(const std::string &cgiOutput) std::string headerName = headerLine.substr(0, separatorPos); std::string headerValue = headerLine.substr(separatorPos + 2); setHeader(headerName, headerValue); + // std::cout << "Header: " << headerName << ": " << headerValue << std::endl; + if (headerName == "Status") + { + std::size_t spacePos = headerValue.find(" "); + if (spacePos != std::string::npos) + { + std::string statusCodeStr = headerValue.substr(0, spacePos); + int statusCode = std::atoi(statusCodeStr.c_str()); + setStatusCode(statusCode, ""); + } + } } } setBody(bodyPart); // At his point we are done with the CGI so setIsCGI(false) // setIsCGI(true); - setStatusCode(200, ""); + if (_statusCode == 0) + setStatusCode(200, "OK"); return; } diff --git a/src/Listen.cpp b/src/Listen.cpp index bffa4d07..e0dbc328 100644 --- a/src/Listen.cpp +++ b/src/Listen.cpp @@ -56,7 +56,7 @@ Listen::Listen(const Listen &obj) _options = obj._options; _hasIpOrPort = obj._hasIpOrPort; - Debug::log("Listen copy constructor called", Debug::OCF); + // Debug::log("Listen copy constructor called", Debug::OCF); } Listen &Listen::operator=(const Listen &obj) diff --git a/src/MetaVariables.cpp b/src/MetaVariables.cpp index 45782004..1a943660 100644 --- a/src/MetaVariables.cpp +++ b/src/MetaVariables.cpp @@ -131,26 +131,13 @@ void MetaVariables::subtractQueryFromPathInfo(std::string &pathInfo, const std:: void MetaVariables::HTTPRequestToMetaVars(const HTTPRequest &request, MetaVariables &env) { - // TODO: several metavars have to be set from configfile - - // This line will have the code put the interpreter path as argv[0] to execve - // env.setVar("X_INTERPRETER_PATH", "/home/lmangall/.brew/bin/python3"); // school computer... env.setVar("X_INTERPRETER_PATH", X_INTERPRETER_PATH); - - //________General variables - // Set the method used for the request (e.g., GET, POST) env.setVar("REQUEST_METHOD", request.getMethod()); - // Set the protocol version used in the request (e.g., HTTP/1.1) env.setVar("PROTOCOL_VERSION", request.getProtocolVersion()); - env.setVar("SERVER_PORT", request.getSingleHeader("listen").second); - - //_______Server-related variables env.setVar("SERVER_SOFTWARE", "Server_of_people_identifying_as_objects/1.0"); env.setVar("SERVER_NAME", request.getSingleHeader("host").second); env.setVar("GATEWAY_INTERFACE", "CGI/1.1"); - - //_______Path-related variables std::string queryString = formatQueryString(request.getQueryString()); env.setVar("QUERY_STRING", queryString); @@ -164,22 +151,15 @@ void MetaVariables::HTTPRequestToMetaVars(const HTTPRequest &request, MetaVariab std::string host = request.getSingleHeader("host").second; std::string scriptName = pathComponents.first; std::string pathInfo = pathComponents.second; - Debug::log("MetaVariables::HTTPRequestToMetaVars: root: " + root, Debug::NORMAL); Debug::log("MetaVariables::HTTPRequestToMetaVars: host: " + host, Debug::NORMAL); Debug::log("MetaVariables::HTTPRequestToMetaVars: scriptName: " + scriptName, Debug::NORMAL); Debug::log("MetaVariables::HTTPRequestToMetaVars: pathInfo: " + pathInfo, Debug::NORMAL); - subtractQueryFromPathInfo(pathInfo, queryString); - env.setVar("PATH_INFO", pathInfo); - // std::string pathTranslated = translatePathToPhysical(scriptVirtualPath, pathInfo); // Implement this function env.setVar("PATH_TRANSLATED", root + host + scriptName); env.setVar("SCRIPT_NAME", scriptName); - // The query string from the URL sent by the client - - //_______set the metadata from the headers of the request std::pair contentTypeHeader = request.getSingleHeader("Content-Type"); if (!contentTypeHeader.first.empty()) { @@ -189,14 +169,15 @@ void MetaVariables::HTTPRequestToMetaVars(const HTTPRequest &request, MetaVariab { env.setVar("CONTENT_TYPE", ""); } - std::pair contentLengthHeader = request.getSingleHeader("Content-Length"); + std::pair contentLengthHeader = request.getSingleHeader("content-length"); if (!contentLengthHeader.first.empty()) { env.setVar("CONTENT_LENGTH", contentLengthHeader.second); + Debug::log("Content-Length header found.", Debug::NORMAL); } else { - env.setVar("CONTENT_LENGTH", "0"); + Debug::log("Content-Length header not found.", Debug::NORMAL); } } diff --git a/src/Parser.cpp b/src/Parser.cpp index 1652b1b5..83583a28 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -173,7 +173,7 @@ void Parser::parseHeaders(const char *request, HTTPRequest &req, HTTPResponse &r if (!hasCRLF(request, i, 0)) return (res.setStatusCode(400, "No CRLF after headers")); if (!hasMandatoryHeaders(req, res)) - return ; + return; _headersAreParsed = true; saveCokies(req); } @@ -219,7 +219,7 @@ void Parser::parseFileUpload(const std::string &request, HTTPRequest &req, HTTPR // ----------------UTILS---------------------------- -bool Parser::hasMandatoryHeaders(HTTPRequest &req, HTTPResponse& res) +bool Parser::hasMandatoryHeaders(HTTPRequest &req, HTTPResponse &res) { _isChunked = false; int isHost = 0; @@ -619,9 +619,8 @@ bool Parser::isOrigForm(std::string &requestTarget, int &queryStart) bool Parser::isValidContentType(std::string type) { -if (type == "text/plain" || type == "text/html" || \ - type.substr(0, 30) == "multipart/form-data; boundary=" \ - || type == "application/octet-stream") + if (type == "text/plain" || type == "text/html" || type.substr(0, 30) == "multipart/form-data; boundary=" || + type == "application/octet-stream" || type == "application/x-www-form-urlencoded") return (true); return (false); } diff --git a/src/Router.cpp b/src/Router.cpp index 2efdc273..145e9c2f 100644 --- a/src/Router.cpp +++ b/src/Router.cpp @@ -66,14 +66,13 @@ void Router::routeRequest(HTTPRequest &request, HTTPResponse &response) request.setRoot(root); adaptPathForFirefox(request); - Debug::log("Routing Request: path = " + request.getPath(), Debug::NORMAL); PathValidation pathResult = pathIsValid(response, request); Debug::log("Routing Request: pathResult = " + toString(pathResult), Debug::NORMAL); Debug::log("Path requested: " + request.getPath(), Debug::NORMAL); // check if method is allowed - + if (!_directive._allowedMethods.empty()) { for (size_t i = 0; i < _directive._allowedMethods.size(); i++) @@ -110,6 +109,7 @@ void Router::routeRequest(HTTPRequest &request, HTTPResponse &response) } else { + // std::cout << "Path is a static content, handling as static content" << std::endl; StaticContentHandler staticContentHandler; staticContentHandler.handleRequest(request, response); } diff --git a/src/Server.cpp b/src/Server.cpp index 71b1cef7..1ac696d5 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -75,13 +75,13 @@ void Server::startPollEventLoop() timeout = CLIENT_POLL_TIMEOUT_MS; // 15 seconds else timeout = -1; - //printConnections("BEFORE POLL", _FDs, _connections, true); + // printConnections("BEFORE POLL", _FDs, _connections, true); Debug::log(toString(CYAN) + "++++++++++++++ #" + toString(pollCounter) + \ " Waiting for new connection or Polling +++++++++++++++" + toString(RESET), Debug::SERVER); int ret = poll(_FDs.data(), _FDs.size(), timeout); pollCounter++; //printFrame("POLL EVENT DETECTED", true); - //printConnections("AFTER POLL", _FDs, _connections, true); + // printConnections("AFTER POLL", _FDs, _connections, true); if (ret > 0) { size_t originalSize = _FDs.size(); @@ -104,7 +104,7 @@ void Server::startPollEventLoop() } else if (_FDs[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { - if (i == 0) + if (_connections[i].getType() == SERVER) handleServerSocketError(); else handleClientSocketError(_FDs[i].fd, i); @@ -139,6 +139,7 @@ void Server::waitCGI() { if (_connections[i].getHasCGI() && _connections[i].getCGIPid() == pid) { + // std::cout << "CGI has exited" << std::endl; Debug::log("CGI has exited", Debug::CGI); _connections[i].setCGIExitStatus(status); _connections[i].setCGIHasCompleted(true); @@ -238,23 +239,27 @@ void Server::readFromClient(Connection &conn, size_t &i, Parser &parser, HTTPReq if (parser.getHeadersComplete() && request.getMethod() == "GET") conn.setHasFinishedReading(true); - if (request.getMethod() == "GET" || request.getMethod() == "DELETE" || request.getMethod() == "SALAD") + if (request.getMethod() == "GET" || request.getMethod() == "SALAD") Debug::log("GET request, no body to read", Debug::NORMAL); else - handlePostRequest(conn, parser, request, response); + handlePostAndDelete(conn, parser, request, response); } -void Server::handlePostRequest(Connection &conn, Parser &parser, HTTPRequest &request, HTTPResponse &response) +void Server::handlePostAndDelete(Connection &conn, Parser &parser, HTTPRequest &request, HTTPResponse &response) { + Debug::log("Entering handlePostAndDelete", Debug::NORMAL); + if (parser.getIsChunked() && !conn.getHasReadSocket()) { Debug::log("Chunked body", Debug::NORMAL); - if (!conn.readChunkedBody(parser)) + // TODO: (!conn.readBody(parser, request, response)) - does not make sense here + if (!conn.readChunkedBody(parser)) // && (!conn.readBody(parser, request, response)) { // only in case of system error == do not send response Debug::log("Error reading chunked body", Debug::OCF); conn.setCanBeClosed(true); conn.setHasFinishedReading(true); + conn.setHasDataToSend(true); return; } conn.setHasReadSocket(true); @@ -281,8 +286,11 @@ void Server::handlePostRequest(Connection &conn, Parser &parser, HTTPRequest &re } if (!parser.getBodyComplete() && request.getContentLength() != 0 && - parser.getBuffer().size() == request.getContentLength()) + parser.getBuffer().size() >= request.getContentLength()) { + // std::cout << "Body complete" << std::endl; + // std::cout << YELLOW << parser.getBuffer() << RESET << std::endl; + request.setBody(parser.getBuffer()); parser.setBodyComplete(true); conn.setHasFinishedReading(true); return; @@ -366,7 +374,7 @@ void Server::buildResponse(Connection &conn, size_t &i, HTTPRequest &request, HT void Server::readCGIPipe(Connection &conn, HTTPResponse &response) { - Debug::log("Entering buildCGIResponse", Debug::CGI); + Debug::log("Entering readCGIResponse", Debug::CGI); std::string cgiOutput; int *pipeFD; pipeFD = response.getCGIpipeFD(); @@ -406,7 +414,9 @@ void Server::readCGIPipe(Connection &conn, HTTPResponse &response) { response.setIsCGI(false); conn.setHasDataToSend(true); + response.setBody(conn.getCGIOutputBuffer()); response.CGIStringToResponse(conn.getCGIOutputBuffer()); + // std::cout << response << std::endl; close(pipeFD[0]); } } @@ -425,6 +435,7 @@ void Server::writeToClient(Connection &conn, size_t &i, HTTPResponse &response) { conn.setResponseString(response.objToString()); conn.setResponseSize(response.objToString().size()); + // std::cout << response.objToString() << std::endl; sendResponseCounter = 0; } @@ -800,7 +811,7 @@ void Server::acceptNewConnection(Connection &conn) void Server::handleServerSocketError() { static int errorCounter = 0; - perror("poll server socket error"); + // perror("poll server socket error"); if (errorCounter > 5) { Debug::log("Too many errors on server socket. Exiting.", Debug::SERVER); @@ -812,13 +823,15 @@ void Server::handleServerSocketError() void Server::handleClientSocketError(int clientFD, size_t &i) { Debug::log("Entering handleClientSocketError", Debug::SERVER); - close(clientFD); + // close(clientFD); + (void)clientFD; + closeClientConnection(_connections[i], i); /* start together */ - _FDs.erase(_FDs.begin() + i); - _connections.erase(_connections.begin() + i); + // _FDs.erase(_FDs.begin() + i); + // _connections.erase(_connections.begin() + i); /* end together */ - --i; - perror("poll client socket error"); + // --i; + // perror("poll client socket error"); } // Is not the socket timeout, but the poll timeout diff --git a/src/Server.hpp b/src/Server.hpp index e99194e3..38db2e35 100644 --- a/src/Server.hpp +++ b/src/Server.hpp @@ -90,7 +90,7 @@ class Server /* for handleConnection */ void readFromClient(Connection &conn, size_t &i, Parser &parser, HTTPRequest &request, HTTPResponse &response); - void handlePostRequest(Connection &conn, Parser &parser, HTTPRequest &request, HTTPResponse &response); + void handlePostAndDelete(Connection &conn, Parser &parser, HTTPRequest &request, HTTPResponse &response); void buildResponse(Connection &conn, size_t &i, HTTPRequest &request, HTTPResponse &response); void readCGIPipe(Connection &conn, HTTPResponse &response); void writeToClient(Connection &conn, size_t &i, HTTPResponse &response); diff --git a/src/main.cpp b/src/main.cpp index d1cc62ac..8f37a877 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,7 +7,7 @@ int main(int argc, char **argv) { Debug::enable(false); - Debug::setLevel(Debug::SERVER); + Debug::setLevel(Debug::CGI); if (argc > 2) { @@ -19,7 +19,7 @@ int main(int argc, char **argv) if (!config.getErrorMessage().empty()) return 1; - //std::cout << config << std::endl; // should be in the DEBUG? + // std::cout << config << std::endl; // should be in the DEBUG? EventManager eventManager; Server webserv(config, eventManager); diff --git a/tests/CGI.cpp b/tests/CGI.cpp new file mode 100644 index 00000000..8aeb8726 --- /dev/null +++ b/tests/CGI.cpp @@ -0,0 +1,235 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFFER_SIZE 1024 +#define PORT 8080 +#define POLL_TIMOUT 2000 +#define COLOR_GREEN "\033[32m" +#define COLOR_RED "\033[31m" +#define COLOR_RESET "\033[0m" +#define PRINT_RESPONSE 0 + +bool is_error = false; + +struct HTTPTest +{ + std::string request; + std::string expectedResponse; + + HTTPTest(std::string request, std::string expectedResponse) : request(request), expectedResponse(expectedResponse) + { + } +}; + +std::string extractStatusCode(const std::string &response) +{ + std::regex statusLineRegex(R"(HTTP/\d+\.\d+\s+(\d+)\s+.*\r\n)"); + std::smatch matches; + if (std::regex_search(response, matches, statusLineRegex) && matches.size() > 1) + { + return matches[1].str(); // The first sub-match is the status code + } + else + { + std::cerr << "Invalid or malformed HTTP response." << std::endl; + return ""; + } + std::istringstream responseStream(response); + std::string httpVersion; + std::string statusCode; + + responseStream >> httpVersion >> statusCode; + + return statusCode; +} + +void evalSendOutput(ssize_t bytesSent, const char *request, int clientSocket) +{ + size_t requestLength = strlen(request); + + if (bytesSent < 0) + std::cerr << "Failed to send data." << std::endl; + else if (bytesSent == 0 && requestLength > 0) + std::cerr << "Unexpected scenario: No data was sent despite a request being available." << std::endl; + else if (bytesSent == 0 && requestLength == 0) + std::cerr << "You tried to send an empty request. Why?" << std::endl; + else if (bytesSent < requestLength) + std::cerr << "Request was sent successfully, but not all data was sent." << std::endl; + else if (bytesSent > 0 && requestLength == bytesSent) + { + std::cout << "Request sent successfully" << std::endl; + return; + } + else + std::cerr << "Something impossible happened." << std::endl; + + // For all error cases, we close the socket. + std::cerr << "Request: " << request << std::endl; + close(clientSocket); +} + +bool waitForResponseWitPoll(int socketFd, int timoutMilliseconds) +{ + struct pollfd pollFd; + pollFd.fd = socketFd; + pollFd.events = POLLIN; + pollFd.revents = 0; + std::cout << "Waiting for response..." << std::endl; + int ret = poll(&pollFd, 1, timoutMilliseconds); + if (ret < 0) + { + std::cerr << "Poll failed" << std::endl; + return false; + } + else if (ret == 0) + { + std::cerr << "Poll timed out" << std::endl; + return false; + } + else if (pollFd.revents & POLLIN) + { + return true; + } + else + { + std::cerr << "Poll returned an unexpected value" << std::endl; + return false; + } +} + +void sendData(const std::vector &tests, sockaddr_in serverAddress) +{ + for (size_t i = 0; i < tests.size(); ++i) + { + const auto &test = tests[i]; + std::cout << "--------------------------------" << std::endl; + std::cout << "Processing request #" << i + 1 << std::endl; + std::cout << "Request: " << test.request << std::endl; + int clientSocket = socket(AF_INET, SOCK_STREAM, 0); + if (clientSocket < 0) + { + std::cerr << "Socket creation failed" << std::endl; + continue; + } + if (connect(clientSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress)) < 0) + { + std::cerr << "Connection failed" << std::endl; + close(clientSocket); + continue; + } + ssize_t bytesSent = send(clientSocket, test.request.c_str(), test.request.size(), 0); + + char buffer[BUFFER_SIZE]; + if (waitForResponseWitPoll(clientSocket, POLL_TIMOUT * 10)) + { + ssize_t bytesRead = read(clientSocket, buffer, BUFFER_SIZE); + if (bytesRead < 0) + std::cerr << "Failed to read data." << std::endl; + else if (bytesRead == 0) + std::cerr << "No data was read." << std::endl; + else + { + buffer[bytesRead] = '\0'; + std::string statusCode = extractStatusCode(buffer); + if (statusCode == test.expectedResponse) + { + if (PRINT_RESPONSE) + std::cout << "Response: " << buffer << std::endl; + std::cout << "Status code: " << statusCode << std::endl; + std::cout << COLOR_GREEN "✅ Test Passed" COLOR_RESET << std::endl; + } + else + { + if (PRINT_RESPONSE) + std::cout << "Response: " << buffer << std::endl; + std::cout << "Status code: " << statusCode << std::endl; + std::cerr << COLOR_RED "❌ Test Failed" COLOR_RESET << std::endl; + is_error = true; + } + } + } + else + { + std::cerr << "No response was received" << std::endl; + } + close(clientSocket); + sleep(1); + std::cout << "--------------------------------" << std::endl; + } +} + +// void cgi(sockaddr_in serverAdress) +// { +// std::string queryString = "?name=Jasasane&salad=Caesar"; +// std::stringstream request; +// request << "POST /database/salad_db.py" << queryString << " HTTP/1.1\r\n" +// << "Host: www.saladbook.xyz\r\n" +// << "Content-Length: 0\r\n" +// << "\r\n"; + +// std::vector tests = {HTTPTest(request.str(), "200")}; +// sendData(tests, serverAdress); +// } + +void add_entry(sockaddr_in serverAdress) +{ + std::cout << "Executing POST request..." << std::endl; + std::string postData = "name=Jane&salad=Caesar"; + std::stringstream request; + request << "POST /database/salad_db.py HTTP/1.1\r\n" + << "Host: www.saladbook.xyz\r\n" + << "Content-Type: application/x-www-form-urlencoded\r\n" + << "content-Length: " << postData.size() << "\r\n" + << "\r\n" + << postData << "\r\n\r\n"; + + std::vector tests = {HTTPTest(request.str(), "200")}; + sendData(tests, serverAdress); +} + +void delete_entry(sockaddr_in serverAdress) +{ + std::cout << "Executing DELETE request..." << std::endl; + std::string deleteData = "name=Jane"; + std::stringstream request; + request << "DELETE /database/salad_db.py HTTP/1.1\r\n" + << "Host: www.saladbook.xyz\r\n" + << "Content-Type: application/x-www-form-urlencoded\r\n" + << "Content-Length: " << deleteData.size() << "\r\n" + << "\r\n" + << deleteData << "\r\n\r\n"; + + std::vector tests = {HTTPTest(request.str(), "200")}; + sendData(tests, serverAdress); +} + +int main(void) +{ + sockaddr_in serverAddress; + serverAddress.sin_family = AF_INET; + serverAddress.sin_port = htons(PORT); + serverAddress.sin_addr.s_addr = INADDR_ANY; + + // NOTE: when you run a test, configfile and dir structure should fit + // salad(serverAddress); // standard routing tests + // delete_entry(serverAddress); // DELETE request + std::cout << "Waiting for the server to delete the entry..." << std::endl; + sleep(2); + std::cout << "\033[34m" << std::endl << "Continuing with the tests..." << "\033[0m" << std::endl; + add_entry(serverAddress); // POST request + + if (is_error) + exit(1); + else if (!is_error) + std::cout << std::endl << " 🎉 All tests passed 🎉" << std::endl << std::endl; + exit(0); +} diff --git a/var/www.saladbook.xyz/database/database.json b/var/www.saladbook.xyz/database/database.json new file mode 100644 index 00000000..5d041c1c --- /dev/null +++ b/var/www.saladbook.xyz/database/database.json @@ -0,0 +1,6 @@ +{ + "Stefano": "Carrot", + "Leo": "nicoise", + "Daniil": "with_beets", + "Jane": "Caesar" +} \ No newline at end of file diff --git a/var/www.saladbook.xyz/database/salad_db.py b/var/www.saladbook.xyz/database/salad_db.py index 80a525cf..b5d093cc 100755 --- a/var/www.saladbook.xyz/database/salad_db.py +++ b/var/www.saladbook.xyz/database/salad_db.py @@ -2,54 +2,124 @@ import cgi import os import json +import sys +from urllib.parse import parse_qs -//export QUERY_STRING="action=add&name=John&salad=Caesar" - +def get_script_directory(): + """Return the directory in which the script is located.""" + return os.path.dirname(os.path.realpath(__file__)) def load_database(filename): """Load the JSON database from a file.""" + log(f"Attempting to load database from {filename}") if os.path.exists(filename): - with open(filename, "r") as file: - return json.load(file) + try: + with open(filename, "r") as file: + return json.load(file) + except json.JSONDecodeError: + log("Error: Database file contains invalid JSON. Unable to load data.") + return None + except Exception as e: + log(f"Error: Failed to read the database file. {e}") + return None else: + log(f"Database file {filename} does not exist.") return {} def save_database(data, filename): """Save the JSON database to a file.""" - with open(filename, "w") as file: - json.dump(data, file, indent=4) + try: + with open(filename, "w") as file: + json.dump(data, file, indent=4) + log(f"Database saved to {filename}") + except Exception as e: + log(f"Error: Failed to save the database file. {e}") + +def initialize_database(filename): + """Initialize the database file if it does not exist.""" + if not os.path.exists(filename): + data = {} + save_database(data, filename) + return data + else: + return load_database(filename) + +def log(message): + """Add message to log list.""" + logs.append(f"") + +def print_response(status_code, status_message, body): + """Print the HTTP response with status code, status message, and body.""" + print(f"Status: {status_code} {status_message}") + print("Content-Type: text/html") + print("\r\n") + response_body = f"{json.dumps(body)}{''.join(logs)}" + print(response_body) def main(): - print("Content-type: text/html\n") - print() + global logs + logs = [] + + # Get the directory where the script is located + script_directory = get_script_directory() + filename = os.path.join(script_directory, "database.json") + log(f"Database filename is {filename}") - # Load existing data - filename = "database.json" - data = load_database(filename) + # Initialize and load existing data + data = initialize_database(filename) + if data is None: + data = {} + log(f"Loaded data: {data}") - # Simulating CGI environment for demonstration - query_string = os.getenv('QUERY_STRING', '') - form = cgi.FieldStorage(fp=None, headers=None, environ={'REQUEST_METHOD':'GET', 'CONTENT_TYPE':'application/x-www-form-urlencoded', 'QUERY_STRING': query_string}) + # Determine the request method + method = os.getenv('REQUEST_METHOD', '').upper() - action = form.getvalue('action') - name = form.getvalue('name') - salad = form.getvalue('salad') + if method in ['POST', 'DELETE']: + if method == 'POST': + # Read the input data from stdin + content_length = int(os.getenv('CONTENT_LENGTH', 0)) + post_data = sys.stdin.read(content_length) + log(f"Received POST data: {post_data}") + form_data = parse_qs(post_data) + elif method == 'DELETE': + content_length = int(os.getenv('CONTENT_LENGTH', 0)) + delete_data = sys.stdin.read(content_length) + log(f"Received DELETE data: {delete_data}") + form_data = parse_qs(delete_data) + + # Debugging: Print the form keys and values + for key in form_data.keys(): + log(f"form[{key}]={form_data[key]}") - if action == 'add' and name and salad: - # Add or update an entry - data[name] = salad - save_database(data, filename) - print(f"Entry added or updated successfully: {name} likes {salad}.") - elif action == 'delete' and name: - # Attempt to delete an entry - if name in data: - del data[name] + log(f"form_data={form_data}") + + name = form_data.get('name', [None])[0] + salad = form_data.get('salad', [None])[0] if method == 'POST' else None + + log(f"method={method}, name={name}, salad={salad}") + + if method == 'POST' and name and salad: + # Add or update an entry + data[name] = salad save_database(data, filename) - print(f"Entry deleted successfully: {name}.") + log(f"Entry added or updated successfully: {name} likes {salad}.") + print_response(200, "OK", {"message": f"Entry added or updated successfully: {name} likes {salad}."}) + elif method == 'DELETE' and name: + # Attempt to delete an entry + if name in data: + del data[name] + save_database(data, filename) + log(f"Entry deleted successfully: {name}.") + print_response(200, "OK", {"message": f"Entry deleted successfully: {name}."}) + else: + log(f"Entry not found: {name}.") + print_response(404, "Not Found", {"error": f"Entry not found: {name}."}) else: - print(f"Entry not found: {name}.") + log("Invalid request. Make sure you provide name and salad parameters for adding, or name for deleting.") + print_response(400, "Bad Request", {"error": "Invalid request. Make sure you provide name and salad parameters for adding, or name for deleting."}) else: - print("Invalid request.") + log("Unsupported request method. Please use POST or DELETE.") + print_response(405, "Method Not Allowed", {"error": "Unsupported request method. Please use POST or DELETE."}) if __name__ == "__main__": main() diff --git a/var/www.saladbook.xyzchunked_upload.jpg b/var/www.saladbook.xyzchunked_upload.jpg new file mode 100644 index 00000000..d2626b88 --- /dev/null +++ b/var/www.saladbook.xyzchunked_upload.jpg @@ -0,0 +1,2 @@ +name=Jane&salad=Caesar +