diff --git a/data/www/test_post.py b/data/www/test_post.py new file mode 100755 index 0000000..6071987 --- /dev/null +++ b/data/www/test_post.py @@ -0,0 +1,17 @@ +import sys +import select + +def main(): + + + print('From cgi: waiting on the servers message', file=sys.stderr) + + print(sys.stdin.readlines()) + + # if input_data.strip(): + # sys.stdout.write(input_data) + + print('From cgi: after writing to the server', file=sys.stderr) + +if __name__ == "__main__": + main() diff --git a/include/CGI.hpp b/include/CGI.hpp index 1c709bc..9796654 100644 --- a/include/CGI.hpp +++ b/include/CGI.hpp @@ -1,20 +1,51 @@ #ifndef CGI_HPP #define CGI_HPP +#include +#include +#include +#include +#include + #define READ_END 0 #define WRITE_END 1 -// Common gateway interface +class Client; +class Poll; class CGI { - private: +private: + pid_t _pid; + size_t _bodyBytesWritten; + std::string _executable; + std::string _pathInfo; + std::string _subPathInfo; + std::string _queryString; + public: CGI(); CGI(const CGI &src) = delete; CGI &operator=(const CGI &rhs) = delete; ~CGI(); - void execute(); + + ClientState start(Poll &poll, Client &client, size_t body_length, + std::unordered_map> &active_pipes); + ClientState parseURIForCGI(std::string requestTarget); + void execute(std::string executable); + bool fileExists(const std::string& filePath); + bool isExecutable(const std::string& filePath); + + const std::string& getExecutable(void) const; + void setExecutable(std::string executable); + const pid_t& getPid(void) const; + + ClientState send(Client &client ,std::string body, size_t bodyLength); + size_t getBufferSize(size_t bodyLength); + ClientState receive(Client &client); + + std::string body; + int pipe_fd[2]; }; #endif diff --git a/include/Client.hpp b/include/Client.hpp index fb0e010..4f65623 100644 --- a/include/Client.hpp +++ b/include/Client.hpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include class Client { @@ -16,16 +18,27 @@ class Client const Client &operator=(const Client &other) = delete; ~Client(); - ClientState handleConnection(short events); + ClientState handleConnection(short events, Poll &poll, Client &client, + std::unordered_map> &active_pipes); int getFD(void) const; + int *getCgiToServerFd(void); + int *getServerToCgiFd(void); + HTTPRequest &getRequest(void); + void setState(ClientState state); + + bool cgiBodyIsSent; + bool cgiHasBeenRead; + bool KO; private: - HTTPRequest _request; - HTTPResponse _response; - FileManager _file_manager; - CGI _cgi; - Socket _socket; - ClientState _state; + HTTPRequest _request; + HTTPResponse _response; + FileManager _file_manager; + CGI _cgi; + Socket _socket; + ClientState _state; + int _serverToCgiFd[2]; + int _cgiToServerFd[2]; }; #endif diff --git a/include/ClientState.hpp b/include/ClientState.hpp index 829999f..bdd3223 100644 --- a/include/ClientState.hpp +++ b/include/ClientState.hpp @@ -4,11 +4,14 @@ enum class ClientState { Receiving, + CGI_Start, + CGI_Write, + CGI_Read, Loading, Sending, Done, Error, - Unkown, + Unknown, }; #endif diff --git a/include/FileManager.hpp b/include/FileManager.hpp index 8f08a4c..c07bb5a 100644 --- a/include/FileManager.hpp +++ b/include/FileManager.hpp @@ -9,8 +9,10 @@ class FileManager { private: - std::string _response; - std::fstream _request_target; + std::string _response; + std::fstream _request_target; + size_t _bytes_sent; + // CGI _cgi; public: FileManager(); @@ -26,10 +28,13 @@ class FileManager ClientState loadErrorPage(void); ClientState manage(HTTPMethod method, const std::string &filename, const std::string &body); + ClientState manageCgi(std::string http_version, const std::string &body); ClientState manageGet(void); ClientState managePost(const std::string &body); ClientState manageDelete(const std::string &reqest_target_path); + // CGI APPEND FUNCTION + const std::string &getResponse(void) const; }; diff --git a/include/HTTPRequest.hpp b/include/HTTPRequest.hpp index ca893a7..51e9f0d 100644 --- a/include/HTTPRequest.hpp +++ b/include/HTTPRequest.hpp @@ -47,15 +47,21 @@ class HTTPRequest const std::string &getBody(void) const; ClientState receive(int fd); + const std::string &getExecutable(void) const; + void setCGIToTrue(void); + const bool &CGITrue(void) const; + const size_t &getBodyLength(void) const; + private: - ssize_t _bytes_read; - size_t _content_length; - HTTPMethod _methodType; + ssize_t _bytes_read; + size_t _content_length; + HTTPMethod _methodType; std::string _http_request; std::string _request_target; std::string _http_version; std::string _body; std::unordered_map _headers; + bool _cgi; size_t parseStartLine(size_t &i); size_t parseHeaders(size_t &i); diff --git a/include/HTTPServer.hpp b/include/HTTPServer.hpp index e1d7e18..111ca4c 100644 --- a/include/HTTPServer.hpp +++ b/include/HTTPServer.hpp @@ -25,11 +25,17 @@ class HTTPServer Poll _poll; std::unordered_map> _active_servers; std::unordered_map> _active_clients; + std::unordered_map> _active_pipes; void setupServers(void); void handleActivePollFDs(); void handleNewConnection(int fd); - void handleExistingConnection(const pollfd &poll_fd); + void handleExistingConnection(const pollfd &poll_fd, Poll &poll, Client &client, + std::unordered_map> &active_pipes); + Client &findClientByFd(int targetFd); + Client &getClientByPipeFd(int pipe_fd); + void handlePipeConnection(const pollfd &poll_fd, Poll &poll, Client &client, + std::unordered_map> &active_pipes); }; #endif diff --git a/include/Socket.hpp b/include/Socket.hpp index 14ca090..e426656 100644 --- a/include/Socket.hpp +++ b/include/Socket.hpp @@ -4,6 +4,9 @@ #include #include +// MAX_PENDING_CONNECTION SHOULDN'T BE 1 +// WHY 10? WE CAN ALSO SET IT ON 1024, THAT'S +// MORE SAFE, CONCERNING THE AVAILABILITY (siege -b) #define MAX_PENDING_CONNECTIONS 10 typedef struct sockaddr_in t_sockaddr_in; diff --git a/out.txt b/out.txt index 40f3220..e69de29 100644 --- a/out.txt +++ b/out.txt @@ -1,2 +0,0 @@ -['hello world!\n', 'goodbye world!\n'] -Content-type: text/html diff --git a/src/CGI.cpp b/src/CGI.cpp index e3983ff..741d8e9 100644 --- a/src/CGI.cpp +++ b/src/CGI.cpp @@ -1,106 +1,202 @@ -#include -#include -#include +#include "ClientException.hpp" +#include "Client.hpp" +#include "Poll.hpp" +#include "CGI.hpp" +#include "Logger.hpp" +#include "SystemException.hpp" +#include #include +#include #include #include +#include -CGI::CGI() +CGI::CGI() : _bodyBytesWritten(0) { + _pathInfo = ""; + _subPathInfo = ""; + _queryString = ""; +} + +CGI::~CGI() {} + +const std::string &CGI::getExecutable(void) const { + return (_executable); +} + +void CGI::setExecutable(std::string executable) { + _executable = executable; +} + +const pid_t &CGI::getPid(void) const { + return (_pid); +} + +size_t CGI::getBufferSize(size_t bodyLength) +{ + size_t bufferSize = 0; + + if (BUFFER_SIZE > bodyLength) + bufferSize = bodyLength; + else if (BUFFER_SIZE > (bodyLength - _bodyBytesWritten)) + bufferSize = bodyLength - _bodyBytesWritten; + else + bufferSize = BUFFER_SIZE; + return (bufferSize); +} + +ClientState CGI::send(Client &client ,std::string body, size_t bodyLength) +{ + Logger &logger = Logger::getInstance(); + ssize_t bytesWritten = 0; + + logger.log(INFO, "GCI::send is called"); + logger.log(DEBUG, "body: " + body); + + if (!client.cgiBodyIsSent) + bytesWritten = write(client.getServerToCgiFd()[WRITE_END], body.c_str(), getBufferSize(bodyLength)); + if (bytesWritten == SYSTEM_ERROR) + throw ClientException(StatusCode::InternalServerError); + logger.log(DEBUG, "bytesWritten: %", bytesWritten); + _bodyBytesWritten += bytesWritten; + if (_bodyBytesWritten >= bodyLength) + { + client.cgiBodyIsSent = true; + logger.log(DEBUG, "ServerToCgiFd()[WRITE_END] is now being closed"); + close(client.getServerToCgiFd()[WRITE_END]); + return (ClientState::CGI_Read); + } + logger.log(INFO, "CGI body: " + std::to_string(bytesWritten)); + return (ClientState::CGI_Write); +} + +ClientState CGI::receive(Client &client) { + Logger &logger = Logger::getInstance(); + ssize_t bytesRead = 0; + char buffer[1024]; + + bzero(buffer, sizeof(buffer)); + logger.log(INFO, "CGI::receive is called"); + int status; + waitpid(_pid, &status, 0); + if (status == SYSTEM_ERROR) throw ClientException(StatusCode::InternalServerError); + bytesRead = read(client.getCgiToServerFd()[READ_END], buffer, sizeof(buffer)); + if (bytesRead == SYSTEM_ERROR) throw ClientException(StatusCode::InternalServerError); + logger.log(DEBUG, "Bytes read: " + std::to_string(bytesRead)); + logger.log(DEBUG, "buffer:\n" + std::string(buffer)); + body += buffer; + logger.log(DEBUG, "sizeof(buffer): %", sizeof(buffer)); + if (bytesRead < (ssize_t) sizeof(buffer)) + { + client.cgiHasBeenRead = true; + logger.log(DEBUG, "body in GCI::receive:\n" + body); + close(client.getCgiToServerFd()[READ_END]); + return (ClientState::Sending); + } + return (ClientState::CGI_Read); +} + +bool CGI::fileExists(const std::string& filePath) { + return (std::filesystem::exists(filePath) && std::filesystem::is_regular_file(filePath)); +} + +bool CGI::isExecutable(const std::string& filePath) +{ + std::filesystem::file_status fileStat = std::filesystem::status(filePath); + + return std::filesystem::is_regular_file(fileStat) && (fileStat.permissions() & + std::filesystem::perms::owner_exec) != std::filesystem::perms::none; } -CGI::~CGI() +void CGI::execute(std::string executable) { + const char *env[] = {_pathInfo.c_str(), _queryString.c_str(), NULL}; + Logger &logger = Logger::getInstance(); + std::string bin = "python3"; + + logger.log(ERROR, "CGI::execute is called"); + logger.log(ERROR, "Executable: %", executable); + std::string executableWithPath = "data/www" + executable; + logger.log(ERROR, "executableWithPath: " + executableWithPath); + const char *const argv[] = {bin.c_str(), executableWithPath.c_str(), _subPathInfo.c_str(), NULL}; + const char *path = "/usr/bin/python3"; + if (execve(path, (char *const *)argv, (char *const*) env) == SYSTEM_ERROR) throw ClientException(StatusCode::InternalServerError); } -void CGI::execute() +ClientState CGI::start(Poll &poll, Client &client, size_t bodyLength, + std::unordered_map> &active_pipes) { - int infile = open("in.txt", O_RDONLY, 0666); - int outfile = open("out.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666); + (void)client; Logger &logger = Logger::getInstance(); - int in[2]; - int out[2]; - pid_t pid; - - logger.log(INFO, "launcging CGI"); - if (pipe(in) == SYSTEM_ERROR) - throw SystemException("Pipe"); - if (pipe(out) == SYSTEM_ERROR) - throw SystemException("Pipe"); - - pid = fork(); - if (pid == SYSTEM_ERROR) - throw SystemException("Fork"); - else if (pid == 0) + + logger.log(DEBUG, "CGI::start called"); + logger.log(DEBUG, "Executable: %", _executable); + if (pipe(client.getServerToCgiFd()) == SYSTEM_ERROR) throw ClientException(StatusCode::InternalServerError); + if (pipe(client.getCgiToServerFd()) == SYSTEM_ERROR) throw ClientException(StatusCode::InternalServerError); + _pid = fork(); + if (_pid == SYSTEM_ERROR) throw ClientException(StatusCode::InternalServerError); + if (_pid == 0) + { + if (close(client.getServerToCgiFd()[WRITE_END]) == SYSTEM_ERROR) throw ClientException(StatusCode::InternalServerError); + if (close(client.getCgiToServerFd()[READ_END]) == SYSTEM_ERROR) throw ClientException(StatusCode::InternalServerError); + if (dup2(client.getServerToCgiFd()[READ_END], STDIN_FILENO) == SYSTEM_ERROR) throw ClientException(StatusCode::InternalServerError); + if (close(client.getServerToCgiFd()[READ_END]) == SYSTEM_ERROR) throw ClientException(StatusCode::InternalServerError); + if (dup2(client.getCgiToServerFd()[WRITE_END], STDOUT_FILENO) == SYSTEM_ERROR) throw ClientException(StatusCode::InternalServerError); + if (close(client.getCgiToServerFd()[WRITE_END]) == SYSTEM_ERROR) throw ClientException(StatusCode::InternalServerError); + execute(_executable); + } + logger.log(DEBUG, "CGI::start after else if (_pid == 0)"); + if (close(client.getServerToCgiFd()[READ_END]) == SYSTEM_ERROR) throw ClientException(StatusCode::InternalServerError); + if (close(client.getCgiToServerFd()[WRITE_END]) == SYSTEM_ERROR) throw ClientException(StatusCode::InternalServerError); + logger.log(DEBUG, "CGI::start after closing"); + logger.log(DEBUG, "bodyLength: %", bodyLength); + logger.log(DEBUG, "cgiBodyIsSent: %", client.cgiBodyIsSent); + std::shared_ptr pipe = std::make_shared(client.getCgiToServerFd()[READ_END]); + active_pipes.emplace(client.getCgiToServerFd()[READ_END], pipe); + poll.addPollFD(client.getCgiToServerFd()[READ_END], POLLIN); + logger.log(DEBUG, "CGI::start after closing WRITE_END and start reading"); + if (bodyLength != 0 && !client.cgiBodyIsSent) { - if (close(in[WRITE_END]) == SYSTEM_ERROR) - { - logger.log(ERROR, "close in[WRITE_END] error" + - std::string(strerror(errno))); - _exit(127); - } - if (close(out[READ_END]) == SYSTEM_ERROR) - { - logger.log(ERROR, "close out[READ_END] error" + - std::string(strerror(errno))); - _exit(127); - } - if (dup2(in[READ_END], STDIN_FILENO) == SYSTEM_ERROR) - { - logger.log(ERROR, "dup2 in[READ_END] error" + - std::string(strerror(errno))); - _exit(127); - } - if (close(in[READ_END]) == SYSTEM_ERROR) - { - logger.log(ERROR, "close in[READ_END] error" + - std::string(strerror(errno))); - _exit(127); - } - if (dup2(out[WRITE_END], STDOUT_FILENO) == SYSTEM_ERROR) - { - logger.log(ERROR, "dup2 out[WRITE_END] error" + - std::string(strerror(errno))); - _exit(127); - } - if (close(out[WRITE_END]) == SYSTEM_ERROR) - { - logger.log(ERROR, "close out[WRITE_END] error" + - std::string(strerror(errno))); - _exit(127); - } - std::string bin = "python3"; - std::string script = "./cgi-bin/print.py"; - const char *const argv[] = {bin.c_str(), script.c_str(), NULL}; - char *const envp[] = {NULL}; - const char *path = "/usr/bin/python3"; - execve(path, (char *const *)argv, envp); - logger.log(ERROR, "execve error" + std::string(strerror(errno))); - _exit(127); + logger.log(DEBUG, "ClientState::CGI_Write is now being called"); + std::shared_ptr pipe = std::make_shared(client.getServerToCgiFd()[WRITE_END]); + active_pipes.emplace(client.getServerToCgiFd()[WRITE_END], pipe); + poll.addPollFD(client.getServerToCgiFd()[WRITE_END], POLLOUT); + return (ClientState::CGI_Write); } - assert(close(in[READ_END]) && "close in[READ_END] error"); - assert(close(out[WRITE_END]) && "close out[WRITE_END] error"); - - char buffer_out[1024]; - char buffer_in[1024]; - int read_bytes; - bzero(buffer_in, sizeof(buffer_in)); - bzero(buffer_out, sizeof(buffer_out)); - - read_bytes = read(infile, buffer_in, sizeof(buffer_in)); - logger.log(INFO, "buffer_in: " + std::string(buffer_in)); - if (write(in[WRITE_END], buffer_in, read_bytes) == SYSTEM_ERROR) - logger.log(ERROR, "write error" + std::string(strerror(errno))); - - assert(close(in[WRITE_END]) && "close in[WRITE_END] error"); - if ((read_bytes = read(out[READ_END], buffer_out, sizeof(buffer_out))) == - SYSTEM_ERROR) - logger.log(ERROR, "read error" + std::string(strerror(errno))); - logger.log(INFO, "buffer_out: " + std::string(buffer_out)); - write(outfile, buffer_out, read_bytes); - close(out[READ_END]); - - assert(close(infile) && "close infile error"); - assert(close(outfile) && "close outfile error"); + if (close(client.getServerToCgiFd()[WRITE_END]) == SYSTEM_ERROR) throw ClientException(StatusCode::InternalServerError); + return (ClientState::CGI_Read); } + +ClientState CGI::parseURIForCGI(std::string requestTarget) +{ + Logger &logger = Logger::getInstance(); + logger.log(DEBUG, "parseURIForCGI is called"); + logger.log(DEBUG, "requestTarget: %", requestTarget); + std::string filenameExtension = ".py"; + size_t lengthFilenameExtension = std::strlen(filenameExtension.c_str()); + size_t filenameExtensionPos = requestTarget.find(filenameExtension); + + logger.log(DEBUG, "Length of filenameExtension: %", lengthFilenameExtension); + if (filenameExtensionPos == std::string::npos) + return (ClientState::Error); + _executable = requestTarget.substr(0, filenameExtensionPos + lengthFilenameExtension); + bool skip = false; + logger.log(DEBUG, "Executable is: " + _executable); + if (!fileExists("data/www" + _executable) || !isExecutable("data/www" + _executable)) + throw ClientException(StatusCode::NotFound); + if (filenameExtensionPos + lengthFilenameExtension >= std::strlen(requestTarget.c_str()) - 1) { + return (ClientState::CGI_Start); + } + std::string remaining = requestTarget.substr(filenameExtensionPos + lengthFilenameExtension, std::string::npos); + size_t questionMarkPos = remaining.find('?'); + if (remaining.at(0) == '/' && !skip) { + _subPathInfo = remaining.substr(0, questionMarkPos); + _pathInfo = "PATH_INFO=" + _subPathInfo; + } + if (!skip) + _queryString = "QUERY_STRING=" + remaining.substr(questionMarkPos, std::string::npos); + logger.log(DEBUG, _pathInfo); + logger.log(DEBUG, _queryString); + return (ClientState::CGI_Start); +} \ No newline at end of file diff --git a/src/Client.cpp b/src/Client.cpp index 1911c29..c658007 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -1,12 +1,20 @@ -#include -#include -#include +#include "CGI.hpp" +#include "Poll.hpp" +#include "Client.hpp" +#include "ClientException.hpp" +#include "Logger.hpp" +#include +#include #include Client::Client(const int &server_fd) : _socket(server_fd) { _socket.setupClient(); + _state = ClientState::Receiving; + cgiBodyIsSent = false; + cgiHasBeenRead = false; + KO = false; } Client::~Client() @@ -18,24 +26,72 @@ int Client::getFD(void) const return (_socket.getFD()); } -ClientState Client::handleConnection(short events) +int *Client::getCgiToServerFd(void) { + return (_cgiToServerFd); +} + +int *Client::getServerToCgiFd(void) { + return (_serverToCgiFd); +} + +HTTPRequest &Client::getRequest(void) { + return (_request); +} + +void Client::setState(ClientState state) { + _state = state; +} + +ClientState Client::handleConnection(short events, Poll &poll, Client &client, + std::unordered_map> &active_pipes) { Logger &logger = Logger::getInstance(); logger.log(INFO, "Handling client connection on fd: " + std::to_string(_socket.getFD())); try { - if (events & POLLIN) + if (events & POLLIN && _state == ClientState::Receiving) { + logger.log(DEBUG, "ClientState::Receiving"); _state = _request.receive(_socket.getFD()); + _request.setCGIToTrue(); // TODO: method returns if current request is a CGI + return (_state); + } + else if (events & POLLOUT && _state == ClientState::CGI_Start) + { + logger.log(DEBUG, "ClientState::CGI_Start"); + _state = _cgi.start(poll, client, _request.getBodyLength(), active_pipes); + return (_state); + } + else if (events & POLLOUT && _state == ClientState::CGI_Write) + { + logger.log(DEBUG, "ClientState::CGI_Write"); + _state = _cgi.send(client, _request.getBody(), _request.getBodyLength()); + return (_state); + } + else if (events & POLLIN && _state == ClientState::CGI_Read) + { + logger.log(DEBUG, "ClientState::CGI_Read"); + _state = _cgi.receive(client); + if (client.cgiHasBeenRead == true) { + _state = _file_manager.manageCgi(_request.getHTTPVersion(), _cgi.body); + logger.log(DEBUG, "response:\n\n" + _file_manager.getResponse()); + } return (_state); } else if (events & POLLOUT && _state == ClientState::Loading) { + logger.log(DEBUG, "ClientState::Loading"); + if (_request.CGITrue() == true && _request.getMethodType() != HTTPMethod::DELETE) { + + _state = _cgi.parseURIForCGI(_request.getRequestTarget()); + logger.log(DEBUG, "executable: " + _cgi.getExecutable()); + return (_state); + } _state = _file_manager.manage( _request.getMethodType(), "./data/www" + _request.getRequestTarget(), - _request.getBody()); // TODO: resolve location + _request.getBody()); return (_state); } else if (events & POLLOUT && _state == ClientState::Error) @@ -45,6 +101,12 @@ ClientState Client::handleConnection(short events) } else if (events & POLLOUT && _state == ClientState::Sending) { + logger.log(DEBUG, "ClientState::Sending"); + if (KO == true) + { + _state =_response.send(_socket.getFD(), "HTTP/1.1 500 KO\t\n\t\n"); + return (_state); + } _state = _response.send(_socket.getFD(), _file_manager.getResponse()); return (_state); @@ -53,11 +115,13 @@ ClientState Client::handleConnection(short events) catch (ClientException &e) { logger.log(ERROR, "Client exception: " + std::string(e.what())); + if (_request.CGITrue() == true && _request.getMethodType() != HTTPMethod::DELETE) + _exit(1); _response.clear(); _response.append(e.what()); _state = _file_manager.openErrorPage( - "./data/errors", e.getStatusCode()); // TODO: resolve location + "./data/errors", e.getStatusCode()); return (_state); } - return (ClientState::Unkown); + return (ClientState::Unknown); } diff --git a/src/FileManager.cpp b/src/FileManager.cpp index 03328bf..8cbc1d5 100644 --- a/src/FileManager.cpp +++ b/src/FileManager.cpp @@ -5,7 +5,7 @@ #include #include -FileManager::FileManager() : _response(), _request_target() +FileManager::FileManager() : _response(), _request_target(), _bytes_sent(0) { } @@ -95,14 +95,23 @@ ClientState FileManager::manageGet(void) ClientState FileManager::managePost(const std::string &body) { Logger &logger = Logger::getInstance(); - size_t pos = _request_target.tellp(); + ssize_t pos = _request_target.tellp(); + size_t bytes_to_send = 0; + + logger.log(DEBUG, "managePost method is called"); - _request_target.write(body.c_str() + pos, BUFFER_SIZE); - logger.log(DEBUG, "post buffer: " + body.substr(pos, BUFFER_SIZE)); + logger.log(DEBUG, "pos: %", pos); + if (body.length() - _bytes_sent < BUFFER_SIZE) + bytes_to_send = body.length() - _bytes_sent; + else + bytes_to_send = BUFFER_SIZE; + + _request_target.write(body.c_str() + pos, bytes_to_send); + _bytes_sent += bytes_to_send; if (_request_target.fail()) throw ClientException(StatusCode::InternalServerError); - if (_request_target.eof()) + if (_bytes_sent == body.size()) return (ClientState::Sending); return (ClientState::Loading); } @@ -137,7 +146,13 @@ ClientState FileManager::manage(HTTPMethod method, openPostFile(request_target_path); return (managePost(body)); } - return (ClientState::Unkown); + return (ClientState::Unknown); +} + +ClientState FileManager::manageCgi(std::string http_version, const std::string &body) +{ + _response = http_version + " 200 OK\t\n\t\n" + body; + return (ClientState::Sending); } const std::string &FileManager::getResponse(void) const diff --git a/src/HTTPRequest.cpp b/src/HTTPRequest.cpp index 25c80e4..cb3dcad 100644 --- a/src/HTTPRequest.cpp +++ b/src/HTTPRequest.cpp @@ -1,6 +1,8 @@ +#include "ClientException.hpp" #include #include #include +#include "ClientState.hpp" #include @@ -8,7 +10,8 @@ HTTPRequest::HTTPRequest() : _bytes_read(0), _content_length(0), _methodType(HTTPMethod::UNKNOWN), - _http_request(), _request_target(), _http_version(), _body(), _headers() + _http_request(), _request_target(), _http_version(), _body(), _headers(), + _cgi(false) { } @@ -68,11 +71,25 @@ const std::string &HTTPRequest::getBody(void) const return (_body); } +const size_t &HTTPRequest::getBodyLength(void) const { + return (_content_length); +} + +void HTTPRequest::setCGIToTrue(void) { + _cgi = true; +} + +const bool &HTTPRequest::CGITrue(void) const { + return (_cgi); +} + + size_t HTTPRequest::parseStartLine(size_t &i) { Logger &logger = Logger::getInstance(); size_t pos; + logger.log(DEBUG, "_http_request: %", _http_request); pos = _http_request.find(' ', i); setMethodType(_http_request.substr(i, pos - i)); i = pos + 1; @@ -112,12 +129,13 @@ ClientState HTTPRequest::receive(int client_fd) Logger &logger = Logger::getInstance(); char buffer[BUFFER_SIZE]; std::string header_end; - size_t pos; size_t i = 0; + size_t pos; _bytes_read = read(client_fd, buffer, BUFFER_SIZE); if (_bytes_read == SYSTEM_ERROR) - throw SystemException("Read failed on: " + std::to_string(client_fd)); + throw ClientException(StatusCode::InternalServerError); + logger.log(DEBUG, "in receive _bytes_read is: %", _bytes_read); if (_content_length != 0) { _body += std::string(buffer, _bytes_read); @@ -143,7 +161,10 @@ ClientState HTTPRequest::receive(int client_fd) if (_content_length == 0) return (ClientState::Loading); _body += _http_request.substr(pos + 2); + if (std::strlen(_body.c_str()) == _content_length) + return (ClientState::Loading); return (ClientState::Receiving); + } else return (ClientState::Loading); @@ -151,4 +172,4 @@ ClientState HTTPRequest::receive(int client_fd) pos = parseHeaders(i); } return (ClientState::Receiving); -} +} \ No newline at end of file diff --git a/src/HTTPServer.cpp b/src/HTTPServer.cpp index 5aec298..b18096c 100644 --- a/src/HTTPServer.cpp +++ b/src/HTTPServer.cpp @@ -1,9 +1,11 @@ #include #include #include +#include "HTTPRequest.hpp" +#include "ClientException.hpp" HTTPServer::HTTPServer(const std::string &config_file_path) -try : _parser(config_file_path), _poll(), _active_servers(), _active_clients() +try : _parser(config_file_path), _poll(), _active_servers(), _active_clients(), _active_pipes() { } catch (const std::runtime_error &e) @@ -17,6 +19,26 @@ HTTPServer::~HTTPServer() { } +Client &HTTPServer::findClientByFd(int targetFd) +{ + auto it = _active_clients.find(targetFd); + + if (it != _active_clients.end()) + return *(it->second); + throw std::runtime_error("Error: findClientByFd"); +} + +Client &HTTPServer::getClientByPipeFd(int pipe_fd) { + for (const auto& entry : _active_clients) { + const auto& client = entry.second; + if (client->getCgiToServerFd()[READ_END] == pipe_fd || + client->getServerToCgiFd()[WRITE_END] == pipe_fd) { + return *client; + } + } + throw std::runtime_error("Error: getClientByPipeFd"); +} + int HTTPServer::run() { Logger &logger = Logger::getInstance(); @@ -32,10 +54,9 @@ int HTTPServer::run() logger.log(FATAL, e.what()); return (EXIT_FAILURE); } - return (EXIT_SUCCESS); - while (true) { + logger.log(DEBUG, "while loop HTTPServer::run"); try { handleActivePollFDs(); @@ -46,6 +67,7 @@ int HTTPServer::run() return (EXIT_FAILURE); } } + return (EXIT_SUCCESS); } void HTTPServer::setupServers(void) @@ -68,6 +90,8 @@ void HTTPServer::setupServers(void) void HTTPServer::handleActivePollFDs() { Logger &logger = Logger::getInstance(); + logger.log(DEBUG, "HTTPServer::handleActivePollFDs"); + _poll.pollFDs(); for (const auto &poll_fd : _poll.getPollFDs()) { @@ -75,6 +99,20 @@ void HTTPServer::handleActivePollFDs() continue; try { + if (poll_fd.revents & POLLHUP) // this code block is solving a thrown exception from the cgi's child process + { + Client &client = getClientByPipeFd(poll_fd.fd); + if (client.getRequest().CGITrue() && client.getRequest().getMethodType() != HTTPMethod::DELETE) + { + _poll.removeFD(poll_fd.fd); + _active_pipes.erase(poll_fd.fd); + _poll.setEvents(client.getFD(), POLLOUT); + client.setState(ClientState::Sending); + client.KO = true; + handleExistingConnection(poll_fd, _poll, client, _active_pipes); + continue; + } + } _poll.checkREvents(poll_fd.revents); } catch (const Poll::PollException &e) @@ -89,38 +127,79 @@ void HTTPServer::handleActivePollFDs() _poll.pollEventsToString(poll_fd.revents)); if (_active_servers.find(poll_fd.fd) != _active_servers.end()) handleNewConnection(poll_fd.fd); - else if (_active_clients.find(poll_fd.fd) != _active_clients.end()) - handleExistingConnection(poll_fd); + else if (_active_clients.find(poll_fd.fd) != _active_clients.end()) { + Client &client = findClientByFd(poll_fd.fd); + handleExistingConnection(poll_fd, _poll, client, _active_pipes); + } + else if (_active_pipes.find(poll_fd.fd) != _active_pipes.end()) { + Client &client = getClientByPipeFd(poll_fd.fd); + handlePipeConnection(poll_fd, _poll, client, _active_pipes); + } else throw std::runtime_error("Unknown file descriptor"); } + logger.log(DEBUG, "HTTPServer::handleActivePollFDs -- after for loop"); +} + +void HTTPServer::handlePipeConnection(const pollfd &poll_fd, Poll &poll, Client &client, + std::unordered_map> &active_pipes) +{ + Logger &logger = Logger::getInstance(); + logger.log(DEBUG, "Client % found on poll_fd.fd (pipe): %", &client, poll_fd.fd); + + (&client)->handleConnection(poll_fd.events, poll, client, active_pipes); + if (client.cgiBodyIsSent) + { + logger.log(DEBUG, "remove pipe fd: %", poll_fd.fd); + _poll.removeFD(poll_fd.fd); + active_pipes.erase(poll_fd.fd); + _poll.setEvents(client.getFD(), POLLIN); + } + if (client.cgiHasBeenRead) + { + logger.log(DEBUG, "remove pipe fd: %", poll_fd.fd); + _poll.removeFD(poll_fd.fd); + active_pipes.erase(poll_fd.fd); + _poll.setEvents(client.getFD(), POLLOUT); + } } void HTTPServer::handleNewConnection(int fd) { + Logger &logger = Logger::getInstance(); + logger.log(DEBUG, "HTTPServer::handleNewConnection"); + std::shared_ptr client = std::make_shared(fd); _active_clients.emplace(client->getFD(), client); _poll.addPollFD(client->getFD(), POLLIN); } -void HTTPServer::handleExistingConnection(const pollfd &poll_fd) +void HTTPServer::handleExistingConnection(const pollfd &poll_fd, Poll &poll, Client &client, + std::unordered_map> &active_pipes) { - switch (_active_clients.at(poll_fd.fd)->handleConnection(poll_fd.events)) + Logger &logger = Logger::getInstance(); + logger.log(DEBUG, "HTTPServer::handleExistingConnection"); + + switch ((&client)->handleConnection(poll_fd.events, poll, client, active_pipes)) { - case ClientState::Receiving: - _poll.setEvents(poll_fd.fd, POLLIN); - break; - case ClientState::Loading: - case ClientState::Sending: - case ClientState::Error: - _poll.setEvents(poll_fd.fd, POLLOUT); - break; - case ClientState::Done: - _poll.removeFD(poll_fd.fd); - _active_clients.erase(poll_fd.fd); - break; - default: - throw std::runtime_error( - "Unknown client state"); // TODO custom exception - } + case ClientState::Receiving: + case ClientState::CGI_Read: + _poll.setEvents(poll_fd.fd, POLLIN); + break; + case ClientState::Loading: + case ClientState::Sending: + case ClientState::Error: + case ClientState::CGI_Start: + case ClientState::CGI_Write: + _poll.setEvents(poll_fd.fd, POLLOUT); + break; + case ClientState::Unknown: + case ClientState::Done: + _poll.removeFD(poll_fd.fd); + _active_clients.erase(poll_fd.fd); + break; + default: + throw std::runtime_error( + "Unknown client state"); // TODO custom exception + } } diff --git a/src/Poll.cpp b/src/Poll.cpp index 2262a66..5cb7240 100644 --- a/src/Poll.cpp +++ b/src/Poll.cpp @@ -42,7 +42,9 @@ void Poll::pollFDs(void) Logger &logger = Logger::getInstance(); logger.log(INFO, "Polling " + std::to_string(_poll_fds.size()) + " file descriptors"); + int poll_count = poll(_poll_fds.data(), _poll_fds.size(), NO_TIMEOUT); + logger.log(DEBUG, "foo"); if (poll_count == SYSTEM_ERROR || poll_count == 0) throw SystemException("poll"); // TODO change poll_count 0 handler } diff --git a/src/main.cpp b/src/main.cpp index 7b8357f..7ee367c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,6 +3,10 @@ #include #include +#include +#include +#include + int main(int argc, char **argv) { Logger &logger = Logger::getInstance(); @@ -18,6 +22,36 @@ int main(int argc, char **argv) return (server.run()); } +// &&&&&&&&&&&&&& + +// #include +// #include +// int main(void) { + +// Logger &logger = Logger::getInstance(); +// CGI cgi_request; +// std::string body; + +// cgi_request.execute("data/www/python/test.py", true, body); +// logger.log(INFO, "Body: " + body); + +// return 0; + + // & + + // std::string bin = "python3"; + // std::string script = "data/www/python/test.py"; + // const char *path = "/usr/bin/python3"; + + // const char *const argv[] = {bin.c_str(), script.c_str(), NULL}; + // char *const envp[] = {NULL}; + + // execve(path, (char *const *)argv, envp); + // return 0; +// } + +// ###### + // //#define REQUEST "GET /index HTTP/1.1 balllen \r\nHost: // 192.168.0.199:80\r\nConnection: keep-alive\r\nAccept: */*\r\nUser-Agent: // Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.22 (KHTML, like Gecko) diff --git a/tests/get_request.sh b/tests/get_request.sh index 8923074..242eb53 100755 --- a/tests/get_request.sh +++ b/tests/get_request.sh @@ -5,7 +5,7 @@ SERVER_HOST="localhost" SERVER_PORT="9696" # Request file -REQUEST_FILE="tests/request/delete.txt" +REQUEST_FILE="tests/request/get_cgi.txt" # Use netcat to send the contents of the request file to the server nc "${SERVER_HOST}" "${SERVER_PORT}" < "${REQUEST_FILE}" diff --git a/tests/request/get_cgi.txt b/tests/request/get_cgi.txt new file mode 100755 index 0000000..aeb2933 --- /dev/null +++ b/tests/request/get_cgi.txt @@ -0,0 +1,6 @@ +GET /cgi.py/with/additional/path?and=a&query=string HTTP/1.1 +User-Agent: custom-client +Host: webserv +Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5 +Connection: Keep-Alive + diff --git a/tests/request/post_cgi.txt b/tests/request/post_cgi.txt new file mode 100755 index 0000000..6575eb5 --- /dev/null +++ b/tests/request/post_cgi.txt @@ -0,0 +1,8 @@ +POST /cgi.py/with/additional/path?and=a&query=string HTTP/1.1 +User-Agent: custom-client +Host: webserv +Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5 +Connection: Keep-Alive +Content-length: 42 + +/with/additional/path?and=a&query=string