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
Empty file removed include/http/.gitkeep
Empty file.
2 changes: 2 additions & 0 deletions include/http/HttpResponse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ struct HttpResponse {
std::string status_msg; // "OK", "Not Found", ...
std::map<std::string, std::string> headers; // {"Content-Type": "text/html", ...}
std::string body;

HttpResponse() : status_code(0) {}
};

#endif
10 changes: 4 additions & 6 deletions src/http/cgi/execute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ static std::string buildScriptPath(const HttpRequest& request, const LocationCon
uriWithoutQuery = uriWithoutQuery.substr(0, queryStart);

char currentWorkingDir[4096];
getcwd(currentWorkingDir, sizeof(currentWorkingDir));
if (getcwd(currentWorkingDir, sizeof(currentWorkingDir)) == NULL)
return "";
return std::string(currentWorkingDir) + "/" + location.getRoot() + uriWithoutQuery;
}

Expand Down Expand Up @@ -69,11 +70,6 @@ static std::string readCgiOutputWithTimeout(int pipeReadEnd, pid_t childPid, boo
close(pipeReadEnd);
return "";
}

int childExitStatus = waitpid(childPid, NULL, WNOHANG);
if (childExitStatus > 0)
break;

usleep(5000);
}
}
Expand Down Expand Up @@ -129,6 +125,8 @@ HttpResponse CgiHandler::execute(const HttpRequest& request, const LocationConfi
close(stdinPipe[0]);
close(stdoutPipe[1]);

signal(SIGPIPE, SIG_IGN);

if (!request.body.empty())
{
const char* bodyData = request.body.c_str();
Expand Down
6 changes: 3 additions & 3 deletions src/http/cgi/output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ HttpResponse CgiHandler::parseOutput(const std::string& cgiOutput)
{
HttpResponse response;

std::size_t separatorPos = cgiOutput.find("\r\n\r\n");
std::size_t separatorPos = cgiOutput.find("\r\n\r\n");
bool usesDoubleCRLF = (separatorPos != std::string::npos);
if (separatorPos == std::string::npos)
separatorPos = cgiOutput.find("\n\n");

Expand All @@ -21,8 +22,7 @@ HttpResponse CgiHandler::parseOutput(const std::string& cgiOutput)
return response;
}

bool usesDoubleCRLF = (cgiOutput[separatorPos + 1] != '\n');
std::size_t bodyStart = separatorPos + (usesDoubleCRLF ? 4 : 2);
std::size_t bodyStart = separatorPos + (usesDoubleCRLF ? 4 : 2);

std::string headersSection = cgiOutput.substr(0, separatorPos);
response.body = cgiOutput.substr(bodyStart);
Expand Down
12 changes: 11 additions & 1 deletion src/http/handlers/DeleteHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,24 @@
#include "../../../include/http/utils/HttpUtils.hpp"
#include <unistd.h>
#include <sys/stat.h>
#include <cerrno>

HttpResponse MethodHandler::handleDelete(const HttpRequest& request, const LocationConfig& location)
{
std::string filePath = location.getRoot() + request.uri;
std::string uriPath = request.uri;
std::size_t queryPos = uriPath.find('?');
if (queryPos != std::string::npos)
uriPath = uriPath.substr(0, queryPos);

std::string filePath = location.getRoot() + uriPath;

struct stat fileInfo;
if (stat(filePath.c_str(), &fileInfo) == -1)
{
if (errno == EACCES || errno == EPERM)
return buildHttpError(403, "Forbidden");
return buildHttpError(404, "Not Found");
}

if (S_ISDIR(fileInfo.st_mode))
return buildHttpError(403, "Forbidden");
Expand Down
36 changes: 31 additions & 5 deletions src/http/handlers/GetHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <sys/stat.h>
#include <sstream>
#include <dirent.h>
#include <cerrno>

static HttpResponse buildAutoindex(const std::string& directoryPath, const std::string& requestUri)
{
Expand All @@ -18,6 +19,8 @@ static HttpResponse buildAutoindex(const std::string& directoryPath, const std::
while ((dirEntry = readdir(directory)) != NULL)
{
std::string entryName = dirEntry->d_name;
if (entryName == "." || entryName == "..")
continue;
std::string entryLink = requestUri;
if (entryLink[entryLink.size() - 1] != '/')
entryLink += '/';
Expand All @@ -40,33 +43,56 @@ static HttpResponse buildAutoindex(const std::string& directoryPath, const std::

HttpResponse MethodHandler::handleGet(const HttpRequest& request, const LocationConfig& location)
{
std::string filePath = location.getRoot() + request.uri;
std::string uriPath = request.uri;
std::size_t queryPos = uriPath.find('?');
if (queryPos != std::string::npos)
uriPath = uriPath.substr(0, queryPos);

std::string filePath = location.getRoot() + uriPath;

struct stat fileInfo;
if (stat(filePath.c_str(), &fileInfo) == 0 && S_ISDIR(fileInfo.st_mode))
{
if (!location.getIndex().empty())
{
if (filePath[filePath.size() - 1] != '/')
filePath += '/';
filePath += location.getIndex();
std::string indexPath = filePath;
if (indexPath[indexPath.size() - 1] != '/')
indexPath += '/';
indexPath += location.getIndex();

struct stat indexInfo;
if (stat(indexPath.c_str(), &indexInfo) == 0)
filePath = indexPath;
else if (location.getAutoindex())
return buildAutoindex(filePath, uriPath);
else
return buildHttpError(404, "Not Found");
}
else if (location.getAutoindex())
return buildAutoindex(filePath, request.uri);
return buildAutoindex(filePath, uriPath);
else
return buildHttpError(403, "Forbidden");
}

int fileDescriptor = open(filePath.c_str(), O_RDONLY);
if (fileDescriptor == -1)
{
if (errno == EACCES || errno == EPERM)
return buildHttpError(403, "Forbidden");
return buildHttpError(404, "Not Found");
}

HttpResponse response;
char readBuffer[4096];
ssize_t bytesRead;

while ((bytesRead = read(fileDescriptor, readBuffer, sizeof(readBuffer))) > 0)
response.body.append(readBuffer, bytesRead);
if (bytesRead < 0)
{
close(fileDescriptor);
return buildHttpError(500, "Internal Server Error");
}
close(fileDescriptor);

response.status_code = 200;
Expand Down
91 changes: 64 additions & 27 deletions src/http/handlers/MethodHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
#include "../../../include/http/utils/HttpUtils.hpp"
#include "../../../include/http/utils/StringUtils.hpp"
#include <algorithm>
#include <fcntl.h>
#include <unistd.h>
#include <sstream>

static bool isCgiRequest(const HttpRequest& request, const LocationConfig& location)
{
Expand Down Expand Up @@ -30,43 +33,77 @@ bool MethodHandler::isMethodAllowed(const std::string& method, const LocationCon
return std::find(allowedMethods.begin(), allowedMethods.end(), method) != allowedMethods.end();
}

HttpResponse MethodHandler::handle(const HttpRequest& request, const LocationConfig& location, const ServerConfig& server)
static HttpResponse applyCustomErrorPage(const HttpResponse& response,
const ServerConfig& server,
const LocationConfig& location)
{
if (hasPathTraversal(request.uri))
return buildHttpError(400, "Bad Request");
if (response.status_code < 400 || location.getRoot().empty())
return response;

if (location.getPath().empty())
return buildHttpError(404, "Not Found");
const std::map<int, std::string>& errorPages = server.getErrorPages();
std::map<int, std::string>::const_iterator pageIt = errorPages.find(response.status_code);
if (pageIt == errorPages.end())
return response;

if (!isMethodAllowed(request.method, location))
return buildHttpError(405, "Method Not Allowed");
std::string filePath = location.getRoot() + pageIt->second;
int fd = open(filePath.c_str(), O_RDONLY);
if (fd == -1)
return response;

bool bodyLimitIsSet = server.getMaxBodySize() > 0;
bool bodyExceedsLimit = request.body.size() > server.getMaxBodySize();
if (bodyLimitIsSet && bodyExceedsLimit)
return buildHttpError(413, "Payload Too Large");
HttpResponse customResponse = response;
customResponse.body = "";
char buf[4096];
ssize_t bytesRead;

if (!location.getRedirectUrl().empty())
while ((bytesRead = read(fd, buf, sizeof(buf))) > 0)
customResponse.body.append(buf, bytesRead);
if (bytesRead < 0)
{
HttpResponse redirectResponse;
redirectResponse.status_code = 301;
redirectResponse.status_msg = "Moved Permanently";
redirectResponse.headers["Location"] = location.getRedirectUrl();
return redirectResponse;
close(fd);
return response;
}
close(fd);

customResponse.headers["Content-Type"] = getContentType(filePath);
std::ostringstream contentLength;
contentLength << customResponse.body.size();
customResponse.headers["Content-Length"] = contentLength.str();
return customResponse;
}

if (isCgiRequest(request, location))
HttpResponse MethodHandler::handle(const HttpRequest& request, const LocationConfig& location, const ServerConfig& server)
{
HttpResponse response;

if (hasPathTraversal(request.uri))
response = buildHttpError(400, "Bad Request");
else if (location.getPath().empty())
response = buildHttpError(404, "Not Found");
else if (!isMethodAllowed(request.method, location))
response = buildHttpError(405, "Method Not Allowed");
else if (server.getMaxBodySize() > 0 && request.body.size() > server.getMaxBodySize())
response = buildHttpError(413, "Payload Too Large");
else if (!location.getRedirectUrl().empty())
{
response.status_code = 301;
response.status_msg = "Moved Permanently";
response.headers["Location"] = location.getRedirectUrl();
response.headers["Content-Length"] = "0";
return response;
}
else if (isCgiRequest(request, location))
{
CgiHandler cgiHandler;
return cgiHandler.execute(request, location);
response = cgiHandler.execute(request, location);
}
else if (request.method == "GET")
response = handleGet(request, location);
else if (request.method == "POST")
response = handlePost(request, location);
else if (request.method == "DELETE")
response = handleDelete(request, location);
else
response = buildHttpError(405, "Method Not Allowed");

if (request.method == "GET")
return handleGet(request, location);
if (request.method == "POST")
return handlePost(request, location);
if (request.method == "DELETE")
return handleDelete(request, location);

return buildHttpError(405, "Method Not Allowed");
return applyCustomErrorPage(response, server, location);
}
14 changes: 12 additions & 2 deletions src/http/handlers/PostHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <fcntl.h>
#include <unistd.h>
#include <sstream>
#include <cerrno>

HttpResponse MethodHandler::handlePost(const HttpRequest& request, const LocationConfig& location)
{
Expand All @@ -13,15 +14,24 @@ HttpResponse MethodHandler::handlePost(const HttpRequest& request, const Locatio
if (request.body.empty())
return buildHttpError(400, "Bad Request");

std::string filename = extractFilename(request.uri);
std::string uriPath = request.uri;
std::size_t queryPos = uriPath.find('?');
if (queryPos != std::string::npos)
uriPath = uriPath.substr(0, queryPos);

std::string filename = extractFilename(uriPath);
if (filename.empty())
return buildHttpError(400, "Bad Request");

std::string destinationPath = location.getUploadPath() + "/" + filename;

int fileDescriptor = open(destinationPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fileDescriptor == -1)
return buildHttpError(403, "Forbidden");
{
if (errno == EACCES || errno == EPERM)
return buildHttpError(403, "Forbidden");
return buildHttpError(500, "Internal Server Error");
}

const char* bodyData = request.body.data();
std::size_t totalBytesToWrite = request.body.size();
Expand Down
8 changes: 8 additions & 0 deletions src/http/response/ResponseBuilder.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
#include "../../../include/http/ResponseBuilder.hpp"
#include <sstream>
#include <ctime>

std::string ResponseBuilder::build(const HttpResponse& response)
{
std::ostringstream rawOutput;

rawOutput << "HTTP/1.1 " << response.status_code << " " << response.status_msg << "\r\n";

time_t now = time(NULL);
struct tm* gmt = gmtime(&now);
char dateBuf[64];
strftime(dateBuf, sizeof(dateBuf), "%a, %d %b %Y %H:%M:%S GMT", gmt);
rawOutput << "Date: " << dateBuf << "\r\n";
rawOutput << "Connection: close\r\n";

std::map<std::string, std::string>::const_iterator headerIt;
for (headerIt = response.headers.begin(); headerIt != response.headers.end(); ++headerIt)
rawOutput << headerIt->first << ": " << headerIt->second << "\r\n";
Expand Down
11 changes: 9 additions & 2 deletions src/http/router/Router.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,22 @@ LocationConfig Router::route(const HttpRequest& request, const ServerConfig& ser
{
const std::vector<LocationConfig>& locations = server.getLocations();

std::string uriPath = request.uri;
std::size_t queryPos = uriPath.find('?');
if (queryPos != std::string::npos)
uriPath = uriPath.substr(0, queryPos);

LocationConfig bestMatch;
std::size_t longestMatchLength = 0;

for (std::vector<LocationConfig>::const_iterator locationIt = locations.begin();
locationIt != locations.end(); ++locationIt)
{
const std::string& locationPath = locationIt->getPath();
std::string locationPath = locationIt->getPath();
if (locationPath.size() > 1 && locationPath[locationPath.size() - 1] == '/')
locationPath = locationPath.substr(0, locationPath.size() - 1);

if (matchesLocation(locationPath, request.uri) && locationPath.size() > longestMatchLength)
if (matchesLocation(locationPath, uriPath) && locationPath.size() > longestMatchLength)
{
bestMatch = *locationIt;
longestMatchLength = locationPath.size();
Expand Down
13 changes: 10 additions & 3 deletions src/http/utils/StringUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,17 @@ std::string urlDecode(const std::string& encoded)

bool hasPathTraversal(const std::string& uri)
{
std::string decodedUri = urlDecode(uri);
bool containsDotDot = decodedUri.find("..") != std::string::npos;
std::string decoded = urlDecode(uri);

return containsDotDot;
if (decoded.find("/../") != std::string::npos)
return true;
if (decoded.size() >= 3 && decoded.substr(decoded.size() - 3) == "/..")
return true;
if (decoded.size() >= 3 && decoded.substr(0, 3) == "../")
return true;
if (decoded == "..")
return true;
return false;
}

std::string extractQueryString(const std::string& uri)
Expand Down
Loading
Loading