Skip to content

Commit

Permalink
feat(cgi + routing): add pipe to CGI, route properly for POST & CGI
Browse files Browse the repository at this point in the history
  • Loading branch information
lmangall committed May 21, 2024
1 parent d3b91cd commit f2a20fc
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 22 deletions.
69 changes: 50 additions & 19 deletions src/CGIHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,16 @@ void CGIHandler::handleRequest(HTTPRequest &request, HTTPResponse &response)
{
MetaVariables env;
env.HTTPRequestToMetaVars(request, env);
if (!executeCGI(env, response))

std::cout << std::endl;
std::cout << std::endl;
std::cout << std::endl;
std::cout << request << std::endl;
std::cout << request.getBody() << std::endl;
std::cout << std::endl;
std::cout << std::endl;

if (!executeCGI(env, request.getBody(), response))
{
response.setStatusCode(500, "");
response.setBody("500 Internal Server Error");
Expand Down Expand Up @@ -101,25 +110,38 @@ void handleTimeout(int sig)
std::cout << "CGIHandler: Timeout" << std::endl;
}

bool CGIHandler::executeCGI(const MetaVariables &env, HTTPResponse &response)
bool CGIHandler::executeCGI(const MetaVariables &env, std::string body, HTTPResponse &response)
{
std::string cgiOutput;
std::vector<std::string> argv = createArgvForExecve(env);
std::vector<std::string> 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)
Expand All @@ -128,6 +150,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<char *> argvPointers = convertToCStringArray(argv);
Expand All @@ -136,35 +162,40 @@ bool CGIHandler::executeCGI(const MetaVariables &env, HTTPResponse &response)
if (access(argvPointers[0], X_OK) == -1)
{
perror("access");
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)
{
perror("execve");
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);

close(pipeFD[1]);
EventData data = {1, pid}; // Assuming 1 is the event type for CGI started
_eventManager.emit(data); // Emit event indicating a CGI process has started
// conn.addCGI(pid);
_connection.addCGI(pid);
// TODO: is this used? To which process to you want to send this signal/ @Leo
// signal(SIGALRM, handleTimeout);
// alarm(4);

return true;
else
{
close(pipeFD[1]);
close(bodyPipeFD[0]);

write(bodyPipeFD[1], body.c_str(), body.size());
close(bodyPipeFD[1]);

response.setIsCGI(true);
response.setCGIpipeFD(pipeFD);

EventData data = {1, pid}; // Assuming 1 is the event type for CGI started
_eventManager.emit(data); // Emit event indicating a CGI process has started
_connection.addCGI(pid);
// TODO: is this used? To which process to you want to send this signal/ @Leo
// signal(SIGALRM, handleTimeout);
// alarm(4);

return true;
}
return false;
}

void CGIHandler::setFDsRef(std::vector<struct pollfd> *FDsRef)
Expand Down
2 changes: 1 addition & 1 deletion src/CGIHandler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> createArgvForExecve(const MetaVariables &env);
std::vector<char *> convertToCStringArray(const std::vector<std::string> &input);
// void CGIStringToResponse(const std::string &cgiOutput, HTTPResponse &response);
Expand Down
1 change: 1 addition & 0 deletions src/Connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ bool Connection::readBody(Parser &parser, HTTPRequest &req, HTTPResponse &res)
}
else
parser.setBodyComplete(true);
std::cout << YELLOW << "Body content: " << parser.getBuffer() << RESET << std::endl;
std::cout << "Exiting readBody" << std::endl;
return true;
}
Expand Down
112 changes: 112 additions & 0 deletions src/Server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,114 @@ void Server::startPollEventLoop()
}
}
}

std::string getFileExtension(const std::string &fileName)
{
size_t dotIndex = fileName.find_last_of(".");
if (dotIndex == std::string::npos)
{
return "";
}

size_t queryStart = fileName.find("?", dotIndex);
if (queryStart == std::string::npos)
{
return fileName.substr(dotIndex);
}
else
{
return fileName.substr(dotIndex, queryStart - dotIndex - 1);
}
}

bool requestIsCGI(const HTTPRequest &request)
{
// TODO: do not rely on this function which is a copy from Router.cpp
std::vector<std::string> cgiExtensions;
cgiExtensions.push_back(".php");
cgiExtensions.push_back(".py");
cgiExtensions.push_back(".pl");
std::cout << "cgiExtensions: " << cgiExtensions.size() << std::endl;
std::cout << "request target: " << request.getRequestTarget() << std::endl;
if (!cgiExtensions.empty())
{
std::string fileExtension = getFileExtension(request.getRequestTarget());
std::cout << "fileExtension: " << fileExtension << std::endl;
for (size_t i = 0; i < cgiExtensions.size(); i++)
{
std::cout << "cgiExtensions[" << i << "]: " << cgiExtensions[i] << std::endl;
if (cgiExtensions[i] == fileExtension)
{
Debug::log("requestIsCGI: CGI request detected", Debug::NORMAL);
return true;
}
}
}
Debug::log("requestIsCGI: Not a CGI request", Debug::NORMAL);
return false;
}

void handlePostCGIRequest(Connection &conn, Parser &parser, HTTPRequest &request, HTTPResponse &response)
{
std::cout << BLUE << "handlePostCGIRequest" << RESET << std::endl;
if (parser.getIsChunked() && !conn.getHasReadSocket())
{
Debug::log("Chunked body in handlePostCGIRequest", Debug::NORMAL);
if (!conn.readBody(parser, request, response))
{
Debug::log("Error reading body", Debug::OCF);
conn.setCanBeClosed(true);
conn.setHasFinishedReading(true);
return;
}
conn.setHasReadSocket(true);
}
else if (!conn.getHasReadSocket())
{
if (!parser.getBodyComplete() && parser.getBuffer().size() == request.getContentLength())
{
parser.setBodyComplete(true);
conn.setHasFinishedReading(true);
conn.setHasDataToSend(true);
}

else if (!conn.readBody(parser, request, response))
{
Debug::log("Error reading body", Debug::OCF);
conn.setCanBeClosed(false);
conn.setHasFinishedReading(true);
conn.setHasDataToSend(false);
return;
}
}
// TODO: double check this condition, logic
if (!parser.getBodyComplete() && request.getContentLength() != 0 &&
parser.getBuffer().size() == request.getContentLength())
{
// TODO: in the new design we will return here and go to the function where the response is
parser.setBodyComplete(true);
conn.setHasFinishedReading(true);
conn.setCanBeClosed(false);
conn.setHasDataToSend(false);
return;
}
//-----------------------------//

if (!parser.getBodyComplete())
{
Debug::log("Body still incomplete, exiting readFromClient.", Debug::NORMAL);
conn.setHasFinishedReading(false);
conn.setHasReadSocket(true);
return;
}

if (!request.getUploadBoundary().empty())
parser.parseFileUpload(parser.getBuffer(), request, response);

request.setBody(parser.getBuffer());
conn.setHasFinishedReading(true);
}

void Server::readFromClient(Connection &conn, size_t &i, Parser &parser, HTTPRequest &request, HTTPResponse &response)
{
(void)i;
Expand Down Expand Up @@ -222,6 +330,10 @@ void Server::readFromClient(Connection &conn, size_t &i, Parser &parser, HTTPReq

if (request.getMethod() == "GET" || request.getMethod() == "DELETE" || request.getMethod() == "SALAD")
Debug::log("GET request, no body to read", Debug::NORMAL);
if (requestIsCGI(request) == true && request.getMethod() == "POST")
{
handlePostCGIRequest(conn, parser, request, response);
}
else
handlePostRequest(conn, parser, request, response);
}
Expand Down
6 changes: 5 additions & 1 deletion tests/CGI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,16 @@ void cgi(sockaddr_in serverAdress)
<< "Content-Type: application/x-www-form-urlencoded\r\n"
<< "Content-Length: " << postData.size() << "\r\n"
<< "\r\n"
<< postData;
<< postData << "\r\n\r\n";

std::vector<HTTPTest> tests = {HTTPTest(request.str(), "200")};
sendData(tests, serverAdress);
}

// HTTPTest("POST / HTTP/1.1\r\nHost: www.example.com\r\nContent-Length: 17\r\nContent-Type: "
// "text/plain\r\n\r\nThis\r\nis body\r\n\r\n",
// "200"),

int main(void)
{
sockaddr_in serverAddress;
Expand Down
2 changes: 1 addition & 1 deletion var/www.saladbook.xyz/database/salad_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def main():
# Print the environment variables for debugging
for key, value in os.environ.items():
print(f"DEBUG: {key}={value}<br>")
print("<br>")
print("<br><br>")

# Get the directory where the script is located
script_directory = get_script_directory()
Expand Down

0 comments on commit f2a20fc

Please sign in to comment.