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
6 changes: 6 additions & 0 deletions config/site1.conf
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ server {
index index.html index.htm;
allow_methods GET POST DELETE;

location /old {
return 302 /files;
}

location /pages {
autoindex off;
}

location /images {
}

location /files {
autoindex on;
}
}
3 changes: 3 additions & 0 deletions src/handler/cgi_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@ size_t CgiHandler::read_output(char* buf, size_t n)
eof_reached_ = true;
}
else if (bytes_read < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) { // pipe not available for read
return 0;
}
if (errno != EAGAIN && errno != EWOULDBLOCK) {
if (!headers_parsed_) {
set_res_and_quit(HttpResponse::kStatusBadGateway);
Expand Down
1 change: 1 addition & 0 deletions src/handler/redirect_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ RedirectHandler::RedirectHandler(const RouteConfig& rc, const HttpRequest& req)
req_(req),
out_off_(0)
{
LOG(DEBUG) << "REDIRECTION HANDLER";
res_ = HttpResponse::make_response_headers_only(rc_.shared.redirect.code, "", 0, req_);
res_.location = rc_.shared.redirect.url;
out_buf_ = res_.to_string();
Expand Down
193 changes: 163 additions & 30 deletions src/handler/static_file_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,105 @@
#include "util/log_message.hpp"
#include "util/syscall_error.hpp"

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

#include <algorithm>
#include <cstring>
#include <stdexcept>
#include <vector>

static std::string resolve_path(const std::string& path,
const std::vector<std::string>& index_files)
static std::vector<std::string> list_dir(const std::string& dir_path)
{
struct stat sb;
std::string full_path;
std::string not_found = "";
std::vector<std::string> names;

// Happy case: exact match found
if (stat(path.c_str(), &sb) == 0 && S_ISREG(sb.st_mode))
return path;
DIR* d = opendir(dir_path.c_str());
if (!d)
return names;

// Directory: check every index files
if (stat(path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) {
for (size_t i = 0; i < index_files.size(); i++) {
full_path = path + "/" + index_files[i];
if (stat(full_path.c_str(), &sb) == 0 && S_ISREG(sb.st_mode))
return full_path;
}
return not_found;
for (dirent* ent = readdir(d); ent != NULL; ent = readdir(d)) {
std::string name = ent->d_name;

if (name == "." || name == "..")
continue;

names.push_back(name);
}
closedir(d);

// Clean URL fallback: check /foo -> /foo.html
full_path = path + ".html";
if (stat(full_path.c_str(), &sb) == 0 && S_ISREG(sb.st_mode))
return full_path;
std::sort(names.begin(), names.end());
return names;
}

static std::string html_escape(const std::string& s)
{
std::string out;
out.reserve(s.size());
for (size_t i = 0; i < s.size(); ++i) {
char c = s[i];
if (c == '&')
out += "&amp;";
else if (c == '<')
out += "&lt;";
else if (c == '>')
out += "&gt;";
else if (c == '"')
out += "&quot;";
else
out += c;
}
return out;
}

static std::string url_escape_min(const std::string& s)
{
std::string out;
for (size_t i = 0; i < s.size(); ++i) {
if (s[i] == ' ')
out += "%20";
else
out += s[i];
}
return out;
}

static std::string build_autoindex_html(const std::string& fs_dir, const std::string& url_dir)
{
std::vector<std::string> names = list_dir(fs_dir);

std::string body;
body += "<!doctype html><html><head><meta charset=\"utf-8\">";
body += "<title>Index of " + html_escape(url_dir) + "</title></head><body>";
body += "<h1>Index of " + html_escape(url_dir) + "</h1>";
body += "<ul>";

// Parent link (optional)
if (url_dir != "/") {
body += "<li><a href=\"../\">../</a></li>";
}

for (size_t i = 0; i < names.size(); ++i) {
const std::string& name = names[i];

return not_found;
std::string fs_entry = fs_dir;
if (!fs_entry.empty() && fs_entry[fs_entry.size() - 1] != '/')
fs_entry += "/";
fs_entry += name;

struct stat sb;
bool is_dir = (stat(fs_entry.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode));

std::string display = html_escape(name) + (is_dir ? "/" : "");
std::string href = url_escape_min(name) + (is_dir ? "/" : "");

body += "<li><a href=\"" + href + "\">" + display + "</a></li>";
}

body += "</ul></body></html>";
return body;
}

static const char* derive_file_type(const std::string& file_path)
Expand Down Expand Up @@ -72,6 +136,45 @@ static const char* derive_file_type(const std::string& file_path)
return "application/octet-stream";
}

void StaticFileHandler::resolve_path(ResolveResult& resolve) const
{
struct stat sb;
std::string full_path;
const std::vector<std::string>& index_files = rc_.shared.index_files;
// Happy case: exact match found
if (stat(path_.c_str(), &sb) == 0 && S_ISREG(sb.st_mode)) {
resolve.path = path_;
resolve.kind = kFile;
return;
}
// Directory: check every index files
if (stat(path_.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) {
for (size_t i = 0; i < index_files.size(); i++) {
full_path = path_ + "/" + index_files[i];
if (stat(full_path.c_str(), &sb) == 0 && S_ISREG(sb.st_mode)) {
resolve.path = full_path;
resolve.kind = kFile;
return;
}
}
resolve.path = path_;
resolve.kind = kDirNoIndex;
return;
}

// Clean URL fallback: check /foo -> /foo.html
full_path = path_ + ".html";
if (stat(full_path.c_str(), &sb) == 0 && S_ISREG(sb.st_mode)) {
resolve.path = full_path;
resolve.kind = kFile;
return;
}

resolve.path = "";
resolve.kind = kNotFound;
return;
}

StaticFileHandler::StaticFileHandler(const std::string& path,
const RouteConfig& rc,
const HttpRequest& req)
Expand All @@ -83,24 +186,40 @@ StaticFileHandler::StaticFileHandler(const std::string& path,
fd_(-1)

{

if (rc.shared.index_files.empty())
LOG(WARN) << "Index files are empty";
std::string full_path = resolve_path(path, rc.shared.index_files);
if (full_path.empty()) {
LOG(ERROR) << "Couldn't open file " << path;
LOG(DEBUG) << "req_.path = " << req_.path;
struct ResolveResult r;
resolve_path(r);
if (r.kind == kNotFound) {
set_error(HttpResponse::kStatusNotFound);
return;
}
if (r.kind == kDirNoIndex && !rc_.shared.autoindex_enabled) {
set_error(HttpResponse::kStatusForbidden);
return;
}
if (r.kind == kDirNoIndex && rc_.shared.autoindex_enabled && r.path[r.path.size() - 1] != '/') {
set_redirect(HttpResponse::kStatusMovedPermanently, req_.path + "/");
return;
}
if (r.kind == kDirNoIndex && rc_.shared.autoindex_enabled) {
std::string html = build_autoindex_html(r.path, req_.path);
res_ = HttpResponse::make_response_with_body(
HttpResponse::kStatusOk, "text/html; charset=UTF-8", html, req_);

out_buf_ = res_.to_string();
fd_ = -1;
return;
}

struct stat file_stat;
fd_ = open(full_path.data(), O_RDONLY);
fd_ = open(r.path.data(), O_RDONLY);
if (fd_ == -1) {
set_error(HttpResponse::kStatusInternalServerError);
return;
}
stat(full_path.data(), &file_stat);
stat(r.path.data(), &file_stat);
file_size_ = file_stat.st_size;
std::string file_type = derive_file_type(full_path);
std::string file_type = derive_file_type(r.path);
res_ = HttpResponse::make_response_headers_only(
HttpResponse::kStatusOk, file_type, file_size_, req_);
out_buf_ = res_.to_string();
Expand Down Expand Up @@ -152,4 +271,18 @@ void StaticFileHandler::set_error(const HttpResponse::Status code)
{
res_ = HttpResponse::make_error(code, rc_.shared.error_pages, req_);
out_buf_ = res_.to_string();
if (fd_ != -1) {
close(fd_);
fd_ = -1;
}
}

void StaticFileHandler::set_redirect(const HttpResponse::Status code,
const std::string& redirect_path)
{
res_ = HttpResponse::make_response_headers_only(code, "", 0, req_);
// LOG(DEBUG) << "redirect location = " << redirect_path;
res_.location = redirect_path;
out_buf_ = res_.to_string();
fd_ = -1;
}
9 changes: 9 additions & 0 deletions src/handler/static_file_handler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@

#include <string>

enum ResolveKind { kFile, kDirNoIndex, kNotFound };

struct ResolveResult {
ResolveKind kind;
std::string path;
};

class StaticFileHandler : public Handler {
public:
StaticFileHandler(const std::string& path, const RouteConfig& rc, const HttpRequest& req);
Expand All @@ -29,7 +36,9 @@ class StaticFileHandler : public Handler {
private:
StaticFileHandler(const StaticFileHandler&);
StaticFileHandler& operator=(const StaticFileHandler&);
void resolve_path(ResolveResult& resolve) const;
void set_error(const HttpResponse::Status code);
void set_redirect(const HttpResponse::Status code, const std::string& redirect_path);

// constructor args
const std::string& path_;
Expand Down
4 changes: 2 additions & 2 deletions src/http/http_response.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ std::string HttpResponse::to_string() const
if (!inline_body.empty()) {
out << "Content-Length: " << inline_body.size() << "\r\n";
}
else if (content_length > 0) {
else {
out << "Content-Length: " << content_length << "\r\n";
}
if (is_chunked) {
Expand Down Expand Up @@ -165,7 +165,7 @@ HttpResponse HttpResponse::make_response_with_body(HttpResponse::Status status,
res.code = status;
res.content_type = content_type;
res.keep_alive = req.keep_alive;
if (status == 204 || status == 304) {
if (status == kStatusNoContent || status == 304) {
res.inline_body = "";
res.content_length = 0;
}
Expand Down
3 changes: 2 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ int main(void)
setup_signal_handlers();

try {
HttpConfig cfg = load_http_config("config/vitepress.conf");
// HttpConfig cfg = load_http_config("config/vitepress.conf");
HttpConfig cfg = load_http_config("config/site1.conf");

Server server(cfg.servers[0]);
server.init();
Expand Down
2 changes: 1 addition & 1 deletion tests/redirect_handler_unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ UTEST(RedirectHandlerTest, builds_redirect_response_with_location_header)
ASSERT_TRUE(is_301);

// Usually redirects have no body in your implementation
ASSERT_TRUE(resp.find("Content-Length: 0") == std::string::npos);
ASSERT_TRUE(resp.find("Content-Length: 0") != std::string::npos);

// Must end headers correctly (at least one empty line)
ASSERT_TRUE(resp.find("\r\n\r\n") != std::string::npos ||
Expand Down
Empty file added www/site1/files/second_file.txt
Empty file.
1 change: 1 addition & 0 deletions www/site1/pages/someOtherSite.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>someContent</h1>