LeftHookRoll
An HTTP/1.0 compliant web server, as specified by RFC1945
Loading...
Searching...
No Matches
CGIManager.cpp
Go to the documentation of this file.
1#include "../includes/CGIManager.hpp"
2#include "../includes/Request.hpp"
3#include "../includes/AllowedMethods.hpp"
4#include "../includes/FatalExceptions.hpp"
5
6#include <sys/wait.h>
7#include <csignal>
8#include <ctime>
9#include <cerrno>
10
11
12//There's a zombie on your lawn...
13std::set<pid_t> CGIManager::_activePids;
14#ifndef MAX_ACTIVE_CGI_CHILDREN
15# define MAX_ACTIVE_CGI_CHILDREN 64;
16#endif
17
18namespace cgi_utils
19{
21 {
22 // 3 to skip the standard fds.(in,out,err)
23 for (int fd = 3; fd < 1024; ++fd)
24 close(fd);
25 }
26
27 std::string resolveScriptDirectory(const std::string& scriptPath)
28 {
29 size_t slashPos = scriptPath.find_last_of('/');
30 if (slashPos == std::string::npos)
31 return ".";
32 if (slashPos == 0)
33 return "/";
34 return scriptPath.substr(0, slashPos);
35 }
36
37 std::string resolveScriptExecArg(const std::string& scriptPath)
38 {
39 if (!scriptPath.empty() && scriptPath[0] == '/')
40 return scriptPath;
41
42 size_t slashPos = scriptPath.find_last_of('/');
43 if (slashPos == std::string::npos)
44 return scriptPath;
45 return scriptPath.substr(slashPos + 1);
46 }
47
48 void prepareChildExecutionContext(const std::vector<std::string>& scriptArgv, char** execveArgv)
49 {
50 if (scriptArgv.empty())
51 throw FatalException("CGI child fatal: missing script path");
52
53 const std::string& scriptPath = scriptArgv.back();
54 std::string scriptDir = resolveScriptDirectory(scriptPath);
55 if (chdir(scriptDir.c_str()) == -1)
56 throw FatalException("CGI child fatal: chdir() failed");
57
58 std::string scriptExecArg = resolveScriptExecArg(scriptPath);
59 if (scriptArgv.size() > 1)
60 std::strcpy(execveArgv[1], scriptExecArg.c_str());
61 else
62 std::strcpy(execveArgv[0], scriptExecArg.c_str());
63 }
64}
65
66// Canonical Form
67
68CGIManager::CGIManager() : _pId(-1), _execveEnvp(NULL), _execveArgv(NULL)
69{
70 _outPipe[0] = -1;
71 _outPipe[1] = -1;
72}
73
74CGIManager::CGIManager(const CGIManager& other): _pId(other._pId), _query(other._query), _scriptArgv(other._scriptArgv), _env(other._env), _execveEnvp(NULL), _execveArgv(NULL)
75{
76 _outPipe[0] = -1;
77 _outPipe[1] = -1;
78}
79
81{
82 if (this != &other)
83 {
86 _pId = other._pId;
87 _query = other._query;
89 _env = other._env;
90 _execveEnvp = NULL;
91 _execveArgv = NULL;
92 _outPipe[0] = -1;
93 _outPipe[1] = -1;
94 }
95 return *this;
96}
97
103
104// Getters
105
107{
108 return _pId;
109}
110
112{
113 return _outPipe[0];
114}
115
116// Public Behaviour
117
118void CGIManager::prepare(const Request& request, const std::string& scriptPath, const std::string& interpreterOverride)
119{
120 _buildEnvMap(request, scriptPath);
121
122 _scriptArgv.clear();
123 std::string interp;
124 if (!interpreterOverride.empty())
125 interp = interpreterOverride;
126 if (!interp.empty())
127 _scriptArgv.push_back(interp);
128 _scriptArgv.push_back(scriptPath);
129
131}
132
133void CGIManager::execute(int inputFd)
134{
136 throw ClientException(503, "CGIManager::execute: max active CGI children reached");
137
138 if (pipe(_outPipe) == -1)
139 throw ClientException(500, "CGIManager::execute: pipe() failed");
140
141 _pId = fork();
142 if (_pId < 0)
143 {
144 _closePipes();
145 throw ClientException(500, "CGIManager::execute: fork() failed");
146 }
147
148 if (_pId == 0)
149 {
151 if (inputFd >= 0)
152 {
153 if (dup2(inputFd, STDIN_FILENO) == -1)
154 throw FatalException("CGI child fatal: dup2(stdin) failed");
155 }
156 close(_outPipe[0]);
157 if (dup2(_outPipe[1], STDOUT_FILENO) == -1)
158 throw FatalException("CGI child fatal: dup2(stdout) failed");
159 close(_outPipe[1]);
160
162
164 throw FatalException("CGI child fatal: execve() failed");
165 }
166 else
167 {
169 close(_outPipe[1]);
170 _outPipe[1] = -1;
171 }
172}
173
175{
176 if (_pId <= 0)
177 return true;
178
179 int status = 0;
180 pid_t result = waitpid(_pId, &status, WNOHANG);
181 if (result == 0)
182 return false; // still running
183
184 // Process has finished - unregister it
186 _pId = -1;
187 return true;
188}
189
190// ─── Private Helpers ───────────────────────────────────────────────────────
191
192void CGIManager::_buildEnvMap(const Request& request, const std::string& scriptPath)
193{
194 _env.clear();
195
196 _env["REQUEST_METHOD"] = AllowedMethods::methodToString(request.getMethod());
197 _env["QUERY_STRING"] = request.getQuery();
198 _env["SCRIPT_FILENAME"] = scriptPath;
199 _env["SCRIPT_NAME"] = request.getURL();
200 _env["PATH_INFO"] = request.getURL();
201 _env["SERVER_PROTOCOL"] = request.getProtocol();
202 _env["GATEWAY_INTERFACE"] = "CGI/1.1";
203 _env["REDIRECT_STATUS"] = "200";
204
205 // Content headers (only meaningful for POST)
206 std::string ct = request.getHeader("content-type");
207 if (!ct.empty())
208 _env["CONTENT_TYPE"] = ct;
209
210 std::string cl = request.getHeader("content-length");
211 _env["CONTENT_LENGTH"] = (cl.empty()? _env["CONTENT_LENGTH"] = "0" : _env["CONTENT_LENGTH"] = cl);
212
213 const std::map<std::string, std::string>& headers = request.getHeaders();
214 for (std::map<std::string, std::string>::const_iterator it = headers.begin();
215 it != headers.end(); ++it)
216 {
217 std::string key = it->first;
218 for (size_t i = 0; i < key.size(); ++i)
219 {
220 if (key[i] == '-')
221 key[i] = '_';
222 else
223 key[i] = std::toupper(key[i]);
224 }
225 _env["HTTP_" + key] = it->second;
226 }
227}
228
230{
232
233 _execveArgv = new char*[_scriptArgv.size() + 1];
234 for (size_t i = 0; i < _scriptArgv.size(); ++i)
235 {
236 _execveArgv[i] = new char[_scriptArgv[i].size() + 1];
237 std::strcpy(_execveArgv[i], _scriptArgv[i].c_str());
238 }
239 _execveArgv[_scriptArgv.size()] = NULL;
240 _execveEnvp = new char*[_env.size() + 1];
241 size_t idx = 0;
242 for (std::map<std::string, std::string>::const_iterator it = _env.begin();
243 it != _env.end(); ++it, ++idx)
244 {
245 std::string entry = it->first + "=" + it->second;
246 _execveEnvp[idx] = new char[entry.size() + 1];
247 std::strcpy(_execveEnvp[idx], entry.c_str());
248 }
249 _execveEnvp[_env.size()] = NULL;
250}
251
253{
254 if (_execveArgv)
255 {
256 for (size_t i = 0; _execveArgv[i]; ++i)
257 delete[] _execveArgv[i];
258 delete[] _execveArgv;
259 _execveArgv = NULL;
260 }
261 if (_execveEnvp)
262 {
263 for (size_t i = 0; _execveEnvp[i]; ++i)
264 delete[] _execveEnvp[i];
265 delete[] _execveEnvp;
266 _execveEnvp = NULL;
267 }
268}
269
271{
272 if (_outPipe[0] != -1)
273 {
274 close(_outPipe[0]);
275 _outPipe[0] = -1;
276 }
277 if (_outPipe[1] != -1)
278 {
279 close(_outPipe[1]);
280 _outPipe[1] = -1;
281 }
282}
283
284
286{
287 if (pid > 0)
288 _activePids.insert(pid);
289}
290
292{
293 if (_activePids.empty())
294 return;
295
296 std::set<pid_t> stillRunning;
297 for (std::set<pid_t>::iterator it = _activePids.begin(); it != _activePids.end(); ++it)
298 {
299 int status = 0;
300 pid_t result = waitpid(*it, &status, WNOHANG);
301 if (result == 0 || (result < 0 && errno == EINTR))
302 stillRunning.insert(*it);
303 }
304 _activePids = stillRunning;
305}
306
312
314{
315 CGIManager::_activePids.erase(pid);
316}
317
319{
320 if (CGIManager::_activePids.empty())
321 return;
322
323 for (std::set<pid_t>::iterator it = CGIManager::_activePids.begin(); it != CGIManager::_activePids.end(); ++it)
324 {
325 kill(*it, SIGTERM);
326 }
327
328 time_t startTime = time(NULL);
329 const int GRACE_PERIOD = 5;
330
331 while (!CGIManager::_activePids.empty() && (time(NULL) - startTime) < GRACE_PERIOD)
332 {
333 std::set<pid_t> remaining;
334 for (std::set<pid_t>::iterator it = CGIManager::_activePids.begin(); it != CGIManager::_activePids.end(); ++it)
335 {
336 int status;
337 pid_t result = waitpid(*it, &status, WNOHANG);
338 if (result == 0)
339 remaining.insert(*it);
340 }
341 CGIManager::_activePids = remaining;
342
343 if (!CGIManager::_activePids.empty())
344 usleep(100000);
345 }
346
347 if (!CGIManager::_activePids.empty())
348 {
349 for (std::set<pid_t>::iterator it = CGIManager::_activePids.begin(); it != CGIManager::_activePids.end(); ++it)
350 {
351 kill(*it, SIGKILL);
352 }
353 }
354
355 for (std::set<pid_t>::iterator it = CGIManager::_activePids.begin(); it != CGIManager::_activePids.end(); ++it)
356 {
357 int status;
358 waitpid(*it, &status, 0);
359 }
360
362}
#define MAX_ACTIVE_CGI_CHILDREN
static std::string methodToString(HTTPMethod method)
int _outPipe[2]
void _freeExecveArrays()
char ** _execveArgv
static void cleanupAllProcesses()
Cleans up all active CGI processes on server shutdown. Sends SIGTERM to all processes,...
static void _registerPid(pid_t pid)
static std::set< pid_t > _activePids
CGIManager & operator=(const CGIManager &other)
pid_t getPid() const
std::map< std::string, std::string > _env
void _buildEnvMap(const Request &request, const std::string &scriptPath)
void prepare(const Request &request, const std::string &scriptPath, const std::string &interpreterOverride="")
Prepares the environment and arguments for executing a CGI script.
std::vector< std::string > _scriptArgv
void execute(int inputFd)
Forks, redirs input file and outpipe, and executes the CGI script.
bool isDone()
Checks if the child process has finished executing (Non-blocking); waitpid with WNOHANG.
void _closePipes()
int getOutputFd() const
static bool _isSpawnLimitReached()
static void _unregisterPid(pid_t pid)
static void _reapFinishedActivePids()
std::string _query
void _prepExecveArrays()
char ** _execveEnvp
Exception type for failures scoped to a single client request.
const std::string & getQuery() const
Definition Request.cpp:94
HTTPMethod getMethod() const
Definition Request.cpp:82
std::string getHeader(const std::string &key) const
Gets a specific header value.
Definition Request.cpp:267
const std::string & getURL() const
Definition Request.cpp:86
const std::map< std::string, std::string > & getHeaders() const
Definition Request.cpp:98
const std::string & getProtocol() const
Definition Request.cpp:90
std::string resolveScriptExecArg(const std::string &scriptPath)
std::string resolveScriptDirectory(const std::string &scriptPath)
void closeInheritedFds()
void prepareChildExecutionContext(const std::vector< std::string > &scriptArgv, char **execveArgv)