Skip to content

Commit a163bdc

Browse files
feat: cgi handler + unit tests
rebase on dev applying make lint applying make lint
1 parent 10daf07 commit a163bdc

File tree

6 files changed

+377
-68
lines changed

6 files changed

+377
-68
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ deps = $(objs:.o=.d)
7070

7171
# Test sources (keep in alphabetical order)
7272
test_srcs = \
73+
tests/cgi_handler_unittest.cpp \
7374
tests/delete_handler_unittest.cpp \
7475
tests/upload_handler_unittest.cpp \
7576
tests/error_handler_unittest.cpp \

src/config/server_config.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ ServerConfig make_example_config()
7575

7676
RouteConfig loc1;
7777
loc1.config = shared_cfg;
78-
loc1.route_path = "/example";
78+
loc1.route_path = "/bin";
7979
loc1.config.cgi.allowed_methods.push_back("GET");
8080
loc1.config.cgi.allowed_methods.push_back("POST");
8181
loc1.config.cgi.extension = ".py";

src/handler/cgi_handler.cpp

Lines changed: 66 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,17 @@ static std::string to_string_size_t(size_t v)
4141
return oss.str();
4242
}
4343

44-
void CgiHandler::setEnvVar(std::vector<std::string>& child_env_var)
44+
std::string build_error_response(HttpResponse::Status status, std::string inline_body)
45+
{
46+
HttpResponse res;
47+
res.code = status;
48+
res.content_type = "text/html; charset=UTF-8";
49+
res.inline_body = inline_body;
50+
std::string headers = res.to_string();
51+
return (headers);
52+
}
53+
54+
void CgiHandler::set_env_var(std::vector<std::string>& child_env_var)
4555
{
4656
child_env_var.push_back("REQUEST_METHOD=" + saved_request_.method);
4757
child_env_var.push_back("SCRIPT_FILENAME=" + saved_request_.path);
@@ -54,6 +64,7 @@ void CgiHandler::setEnvVar(std::vector<std::string>& child_env_var)
5464
child_env_var.push_back("SERVER_NAME=localhost"); // to be updated based on config or removed
5565
child_env_var.push_back("SERVER_PROTOCOL=HTTP/1.1");
5666
child_env_var.push_back("SERVER_PORT=" + to_string_size_t(WEBSERV_DEFAULT_PORT));
67+
child_env_var.push_back("PATH=/usr/bin:/bin");
5768
}
5869

5970
// constructor needs to query the request, create the env var,
@@ -68,40 +79,47 @@ CgiHandler::CgiHandler(const std::string& path, const HttpRequest& saved_request
6879
body_length_(saved_request.content_length),
6980
headers_parsed_(false),
7081
headers_sent_(false),
82+
eob_reached_(false),
7183
eoo_reached_(false),
72-
child_reaped_(false),
73-
pipe_blocked_(false)
84+
child_reaped_(false)
7485
{
75-
input_fd[0] = -1;
76-
input_fd[1] = -1;
77-
output_fd[0] = -1;
78-
output_fd[1] = -1;
86+
input_fd_[0] = -1;
87+
input_fd_[1] = -1;
88+
output_fd_[0] = -1;
89+
output_fd_[1] = -1;
7990

8091
// STEP 0 - Check that file exisst and is executable
8192
struct stat sb;
82-
if (stat(path.data(), &sb) == -1 || !S_ISREG(sb.st_mode) ||
83-
!(sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) || access(path.data(), X_OK) != 0) {
84-
// error
93+
if (stat(path.c_str(), &sb) == -1 || !S_ISREG(sb.st_mode) ||
94+
!(sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) || access(path.c_str(), X_OK) != 0) {
95+
headers_ = build_error_response(HttpResponse::kStatusForbidden, "<h1>403 Forbidden</h1>");
96+
headers_parsed_ = true;
97+
headers_sent_ = false;
98+
headers_off_ = 0;
99+
eoo_reached_ = true;
100+
child_reaped_ = true;
101+
return;
85102
}
86103

87104
// STEP 1 - Prepare key=value pair vector to be passed to child process to set env var
88105
std::vector<std::string> child_env_var;
89106
std::vector<char*> envp;
90-
setEnvVar(child_env_var); // -> will be passed to execve( , , char *envp[])
107+
set_env_var(child_env_var); // -> will be passed to execve( , , char *envp[])
91108

92109
// STEP 2 - set up pipes
93110
if (saved_request_.method == "POST") {
94-
if (pipe(input_fd) == -1) {
111+
if (pipe(input_fd_) == -1) {
95112
// some error to be sent
96113
return;
97114
}
98115
}
99116

100-
if (pipe(output_fd) == -1) {
117+
if (pipe(output_fd_) == -1) {
101118
// some error to be sent
102119
return;
103120
}
104121

122+
LOG(DEBUG) << "CgiHandler constructed";
105123
// STEP 3 - Fork child process
106124
pid_ = fork();
107125
if (pid_ == -1) {
@@ -112,14 +130,14 @@ CgiHandler::CgiHandler(const std::string& path, const HttpRequest& saved_request
112130
if (pid_ == 0) { // child process - setting up pipes
113131
// closing the write end and replacing STDIN by the read end of the input pipe
114132
if (saved_request_.method == "POST") {
115-
close(input_fd[1]);
116-
dup2(input_fd[0], STDIN_FILENO);
117-
close(input_fd[0]); // as now duplicated to STDIN
133+
close(input_fd_[1]);
134+
dup2(input_fd_[0], STDIN_FILENO);
135+
close(input_fd_[0]); // as now duplicated to STDIN
118136
}
119137
// closing the read end and replacing STDOUT by the write end of the output pipe
120-
close(output_fd[0]);
121-
dup2(output_fd[1], STDOUT_FILENO);
122-
close(output_fd[1]); // as now duplicated to STDOUT
138+
close(output_fd_[0]);
139+
dup2(output_fd_[1], STDOUT_FILENO);
140+
close(output_fd_[1]); // as now duplicated to STDOUT
123141

124142
std::vector<char*> envp;
125143
envp.reserve(child_env_var.size() + 1);
@@ -140,36 +158,36 @@ CgiHandler::CgiHandler(const std::string& path, const HttpRequest& saved_request
140158
// parent process - setting up pipes
141159
// closing read end of input_fd
142160
if (saved_request_.method == "POST") {
143-
close(input_fd[0]);
144-
fcntl(input_fd[1], F_SETFL, O_NONBLOCK);
161+
close(input_fd_[0]);
162+
fcntl(input_fd_[1], F_SETFL, O_NONBLOCK);
145163
}
146164
// closing write end of the output_fd
147-
close(output_fd[1]);
148-
fcntl(output_fd[0], F_SETFL, O_NONBLOCK);
165+
close(output_fd_[1]);
166+
fcntl(output_fd_[0], F_SETFL, O_NONBLOCK);
149167
}
150168
}
151169

152170
CgiHandler::~CgiHandler()
153171
{
154-
if (input_fd[1] != -1) {
155-
close(input_fd[1]);
156-
input_fd[1] = -1;
172+
if (input_fd_[1] != -1) {
173+
close(input_fd_[1]);
174+
input_fd_[1] = -1;
157175
}
158176

159-
if (output_fd[0] != -1) {
160-
close(output_fd[0]);
161-
output_fd[0] = -1;
177+
if (output_fd_[0] != -1) {
178+
close(output_fd_[0]);
179+
output_fd_[0] = -1;
162180
}
163181
}
164182

165-
int CgiHandler::childReaped(void)
183+
int CgiHandler::child_reaped(void) const
166184
{
167185
if (child_reaped_) {
168-
// LOG(DEBUG) << "child_reaped == true";
186+
LOG(DEBUG) << "child_reaped == true";
169187
return 1;
170188
}
171189

172-
int status;
190+
int status = 0;
173191
pid_t r = waitpid(pid_, &status, WNOHANG);
174192
if (r == 0) {
175193
return 0;
@@ -186,16 +204,6 @@ int CgiHandler::childReaped(void)
186204
return 0;
187205
}
188206

189-
std::string build_error_response(HttpResponse::Status status, std::string inline_body)
190-
{
191-
HttpResponse res;
192-
res.code = status;
193-
res.content_type = "text/html; charset=UTF-8";
194-
res.inline_body = inline_body;
195-
std::string headers_ = res.to_string();
196-
return (headers_);
197-
}
198-
199207
int CgiHandler::parse_headers(std::string& cgi_headers, HttpResponse& res)
200208
{
201209
// Extract status code
@@ -242,9 +250,6 @@ bool CgiHandler::has_output() const
242250

243251
size_t CgiHandler::read_data(char* buf, size_t n)
244252
{
245-
LOG(DEBUG) << "read_data()";
246-
childReaped();
247-
248253
if (headers_parsed_ && !headers_sent_) {
249254
size_t remain = headers_.size() - headers_off_;
250255
size_t to_copy = std::min(remain, n);
@@ -274,16 +279,16 @@ size_t CgiHandler::read_data(char* buf, size_t n)
274279

275280
char tmp[4096];
276281

277-
ssize_t bytes_read = read(output_fd[0], tmp, sizeof(tmp));
282+
ssize_t bytes_read = read(output_fd_[0], tmp, sizeof(tmp));
278283
LOG(DEBUG) << "bytes_read = " << bytes_read;
279284
if (bytes_read == 0) {
280285
eoo_reached_ = true;
281-
close(output_fd[0]);
282-
output_fd[0] = -1;
286+
close(output_fd_[0]);
287+
output_fd_[0] = -1;
283288
return 0; // finished reading
284289
}
285290
if (bytes_read < 0) {
286-
if (errno == EAGAIN || errno || EWOULDBLOCK) {
291+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
287292
return 0; // pipe not available for read
288293
}
289294
headers_.append(
@@ -293,21 +298,26 @@ size_t CgiHandler::read_data(char* buf, size_t n)
293298
headers_sent_ = false;
294299
headers_off_ = 0;
295300
eoo_reached_ = true;
296-
pipe_blocked_ = false;
297301
return 0;
298302
}
299303

300304
if (!headers_parsed_) {
301305
raw_output_.append(tmp, bytes_read);
302306

303307
size_t header_end = raw_output_.find("\r\n\r\n");
308+
size_t sep_len = 4;
309+
310+
if (header_end == std::string::npos) {
311+
header_end = raw_output_.find("\n\n");
312+
sep_len = 2;
313+
}
304314
if (header_end == std::string::npos) {
305315
// Still waiting for full CGI header block
306316
return 0;
307317
}
308318

309319
std::string cgi_headers = raw_output_.substr(0, header_end);
310-
std::string remainder = raw_output_.substr(header_end + 4);
320+
std::string remainder = raw_output_.substr(header_end + sep_len);
311321

312322
HttpResponse res;
313323
if (parse_headers(cgi_headers, res) != 0) {
@@ -335,12 +345,12 @@ size_t CgiHandler::read_data(char* buf, size_t n)
335345
return 0;
336346
}
337347

338-
// We never write to this handler (read-only)
339348
size_t CgiHandler::write_data(const char* buf, size_t n)
340349
{
341-
ssize_t bytes = write(input_fd[1], buf, n);
350+
LOG(DEBUG) << "Attempt to write to pipe";
351+
ssize_t bytes = write(input_fd_[1], buf, n);
342352
if (bytes < 0) {
343-
if (errno == EAGAIN || errno || EWOULDBLOCK) {
353+
if (errno == EAGAIN || errno == EWOULDBLOCK) {
344354
return 0; // pipe not available for write
345355
}
346356
eob_reached_ = true;
@@ -360,8 +370,8 @@ size_t CgiHandler::write_data(const char* buf, size_t n)
360370

361371
return (bytes);
362372
}
363-
close(input_fd[1]);
364-
input_fd[1] = -1;
373+
close(input_fd_[1]);
374+
input_fd_[1] = -1;
365375
LOG(INFO) << "CgiHandler : Body fully written to STDIN";
366376
return (0);
367377
}

src/handler/cgi_handler.hpp

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,32 @@ class CgiHandler : public Handler {
1818
virtual size_t read_data(char* buf, size_t n);
1919
virtual size_t write_data(const char* buf, size_t n);
2020

21-
void setEnvVar(std::vector<std::string>& child_env_var);
22-
int childReaped(void);
21+
void set_env_var(std::vector<std::string>& child_env_var);
22+
int child_reaped(void) const;
2323
int parse_headers(std::string& cgi_headers, HttpResponse& res);
2424

2525
virtual bool has_output() const;
26-
virtual bool needs_input() const { return bytes_written_body_ < body_length_; };
26+
virtual bool needs_input() const
27+
{
28+
return input_fd_[1] != -1 && bytes_written_body_ < body_length_;
29+
};
2730
virtual bool is_done() const
2831
{
32+
child_reaped();
2933
return eoo_reached_ && !needs_input() && child_reaped_ && !has_output();
3034
}
3135
const std::string& path() const { return path_; }
3236

33-
virtual int cgi_read_fd() const { return output_fd[0]; };
34-
virtual int cgi_write_fd() const { return input_fd[1]; };
37+
virtual int cgi_read_fd() const { return output_fd_[0]; };
38+
virtual int cgi_write_fd() const { return input_fd_[1]; };
3539

3640
private:
3741
CgiHandler(const CgiHandler&);
3842
CgiHandler& operator=(const CgiHandler&);
3943

4044
// bool has_body() const { return body_length_ > 0; }
4145
bool headers_sent() const { return headers_off_ == headers_.size(); }
42-
bool body_written_to_STDIN() const { return eob_reached_; }
46+
// bool body_written_to_STDIN() const { return eob_reached_; }
4347

4448
const std::string path_;
4549

@@ -62,15 +66,14 @@ class CgiHandler : public Handler {
6266
bool headers_sent_;
6367
bool eob_reached_;
6468
bool eoo_reached_;
65-
bool child_reaped_;
66-
bool pipe_blocked_;
69+
mutable bool child_reaped_;
6770

6871
// pipes
69-
int input_fd[2];
70-
int output_fd[2];
72+
int input_fd_[2];
73+
int output_fd_[2];
7174

7275
// child process pid
73-
pid_t pid_;
76+
mutable pid_t pid_;
7477
};
7578

7679
#endif

0 commit comments

Comments
 (0)