Skip to content

Commit 7e40fcc

Browse files
moving to 42
1 parent e16b973 commit 7e40fcc

File tree

2 files changed

+176
-31
lines changed

2 files changed

+176
-31
lines changed

src/handler/static_file_handler.cpp

Lines changed: 167 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,110 @@
55
#include "util/log_message.hpp"
66
#include "util/syscall_error.hpp"
77

8+
#include <dirent.h>
89
#include <errno.h>
910
#include <fcntl.h>
11+
#include <sys/stat.h>
1012
#include <unistd.h>
1113

14+
#include <algorithm>
1215
#include <cstring>
1316
#include <stdexcept>
1417
#include <vector>
1518

16-
static std::string resolve_path(const std::string& path,
17-
const std::vector<std::string>& index_files)
19+
#define DIR_NO_INDEX "dir_no_index"
20+
#define NOT_FOUND "not_found"
21+
22+
static std::vector<std::string> list_dir(const std::string& dir_path)
1823
{
19-
struct stat sb;
20-
std::string full_path;
21-
std::string not_found = "";
24+
std::vector<std::string> names;
2225

23-
// Happy case: exact match found
24-
if (stat(path.c_str(), &sb) == 0 && S_ISREG(sb.st_mode))
25-
return path;
26+
DIR* d = opendir(dir_path.c_str());
27+
if (!d)
28+
return names; // caller decides 403/500 etc.
2629

27-
// Directory: check every index files
28-
if (stat(path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) {
29-
for (size_t i = 0; i < index_files.size(); i++) {
30-
full_path = path + "/" + index_files[i];
31-
if (stat(full_path.c_str(), &sb) == 0 && S_ISREG(sb.st_mode))
32-
return full_path;
33-
}
34-
return not_found;
30+
for (dirent* ent = readdir(d); ent != NULL; ent = readdir(d)) {
31+
std::string name = ent->d_name;
32+
33+
if (name == "." || name == "..")
34+
continue;
35+
36+
names.push_back(name);
3537
}
38+
closedir(d);
3639

37-
// Clean URL fallback: check /foo -> /foo.html
38-
full_path = path + ".html";
39-
if (stat(full_path.c_str(), &sb) == 0 && S_ISREG(sb.st_mode))
40-
return full_path;
40+
std::sort(names.begin(), names.end());
41+
return names;
42+
}
43+
44+
static std::string html_escape(const std::string& s)
45+
{
46+
std::string out;
47+
out.reserve(s.size());
48+
for (size_t i = 0; i < s.size(); ++i) {
49+
char c = s[i];
50+
if (c == '&')
51+
out += "&amp;";
52+
else if (c == '<')
53+
out += "&lt;";
54+
else if (c == '>')
55+
out += "&gt;";
56+
else if (c == '"')
57+
out += "&quot;";
58+
else
59+
out += c;
60+
}
61+
return out;
62+
}
4163

42-
return not_found;
64+
// very minimal url-encoding for spaces; if you want full correctness,
65+
// implement percent-encoding for all non-unreserved chars.
66+
static std::string url_escape_min(const std::string& s)
67+
{
68+
std::string out;
69+
for (size_t i = 0; i < s.size(); ++i) {
70+
if (s[i] == ' ')
71+
out += "%20";
72+
else
73+
out += s[i];
74+
}
75+
return out;
76+
}
77+
78+
static std::string build_autoindex_html(const std::string& fs_dir, const std::string& url_dir)
79+
{
80+
std::vector<std::string> names = list_dir(fs_dir);
81+
82+
std::string body;
83+
body += "<!doctype html><html><head><meta charset=\"utf-8\">";
84+
body += "<title>Index of " + html_escape(url_dir) + "</title></head><body>";
85+
body += "<h1>Index of " + html_escape(url_dir) + "</h1>";
86+
body += "<ul>";
87+
88+
// Parent link (optional)
89+
if (url_dir != "/") {
90+
body += "<li><a href=\"../\">../</a></li>";
91+
}
92+
93+
for (size_t i = 0; i < names.size(); ++i) {
94+
const std::string& name = names[i];
95+
96+
std::string fs_entry = fs_dir;
97+
if (!fs_entry.empty() && fs_entry[fs_entry.size() - 1] != '/')
98+
fs_entry += "/";
99+
fs_entry += name;
100+
101+
struct stat sb;
102+
bool is_dir = (stat(fs_entry.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode));
103+
104+
std::string display = html_escape(name) + (is_dir ? "/" : "");
105+
std::string href = url_escape_min(name) + (is_dir ? "/" : "");
106+
107+
body += "<li><a href=\"" + href + "\">" + display + "</a></li>";
108+
}
109+
110+
body += "</ul></body></html>";
111+
return body;
43112
}
44113

45114
static const char* derive_file_type(const std::string& file_path)
@@ -70,6 +139,45 @@ static const char* derive_file_type(const std::string& file_path)
70139
return "application/octet-stream";
71140
}
72141

142+
void StaticFileHandler::resolve_path(ResolveResult& resolve) const
143+
{
144+
struct stat sb;
145+
std::string full_path;
146+
const std::vector<std::string>& index_files = rc_.shared.index_files;
147+
// Happy case: exact match found
148+
if (stat(path_.c_str(), &sb) == 0 && S_ISREG(sb.st_mode)) {
149+
resolve.path = path_;
150+
resolve.kind = kFile;
151+
return;
152+
}
153+
// Directory: check every index files
154+
if (stat(path_.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) {
155+
for (size_t i = 0; i < index_files.size(); i++) {
156+
full_path = path_ + "/" + index_files[i];
157+
if (stat(full_path.c_str(), &sb) == 0 && S_ISREG(sb.st_mode)) {
158+
resolve.path = full_path;
159+
resolve.kind = kFile;
160+
return;
161+
}
162+
}
163+
resolve.path = path_;
164+
resolve.kind = kDirNoIndex;
165+
return;
166+
}
167+
168+
// Clean URL fallback: check /foo -> /foo.html
169+
full_path = path_ + ".html";
170+
if (stat(full_path.c_str(), &sb) == 0 && S_ISREG(sb.st_mode)) {
171+
resolve.path = full_path;
172+
resolve.kind = kFile;
173+
return;
174+
}
175+
176+
resolve.path = "";
177+
resolve.kind = kNotFound;
178+
return;
179+
}
180+
73181
StaticFileHandler::StaticFileHandler(const std::string& path,
74182
const RouteConfig& rc,
75183
const HttpRequest& req)
@@ -81,24 +189,39 @@ StaticFileHandler::StaticFileHandler(const std::string& path,
81189
fd_(-1)
82190

83191
{
84-
85-
if (rc.shared.index_files.empty())
86-
LOG(WARN) << "Index files are empty";
87-
std::string full_path = resolve_path(path, rc.shared.index_files);
88-
if (full_path.empty()) {
89-
LOG(ERROR) << "Couldn't open file " << path;
192+
struct ResolveResult r;
193+
resolve_path(r);
194+
if (r.kind == kNotFound) {
90195
set_error(HttpResponse::kStatusNotFound);
91196
return;
92197
}
198+
if (r.kind == kDirNoIndex && !rc_.shared.autoindex_enabled) {
199+
set_error(HttpResponse::kStatusForbidden);
200+
return;
201+
}
202+
if (r.kind == kDirNoIndex && rc_.shared.autoindex_enabled && r.path[r.path.size() - 1] != '/') {
203+
set_redirect(HttpResponse::kStatusMovedPermanently, r.path + "/");
204+
return;
205+
}
206+
if (r.kind == kDirNoIndex && rc_.shared.autoindex_enabled) {
207+
std::string html = build_autoindex_html(path_, r.path);
208+
res_ = HttpResponse::make_response_with_body(
209+
HttpResponse::kStatusOk, "text/html; charset=UTF-8", html, req_);
210+
211+
out_buf_ = res_.to_string();
212+
fd_ = -1;
213+
return;
214+
}
215+
93216
struct stat file_stat;
94-
fd_ = open(full_path.data(), O_RDONLY);
217+
fd_ = open(r.path.data(), O_RDONLY);
95218
if (fd_ == -1) {
96219
set_error(HttpResponse::kStatusInternalServerError);
97220
return;
98221
}
99-
stat(full_path.data(), &file_stat);
222+
stat(r.path.data(), &file_stat);
100223
file_size_ = file_stat.st_size;
101-
std::string file_type = derive_file_type(full_path);
224+
std::string file_type = derive_file_type(r.path);
102225
res_ = HttpResponse::make_response_headers_only(
103226
HttpResponse::kStatusOk, file_type, file_size_, req_);
104227
out_buf_ = res_.to_string();
@@ -150,4 +273,17 @@ void StaticFileHandler::set_error(const HttpResponse::Status code)
150273
{
151274
res_ = HttpResponse::make_error(code, rc_.shared.error_pages, req_);
152275
out_buf_ = res_.to_string();
153-
}
276+
if (fd_ != -1) {
277+
close(fd_);
278+
fd_ = -1;
279+
}
280+
}
281+
282+
void StaticFileHandler::set_redirect(const HttpResponse::Status code,
283+
const std::string& redirect_path)
284+
{
285+
res_ = HttpResponse::make_response_headers_only(code, "", 0, req_);
286+
res_.location = redirect_path;
287+
out_buf_ = res_.to_string();
288+
fd_ = -1;
289+
}

src/handler/static_file_handler.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99

1010
#include <string>
1111

12+
enum ResolveKind { kFile, kDirNoIndex, kNotFound };
13+
14+
struct ResolveResult {
15+
ResolveKind kind;
16+
std::string path;
17+
};
18+
1219
class StaticFileHandler : public Handler {
1320
public:
1421
StaticFileHandler(const std::string& path, const RouteConfig& rc, const HttpRequest& req);
@@ -28,7 +35,9 @@ class StaticFileHandler : public Handler {
2835
private:
2936
StaticFileHandler(const StaticFileHandler&);
3037
StaticFileHandler& operator=(const StaticFileHandler&);
38+
void resolve_path(ResolveResult& resolve) const;
3139
void set_error(const HttpResponse::Status code);
40+
void set_redirect(const HttpResponse::Status code, const std::string& redirect_path);
3241

3342
// constructor args
3443
const std::string& path_;

0 commit comments

Comments
 (0)