1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/http
7  
// Official repository: https://github.com/cppalliance/http
8  
//
8  
//
9  

9  

10  
#include <boost/http/server/serve_index.hpp>
10  
#include <boost/http/server/serve_index.hpp>
11  
#include <boost/http/server/accepts.hpp>
11  
#include <boost/http/server/accepts.hpp>
12  
#include <boost/http/server/escape_html.hpp>
12  
#include <boost/http/server/escape_html.hpp>
13  
#include <boost/http/server/encode_url.hpp>
13  
#include <boost/http/server/encode_url.hpp>
14  
#include <boost/http/field.hpp>
14  
#include <boost/http/field.hpp>
15  
#include <boost/http/status.hpp>
15  
#include <boost/http/status.hpp>
16  
#include <chrono>
16  
#include <chrono>
17  
#include <filesystem>
17  
#include <filesystem>
18  
#include <string>
18  
#include <string>
19  
#include <vector>
19  
#include <vector>
20  

20  

21  
namespace boost {
21  
namespace boost {
22  
namespace http {
22  
namespace http {
23  

23  

24  
namespace {
24  
namespace {
25  

25  

26  
// Append an HTTP rel-path to a local filesystem path.
26  
// Append an HTTP rel-path to a local filesystem path.
27  
void
27  
void
28  
path_cat(
28  
path_cat(
29  
    std::string& result,
29  
    std::string& result,
30  
    core::string_view prefix,
30  
    core::string_view prefix,
31  
    core::string_view suffix)
31  
    core::string_view suffix)
32  
{
32  
{
33  
    result = prefix;
33  
    result = prefix;
34  

34  

35  
#ifdef _WIN32
35  
#ifdef _WIN32
36  
    char constexpr path_separator = '\\';
36  
    char constexpr path_separator = '\\';
37  
#else
37  
#else
38  
    char constexpr path_separator = '/';
38  
    char constexpr path_separator = '/';
39  
#endif
39  
#endif
40  
    if(! result.empty() && result.back() == path_separator)
40  
    if(! result.empty() && result.back() == path_separator)
41  
        result.resize(result.size() - 1);
41  
        result.resize(result.size() - 1);
42  

42  

43  
#ifdef _WIN32
43  
#ifdef _WIN32
44  
    for(auto& c : result)
44  
    for(auto& c : result)
45  
        if(c == '/')
45  
        if(c == '/')
46  
            c = path_separator;
46  
            c = path_separator;
47  
#endif
47  
#endif
48  
    for(auto const& c : suffix)
48  
    for(auto const& c : suffix)
49  
    {
49  
    {
50  
        if(c == '/')
50  
        if(c == '/')
51  
            result.push_back(path_separator);
51  
            result.push_back(path_separator);
52  
        else
52  
        else
53  
            result.push_back(c);
53  
            result.push_back(c);
54  
    }
54  
    }
55  
}
55  
}
56  

56  

57  
struct dir_entry
57  
struct dir_entry
58  
{
58  
{
59  
    std::string name;
59  
    std::string name;
60  
    bool is_dir = false;
60  
    bool is_dir = false;
61  
    std::uint64_t size = 0;
61  
    std::uint64_t size = 0;
62  
    std::uint64_t mtime = 0;
62  
    std::uint64_t mtime = 0;
63  
};
63  
};
64  

64  

65  
// Directories first, then case-insensitive alphabetical
65  
// Directories first, then case-insensitive alphabetical
66  
bool
66  
bool
67  
entry_less(
67  
entry_less(
68  
    dir_entry const& a,
68  
    dir_entry const& a,
69  
    dir_entry const& b) noexcept
69  
    dir_entry const& b) noexcept
70  
{
70  
{
71  
    if(a.is_dir != b.is_dir)
71  
    if(a.is_dir != b.is_dir)
72  
        return a.is_dir;
72  
        return a.is_dir;
73  

73  

74  
    // Case-insensitive compare
74  
    // Case-insensitive compare
75  
    auto const& an = a.name;
75  
    auto const& an = a.name;
76  
    auto const& bn = b.name;
76  
    auto const& bn = b.name;
77  
    auto const n = (std::min)(an.size(), bn.size());
77  
    auto const n = (std::min)(an.size(), bn.size());
78  
    for(std::size_t i = 0; i < n; ++i)
78  
    for(std::size_t i = 0; i < n; ++i)
79  
    {
79  
    {
80  
        auto ac = static_cast<unsigned char>(an[i]);
80  
        auto ac = static_cast<unsigned char>(an[i]);
81  
        auto bc = static_cast<unsigned char>(bn[i]);
81  
        auto bc = static_cast<unsigned char>(bn[i]);
82  
        if(ac >= 'A' && ac <= 'Z') ac += 32;
82  
        if(ac >= 'A' && ac <= 'Z') ac += 32;
83  
        if(bc >= 'A' && bc <= 'Z') bc += 32;
83  
        if(bc >= 'A' && bc <= 'Z') bc += 32;
84  
        if(ac != bc)
84  
        if(ac != bc)
85  
            return ac < bc;
85  
            return ac < bc;
86  
    }
86  
    }
87  
    return an.size() < bn.size();
87  
    return an.size() < bn.size();
88  
}
88  
}
89  

89  

90  
std::uint64_t
90  
std::uint64_t
91  
to_epoch(std::filesystem::file_time_type tp)
91  
to_epoch(std::filesystem::file_time_type tp)
92  
{
92  
{
93  
    auto const sctp = std::chrono::clock_cast<
93  
    auto const sctp = std::chrono::clock_cast<
94  
        std::chrono::system_clock>(tp);
94  
        std::chrono::system_clock>(tp);
95  
    auto const dur = sctp.time_since_epoch();
95  
    auto const dur = sctp.time_since_epoch();
96  
    return static_cast<std::uint64_t>(
96  
    return static_cast<std::uint64_t>(
97  
        std::chrono::duration_cast<
97  
        std::chrono::duration_cast<
98  
            std::chrono::seconds>(dur).count());
98  
            std::chrono::seconds>(dur).count());
99  
}
99  
}
100  

100  

101  
std::string
101  
std::string
102  
format_size(std::uint64_t bytes)
102  
format_size(std::uint64_t bytes)
103  
{
103  
{
104  
    if(bytes < 1024)
104  
    if(bytes < 1024)
105  
        return std::to_string(bytes) + " B";
105  
        return std::to_string(bytes) + " B";
106  
    if(bytes < 1024 * 1024)
106  
    if(bytes < 1024 * 1024)
107  
        return std::to_string(bytes / 1024) + " KB";
107  
        return std::to_string(bytes / 1024) + " KB";
108  
    if(bytes < 1024 * 1024 * 1024)
108  
    if(bytes < 1024 * 1024 * 1024)
109  
        return std::to_string(bytes / (1024 * 1024)) + " MB";
109  
        return std::to_string(bytes / (1024 * 1024)) + " MB";
110  
    return std::to_string(
110  
    return std::to_string(
111  
        bytes / (1024ULL * 1024 * 1024)) + " GB";
111  
        bytes / (1024ULL * 1024 * 1024)) + " GB";
112  
}
112  
}
113  

113  

114  
std::string
114  
std::string
115  
format_time(std::uint64_t epoch)
115  
format_time(std::uint64_t epoch)
116  
{
116  
{
117  
    if(epoch == 0)
117  
    if(epoch == 0)
118  
        return "-";
118  
        return "-";
119  

119  

120  
    auto const t = static_cast<std::time_t>(epoch);
120  
    auto const t = static_cast<std::time_t>(epoch);
121  
    std::tm tm;
121  
    std::tm tm;
122  
#ifdef _WIN32
122  
#ifdef _WIN32
123  
    gmtime_s(&tm, &t);
123  
    gmtime_s(&tm, &t);
124  
#else
124  
#else
125  
    gmtime_r(&t, &tm);
125  
    gmtime_r(&t, &tm);
126  
#endif
126  
#endif
127  
    char buf[64];
127  
    char buf[64];
128  
    std::strftime(buf, sizeof(buf),
128  
    std::strftime(buf, sizeof(buf),
129  
        "%Y-%m-%d %H:%M:%S", &tm);
129  
        "%Y-%m-%d %H:%M:%S", &tm);
130  
    return buf;
130  
    return buf;
131  
}
131  
}
132  

132  

133  

133  

134  
std::string
134  
std::string
135  
render_html(
135  
render_html(
136  
    core::string_view dir,
136  
    core::string_view dir,
137  
    std::vector<dir_entry> const& entries,
137  
    std::vector<dir_entry> const& entries,
138  
    bool show_parent)
138  
    bool show_parent)
139  
{
139  
{
140  
    std::string body;
140  
    std::string body;
141  
    body.reserve(4096);
141  
    body.reserve(4096);
142  

142  

143  
    body.append(
143  
    body.append(
144  
        "<!DOCTYPE html>\n"
144  
        "<!DOCTYPE html>\n"
145  
        "<html>\n<head>\n"
145  
        "<html>\n<head>\n"
146  
        "<meta charset=\"utf-8\">\n"
146  
        "<meta charset=\"utf-8\">\n"
147  
        "<meta name=\"viewport\" "
147  
        "<meta name=\"viewport\" "
148  
            "content=\"width=device-width\">\n"
148  
            "content=\"width=device-width\">\n"
149  
        "<title>Index of ");
149  
        "<title>Index of ");
150  
    body.append(escape_html(dir));
150  
    body.append(escape_html(dir));
151  
    body.append(
151  
    body.append(
152  
        "</title>\n"
152  
        "</title>\n"
153  
        "<style>\n"
153  
        "<style>\n"
154  
        "body { font-family: -apple-system, "
154  
        "body { font-family: -apple-system, "
155  
            "BlinkMacSystemFont, sans-serif; "
155  
            "BlinkMacSystemFont, sans-serif; "
156  
            "margin: 2em; }\n"
156  
            "margin: 2em; }\n"
157  
        "h1 { font-size: 1.4em; }\n"
157  
        "h1 { font-size: 1.4em; }\n"
158  
        "table { border-collapse: collapse; "
158  
        "table { border-collapse: collapse; "
159  
            "width: 100%; max-width: 900px; }\n"
159  
            "width: 100%; max-width: 900px; }\n"
160  
        "th, td { text-align: left; "
160  
        "th, td { text-align: left; "
161  
            "padding: 0.4em 1em; }\n"
161  
            "padding: 0.4em 1em; }\n"
162  
        "th { border-bottom: 2px solid #ddd; }\n"
162  
        "th { border-bottom: 2px solid #ddd; }\n"
163  
        "td { border-bottom: 1px solid #eee; }\n"
163  
        "td { border-bottom: 1px solid #eee; }\n"
164  
        "a { text-decoration: none; "
164  
        "a { text-decoration: none; "
165  
            "color: #0366d6; }\n"
165  
            "color: #0366d6; }\n"
166  
        "a:hover { text-decoration: underline; }\n"
166  
        "a:hover { text-decoration: underline; }\n"
167  
        ".size, .date { color: #586069; }\n"
167  
        ".size, .date { color: #586069; }\n"
168  
        "</style>\n"
168  
        "</style>\n"
169  
        "</head>\n<body>\n"
169  
        "</head>\n<body>\n"
170  
        "<h1>Index of ");
170  
        "<h1>Index of ");
171  
    body.append(escape_html(dir));
171  
    body.append(escape_html(dir));
172  
    body.append("</h1>\n");
172  
    body.append("</h1>\n");
173  

173  

174  
    body.append(
174  
    body.append(
175  
        "<table>\n"
175  
        "<table>\n"
176  
        "<tr><th>Name</th>"
176  
        "<tr><th>Name</th>"
177  
        "<th>Size</th>"
177  
        "<th>Size</th>"
178  
        "<th>Modified</th></tr>\n");
178  
        "<th>Modified</th></tr>\n");
179  

179  

180  
    if(show_parent)
180  
    if(show_parent)
181  
    {
181  
    {
182  
        body.append(
182  
        body.append(
183  
            "<tr><td><a href=\"../\">"
183  
            "<tr><td><a href=\"../\">"
184  
            "..</a></td>"
184  
            "..</a></td>"
185  
            "<td class=\"size\">-</td>"
185  
            "<td class=\"size\">-</td>"
186  
            "<td class=\"date\">-</td></tr>\n");
186  
            "<td class=\"date\">-</td></tr>\n");
187  
    }
187  
    }
188  

188  

189  
    for(auto const& e : entries)
189  
    for(auto const& e : entries)
190  
    {
190  
    {
191  
        auto display_name = escape_html(e.name);
191  
        auto display_name = escape_html(e.name);
192  
        auto href = encode_url(e.name);
192  
        auto href = encode_url(e.name);
193  
        if(e.is_dir)
193  
        if(e.is_dir)
194  
            href += '/';
194  
            href += '/';
195  

195  

196  
        body.append("<tr><td><a href=\"");
196  
        body.append("<tr><td><a href=\"");
197  
        body.append(href);
197  
        body.append(href);
198  
        body.append("\">");
198  
        body.append("\">");
199  
        body.append(display_name);
199  
        body.append(display_name);
200  
        if(e.is_dir)
200  
        if(e.is_dir)
201  
            body.append("/");
201  
            body.append("/");
202  
        body.append("</a></td>");
202  
        body.append("</a></td>");
203  
        body.append("<td class=\"size\">");
203  
        body.append("<td class=\"size\">");
204  
        body.append(e.is_dir ? "-" : format_size(e.size));
204  
        body.append(e.is_dir ? "-" : format_size(e.size));
205  
        body.append("</td>");
205  
        body.append("</td>");
206  
        body.append("<td class=\"date\">");
206  
        body.append("<td class=\"date\">");
207  
        body.append(format_time(e.mtime));
207  
        body.append(format_time(e.mtime));
208  
        body.append("</td></tr>\n");
208  
        body.append("</td></tr>\n");
209  
    }
209  
    }
210  

210  

211  
    body.append("</table>\n</body>\n</html>\n");
211  
    body.append("</table>\n</body>\n</html>\n");
212  
    return body;
212  
    return body;
213  
}
213  
}
214  

214  

215  
std::string
215  
std::string
216  
render_json(
216  
render_json(
217  
    std::vector<dir_entry> const& entries)
217  
    std::vector<dir_entry> const& entries)
218  
{
218  
{
219  
    std::string body;
219  
    std::string body;
220  
    body.reserve(1024);
220  
    body.reserve(1024);
221  
    body.push_back('[');
221  
    body.push_back('[');
222  

222  

223  
    bool first = true;
223  
    bool first = true;
224  
    for(auto const& e : entries)
224  
    for(auto const& e : entries)
225  
    {
225  
    {
226  
        if(! first)
226  
        if(! first)
227  
            body.push_back(',');
227  
            body.push_back(',');
228  
        first = false;
228  
        first = false;
229  

229  

230  
        body.append("{\"name\":\"");
230  
        body.append("{\"name\":\"");
231  

231  

232  
        // Escape JSON string
232  
        // Escape JSON string
233  
        for(auto c : e.name)
233  
        for(auto c : e.name)
234  
        {
234  
        {
235  
            switch(c)
235  
            switch(c)
236  
            {
236  
            {
237  
            case '"':  body.append("\\\""); break;
237  
            case '"':  body.append("\\\""); break;
238  
            case '\\': body.append("\\\\"); break;
238  
            case '\\': body.append("\\\\"); break;
239  
            case '\n': body.append("\\n");  break;
239  
            case '\n': body.append("\\n");  break;
240  
            case '\r': body.append("\\r");  break;
240  
            case '\r': body.append("\\r");  break;
241  
            case '\t': body.append("\\t");  break;
241  
            case '\t': body.append("\\t");  break;
242  
            default:   body.push_back(c);   break;
242  
            default:   body.push_back(c);   break;
243  
            }
243  
            }
244  
        }
244  
        }
245  

245  

246  
        body.append("\",\"type\":\"");
246  
        body.append("\",\"type\":\"");
247  
        body.append(e.is_dir ? "directory" : "file");
247  
        body.append(e.is_dir ? "directory" : "file");
248  
        body.append("\",\"size\":");
248  
        body.append("\",\"size\":");
249  
        body.append(std::to_string(e.size));
249  
        body.append(std::to_string(e.size));
250  
        body.append(",\"mtime\":");
250  
        body.append(",\"mtime\":");
251  
        body.append(std::to_string(e.mtime));
251  
        body.append(std::to_string(e.mtime));
252  
        body.push_back('}');
252  
        body.push_back('}');
253  
    }
253  
    }
254  

254  

255  
    body.push_back(']');
255  
    body.push_back(']');
256  
    return body;
256  
    return body;
257  
}
257  
}
258  

258  

259  
std::string
259  
std::string
260  
render_plain(
260  
render_plain(
261  
    std::vector<dir_entry> const& entries)
261  
    std::vector<dir_entry> const& entries)
262  
{
262  
{
263  
    std::string body;
263  
    std::string body;
264  
    body.reserve(1024);
264  
    body.reserve(1024);
265  
    for(auto const& e : entries)
265  
    for(auto const& e : entries)
266  
    {
266  
    {
267  
        body.append(e.name);
267  
        body.append(e.name);
268  
        if(e.is_dir)
268  
        if(e.is_dir)
269  
            body.push_back('/');
269  
            body.push_back('/');
270  
        body.push_back('\n');
270  
        body.push_back('\n');
271  
    }
271  
    }
272  
    return body;
272  
    return body;
273  
}
273  
}
274  

274  

275  
} // (anon)
275  
} // (anon)
276  

276  

277  
//------------------------------------------------
277  
//------------------------------------------------
278  

278  

279  
struct serve_index::impl
279  
struct serve_index::impl
280  
{
280  
{
281  
    std::string root;
281  
    std::string root;
282  
    serve_index::options opts;
282  
    serve_index::options opts;
283  

283  

284  
    impl(
284  
    impl(
285  
        core::string_view root_,
285  
        core::string_view root_,
286  
        serve_index::options const& opts_)
286  
        serve_index::options const& opts_)
287  
        : root(root_)
287  
        : root(root_)
288  
        , opts(opts_)
288  
        , opts(opts_)
289  
    {
289  
    {
290  
    }
290  
    }
291  
};
291  
};
292  

292  

293  
serve_index::
293  
serve_index::
294  
~serve_index()
294  
~serve_index()
295  
{
295  
{
296  
    delete impl_;
296  
    delete impl_;
297  
}
297  
}
298  

298  

299  
serve_index::
299  
serve_index::
300  
serve_index(core::string_view root)
300  
serve_index(core::string_view root)
301  
    : serve_index(root, options{})
301  
    : serve_index(root, options{})
302  
{
302  
{
303  
}
303  
}
304  

304  

305  
serve_index::
305  
serve_index::
306  
serve_index(
306  
serve_index(
307  
    core::string_view root,
307  
    core::string_view root,
308  
    options const& opts)
308  
    options const& opts)
309  
    : impl_(new impl(root, opts))
309  
    : impl_(new impl(root, opts))
310  
{
310  
{
311  
}
311  
}
312  

312  

313  
serve_index::
313  
serve_index::
314  
serve_index(serve_index&& other) noexcept
314  
serve_index(serve_index&& other) noexcept
315  
    : impl_(other.impl_)
315  
    : impl_(other.impl_)
316  
{
316  
{
317  
    other.impl_ = nullptr;
317  
    other.impl_ = nullptr;
318  
}
318  
}
319  

319  

320  
route_task
320  
route_task
321  
serve_index::
321  
serve_index::
322  
operator()(route_params& rp) const
322  
operator()(route_params& rp) const
323  
{
323  
{
324  
    // Only handle GET and HEAD
324  
    // Only handle GET and HEAD
325  
    if(rp.req.method() != method::get &&
325  
    if(rp.req.method() != method::get &&
326  
        rp.req.method() != method::head)
326  
        rp.req.method() != method::head)
327  
    {
327  
    {
328  
        if(impl_->opts.fallthrough)
328  
        if(impl_->opts.fallthrough)
329  
            co_return route_next;
329  
            co_return route_next;
330  

330  

331  
        rp.res.set_status(status::method_not_allowed);
331  
        rp.res.set_status(status::method_not_allowed);
332  
        rp.res.set(field::allow, "GET, HEAD, OPTIONS");
332  
        rp.res.set(field::allow, "GET, HEAD, OPTIONS");
333  
        auto [ec] = co_await rp.send();
333  
        auto [ec] = co_await rp.send();
334  
        if(ec)
334  
        if(ec)
335  
            co_return route_error(ec);
335  
            co_return route_error(ec);
336  
        co_return route_done;
336  
        co_return route_done;
337  
    }
337  
    }
338  

338  

339  
    auto req_path = rp.url.path();
339  
    auto req_path = rp.url.path();
340  

340  

341  
    // Build filesystem path
341  
    // Build filesystem path
342  
    std::string path;
342  
    std::string path;
343  
    path_cat(path, impl_->root, req_path);
343  
    path_cat(path, impl_->root, req_path);
344  

344  

345  
    // Must be a directory
345  
    // Must be a directory
346  
    std::error_code fec;
346  
    std::error_code fec;
347  
    auto fs_status = std::filesystem::status(path, fec);
347  
    auto fs_status = std::filesystem::status(path, fec);
348  
    if(fec || fs_status.type() !=
348  
    if(fec || fs_status.type() !=
349  
        std::filesystem::file_type::directory)
349  
        std::filesystem::file_type::directory)
350  
        co_return route_next;
350  
        co_return route_next;
351  

351  

352  
    // Redirect if missing trailing slash
352  
    // Redirect if missing trailing slash
353  
    if(req_path.empty() || req_path.back() != '/')
353  
    if(req_path.empty() || req_path.back() != '/')
354  
    {
354  
    {
355  
        std::string location(req_path);
355  
        std::string location(req_path);
356  
        location += '/';
356  
        location += '/';
357  
        rp.res.set_status(status::moved_permanently);
357  
        rp.res.set_status(status::moved_permanently);
358  
        rp.res.set(field::location, location);
358  
        rp.res.set(field::location, location);
359  
        auto [ec] = co_await rp.send("");
359  
        auto [ec] = co_await rp.send("");
360  
        if(ec)
360  
        if(ec)
361  
            co_return route_error(ec);
361  
            co_return route_error(ec);
362  
        co_return route_done;
362  
        co_return route_done;
363  
    }
363  
    }
364  

364  

365  
    // Read directory entries
365  
    // Read directory entries
366  
    std::vector<dir_entry> entries;
366  
    std::vector<dir_entry> entries;
367  
    {
367  
    {
368  
        std::filesystem::directory_iterator it(path, fec);
368  
        std::filesystem::directory_iterator it(path, fec);
369  
        if(fec)
369  
        if(fec)
370  
            co_return route_next;
370  
            co_return route_next;
371  

371  

372  
        for(auto const& de :
372  
        for(auto const& de :
373  
            std::filesystem::directory_iterator(path, fec))
373  
            std::filesystem::directory_iterator(path, fec))
374  
        {
374  
        {
375  
            auto name = de.path().filename().string();
375  
            auto name = de.path().filename().string();
376  

376  

377  
            // Skip hidden files unless configured
377  
            // Skip hidden files unless configured
378  
            if(! impl_->opts.hidden &&
378  
            if(! impl_->opts.hidden &&
379  
                ! name.empty() && name[0] == '.')
379  
                ! name.empty() && name[0] == '.')
380  
                continue;
380  
                continue;
381  

381  

382  
            dir_entry e;
382  
            dir_entry e;
383  
            e.name = std::move(name);
383  
            e.name = std::move(name);
384  

384  

385  
            std::error_code sec;
385  
            std::error_code sec;
386  
            e.is_dir = de.is_directory(sec);
386  
            e.is_dir = de.is_directory(sec);
387  
            if(! e.is_dir)
387  
            if(! e.is_dir)
388  
                e.size = de.file_size(sec);
388  
                e.size = de.file_size(sec);
389  
            auto lwt = de.last_write_time(sec);
389  
            auto lwt = de.last_write_time(sec);
390  
            if(! sec)
390  
            if(! sec)
391  
                e.mtime = to_epoch(lwt);
391  
                e.mtime = to_epoch(lwt);
392  

392  

393  
            entries.push_back(std::move(e));
393  
            entries.push_back(std::move(e));
394  
        }
394  
        }
395  
    }
395  
    }
396  

396  

397  
    std::sort(entries.begin(), entries.end(), entry_less);
397  
    std::sort(entries.begin(), entries.end(), entry_less);
398  

398  

399  
    // Determine ".." display
399  
    // Determine ".." display
400  
    std::filesystem::path root_canonical(impl_->root);
400  
    std::filesystem::path root_canonical(impl_->root);
401  
    std::filesystem::path dir_canonical(path);
401  
    std::filesystem::path dir_canonical(path);
402  
    {
402  
    {
403  
        std::error_code ec2;
403  
        std::error_code ec2;
404  
        root_canonical =
404  
        root_canonical =
405  
            std::filesystem::canonical(root_canonical, ec2);
405  
            std::filesystem::canonical(root_canonical, ec2);
406  
        dir_canonical =
406  
        dir_canonical =
407  
            std::filesystem::canonical(dir_canonical, ec2);
407  
            std::filesystem::canonical(dir_canonical, ec2);
408  
    }
408  
    }
409  
    bool show_up = impl_->opts.show_parent &&
409  
    bool show_up = impl_->opts.show_parent &&
410  
        dir_canonical != root_canonical;
410  
        dir_canonical != root_canonical;
411  

411  

412  
    // Content negotiation
412  
    // Content negotiation
413  
    accepts ac( rp.req );
413  
    accepts ac( rp.req );
414  
    auto type = ac.type({ "html", "json", "text" });
414  
    auto type = ac.type({ "html", "json", "text" });
415  

415  

416  
    std::string body;
416  
    std::string body;
417  
    std::string_view content_type;
417  
    std::string_view content_type;
418  
    if( type == "json" )
418  
    if( type == "json" )
419  
    {
419  
    {
420  
        body = render_json(entries);
420  
        body = render_json(entries);
421  
        content_type = "application/json; charset=utf-8";
421  
        content_type = "application/json; charset=utf-8";
422  
    }
422  
    }
423  
    else if( type == "text" )
423  
    else if( type == "text" )
424  
    {
424  
    {
425  
        body = render_plain(entries);
425  
        body = render_plain(entries);
426  
        content_type = "text/plain; charset=utf-8";
426  
        content_type = "text/plain; charset=utf-8";
427  
    }
427  
    }
428  
    else
428  
    else
429  
    {
429  
    {
430  
        body = render_html(req_path, entries, show_up);
430  
        body = render_html(req_path, entries, show_up);
431  
        content_type = "text/html; charset=utf-8";
431  
        content_type = "text/html; charset=utf-8";
432  
    }
432  
    }
433  

433  

434  
    rp.res.set(field::content_type, content_type);
434  
    rp.res.set(field::content_type, content_type);
435  
    rp.res.set("X-Content-Type-Options", "nosniff");
435  
    rp.res.set("X-Content-Type-Options", "nosniff");
436  

436  

437  
    auto [ec] = co_await rp.send(body);
437  
    auto [ec] = co_await rp.send(body);
438  
    if(ec)
438  
    if(ec)
439  
        co_return route_error(ec);
439  
        co_return route_error(ec);
440  
    co_return route_done;
440  
    co_return route_done;
441  
}
441  
}
442  

442  

443  
} // http
443  
} // http
444  
} // boost
444  
} // boost