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+ static std::vector<std::string> list_dir (const std::string& dir_path)
1820{
19- struct stat sb;
20- std::string full_path;
21- std::string not_found = " " ;
21+ std::vector<std::string> names;
2222
23- // Happy case: exact match found
24- if (stat (path. c_str (), &sb) == 0 && S_ISREG (sb. st_mode ) )
25- return path ;
23+ DIR* d = opendir (dir_path. c_str ());
24+ if (!d )
25+ return names ;
2626
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;
27+ for (dirent* ent = readdir (d); ent != NULL ; ent = readdir (d)) {
28+ std::string name = ent->d_name ;
29+
30+ if (name == " ." || name == " .." )
31+ continue ;
32+
33+ names.push_back (name);
3534 }
35+ closedir (d);
3636
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;
37+ std::sort (names.begin (), names.end ());
38+ return names;
39+ }
40+
41+ static std::string html_escape (const std::string& s)
42+ {
43+ std::string out;
44+ out.reserve (s.size ());
45+ for (size_t i = 0 ; i < s.size (); ++i) {
46+ char c = s[i];
47+ if (c == ' &' )
48+ out += " &" ;
49+ else if (c == ' <' )
50+ out += " <" ;
51+ else if (c == ' >' )
52+ out += " >" ;
53+ else if (c == ' "' )
54+ out += " "" ;
55+ else
56+ out += c;
57+ }
58+ return out;
59+ }
60+
61+ static std::string url_escape_min (const std::string& s)
62+ {
63+ std::string out;
64+ for (size_t i = 0 ; i < s.size (); ++i) {
65+ if (s[i] == ' ' )
66+ out += " %20" ;
67+ else
68+ out += s[i];
69+ }
70+ return out;
71+ }
72+
73+ static std::string build_autoindex_html (const std::string& fs_dir, const std::string& url_dir)
74+ {
75+ std::vector<std::string> names = list_dir (fs_dir);
76+
77+ std::string body;
78+ body += " <!doctype html><html><head><meta charset=\" utf-8\" >" ;
79+ body += " <title>Index of " + html_escape (url_dir) + " </title></head><body>" ;
80+ body += " <h1>Index of " + html_escape (url_dir) + " </h1>" ;
81+ body += " <ul>" ;
82+
83+ // Parent link (optional)
84+ if (url_dir != " /" ) {
85+ body += " <li><a href=\" ../\" >../</a></li>" ;
86+ }
87+
88+ for (size_t i = 0 ; i < names.size (); ++i) {
89+ const std::string& name = names[i];
4190
42- return not_found;
91+ std::string fs_entry = fs_dir;
92+ if (!fs_entry.empty () && fs_entry[fs_entry.size () - 1 ] != ' /' )
93+ fs_entry += " /" ;
94+ fs_entry += name;
95+
96+ struct stat sb;
97+ bool is_dir = (stat (fs_entry.c_str (), &sb) == 0 && S_ISDIR (sb.st_mode ));
98+
99+ std::string display = html_escape (name) + (is_dir ? " /" : " " );
100+ std::string href = url_escape_min (name) + (is_dir ? " /" : " " );
101+
102+ body += " <li><a href=\" " + href + " \" >" + display + " </a></li>" ;
103+ }
104+
105+ body += " </ul></body></html>" ;
106+ return body;
43107}
44108
45109static const char * derive_file_type (const std::string& file_path)
@@ -72,6 +136,45 @@ static const char* derive_file_type(const std::string& file_path)
72136 return " application/octet-stream" ;
73137}
74138
139+ void StaticFileHandler::resolve_path (ResolveResult& resolve) const
140+ {
141+ struct stat sb;
142+ std::string full_path;
143+ const std::vector<std::string>& index_files = rc_.shared .index_files ;
144+ // Happy case: exact match found
145+ if (stat (path_.c_str (), &sb) == 0 && S_ISREG (sb.st_mode )) {
146+ resolve.path = path_;
147+ resolve.kind = kFile ;
148+ return ;
149+ }
150+ // Directory: check every index files
151+ if (stat (path_.c_str (), &sb) == 0 && S_ISDIR (sb.st_mode )) {
152+ for (size_t i = 0 ; i < index_files.size (); i++) {
153+ full_path = path_ + " /" + index_files[i];
154+ if (stat (full_path.c_str (), &sb) == 0 && S_ISREG (sb.st_mode )) {
155+ resolve.path = full_path;
156+ resolve.kind = kFile ;
157+ return ;
158+ }
159+ }
160+ resolve.path = path_;
161+ resolve.kind = kDirNoIndex ;
162+ return ;
163+ }
164+
165+ // Clean URL fallback: check /foo -> /foo.html
166+ full_path = path_ + " .html" ;
167+ if (stat (full_path.c_str (), &sb) == 0 && S_ISREG (sb.st_mode )) {
168+ resolve.path = full_path;
169+ resolve.kind = kFile ;
170+ return ;
171+ }
172+
173+ resolve.path = " " ;
174+ resolve.kind = kNotFound ;
175+ return ;
176+ }
177+
75178StaticFileHandler::StaticFileHandler (const std::string& path,
76179 const RouteConfig& rc,
77180 const HttpRequest& req)
@@ -83,24 +186,40 @@ StaticFileHandler::StaticFileHandler(const std::string& path,
83186 fd_(-1 )
84187
85188{
86-
87- if (rc.shared .index_files .empty ())
88- LOG (WARN) << " Index files are empty" ;
89- std::string full_path = resolve_path (path, rc.shared .index_files );
90- if (full_path.empty ()) {
91- LOG (ERROR) << " Couldn't open file " << path;
189+ LOG (DEBUG) << " req_.path = " << req_.path ;
190+ struct ResolveResult r;
191+ resolve_path (r);
192+ if (r.kind == kNotFound ) {
92193 set_error (HttpResponse::kStatusNotFound );
93194 return ;
94195 }
196+ if (r.kind == kDirNoIndex && !rc_.shared .autoindex_enabled ) {
197+ set_error (HttpResponse::kStatusForbidden );
198+ return ;
199+ }
200+ if (r.kind == kDirNoIndex && rc_.shared .autoindex_enabled && r.path [r.path .size () - 1 ] != ' /' ) {
201+ set_redirect (HttpResponse::kStatusMovedPermanently , req_.path + " /" );
202+ return ;
203+ }
204+ if (r.kind == kDirNoIndex && rc_.shared .autoindex_enabled ) {
205+ std::string html = build_autoindex_html (r.path , req_.path );
206+ res_ = HttpResponse::make_response_with_body (
207+ HttpResponse::kStatusOk , " text/html; charset=UTF-8" , html, req_);
208+
209+ out_buf_ = res_.to_string ();
210+ fd_ = -1 ;
211+ return ;
212+ }
213+
95214 struct stat file_stat;
96- fd_ = open (full_path .data (), O_RDONLY);
215+ fd_ = open (r. path .data (), O_RDONLY);
97216 if (fd_ == -1 ) {
98217 set_error (HttpResponse::kStatusInternalServerError );
99218 return ;
100219 }
101- stat (full_path .data (), &file_stat);
220+ stat (r. path .data (), &file_stat);
102221 file_size_ = file_stat.st_size ;
103- std::string file_type = derive_file_type (full_path );
222+ std::string file_type = derive_file_type (r. path );
104223 res_ = HttpResponse::make_response_headers_only (
105224 HttpResponse::kStatusOk , file_type, file_size_, req_);
106225 out_buf_ = res_.to_string ();
@@ -152,4 +271,18 @@ void StaticFileHandler::set_error(const HttpResponse::Status code)
152271{
153272 res_ = HttpResponse::make_error (code, rc_.shared .error_pages , req_);
154273 out_buf_ = res_.to_string ();
274+ if (fd_ != -1 ) {
275+ close (fd_);
276+ fd_ = -1 ;
277+ }
278+ }
279+
280+ void StaticFileHandler::set_redirect (const HttpResponse::Status code,
281+ const std::string& redirect_path)
282+ {
283+ res_ = HttpResponse::make_response_headers_only (code, " " , 0 , req_);
284+ // LOG(DEBUG) << "redirect location = " << redirect_path;
285+ res_.location = redirect_path;
286+ out_buf_ = res_.to_string ();
287+ fd_ = -1 ;
155288}
0 commit comments