LeftHookRoll
An HTTP/1.0 compliant web server, as specified by RFC1945
Loading...
Searching...
No Matches
Response.cpp
Go to the documentation of this file.
1#include "../includes/Response.hpp"
2#include "../includes/LocationConf.hpp"
3#include "../includes/CGIManager.hpp"
4#include "../includes/FatalExceptions.hpp"
5
6#include <sys/stat.h>
7#include <sys/socket.h>
8#include <fcntl.h>
9#include <dirent.h>
10#include <unistd.h>
11#include <cerrno>
12#include <cstring>
13#include <ctime>
14#include <sstream>
15#include <cctype>
16#include <algorithm>
17#include <cstdio>
18#include <csignal>
19#include <sys/wait.h>
20
21extern volatile sig_atomic_t g_sigpipe;
22
23
24static const size_t RESPONSE_SEND_CHUNK = 16384;
25
26namespace {
27
28void throwIfSigpipe(const std::string& context)
29{
30 if (g_sigpipe != 0)
31 {
32 g_sigpipe = 0;
33 throw ClientException(502, std::string("SIGPIPE while ") + context);
34 }
35}
36
37bool isSetCookieHeader(const std::string& key)
38{
39 if (key.size() != 10)
40 return false;
41 const char* expected = "set-cookie";
42 for (size_t i = 0; i < key.size(); ++i)
43 {
44 if (static_cast<char>(tolower(static_cast<unsigned char>(key[i]))) != expected[i])
45 return false;
46 }
47 return true;
48}
49
50const LocationConf* matchLocation(const std::string& url, const ServerConf& config)
51{
52 const std::vector<LocationConf>& locations = config.getLocations();
53 const LocationConf* best = NULL;
54 size_t longestMatch = 0;
55
56 for (size_t i = 0; i < locations.size(); ++i)
57 {
58 const std::string& lpath = locations[i].getPath();
59 bool matches = false;
60
61 if (lpath == "/")
62 matches = true;
63 else if (url == lpath)
64 matches = true;
65 else if (url.size() > lpath.size() && url[lpath.size()] == '/')
66 matches = (url.substr(0, lpath.size()) == lpath);
67
68 if (matches && lpath.size() >= longestMatch)
69 {
70 longestMatch = lpath.size();
71 best = &locations[i];
72 }
73 }
74 return best;
75}
76
77std::string detectContentType(const std::string& path)
78{
79 size_t dotPos = path.rfind('.');
80 if (dotPos == std::string::npos)
81 return "application/octet-stream";
82
83 std::string ext = path.substr(dotPos + 1);
84 for (size_t i = 0; i < ext.size(); ++i)
85 ext[i] = static_cast<char>(tolower(static_cast<unsigned char>(ext[i])));
86
87 if (ext == "html" || ext == "htm")
88 return "text/html";
89 if (ext == "css")
90 return "text/css";
91 if (ext == "js")
92 return "application/javascript";
93 if (ext == "json")
94 return "application/json";
95 if (ext == "xml")
96 return "application/xml";
97 if (ext == "txt")
98 return "text/plain";
99 if (ext == "png")
100 return "image/png";
101 if (ext == "jpg" || ext == "jpeg")
102 return "image/jpeg";
103 if (ext == "gif")
104 return "image/gif";
105 if (ext == "svg")
106 return "image/svg+xml";
107 if (ext == "ico")
108 return "image/x-icon";
109 if (ext == "pdf")
110 return "application/pdf";
111 if (ext == "zip")
112 return "application/zip";
113 return "application/octet-stream";
114}
115
116std::string sizeToString(size_t n) {
117 std::ostringstream ss;
118 ss << n;
119 return ss.str();
120}
121
122std::string currentHttpDate() {
123 time_t now = time(NULL);
124 struct tm* gmt = gmtime(&now);
125 char buf[64];
126 strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", gmt);
127 return std::string(buf);
128}
129
130std::string buildAutoIndexPage(const std::string& url, const std::string& dirPath)
131{
132 DIR* dir = opendir(dirPath.c_str());
133 if (!dir)
134 return "";
135
136 std::string html;
137 html = "<!DOCTYPE html>\r\n<html>\r\n<head><meta charset=\"utf-8\"><title>Index of ";
138 html += url;
139 html += "</title></head>\r\n<body>\r\n<h1>Index of ";
140 html += url;
141 html += "</h1>\r\n<hr>\r\n<pre>\r\n";
142
143 if (url != "/")
144 html += "<a href=\"../\">../</a>\r\n";
145
146 struct dirent* entry;
147 while ((entry = readdir(dir)) != NULL)
148 {
149 std::string name(entry->d_name);
150 if (name == "." || name == "..")
151 continue;
152
153 std::string fullPath = dirPath + "/" + name;
154 struct stat st;
155 if (stat(fullPath.c_str(), &st) == 0 && S_ISDIR(st.st_mode))
156 name += "/";
157 html += "<a href=\"" + name + "\">" + name + "</a>\r\n";
158 }
159 closedir(dir);
160 html += "</pre>\r\n<hr>\r\n</body>\r\n</html>";
161 return html;
162}
163
164std::string buildDefaultErrorHtml(const std::string& code, const std::string& phrase)
165{
166 std::string html;
167 html = "<!DOCTYPE html>\r\n<html>\r\n<head><meta charset=\"utf-8\"><title>";
168 html += code + " " + phrase;
169 html += "</title></head>\r\n<body>\r\n<h1>";
170 html += code + " " + phrase;
171 html += "</h1>\r\n<p>LeftHookRoll/1.0</p>\r\n</body>\r\n</html>";
172 return html;
173}
174
175bool isPathSafe(const std::string& root, const std::string& resolved)
176{
177 if (resolved.size() < root.size())
178 return false;
179 return resolved.substr(0, root.size()) == root;
180}
181
182std::string getFileExtension(const std::string& path)
183{
184 size_t dotPos = path.rfind('.');
185 if (dotPos == std::string::npos)
186 return "";
187 size_t slashPos = path.rfind('/');
188 if (slashPos != std::string::npos && slashPos > dotPos)
189 return "";
190 return path.substr(dotPos);
191}
192
193std::string extractFilenameFromUrl(const std::string& url)
194{
195 size_t pos = url.rfind('/');
196 if (pos == std::string::npos || pos == url.size() - 1)
197 return "";
198 return url.substr(pos + 1);
199}
200
201}
202
204 : _statusCode("200"),
205 _version("HTTP/1.0"),
206 _response_phrase("OK"),
207 _writeBufferSize(RESPONSE_SEND_CHUNK),
208 _responseDataStore(),
209 _totalBytesSent(0),
210 _headers(),
211 _setCookies(),
212 _fileFd(-1),
213 _fileSize(0),
214 _streamBuf(),
215 _streamBufLen(0),
216 _streamBufSent(0),
217 _cgiInstance(NULL),
218 _currentChunkSize(0),
219 _buildPhase(BUILD_IDLE),
220 _cachedConfig(NULL),
221 _postOutFd(-1),
222 _postWritePos(0),
223 _responseState(SENDING_RES_HEAD),
224 _headerBuffer()
225{}
226
228 : _statusCode(other._statusCode),
229 _version(other._version),
230 _response_phrase(other._response_phrase),
231 _writeBufferSize(other._writeBufferSize),
232 _responseDataStore(other._responseDataStore),
233 _totalBytesSent(other._totalBytesSent),
234 _headers(other._headers),
235 _setCookies(other._setCookies),
236 _fileFd(-1),
237 _fileSize(other._fileSize),
238 _streamBuf(other._streamBuf),
239 _streamBufLen(other._streamBufLen),
240 _streamBufSent(other._streamBufSent),
241 _cgiInstance(NULL),
242 _currentChunkSize(other._currentChunkSize),
243 _buildPhase(other._buildPhase),
244 _cachedConfig(other._cachedConfig),
245 _postOutFd(-1),
246 _postWritePos(other._postWritePos),
247 _postFilename(other._postFilename),
248 _responseState(other._responseState),
249 _headerBuffer(other._headerBuffer)
250{}
251
253 if (this != &other) {
254 _statusCode = other._statusCode;
255 _version = other._version;
260 _headers = other._headers;
261 _setCookies = other._setCookies;
262 if (_fileFd != -1)
263 {
264 close(_fileFd);
265 _fileFd = -1;
266 }
267 _fileSize = other._fileSize;
268 _streamBuf = other._streamBuf;
272 _buildPhase = other._buildPhase;
274 if (_postOutFd != -1)
275 {
276 close(_postOutFd);
277 _postOutFd = -1;
278 }
283 delete _cgiInstance;
284 _cgiInstance = NULL;
285 }
286 return *this;
287}
288
290 if (_fileFd != -1)
291 close(_fileFd);
292 if (_postOutFd != -1)
293 close(_postOutFd);
294 delete _cgiInstance;
295}
296
297
299{
300 // Resume incremental POST write if already in progress
302 return _continuePostWrite(req);
303
304 // CGI still running — check if done
306 return false;
307
308 _cachedConfig = &config;
309
310 if (req.getStatusCode() != "200")
311 {
312 buildErrorPage(req.getStatusCode(), config);
313 return true;
314 }
315
316 const LocationConf* loc = matchLocation(req.getURL(), config);
317 if (!loc)
318 {
319 buildErrorPage("404", config);
320 return true;
321 }
322
323 if (!loc->isMethodAllowed(req.getMethod()))
324 {
325 buildErrorPage("405", config);
326 return true;
327 }
328
329 if (!loc->getReturnCode().empty())
330 {
331 _statusCode = loc->getReturnCode();
333 if (!loc->getReturnURL().empty())
334 addHeader("Location", loc->getReturnURL());
335 addHeader("Content-Length", "0");
336 addHeader("Date", currentHttpDate());
337 addHeader("Connection", "close");
340 return true;
341 }
342
343 // CGI detection: check if the URL's file extension has a mapped interpreter
344 std::string ext = getFileExtension(req.getURL());
345 if (!ext.empty() && loc->isCgiExtension(ext))
346 return _handleCGI(req, *loc, config);
347
348 if (req.getMethod() == GET)
349 {
350 _handleGet(req, *loc, config);
351 return true;
352 }
353 if (req.getMethod() == POST)
354 return _handlePost(req, *loc, config);
355 if (req.getMethod() == DELETE)
356 {
357 _handleDelete(req, *loc, config);
358 return true;
359 }
360 buildErrorPage("501", config);
361 return true;
362}
363
364void Response::buildErrorPage(const std::string& code, const ServerConf& config)
365{
367 _totalBytesSent = 0;
368 _headers.clear();
369 _setCookies.clear();
370 _headerBuffer.clear();
371 if (_fileFd != -1)
372 {
373 close(_fileFd);
374 _fileFd = -1;
375 }
376 _fileSize = 0;
377 _streamBufLen = 0;
378 _streamBufSent = 0;
379 if (_postOutFd != -1)
380 {
381 close(_postOutFd);
382 _postOutFd = -1;
383 }
385
386 _statusCode = code;
388
389 std::string customPath = config.getErrorPagePath(code);
390 if (!customPath.empty())
391 {
392 int fd = open(customPath.c_str(), O_RDONLY);
393 if (fd >= 0)
394 {
395 char buf[4096];
396 ssize_t n;
397 while ((n = read(fd, buf, sizeof(buf))) > 0)
398 _responseDataStore.append(buf, static_cast<size_t>(n));
399 close(fd);
400 _finalizeSuccess("text/html");
401 return;
402 }
403 }
404 std::string body = buildDefaultErrorHtml(code, _response_phrase);
406 _finalizeSuccess("text/html");
407}
408
410{
412 return _sendHeader(fd);
414 return _sendBodyStatic(fd);
415 return false;
416}
417
419{
420 throwIfSigpipe("sending response header");
421
422 size_t headerSize = _headerBuffer.size();
423 size_t remaining = headerSize - _totalBytesSent;
424 size_t toSend = std::min(remaining, _writeBufferSize);
425
426 ssize_t sent = send(fd, _headerBuffer.c_str() + _totalBytesSent, toSend, MSG_DONTWAIT);
427 throwIfSigpipe("sending response header");
428 if (sent <= 0)
429 return true;
430 _totalBytesSent += static_cast<size_t>(sent);
431
432 if (_totalBytesSent < headerSize)
433 return false;
434
435 if (_responseDataStore.getSize() == 0 && _fileFd == -1)
436 return true;
437
439 return _sendBodyStatic(fd);
440}
441
443{
444 if (_fileFd != -1)
445 return _sendBodyFile(fd);
447 return _sendBodyDataStore(fd);
448}
449
451{
452 throwIfSigpipe("sending response body file chunk");
453
454 if (_streamBufLen == 0)
455 {
457 ssize_t bytesRead = read(_fileFd, &_streamBuf[0], _streamBuf.size());
458 if (bytesRead <= 0)
459 {
460 close(_fileFd);
461 _fileFd = -1;
462 return true;
463 }
464 _streamBufLen = static_cast<size_t>(bytesRead);
465 _streamBufSent = 0;
466 }
467
468 ssize_t sent = send(fd, &_streamBuf[0] + _streamBufSent,
469 _streamBufLen - _streamBufSent, MSG_DONTWAIT);
470 throwIfSigpipe("sending response body file chunk");
471 if (sent <= 0)
472 {
473 close(_fileFd);
474 _fileFd = -1;
475 return true;
476 }
477 _streamBufSent += static_cast<size_t>(sent);
478 _totalBytesSent += static_cast<size_t>(sent);
479
481 _streamBufLen = 0;
482
484 {
485 close(_fileFd);
486 _fileFd = -1;
487 return true;
488 }
489 return false;
490}
491
493{
494 throwIfSigpipe("sending response body datastore chunk");
495
496 size_t bodySize = _responseDataStore.getSize();
497
499 {
500 size_t bodyOffset = _totalBytesSent - _headerBuffer.size();
501 size_t bodyRemaining = bodySize - bodyOffset;
502 if (bodyRemaining == 0)
503 return true;
504
505 size_t toSend = std::min(bodyRemaining, _writeBufferSize);
506 const std::vector<char>& vec = _responseDataStore.getVector();
507 ssize_t sent = send(fd, &vec[0] + bodyOffset, toSend, MSG_DONTWAIT);
508 throwIfSigpipe("sending response body datastore chunk");
509 if (sent <= 0)
510 return true;
511 _totalBytesSent += static_cast<size_t>(sent);
512 return (_totalBytesSent == _headerBuffer.size() + bodySize);
513 }
514
515 if (_streamBufLen == 0)
516 {
518 size_t bytesRead = _responseDataStore.read(&_streamBuf[0], _writeBufferSize);
519 if (bytesRead == 0)
520 return true;
521 _streamBufLen = bytesRead;
522 _streamBufSent = 0;
523 }
524
525 ssize_t sent = send(fd, &_streamBuf[0] + _streamBufSent,
526 _streamBufLen - _streamBufSent, MSG_DONTWAIT);
527 throwIfSigpipe("sending response body datastore chunk");
528 if (sent <= 0)
529 return true;
530 _streamBufSent += static_cast<size_t>(sent);
531 _totalBytesSent += static_cast<size_t>(sent);
532
534 _streamBufLen = 0;
535
536 return (_totalBytesSent == _headerBuffer.size() + bodySize);
537}
538
539
540void Response::_handleGet(const Request& req, const LocationConf& loc, const ServerConf& config)
541{
542 const std::string& root = loc.getRoot();
543 const std::string url = req.getURL();
544 addCookie(req);
545
546 std::string resolvedPath = root + url;
547 if (resolvedPath.size() > 1 && resolvedPath[resolvedPath.size() - 1] == '/')
548 resolvedPath = resolvedPath.substr(0, resolvedPath.size() - 1);
549
550 if (!isPathSafe(root, resolvedPath))
551 {
552 buildErrorPage("400", config);
553 return;
554 }
555
556 struct stat st;
557 if (stat(resolvedPath.c_str(), &st) != 0)
558 {
559 buildErrorPage("404", config);
560 return;
561 }
562
563 if (S_ISDIR(st.st_mode))
564 {
565 if (!loc.getDefaultPage().empty())
566 {
567 std::string indexPath = resolvedPath + "/" + loc.getDefaultPage();
568 struct stat ist;
569 if (stat(indexPath.c_str(), &ist) == 0 && S_ISREG(ist.st_mode))
570 {
571 _serveFile(indexPath, config);
572 return;
573 }
574 }
575
576 if (loc.getAutoIndex())
577 {
578 std::string dirUrl = url;
579 if (dirUrl.empty() || dirUrl[dirUrl.size() - 1] != '/')
580 dirUrl += '/';
581
582 std::string listing = buildAutoIndexPage(dirUrl, resolvedPath);
583 if (listing.empty())
584 {
585 buildErrorPage("500", config);
586 return;
587 }
588 _responseDataStore.append(listing);
589 _statusCode = "200";
590 _response_phrase = "OK";
591 _finalizeSuccess("text/html");
592 return;
593 }
594
595 buildErrorPage("403", config);
596 return;
597 }
598
599 if (!S_ISREG(st.st_mode))
600 {
601 buildErrorPage("403", config);
602 return;
603 }
604
605 _serveFile(resolvedPath, config);
606}
607
608bool Response::_handlePost(Request& req, const LocationConf& loc, const ServerConf& config)
609{
610 const std::string& storageDir = loc.getStorageLocation();
611 addCookie(req);
612
613 if (storageDir.empty())
614 {
615 buildErrorPage("503", config);
616 return true;
617 }
618
619
620 std::string filename = extractFilenameFromUrl(req.getURL());
621 if (filename.empty())
622 {
623 std::ostringstream ss;
624 ss << "upload_" << time(NULL);
625 filename = ss.str();
626 }
627
628 std::string destPath = storageDir + "/" + filename;
629
630 _postOutFd = open(destPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
631 if (_postOutFd < 0)
632 {
633 buildErrorPage("500", config);
634 return true;
635 }
636
637 _postWritePos = 0;
638 _postFilename = filename;
640
641 DataStore& body = req.getBodyStore();
642 if (body.getMode() != RAM)
643 body.resetReadPosition();
644
645 return _continuePostWrite(req);
646}
647
649{
650 throwIfSigpipe("writing request body to upload/CGI input");
651
652 DataStore& body = req.getBodyStore();
653 bool wroteAll = false;
654
655 if (body.getMode() == RAM)
656 {
657 const std::vector<char>& vec = body.getVector();
658 if (!vec.empty() && _postWritePos < vec.size())
659 {
660 size_t remaining = vec.size() - _postWritePos;
661 size_t toWrite = std::min(remaining, _writeBufferSize);
662 ssize_t written = write(_postOutFd, &vec[0] + _postWritePos, toWrite);
663 throwIfSigpipe("writing request body to upload/CGI input");
664 if (written <= 0)
665 {
666 close(_postOutFd);
667 _postOutFd = -1;
669 if (_cachedConfig)
671 return true;
672 }
673 _postWritePos += static_cast<size_t>(written);
674 }
675 wroteAll = (vec.empty() || _postWritePos >= vec.size());
676 }
677 else
678 {
679 char buf[RESPONSE_SEND_CHUNK];
680 size_t n = body.read(buf, sizeof(buf));
681 if (n > 0)
682 {
683 ssize_t written = write(_postOutFd, buf, n);
684 throwIfSigpipe("writing request body to upload/CGI input");
685 if (written < 0 || static_cast<size_t>(written) != n)
686 {
687 close(_postOutFd);
688 _postOutFd = -1;
690 if (_cachedConfig)
692 return true;
693 }
694 }
695 wroteAll = (body.getReadPosition() >= body.getSize());
696 }
697
698 if (!wroteAll)
699 return false;
700
701 close(_postOutFd);
702 _postOutFd = -1;
704 std::string respBody = "<!DOCTYPE html>\r\n<html><body><p>File uploaded successfully.</p></body></html>";
705 _responseDataStore.append(respBody);
706 _statusCode = "201";
707 _response_phrase = "Created";
708 addHeader("Location", "/" + _postFilename);
709 _finalizeSuccess("text/html");
710 return true;
711}
712
713bool Response::_handleCGI(Request& req, const LocationConf& loc, const ServerConf& config)
714{
715 const std::string& root = loc.getRoot();
716 const std::string url = req.getURL();
717
718 this->addCookie(req);
719 std::string scriptPath = root + url;
720
721 if (!isPathSafe(root, scriptPath))
722 {
723 buildErrorPage("400", config);
724 return true;
725 }
726
727 struct stat st;
728 if (stat(scriptPath.c_str(), &st) != 0 || !S_ISREG(st.st_mode))
729 {
730 buildErrorPage("404", config);
731 return true;
732 }
733
734 // !this is very important!
735 // our whole dumb implementation depends on the data store being forced into
736 // file format to simplify input redirection for CGI.
737 DataStore& body = req.getBodyStore();
738 if (body.getSize() > 0 && body.getMode() == RAM)
739 body.switchToFileMode();
740
741 if (body.getMode() == FILE_MODE)
742 body.resetReadPosition();
743
744 std::string ext = getFileExtension(url);
745 _cgiInstance = new CGIManager();
746 _cgiInstance->prepare(req, scriptPath, loc.getCgiInterpreter(ext));
747
748 int inputFd = -1;
749 if (body.getSize() > 0 && body.getMode() == FILE_MODE)
750 inputFd = body.getFd();
751
752 try
753 {
754 _cgiInstance->execute(inputFd);
755 }
756 catch (const ClientException& e)
757 {
758 delete _cgiInstance;
759 _cgiInstance = NULL;
760 if (e.getStatusCode() == 503)
761 {
762 buildErrorPage("503", config);
763 addHeader("Retry-After", "5");
764 return true;
765 }
766 throw;
767 }
769 _cachedConfig = &config;
770 return false;
771}
772
773void Response::_handleDelete(const Request& req, const LocationConf& loc, const ServerConf& config)
774{
775 const std::string& root = loc.getRoot();
776 const std::string url = req.getURL();
777 addCookie(req);
778
779 std::string resolvedPath = root + url;
780 if (resolvedPath.size() > 1 && resolvedPath[resolvedPath.size() - 1] == '/')
781 resolvedPath = resolvedPath.substr(0, resolvedPath.size() - 1);
782
783 if (!isPathSafe(root, resolvedPath))
784 {
785 buildErrorPage("400", config);
786 return;
787 }
788
789 struct stat st;
790 if (stat(resolvedPath.c_str(), &st) != 0)
791 {
792 buildErrorPage("404", config);
793 return;
794 }
795
796 if (!S_ISREG(st.st_mode))
797 {
798 buildErrorPage("403", config);
799 return;
800 }
801
802 if (std::remove(resolvedPath.c_str()) != 0)
803 {
804 if (errno == EACCES || errno == EPERM)
805 buildErrorPage("403", config);
806 else
807 buildErrorPage("500", config);
808 return;
809 }
810
811 _statusCode = "204";
812 _response_phrase = "No Content";
813 addHeader("Content-Length", "0");
814 addHeader("Date", currentHttpDate());
815 addHeader("Connection", "close");
818}
819
820void Response::_finalizeSuccess(const std::string& contentType)
821{
822 addHeader("Content-Type", contentType);
823 addHeader("Content-Length", sizeToString(_responseDataStore.getSize()));
824 addHeader("Date", currentHttpDate());
825 addHeader("Connection", "close");
828}
829
830void Response::_serveFile(const std::string& path, const ServerConf& config)
831{
832 struct stat st;
833 if (stat(path.c_str(), &st) != 0)
834 {
835 buildErrorPage("404", config);
836 return;
837 }
838
839 int fd = open(path.c_str(), O_RDONLY);
840 if (fd < 0)
841 {
842 buildErrorPage("404", config);
843 return;
844 }
845
846 _fileFd = fd;
847 _fileSize = static_cast<size_t>(st.st_size);
848 _streamBufLen = 0;
849 _streamBufSent = 0;
851
852 _statusCode = "200";
853 _response_phrase = "OK";
854 addHeader("Content-Type", detectContentType(path));
855 addHeader("Content-Length", sizeToString(_fileSize));
856 addHeader("Date", currentHttpDate());
857 addHeader("Connection", "close");
860}
861
862
863const std::string& Response::getStatusCode() const { return _statusCode; }
864const std::string& Response::getVersion() const { return _version; }
865const std::string& Response::getResponsePhrase() const { return _response_phrase; }
869
871{
872 if (_cgiInstance)
873 return _cgiInstance->getOutputFd();
874 return -1;
875}
876
878{
879 if (!_cgiInstance)
880 return true;
881
882 int pipeFd = _cgiInstance->getOutputFd();
883 if (pipeFd < 0)
884 return true;
885
886 char buf[RESPONSE_SEND_CHUNK];
887 ssize_t n = ::read(pipeFd, buf, sizeof(buf));
888
889 if (n > 0)
890 {
891 _responseDataStore.append(buf, static_cast<size_t>(n));
892 if (_cgiInstance->isDone())
893 return true;
894 return false;
895 }
896
897 if (n == 0)//EOF
898 {
900 return true;
901 }
902
903 // if (errno == EINTR) TBD
904 // return false;
905 //if it's a real error need to kill.
907 return true;
908}
909
911{
912 if (_cgiInstance)
913 {
914 pid_t pid = _cgiInstance->getPid();
915 if (pid > 0)
916 {
917 kill(pid, SIGKILL);
918 int status;
919 waitpid(pid, &status, 0);
920 // isDone() will reap the CGI process and close the pipe
922 }
923 delete _cgiInstance;
924 _cgiInstance = NULL;
925 }
927 buildErrorPage("504", config);
928}
929
931{
933
934 std::string cgiOutput = _drainDataStore();
935 const ServerConf* config = _cachedConfig;
936
937 if (cgiOutput.empty())
938 {
939 if (config)
940 buildErrorPage("502", *config);
941 else
942 {
943 setStatusCode("502");
944 setResponsePhrase("Bad Gateway");
945 }
946 delete _cgiInstance;
947 _cgiInstance = NULL;
948 return;
949 }
950
951 std::string cgiHeaders;
952 std::string cgiBody;
953 _splitCgiOutput(cgiOutput, cgiHeaders, cgiBody);
954
955 if (cgiHeaders.empty())
956 {
957 if (config)
958 buildErrorPage("502", *config);
959 else
960 {
961 setStatusCode("502");
962 setResponsePhrase("Bad Gateway");
963 }
964 delete _cgiInstance;
965 _cgiInstance = NULL;
966 return;
967 }
968
969 std::string contentType = "text/html";
970 if (!_parseCgiHeaders(cgiHeaders, contentType))
971 {
972 if (config)
973 buildErrorPage("502", *config);
974 else
975 {
976 setStatusCode("502");
977 setResponsePhrase("Bad Gateway");
978 }
979 delete _cgiInstance;
980 _cgiInstance = NULL;
981 return;
982 }
983
985 if (!cgiBody.empty())
986 _responseDataStore.append(cgiBody);
987
988 _finalizeSuccess(contentType);
989
990 delete _cgiInstance;
991 _cgiInstance = NULL;
992}
993
995{
997
998 std::string result;
999 char buf[RESPONSE_SEND_CHUNK];
1000 size_t n;
1001 while ((n = _responseDataStore.read(buf, sizeof(buf))) > 0)
1002 result.append(buf, n);
1003 return result;
1004}
1005
1006void Response::_splitCgiOutput(const std::string& raw, std::string& headers, std::string& body)
1007{
1008 size_t headerEnd = raw.find("\r\n\r\n");
1009 if (headerEnd != std::string::npos)
1010 {
1011 headers = raw.substr(0, headerEnd);
1012 body = raw.substr(headerEnd + 4);
1013 return;
1014 }
1015
1016 headerEnd = raw.find("\n\n");
1017 if (headerEnd != std::string::npos)
1018 {
1019 headers = raw.substr(0, headerEnd);
1020 body = raw.substr(headerEnd + 2);
1021 return;
1022 }
1023
1024 body = raw;
1025}
1026
1027bool Response::_parseCgiHeaders(const std::string& headerBlock, std::string& contentType)
1028{
1029 contentType = "text/html";
1030 _statusCode = "200";
1031 _response_phrase = "OK";
1032
1033 if (headerBlock.empty())
1034 return false;
1035
1036 std::istringstream iss(headerBlock);
1037 std::string line;
1038 while (std::getline(iss, line))
1039 {
1040 if (!line.empty() && line[line.size() - 1] == '\r')
1041 line.erase(line.size() - 1);
1042
1043 if (line.empty())
1044 continue;
1045
1046 size_t colonPos = line.find(':');
1047 if (colonPos == std::string::npos)
1048 return false;
1049
1050 std::string key = line.substr(0, colonPos);
1051 std::string value = line.substr(colonPos + 1);
1052
1053 size_t start = value.find_first_not_of(" \t");
1054 if (start != std::string::npos)
1055 value = value.substr(start);
1056
1057 std::string lowerKey = key;
1058 for (size_t i = 0; i < lowerKey.size(); ++i)
1059 lowerKey[i] = static_cast<char>(tolower(static_cast<unsigned char>(lowerKey[i])));
1060
1061 if (lowerKey == "content-type")
1062 contentType = value;
1063 else if (lowerKey == "status")
1064 {
1065 size_t spacePos = value.find(' ');
1066 if (spacePos != std::string::npos)
1067 {
1068 _statusCode = value.substr(0, spacePos);
1069 _response_phrase = value.substr(spacePos + 1);
1070 }
1071 else
1072 {
1073 _statusCode = value;
1075 }
1076 if (_statusCode.size() != 3
1077 || _statusCode.find_first_not_of("0123456789") != std::string::npos)
1078 return false;
1079 }
1080 else if (lowerKey == "location")
1081 addHeader("Location", value);
1082 else if (lowerKey == "set-cookie")
1083 addHeader("Set-Cookie", value);
1084 }
1085 return true;
1086}
1087
1088void Response::setStatusCode(const std::string& code)
1089{
1090 _statusCode = code;
1092}
1093
1094void Response::setResponsePhrase(const std::string& phrase)
1095{
1096 _response_phrase = phrase;
1097}
1098
1099void Response::addHeader(const std::string& key, const std::string& value)
1100{
1101 if (isSetCookieHeader(key))
1102 {
1103 _setCookies.push_back(value);
1104 if (!_headerBuffer.empty())
1106 return;
1107 }
1108 _headers[key] = value;
1109 if (!_headerBuffer.empty())
1111}
1112
1113/**
1114 * if you want accessible by the HTTP server, not by JavaScript.
1115 * add this V
1116 * cookie += "; HttpOnly";
1117 */
1119{
1120 std::string oldVal = req.getCookie("session_count");
1121 int count = oldVal.empty() ? 0 : std::atoi(oldVal.c_str());
1122
1123 count++;
1124 std::stringstream ss;
1125 ss << count;
1126 std::string cookie = "session_count=" + ss.str();
1127 cookie += "; Path=/";
1128 _setCookies.push_back(cookie);
1129}
1130
1131const std::vector<std::string>& Response::getSetCookies() const
1132{
1133 return _setCookies;
1134}
1135
1136
1138{
1139 std::string result;
1140 result += _version + " " + _statusCode + " " + _response_phrase + "\r\n";
1141 for (std::map<std::string, std::string>::const_iterator it = _headers.begin();
1142 it != _headers.end(); ++it)
1143 {
1144 result += it->first + ": " + it->second + "\r\n";
1145 }
1146 for (std::vector<std::string>::const_iterator it = _setCookies.begin(); it != _setCookies.end(); ++it)
1147 result += "Set-Cookie: " + *it + "\r\n";
1148 result += "\r\n";
1149 return result;
1150}
1151
1152std::string Response::_lookupReasonPhrase(const std::string& code) {
1153 if (code == "200")
1154 return "OK";
1155 if (code == "201")
1156 return "Created";
1157 if (code == "204")
1158 return "No Content";
1159 if (code == "301")
1160 return "Moved Permanently";
1161 if (code == "302")
1162 return "Found";
1163 if (code == "303")
1164 return "See Other";
1165 if (code == "307")
1166 return "Temporary Redirect";
1167 if (code == "308")
1168 return "Permanent Redirect";
1169 if (code == "400")
1170 return "Bad Request";
1171 if (code == "403")
1172 return "Forbidden";
1173 if (code == "404")
1174 return "Not Found";
1175 if (code == "405")
1176 return "Method Not Allowed";
1177 if (code == "408")
1178 return "Request Timeout";
1179 if (code == "411")
1180 return "Length Required";
1181 if (code == "413")
1182 return "Content Too Large";
1183 if (code == "414")
1184 return "URI Too Long";
1185 if (code == "500")
1186 return "Internal Server Error";
1187 if (code == "501")
1188 return "Not Implemented";
1189 if (code == "502")
1190 return "Bad Gateway";
1191 if (code == "503")
1192 return "Service Unavailable";
1193 if (code == "504")
1194 return "Gateway Timeout";
1195 return "Unknown";
1196}
@ GET
@ POST
@ DELETE
@ RAM
Definition DataStore.hpp:30
@ FILE_MODE
Definition DataStore.hpp:31
volatile sig_atomic_t g_sigpipe
Definition main.cpp:19
static const size_t RESPONSE_SEND_CHUNK
Definition Response.cpp:24
ResponseState
Tracks the progress of sending the response to the client.
Definition Response.hpp:24
@ SENDING_RES_HEAD
Definition Response.hpp:25
@ SENDING_BODY_STATIC
Definition Response.hpp:26
BuildPhase
Tracks the incremental construction of the response body. exclusivly for POST.
Definition Response.hpp:36
@ BUILD_IDLE
Definition Response.hpp:37
@ BUILD_CGI_RUNNING
Definition Response.hpp:39
@ BUILD_POST_WRITING
Definition Response.hpp:38
@ BUILD_DONE
Definition Response.hpp:40
pid_t getPid() const
void prepare(const Request &request, const std::string &scriptPath, const std::string &interpreterOverride="")
Prepares the environment and arguments for executing a CGI script.
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.
int getOutputFd() const
Exception type for failures scoped to a single client request.
int getStatusCode() const
size_t getReadPosition() const
Gets the current read position.
int getFd() const
Returns the file descriptor of the temporary file.
const std::vector< char > & getVector() const
Returns a reference to the RAM buffer.
size_t getSize() const
Gets total bytes currently stored (whether in RAM or File).
void append(const char *data, size_t length)
Appends raw byte data to the store. Automatically transitions from RAM to FILE_MODE if _bufferLimit i...
void switchToFileMode()
Handles the transition from RAM to a temporary file on disk. Uses the immediate unlink() trick to ens...
void resetReadPosition()
Resets the internal read position to the beginning of the data.
size_t read(char *buffer, size_t length)
Reads data from the store starting at the current read position. Advances the internal read position ...
void clear()
Resets the store, clears the vector, and closes the temp file descriptor.
BufferMode getMode() const
Returns the current storage mode (RAM or FILE_MODE).
const std::string & getReturnURL() const
const std::string & getPath() const
std::string getCgiInterpreter(const std::string &ext) const
const std::string & getStorageLocation() const
const std::string & getDefaultPage() const
const std::string & getRoot() const
bool isMethodAllowed(HTTPMethod method) const
Checks if a specific HTTP method is permitted in this location.
bool getAutoIndex() const
const std::string & getReturnCode() const
bool isCgiExtension(const std::string &ext) const
Checks if a given file extension should be handled by CGI.
DataStore & getBodyStore()
Returns a reference to the DataStore to allow direct writing from recv().
Definition Request.cpp:390
HTTPMethod getMethod() const
Definition Request.cpp:82
std::string getStatusCode() const
Definition Request.cpp:370
const std::string & getURL() const
Definition Request.cpp:86
std::string getCookie(const std::string &key) const
Gets a specific cookie value.
Definition Request.cpp:279
CGIManager * _cgiInstance
Definition Response.hpp:130
BuildPhase getBuildPhase() const
Definition Response.cpp:867
std::map< std::string, std::string > _headers
Definition Response.hpp:123
bool _sendBodyDataStore(int fd)
Definition Response.cpp:492
std::vector< char > _streamBuf
Definition Response.hpp:127
const std::string & getVersion() const
Definition Response.cpp:864
size_t _streamBufLen
Definition Response.hpp:128
void setResponsePhrase(const std::string &phrase)
void _finalizeSuccess(const std::string &contentType)
Definition Response.cpp:820
void buildErrorPage(const std::string &code, const ServerConf &config)
Fast-tracks the response to an error state. Loads the appropriate error page from config or default H...
Definition Response.cpp:364
void addCookie(const Request &req)
int _fileFd
Definition Response.hpp:125
const std::vector< std::string > & getSetCookies() const
bool buildResponse(Request &req, const ServerConf &config)
Analyzes the Request and Location settings to prepare the response. Sets the status code,...
Definition Response.cpp:298
size_t _totalBytesSent
Definition Response.hpp:122
std::string _generateHeaderString()
std::string _statusCode
Definition Response.hpp:116
std::string _drainDataStore()
Definition Response.cpp:994
DataStore _responseDataStore
Definition Response.hpp:121
bool _sendBodyFile(int fd)
Definition Response.cpp:450
Response & operator=(const Response &other)
Definition Response.cpp:252
CGIManager * getCgiInstance() const
Definition Response.cpp:868
const ServerConf * _cachedConfig
Definition Response.hpp:135
int getCgiOutputFd() const
Returns the CGI output pipe fd for epoll registration.
Definition Response.cpp:870
bool sendSlice(int fd)
Sends a slice of the response to the client socket. To be called during a POLLOUT event.
Definition Response.cpp:409
size_t _writeBufferSize
Definition Response.hpp:120
void addHeader(const std::string &key, const std::string &value)
Adds a header to the response (e.g., "Content-Type", "text/html").
ResponseState getResponseState() const
Definition Response.cpp:866
int _postOutFd
Definition Response.hpp:136
void finalizeCgiResponse()
Finalizes the CGI response after all output has been read. Parses CGI output headers,...
Definition Response.cpp:930
std::string _lookupReasonPhrase(const std::string &code)
size_t _currentChunkSize
Definition Response.hpp:131
bool readCgiOutput()
Called by ServerManager when the CGI pipe is readable. Reads available data into _responseDataStore.
Definition Response.cpp:877
std::vector< std::string > _setCookies
Definition Response.hpp:124
size_t _postWritePos
Definition Response.hpp:137
void _serveFile(const std::string &path, const ServerConf &config)
Definition Response.cpp:830
bool _parseCgiHeaders(const std::string &headerBlock, std::string &contentType)
void _handleGet(const Request &req, const LocationConf &loc, const ServerConf &config)
Definition Response.cpp:540
const std::string & getResponsePhrase() const
Definition Response.cpp:865
std::string _version
Definition Response.hpp:117
std::string _headerBuffer
Definition Response.hpp:166
void _splitCgiOutput(const std::string &raw, std::string &headers, std::string &body)
BuildPhase _buildPhase
Definition Response.hpp:134
ResponseState _responseState
Definition Response.hpp:140
std::string _response_phrase
Definition Response.hpp:118
const std::string & getStatusCode() const
Definition Response.cpp:863
size_t _streamBufSent
Definition Response.hpp:129
void cgiTimeout(const ServerConf &config)
Called by ServerManager when the CGI process times out. Kills the process and builds a 504 error page...
Definition Response.cpp:910
bool _handlePost(Request &req, const LocationConf &loc, const ServerConf &config)
Definition Response.cpp:608
std::string _postFilename
Definition Response.hpp:138
bool _handleCGI(Request &req, const LocationConf &loc, const ServerConf &config)
Definition Response.cpp:713
void _handleDelete(const Request &req, const LocationConf &loc, const ServerConf &config)
Definition Response.cpp:773
bool _sendHeader(int fd)
Definition Response.cpp:418
void setStatusCode(const std::string &code)
bool _continuePostWrite(Request &req)
Definition Response.cpp:648
bool _sendBodyStatic(int fd)
Definition Response.cpp:442
size_t _fileSize
Definition Response.hpp:126
std::string getErrorPagePath(const std::string &errorCode) const
Retrieves the path to a custom error page if one exists.
const std::vector< LocationConf > & getLocations() const