1#include "../includes/Response.hpp"
2#include "../includes/LocationConf.hpp"
3#include "../includes/CGIManager.hpp"
4#include "../includes/FatalExceptions.hpp"
28void throwIfSigpipe(
const std::string& context)
37bool isSetCookieHeader(
const std::string& key)
41 const char* expected =
"set-cookie";
42 for (
size_t i = 0; i < key.size(); ++i)
44 if (
static_cast<char>(tolower(
static_cast<unsigned char>(key[i]))) != expected[i])
52 const std::vector<LocationConf>& locations = config.
getLocations();
54 size_t longestMatch = 0;
56 for (
size_t i = 0; i < locations.size(); ++i)
58 const std::string& lpath = locations[i].
getPath();
63 else if (url == lpath)
65 else if (url.size() > lpath.size() && url[lpath.size()] ==
'/')
66 matches = (url.substr(0, lpath.size()) == lpath);
68 if (matches && lpath.size() >= longestMatch)
70 longestMatch = lpath.size();
77std::string detectContentType(
const std::string& path)
79 size_t dotPos = path.rfind(
'.');
80 if (dotPos == std::string::npos)
81 return "application/octet-stream";
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])));
87 if (ext ==
"html" || ext ==
"htm")
92 return "application/javascript";
94 return "application/json";
96 return "application/xml";
101 if (ext ==
"jpg" || ext ==
"jpeg")
106 return "image/svg+xml";
108 return "image/x-icon";
110 return "application/pdf";
112 return "application/zip";
113 return "application/octet-stream";
116std::string sizeToString(
size_t n) {
117 std::ostringstream ss;
122std::string currentHttpDate() {
123 time_t now = time(NULL);
124 struct tm* gmt = gmtime(&now);
126 strftime(buf,
sizeof(buf),
"%a, %d %b %Y %H:%M:%S GMT", gmt);
127 return std::string(buf);
130std::string buildAutoIndexPage(
const std::string& url,
const std::string& dirPath)
132 DIR* dir = opendir(dirPath.c_str());
137 html =
"<!DOCTYPE html>\r\n<html>\r\n<head><meta charset=\"utf-8\"><title>Index of ";
139 html +=
"</title></head>\r\n<body>\r\n<h1>Index of ";
141 html +=
"</h1>\r\n<hr>\r\n<pre>\r\n";
144 html +=
"<a href=\"../\">../</a>\r\n";
146 struct dirent* entry;
147 while ((entry = readdir(dir)) != NULL)
149 std::string name(entry->d_name);
150 if (name ==
"." || name ==
"..")
153 std::string fullPath = dirPath +
"/" + name;
155 if (stat(fullPath.c_str(), &st) == 0 && S_ISDIR(st.st_mode))
157 html +=
"<a href=\"" + name +
"\">" + name +
"</a>\r\n";
160 html +=
"</pre>\r\n<hr>\r\n</body>\r\n</html>";
164std::string buildDefaultErrorHtml(
const std::string& code,
const std::string& phrase)
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>";
175bool isPathSafe(
const std::string& root,
const std::string& resolved)
177 if (resolved.size() < root.size())
179 return resolved.substr(0, root.size()) == root;
182std::string getFileExtension(
const std::string& path)
184 size_t dotPos = path.rfind(
'.');
185 if (dotPos == std::string::npos)
187 size_t slashPos = path.rfind(
'/');
188 if (slashPos != std::string::npos && slashPos > dotPos)
190 return path.substr(dotPos);
193std::string extractFilenameFromUrl(
const std::string& url)
195 size_t pos = url.rfind(
'/');
196 if (pos == std::string::npos || pos == url.size() - 1)
198 return url.substr(pos + 1);
204 : _statusCode(
"200"),
205 _version(
"HTTP/1.0"),
206 _response_phrase(
"OK"),
208 _responseDataStore(),
218 _currentChunkSize(0),
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),
237 _fileSize(other._fileSize),
238 _streamBuf(other._streamBuf),
239 _streamBufLen(other._streamBufLen),
240 _streamBufSent(other._streamBufSent),
242 _currentChunkSize(other._currentChunkSize),
243 _buildPhase(other._buildPhase),
244 _cachedConfig(other._cachedConfig),
246 _postWritePos(other._postWritePos),
247 _postFilename(other._postFilename),
248 _responseState(other._responseState),
249 _headerBuffer(other._headerBuffer)
253 if (
this != &other) {
344 std::string ext = getFileExtension(req.
getURL());
390 if (!customPath.empty())
392 int fd = open(customPath.c_str(), O_RDONLY);
397 while ((n = read(fd, buf,
sizeof(buf))) > 0)
420 throwIfSigpipe(
"sending response header");
427 throwIfSigpipe(
"sending response header");
452 throwIfSigpipe(
"sending response body file chunk");
470 throwIfSigpipe(
"sending response body file chunk");
494 throwIfSigpipe(
"sending response body datastore chunk");
501 size_t bodyRemaining = bodySize - bodyOffset;
502 if (bodyRemaining == 0)
507 ssize_t sent = send(fd, &vec[0] + bodyOffset, toSend, MSG_DONTWAIT);
508 throwIfSigpipe(
"sending response body datastore chunk");
527 throwIfSigpipe(
"sending response body datastore chunk");
542 const std::string& root = loc.
getRoot();
543 const std::string url = req.
getURL();
546 std::string resolvedPath = root + url;
547 if (resolvedPath.size() > 1 && resolvedPath[resolvedPath.size() - 1] ==
'/')
548 resolvedPath = resolvedPath.substr(0, resolvedPath.size() - 1);
550 if (!isPathSafe(root, resolvedPath))
557 if (stat(resolvedPath.c_str(), &st) != 0)
563 if (S_ISDIR(st.st_mode))
567 std::string indexPath = resolvedPath +
"/" + loc.
getDefaultPage();
569 if (stat(indexPath.c_str(), &ist) == 0 && S_ISREG(ist.st_mode))
578 std::string dirUrl = url;
579 if (dirUrl.empty() || dirUrl[dirUrl.size() - 1] !=
'/')
582 std::string listing = buildAutoIndexPage(dirUrl, resolvedPath);
599 if (!S_ISREG(st.st_mode))
613 if (storageDir.empty())
620 std::string filename = extractFilenameFromUrl(req.
getURL());
621 if (filename.empty())
623 std::ostringstream ss;
624 ss <<
"upload_" << time(NULL);
628 std::string destPath = storageDir +
"/" + filename;
630 _postOutFd = open(destPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
650 throwIfSigpipe(
"writing request body to upload/CGI input");
653 bool wroteAll =
false;
657 const std::vector<char>& vec = body.
getVector();
663 throwIfSigpipe(
"writing request body to upload/CGI input");
680 size_t n = body.
read(buf,
sizeof(buf));
684 throwIfSigpipe(
"writing request body to upload/CGI input");
685 if (written < 0 ||
static_cast<size_t>(written) != n)
704 std::string respBody =
"<!DOCTYPE html>\r\n<html><body><p>File uploaded successfully.</p></body></html>";
715 const std::string& root = loc.
getRoot();
716 const std::string url = req.
getURL();
719 std::string scriptPath = root + url;
721 if (!isPathSafe(root, scriptPath))
728 if (stat(scriptPath.c_str(), &st) != 0 || !S_ISREG(st.st_mode))
744 std::string ext = getFileExtension(url);
750 inputFd = body.
getFd();
775 const std::string& root = loc.
getRoot();
776 const std::string url = req.
getURL();
779 std::string resolvedPath = root + url;
780 if (resolvedPath.size() > 1 && resolvedPath[resolvedPath.size() - 1] ==
'/')
781 resolvedPath = resolvedPath.substr(0, resolvedPath.size() - 1);
783 if (!isPathSafe(root, resolvedPath))
790 if (stat(resolvedPath.c_str(), &st) != 0)
796 if (!S_ISREG(st.st_mode))
802 if (std::remove(resolvedPath.c_str()) != 0)
804 if (errno == EACCES || errno == EPERM)
833 if (stat(path.c_str(), &st) != 0)
839 int fd = open(path.c_str(), O_RDONLY);
847 _fileSize =
static_cast<size_t>(st.st_size);
854 addHeader(
"Content-Type", detectContentType(path));
887 ssize_t n = ::read(pipeFd, buf,
sizeof(buf));
919 waitpid(pid, &status, 0);
937 if (cgiOutput.empty())
951 std::string cgiHeaders;
955 if (cgiHeaders.empty())
969 std::string contentType =
"text/html";
985 if (!cgiBody.empty())
1002 result.append(buf, n);
1008 size_t headerEnd = raw.find(
"\r\n\r\n");
1009 if (headerEnd != std::string::npos)
1011 headers = raw.substr(0, headerEnd);
1012 body = raw.substr(headerEnd + 4);
1016 headerEnd = raw.find(
"\n\n");
1017 if (headerEnd != std::string::npos)
1019 headers = raw.substr(0, headerEnd);
1020 body = raw.substr(headerEnd + 2);
1029 contentType =
"text/html";
1033 if (headerBlock.empty())
1036 std::istringstream iss(headerBlock);
1038 while (std::getline(iss, line))
1040 if (!line.empty() && line[line.size() - 1] ==
'\r')
1041 line.erase(line.size() - 1);
1046 size_t colonPos = line.find(
':');
1047 if (colonPos == std::string::npos)
1050 std::string key = line.substr(0, colonPos);
1051 std::string value = line.substr(colonPos + 1);
1053 size_t start = value.find_first_not_of(
" \t");
1054 if (start != std::string::npos)
1055 value = value.substr(start);
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])));
1061 if (lowerKey ==
"content-type")
1062 contentType = value;
1063 else if (lowerKey ==
"status")
1065 size_t spacePos = value.find(
' ');
1066 if (spacePos != std::string::npos)
1077 ||
_statusCode.find_first_not_of(
"0123456789") != std::string::npos)
1080 else if (lowerKey ==
"location")
1082 else if (lowerKey ==
"set-cookie")
1101 if (isSetCookieHeader(key))
1120 std::string oldVal = req.
getCookie(
"session_count");
1121 int count = oldVal.empty() ? 0 : std::atoi(oldVal.c_str());
1124 std::stringstream ss;
1126 std::string cookie =
"session_count=" + ss.str();
1127 cookie +=
"; Path=/";
1141 for (std::map<std::string, std::string>::const_iterator it =
_headers.begin();
1144 result += it->first +
": " + it->second +
"\r\n";
1147 result +=
"Set-Cookie: " + *it +
"\r\n";
1158 return "No Content";
1160 return "Moved Permanently";
1166 return "Temporary Redirect";
1168 return "Permanent Redirect";
1170 return "Bad Request";
1176 return "Method Not Allowed";
1178 return "Request Timeout";
1180 return "Length Required";
1182 return "Content Too Large";
1184 return "URI Too Long";
1186 return "Internal Server Error";
1188 return "Not Implemented";
1190 return "Bad Gateway";
1192 return "Service Unavailable";
1194 return "Gateway Timeout";
volatile sig_atomic_t g_sigpipe
static const size_t RESPONSE_SEND_CHUNK
ResponseState
Tracks the progress of sending the response to the client.
BuildPhase
Tracks the incremental construction of the response body. exclusivly for POST.
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.
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().
HTTPMethod getMethod() const
std::string getStatusCode() const
const std::string & getURL() const
std::string getCookie(const std::string &key) const
Gets a specific cookie value.
CGIManager * _cgiInstance
BuildPhase getBuildPhase() const
std::map< std::string, std::string > _headers
bool _sendBodyDataStore(int fd)
std::vector< char > _streamBuf
const std::string & getVersion() const
void setResponsePhrase(const std::string &phrase)
void _finalizeSuccess(const std::string &contentType)
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...
void addCookie(const Request &req)
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,...
std::string _generateHeaderString()
std::string _drainDataStore()
DataStore _responseDataStore
bool _sendBodyFile(int fd)
Response & operator=(const Response &other)
CGIManager * getCgiInstance() const
const ServerConf * _cachedConfig
int getCgiOutputFd() const
Returns the CGI output pipe fd for epoll registration.
bool sendSlice(int fd)
Sends a slice of the response to the client socket. To be called during a POLLOUT event.
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
void finalizeCgiResponse()
Finalizes the CGI response after all output has been read. Parses CGI output headers,...
std::string _lookupReasonPhrase(const std::string &code)
bool readCgiOutput()
Called by ServerManager when the CGI pipe is readable. Reads available data into _responseDataStore.
std::vector< std::string > _setCookies
void _serveFile(const std::string &path, const ServerConf &config)
bool _parseCgiHeaders(const std::string &headerBlock, std::string &contentType)
void _handleGet(const Request &req, const LocationConf &loc, const ServerConf &config)
const std::string & getResponsePhrase() const
std::string _headerBuffer
void _splitCgiOutput(const std::string &raw, std::string &headers, std::string &body)
ResponseState _responseState
std::string _response_phrase
const std::string & getStatusCode() const
void cgiTimeout(const ServerConf &config)
Called by ServerManager when the CGI process times out. Kills the process and builds a 504 error page...
bool _handlePost(Request &req, const LocationConf &loc, const ServerConf &config)
std::string _postFilename
bool _handleCGI(Request &req, const LocationConf &loc, const ServerConf &config)
void _handleDelete(const Request &req, const LocationConf &loc, const ServerConf &config)
void setStatusCode(const std::string &code)
bool _continuePostWrite(Request &req)
bool _sendBodyStatic(int fd)
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