Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions include/http/CgiHandler.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifndef CGI_HANDLER_HPP
#define CGI_HANDLER_HPP

#include "HttpRequest.hpp"
#include "HttpResponse.hpp"
#include "../../include/config/LocationConfig.hpp"
#include <string>
#include <vector>

class CgiHandler {
public:
HttpResponse execute(const HttpRequest& req, const LocationConfig& loc);

private:
std::vector<std::string> buildEnv(const HttpRequest& req, const std::string& scriptPath);
HttpResponse parseOutput(const std::string& raw);
HttpResponse buildError(int code, const std::string& msg);
};

#endif
46 changes: 42 additions & 4 deletions src/http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ RequestParser parser;
HttpRequest req = parser.parse(rawString); // rawString = bytes du socket

if (req.method.empty())
// requête invalide → envoyer 400
// requête invalide → envoyer 400 (voir exemple plus bas)

Router router;
LocationConfig loc = router.route(req, serverConfig);
Expand Down Expand Up @@ -62,12 +62,50 @@ send(fd, raw.c_str(), raw.size(), 0);

| Code | Cas |
|---|---|
| 200 | Fichier servi |
| 200 | Fichier servi / CGI OK |
| 201 | Upload POST réussi |
| 204 | DELETE réussi |
| 301 | Redirection |
| 400 | Requête invalide (path traversal, body vide...) |
| 403 | Accès interdit |
| 403 | Accès interdit (dossier, permissions) |
| 404 | Fichier introuvable |
| 405 | Méthode non autorisée sur cette route |
| 500 | Erreur serveur (ex: upload_store manquant) |
| 500 | Erreur serveur (ex: upload_store manquant, fork fail) |
| 507 | Disque plein (write incomplet sur POST upload) |

---

## CGI

Le CGI est déclenché automatiquement si la `LocationConfig` a `cgi_extension` et `cgi_path` définis, et que l'URI se termine par cette extension.

Config exemple :
```nginx
location /cgi-bin {
methods GET POST;
cgi_extension .py;
cgi_path /usr/bin/python3;
}
```

Un script CGI doit écrire sur stdout :
```
Content-Type: text/html\n
\n
<html>...</html>
```

Un script de démo est disponible dans `www/cgi-bin/hello.py`.

---

## Structure des fichiers

```
src/http/
parser/ — RequestParser
router/ — Router
handlers/ — MethodHandler, GetHandler, PostHandler, DeleteHandler
cgi/ — CgiHandler (execute, env, output)
response/ — ResponseBuilder
```
30 changes: 0 additions & 30 deletions src/http/Router.cpp

This file was deleted.

46 changes: 46 additions & 0 deletions src/http/cgi/env.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#include "../../../include/http/CgiHandler.hpp"
#include <sstream>

static std::string getQueryString(const std::string& uri)
{
std::size_t q = uri.find('?');
if (q == std::string::npos)
return "";
return uri.substr(q + 1);
}

std::vector<std::string> CgiHandler::buildEnv(const HttpRequest& req, const std::string& scriptPath)
{
std::vector<std::string> env;

env.push_back("REQUEST_METHOD=" + req.method);
env.push_back("QUERY_STRING=" + getQueryString(req.uri));
env.push_back("SCRIPT_FILENAME=" + scriptPath);
env.push_back("PATH_INFO=" + req.uri);

std::map<std::string, std::string>::const_iterator it;

it = req.headers.find("Content-Type");
if (it != req.headers.end())
env.push_back("CONTENT_TYPE=" + it->second);
else
env.push_back("CONTENT_TYPE=");

if (!req.body.empty())
{
std::ostringstream ss;
ss << req.body.size();
env.push_back("CONTENT_LENGTH=" + ss.str());
}
else
env.push_back("CONTENT_LENGTH=0");

it = req.headers.find("Host");
if (it != req.headers.end())
env.push_back("HTTP_HOST=" + it->second);

env.push_back("SERVER_PROTOCOL=HTTP/1.1");
env.push_back("GATEWAY_INTERFACE=CGI/1.1");

return env;
}
101 changes: 101 additions & 0 deletions src/http/cgi/execute.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#include "../../../include/http/CgiHandler.hpp"
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>

static std::string scriptPathFrom(const HttpRequest& req, const LocationConfig& loc)
{
std::string uri = req.uri;
std::size_t q = uri.find('?');
if (q != std::string::npos)
uri = uri.substr(0, q);

char cwd[4096];
getcwd(cwd, sizeof(cwd));
return std::string(cwd) + "/" + loc.getRoot() + uri;
}

static void runChild(const std::string& interpreter, const std::string& scriptPath,
char** argv, char** envp,
int stdin_pipe[2], int stdout_pipe[2])
{
dup2(stdin_pipe[0], STDIN_FILENO);
dup2(stdout_pipe[1], STDOUT_FILENO);

close(stdin_pipe[0]); close(stdin_pipe[1]);
close(stdout_pipe[0]); close(stdout_pipe[1]);

std::string dir = scriptPath.substr(0, scriptPath.rfind('/'));
chdir(dir.c_str());

execve(interpreter.c_str(), argv, envp);
_exit(1);
}

static std::string readOutput(int stdout_pipe[2])
{
std::string output;
char buf[4096];
ssize_t n;
while ((n = read(stdout_pipe[0], buf, sizeof(buf))) > 0)
output.append(buf, n);
close(stdout_pipe[0]);
return output;
}

HttpResponse CgiHandler::execute(const HttpRequest& req, const LocationConfig& loc)
{
std::string scriptPath = scriptPathFrom(req, loc);

if (access(scriptPath.c_str(), F_OK) == -1)
return buildError(404, "Not Found");
if (access(scriptPath.c_str(), X_OK) == -1)
return buildError(403, "Forbidden");

std::vector<std::string> envVars = buildEnv(req, scriptPath);
std::vector<char*> envp;
for (std::size_t i = 0; i < envVars.size(); i++)
envp.push_back(const_cast<char*>(envVars[i].c_str()));
envp.push_back(NULL);

std::string interpreter = loc.getCgiPath();
char* argv[3] = {
const_cast<char*>(interpreter.c_str()),
const_cast<char*>(scriptPath.c_str()),
NULL
};

int stdin_pipe[2];
int stdout_pipe[2];
if (pipe(stdin_pipe) == -1 || pipe(stdout_pipe) == -1)
return buildError(500, "Internal Server Error");

pid_t pid = fork();
if (pid == -1)
{
close(stdin_pipe[0]); close(stdin_pipe[1]);
close(stdout_pipe[0]); close(stdout_pipe[1]);
return buildError(500, "Internal Server Error");
}

if (pid == 0)
runChild(interpreter, scriptPath, argv, envp.data(), stdin_pipe, stdout_pipe);

// parent
close(stdin_pipe[0]);
close(stdout_pipe[1]);

if (!req.body.empty())
write(stdin_pipe[1], req.body.c_str(), req.body.size());
close(stdin_pipe[1]);

std::string output = readOutput(stdout_pipe);

int status;
waitpid(pid, &status, 0);

if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
return buildError(500, "Internal Server Error");

return parseOutput(output);
}
84 changes: 84 additions & 0 deletions src/http/cgi/output.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#include "../../../include/http/CgiHandler.hpp"
#include <sstream>

HttpResponse CgiHandler::parseOutput(const std::string& raw)
{
HttpResponse res;

std::size_t sep = raw.find("\r\n\r\n");
if (sep == std::string::npos)
sep = raw.find("\n\n");
if (sep == std::string::npos)
{
res.status_code = 200;
res.status_msg = "OK";
res.body = raw;
res.headers["Content-Type"] = "text/html";
std::ostringstream ss;
ss << res.body.size();
res.headers["Content-Length"] = ss.str();
return res;
}

std::string headersPart = raw.substr(0, sep);
std::size_t bodyStart = sep + (raw[sep + 1] == '\n' ? 2 : 4);
res.body = raw.substr(bodyStart);

res.status_code = 200;
res.status_msg = "OK";

std::istringstream stream(headersPart);
std::string line;
while (std::getline(stream, line))
{
if (!line.empty() && line[line.size() - 1] == '\r')
line.erase(line.size() - 1);
if (line.empty())
continue;

std::size_t colon = line.find(':');
if (colon == std::string::npos)
continue;

std::string key = line.substr(0, colon);
std::string value = line.substr(colon + 1);
std::size_t vs = 0;
while (vs < value.size() && value[vs] == ' ')
vs++;
value = value.substr(vs);

if (key == "Status")
{
std::istringstream ss(value);
ss >> res.status_code;
std::size_t sp = value.find(' ');
if (sp != std::string::npos)
res.status_msg = value.substr(sp + 1);
}
else
res.headers[key] = value;
}

if (res.headers.find("Content-Type") == res.headers.end())
res.headers["Content-Type"] = "text/html";

std::ostringstream ss;
ss << res.body.size();
res.headers["Content-Length"] = ss.str();

return res;
}

HttpResponse CgiHandler::buildError(int code, const std::string& msg)
{
HttpResponse res;
std::ostringstream ss;

res.status_code = code;
res.status_msg = msg;
res.body = "<html><body><h1>" + msg + "</h1></body></html>";
res.headers["Content-Type"] = "text/html";
ss << res.body.size();
res.headers["Content-Length"] = ss.str();
return res;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "../../include/http/MethodHandler.hpp"
#include "../../../include/http/MethodHandler.hpp"
#include <unistd.h>
#include <sys/stat.h>

Expand Down
10 changes: 5 additions & 5 deletions src/http/GetHandler.cpp → src/http/handlers/GetHandler.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "../../include/http/MethodHandler.hpp"
#include "../../../include/http/MethodHandler.hpp"
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
Expand Down Expand Up @@ -49,7 +49,7 @@ static HttpResponse buildAutoindex(const std::string& path, const std::string& u
closedir(dir);
html += "</ul></body></html>";

HttpResponse res;
HttpResponse res;
std::ostringstream ss;
res.status_code = 200;
res.status_msg = "OK";
Expand Down Expand Up @@ -83,9 +83,9 @@ HttpResponse MethodHandler::handleGet(const HttpRequest& req, const LocationConf
if (fd == -1)
return buildError(404, "Not Found");

HttpResponse res;
char buf[4096];
ssize_t n;
HttpResponse res;
char buf[4096];
ssize_t n;

while ((n = read(fd, buf, sizeof(buf))) > 0)
res.body.append(buf, n);
Expand Down
Loading
Loading