11#include <boost/beast/http/file_body.hpp>
23 inline static std::string
to_string(std::filesystem::file_time_type
const& ftime)
27 std::chrono::system_clock::to_time_t(std::chrono::time_point_cast<std::chrono::system_clock::duration>(
28 std::chrono::utc_clock::to_sys(std::chrono::file_clock::to_utc(ftime))));
31 std::chrono::system_clock::to_time_t(std::chrono::time_point_cast<std::chrono::system_clock::duration>(
32 std::chrono::file_clock::to_sys(ftime)));
34 std::string result(1024,
'\0');
36# pragma clang diagnostic push
37# pragma clang diagnostic ignored "-Wdeprecated-declarations"
38 auto size = std::strftime(result.data(), result.size(),
"%a %b %e %H:%M:%S %Y", std::localtime(&cftime));
39# pragma clang diagnostic pop
41 auto size = std::strftime(result.data(), result.size(),
"%a %b %e %H:%M:%S %Y", std::localtime(&cftime));
48 template <
typename RequestListenerT>
61 template <
typename RequestListenerT>
74 this->
serveInfo_.serveOptions.onFileServeComplete)}
77 onError_ = [](std::string
const&) {};
84 namespace http = boost::beast::http;
94 this->
serveInfo_.handler, *this->listener_, session, req, fileAndStatus, this->serveInfo_.serveOptions))
105 switch (req.method())
107 case (http::verb::head):
109 if (!unwrapFlexibleProvider<RequestListenerT, bool>(
114 case (http::verb::options):
118 case (http::verb::get):
120 if (!unwrapFlexibleProvider<RequestListenerT, bool>(
125 case (http::verb::delete_):
127 if (!unwrapFlexibleProvider<RequestListenerT, bool>(
132 case (http::verb::put):
134 if (!unwrapFlexibleProvider<RequestListenerT, bool>(
146 if (fileAndStatus.status.type() == std::filesystem::file_type::none && req.method() != http::verb::put)
155 const auto rawPath = unwrapFlexibleProvider<RequestListenerT, std::filesystem::path>(
162 auto style = unwrapFlexibleProvider<RequestListenerT, std::string>(
171 auto analyzeFile = [
this](boost::beast::string_view target) ->
FileAndStatus {
176 if (std::filesystem::exists(*absolute))
177 return {.file = *absolute, .relative = *relative, .status = std::filesystem::status(*absolute)};
179 return {.file = *absolute, .relative = *relative, .status = std::filesystem::file_status{}};
181 return {.file = {}, .status = std::filesystem::file_status{}};
184 std::pair<std::filesystem::file_type, std::filesystem::path> fileAndStatus;
186 return analyzeFile(
"");
188 return analyzeFile(target);
193 namespace http = boost::beast::http;
194 switch (req.method())
196 case (http::verb::get):
198 if (fileAndStatus.
status.type() == std::filesystem::file_type::directory)
200 if (unwrapFlexibleProvider<RequestListenerT, bool>(
212 return download(session, req, fileAndStatus);
214 case (http::verb::delete_):
217 auto type = fileAndStatus.
status.type();
218 if (type == std::filesystem::file_type::regular || type == std::filesystem::file_type::symlink)
220 std::filesystem::remove(fileAndStatus.
file, ec);
222 else if (fileAndStatus.
status.type() == std::filesystem::file_type::directory)
224 if (unwrapFlexibleProvider<RequestListenerT, bool>(
226 std::filesystem::remove_all(fileAndStatus.
file, ec);
229 if (std::filesystem::is_empty(fileAndStatus.
file))
230 std::filesystem::remove(fileAndStatus.
file, ec);
237 ec = std::make_error_code(std::errc::operation_not_supported);
243 return (
void)session.
send<http::empty_body>(req)->status(http::status::no_content).commit();
245 case (http::verb::put):
247 return upload(session, req, fileAndStatus);
257 namespace http = boost::beast::http;
260 if (fileAndStatus.
status.type() != std::filesystem::file_type::regular &&
261 fileAndStatus.
status.type() != std::filesystem::file_type::symlink)
264 session.
send<http::empty_body>(req)
265 ->status(http::status::ok)
266 .setHeader(http::field::accept_ranges,
"bytes")
267 .setHeader(http::field::content_length, std::to_string(std::filesystem::file_size(fileAndStatus.
file)))
268 .contentType(contentType ? *contentType :
"application/octet-stream")
270 .fail([onError =
onError_](
auto&& err) {
271 onError(err.toString());
277 namespace http = boost::beast::http;
278 std::string allow =
"OPTIONS";
279 if (unwrapFlexibleProvider<RequestListenerT, bool>(
281 allow +=
", GET, HEAD";
282 if (unwrapFlexibleProvider<RequestListenerT, bool>(
285 if (unwrapFlexibleProvider<RequestListenerT, bool>(
289 session.
send<http::empty_body>(req)
290 ->status(http::status::no_content)
291 .setHeader(http::field::allow, allow)
292 .setHeader(http::field::accept_ranges,
"bytes")
298 namespace http = boost::beast::http;
299 std::stringstream document;
300 document <<
"<!DOCTYPE html>\n";
301 document <<
"<html>\n";
302 document <<
"<head>\n";
303 document <<
"<meta charset='utf-8'>\n";
304 document <<
"<meta http-equiv='X-UA-Compatible' content='IE=edge'>\n";
305 document <<
"<style>\n";
307 document <<
"<title>Roar Listing of " << fileAndStatus.
relative.string() <<
"</title>\n";
308 document <<
"<meta name='viewport' content='width=device-width, initial-scale=1'>\n";
309 document <<
"</head>\n";
310 document <<
"<body>\n";
311 document <<
"<h1>Index of " << fileAndStatus.
relative.string() <<
"</h1>\n";
312 document <<
"<table class=\"styled-table\">\n";
313 document <<
"<thead>\n";
314 document <<
"<tr>\n";
324 document <<
"</tr>\n";
325 document <<
"</thead>\n";
326 document <<
"<tbody>\n";
330 for (
auto const& entry : std::filesystem::directory_iterator{fileAndStatus.
file})
333 std::string fn = entry.path().filename().string();
334 auto relative = fileAndStatus.
relative.string();
335 link.reserve(relative.size() +
basePath_.size() + fn.size() + 10);
342 if (!relative.empty() && relative !=
".")
349 document <<
"<tr>\n";
350 document <<
"<td>\n";
351 document <<
"<a href='" << link <<
"'>" << fn <<
"</a>\n";
352 if (entry.is_regular_file())
354 document <<
"<td>" <<
to_string(entry.last_write_time()) <<
"</td>\n";
355 document <<
"<td>" << bytesToHumanReadable<1024>(entry.file_size()) <<
"</td>\n";
364 document <<
"</td>\n";
365 document <<
"</tr>\n";
368 catch (std::exception
const& exc)
370 return session.
sendStandardResponse(boost::beast::http::status::internal_server_error, exc.what());
373 document <<
"</table>\n";
374 document <<
"</tbody>\n";
375 document <<
"</body>\n";
376 document <<
"</html>\n";
378 session.
send<http::string_body>(req)
379 ->status(http::status::ok)
380 .contentType(
"text/html")
381 .body(document.str())
387 namespace http = boost::beast::http;
390 return (
void)session.
send<http::string_body>(req)
391 ->status(http::status::expectation_failed)
392 .setHeader(http::field::connection,
"close")
393 .body(
"Set Expect: 100-continue")
397 switch (fileAndStatus.
status.type())
399 case (std::filesystem::file_type::none):
401 case (std::filesystem::file_type::not_found):
403 case (std::filesystem::file_type::regular):
405 if (unwrapFlexibleProvider<RequestListenerT, bool>(
419 auto body = std::make_shared<http::file_body::value_type>();
420 boost::beast::error_code ec;
421 body->open(fileAndStatus.
file.string().c_str(), boost::beast::file_mode::write, ec);
424 http::status::internal_server_error,
"Cannot open file for writing.");
426 session.
send<http::empty_body>(req)
427 ->status(http::status::continue_)
429 .then([session = session.shared_from_this(),
431 body = std::move(body),
437 session->
read<http::file_body>(req, std::move(*body))
438 ->bodyLimit(*contentLength)
440 .then([](
auto& session,
auto const&) {
443 .fail([session, onError](
Error const& e) {
452 namespace http = boost::beast::http;
453 if (fileAndStatus.
status.type() != std::filesystem::file_type::regular &&
454 fileAndStatus.
status.type() != std::filesystem::file_type::symlink)
460 http::file_body::value_type body;
461 boost::beast::error_code ec;
462 body.open(fileAndStatus.
file.string().c_str(), boost::beast::file_mode::read, ec);
465 http::status::internal_server_error,
"Cannot open file for reading.");
468 auto intermediate = session.
send<http::file_body>(req, std::move(body));
469 intermediate->preparePayload();
470 intermediate->enableCors(req, this->
serveInfo_.routeOptions.cors);
471 intermediate->contentType(contentType ? contentType.value() :
"application/octet-stream");
472 intermediate->commit()
475 onFileServeComplete(wasClosed);
477 .fail([onError =
onError_](
auto&& err) {
478 onError(err.toString());
484 boost::beast::error_code ec;
485 body.
open(fileAndStatus.
file.string().c_str(), std::ios_base::in, ec);
488 http::status::internal_server_error,
"Cannot open file for reading.");
494 ->useFixedTimeout(std::chrono::seconds{10})
498 onFileServeComplete(wasClosed);
500 .fail([onError =
onError_](
auto&& err) {
501 onError(err.toString());
Internal helper class to serve directories.
Definition directory_server.hpp:63
Jail jail_
Definition directory_server.hpp:512
void makeListing(Session &session, EmptyBodyRequest const &req, FileAndStatus const &fileAndStatus) const
Definition directory_server.hpp:296
void operator()(Session &session, EmptyBodyRequest const &req)
Definition directory_server.hpp:82
std::string listingStyle() const
Definition directory_server.hpp:160
DirectoryServer(DirectoryServerConstructionArgs< RequestListenerT > &&args)
Definition directory_server.hpp:65
void handleFileServe(Session &session, EmptyBodyRequest const &req, FileAndStatus const &fileAndStatus) const
Definition directory_server.hpp:191
std::function< void(std::string const &)> onError_
Definition directory_server.hpp:514
FileAndStatus getFileAndStatus(boost::beast::string_view target) const
Definition directory_server.hpp:169
std::function< void(bool)> onFileServeComplete_
Definition directory_server.hpp:515
void sendOptionsResponse(Session &session, EmptyBodyRequest const &req, FileAndStatus const &) const
Definition directory_server.hpp:275
void upload(Session &session, EmptyBodyRequest const &req, FileAndStatus const &fileAndStatus) const
Definition directory_server.hpp:385
std::string basePath_
Definition directory_server.hpp:513
void sendHeadResponse(Session &session, EmptyBodyRequest const &req, FileAndStatus const &fileAndStatus) const
Definition directory_server.hpp:255
void download(Session &session, EmptyBodyRequest const &req, FileAndStatus const &fileAndStatus) const
Definition directory_server.hpp:450
std::filesystem::path resolvePath() const
Definition directory_server.hpp:153
Support range requests for get requests including multipart/byterange.
Definition range_file_body.hpp:23
void open(std::filesystem::path const &filename, std::ios_base::openmode mode, std::error_code &ec)
Opens the file.
Definition range_file_body.hpp:138
void setReadRanges(Ranges const &ranges, std::string_view contentType)
Set the Read Range.
Definition range_file_body.hpp:160
std::optional< std::filesystem::path > relativeToRoot(std::filesystem::path const &other, bool fakeJailAsRoot=false) const
Definition jail.cpp:27
std::optional< std::filesystem::path > pathAsIsInJail(std::filesystem::path const &other) const
First tests if the path is in the jail and then returns a full path of the resource including the jai...
Definition jail.cpp:44
This body is a file body, but with range request support.
Definition range_file_body.hpp:315
This class extends the boost::beast::http::request<BodyT> with additional convenience.
Definition request.hpp:52
bool expectsContinue() const
Returns true if the request expects a continue response.
Definition request.hpp:278
std::optional< Ranges > ranges() const
Extracts the Range header.
Definition request.hpp:329
std::optional< std::size_t > contentLength() const
Returns the content length header.
Definition request.hpp:305
std::string path() const
Returns only the path of the url.
Definition request.hpp:91
Definition session.hpp:41
std::shared_ptr< SendIntermediate< BodyT > > send(Request< OriginalBodyT > const &req, Forwards &&... forwards)
Definition session.hpp:398
void sendStrictTransportSecurityResponse()
Sends a 403 with Strict-Transport-Security. Used only for unencrypted request on enforced HTTPS.
Definition session.cpp:336
void sendStandardResponse(boost::beast::http::status status, std::string_view additionalInfo="")
Sends a standard response like "404 not found".
Definition session.cpp:328
std::shared_ptr< ReadIntermediate< BodyT > > read(Request< OriginalBodyT > req, Forwards &&... forwardArgs)
Read data from the client.
Definition session.hpp:629
bool isSecure() const
Returns whether or not this is an encrypted session.
Definition session.cpp:230
Definition range_file_body.hpp:18
const auto ranges
Definition ranges.cpp:19
constexpr char const * defaultListingStyle
Definition default_listing_style.hpp:5
std::string to_string(AuthorizationScheme scheme)
Definition authorization.cpp:28
std::filesystem::path resolvePath(std::filesystem::path const &path)
Will replace prefixes like "~" and "%appdata%" with actual directories on linux and windows.
Definition special_paths.cpp:73
std::optional< T > unwrapFlexibleProvider(HolderClassT &holder, FlexibleProvider< HolderClassT, T, true > const &flexibleProvider)
Definition flexible_provider.hpp:116
@ Deny
Will respond with a standard forbidden response.
@ Handled
You handled the response, so just end the connection if necessary.
@ Continue
Continue to serve/delete/upload the file.
std::optional< std::string > extensionToMime(std::string const &extension)
Definition mime_type.cpp:699
Definition directory_server.hpp:50
bool serverIsSecure_
Definition directory_server.hpp:51
std::shared_ptr< RequestListenerT > listener_
Definition directory_server.hpp:53
ServeInfo< RequestListenerT > serveInfo_
Definition directory_server.hpp:52
Holds errors that are produced asynchronously anywhere.
Definition error.hpp:20
std::string toString() const
Definition error.hpp:40
Definition request_listener.hpp:92
std::filesystem::path file
Definition request_listener.hpp:93
std::filesystem::path relative
Definition request_listener.hpp:94
std::filesystem::file_status status
Definition request_listener.hpp:95
Definition request_listener.hpp:135