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 += " &" ;
52+ else if (c == ' <' )
53+ out += " <" ;
54+ else if (c == ' >' )
55+ out += " >" ;
56+ else if (c == ' "' )
57+ out += " "" ;
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
45114static 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+
73181StaticFileHandler::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+ }
0 commit comments