207 lines
7.4 KiB
C++
207 lines
7.4 KiB
C++
/*
|
|
Copyright (C) 2021 Greg S.
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>
|
|
*/
|
|
#include <ss4sc/file-utils.hpp>
|
|
|
|
#include <system_error>
|
|
|
|
#include <boost/beast/http/parser.hpp>
|
|
|
|
#include <sys/xattr.h>
|
|
|
|
#include <ss4sc/automime.hpp>
|
|
|
|
std::optional<std::string> file_attr(std::filesystem::path file, std::string_view attr_name) {
|
|
ssize_t attr_len = getxattr(file.native().c_str(), attr_name.data(), nullptr, 0);
|
|
|
|
if(attr_len > 0) {
|
|
std::string attr_value(attr_len, '\0');
|
|
if(getxattr(file.native().c_str(), attr_name.data(), attr_value.data(), attr_value.size()) == -1) {
|
|
if(errno == ENODATA) {
|
|
return std::nullopt;
|
|
}
|
|
throw std::system_error{std::make_error_code(static_cast<std::errc>(errno))};
|
|
}
|
|
|
|
return attr_value;
|
|
} else {
|
|
if(errno == ENODATA) {
|
|
return std::nullopt;
|
|
}
|
|
throw std::system_error{std::make_error_code(static_cast<std::errc>(errno))};
|
|
}
|
|
|
|
}
|
|
|
|
void set_file_attr(std::filesystem::path file, std::string_view attr_name, std::string_view attr_value) {
|
|
if( attr_value.size() == 0 ) {
|
|
if( removexattr(file.native().c_str(), attr_name.data()) != 0 ) {
|
|
if(errno == ENODATA) {
|
|
return;
|
|
}
|
|
throw std::system_error{std::make_error_code(static_cast<std::errc>(errno))};
|
|
}
|
|
return;
|
|
}
|
|
|
|
if( setxattr(file.native().c_str(), attr_name.data(), attr_value.data(), attr_value.size(), 0) != 0 ) {
|
|
throw std::system_error{std::make_error_code(static_cast<std::errc>(errno))};
|
|
}
|
|
}
|
|
|
|
boost::beast::http::response<boost::beast::http::empty_body>
|
|
read_custom_headers(std::filesystem::path file) {
|
|
if(auto attr = file_attr(file, attr_additional); attr) {
|
|
std::array<boost::asio::const_buffer, 3> parse_me = {
|
|
boost::asio::const_buffer{"HTTP/1.1 200 Ok\r\n", 17},
|
|
{attr.value().data(), attr.value().size()},
|
|
{"\r\n\r\n", 4}
|
|
};
|
|
|
|
boost::beast::http::response_parser<boost::beast::http::empty_body> parser;
|
|
boost::beast::error_code ec;
|
|
parser.put(parse_me, ec);
|
|
if(ec) {
|
|
throw std::system_error(ec);
|
|
}
|
|
if(!parser.is_header_done()) {
|
|
throw std::runtime_error("Headers not parsed from xattr");
|
|
}
|
|
|
|
return parser.release();
|
|
}
|
|
return {};
|
|
}
|
|
|
|
template<typename Fields>
|
|
void write_custom_headers(std::filesystem::path file, Fields& response_headers) {
|
|
std::ostringstream ss;
|
|
for(auto& field : response_headers) {
|
|
ss << field.name_string() << ": " << field.value() << "\r\n";
|
|
}
|
|
auto s = ss.str();
|
|
set_file_attr(file, attr_additional, s);
|
|
}
|
|
|
|
template<typename Fields>
|
|
void populate_custom_headers(std::filesystem::path file, Fields& response_headers) {
|
|
auto message = read_custom_headers(file);
|
|
|
|
for( auto& field : message ) {
|
|
if( field.name() == boost::beast::http::field::unknown ) {
|
|
response_headers.insert( field.name_string(), field.value() );
|
|
} else {
|
|
response_headers.insert( field.name(), field.value() );
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename Fields>
|
|
void populate_file_headers(std::filesystem::path file, Fields& response_headers, mime_determination det) {
|
|
populate_custom_headers(file, response_headers);
|
|
|
|
if( auto attr = file_attr(file, attr_mimetype); attr ) {
|
|
if( auto charset_attr = file_attr(file, attr_charset); charset_attr ) {
|
|
response_headers.set( boost::beast::http::field::content_type, boost::str(boost::format("%s; charset=%s") % attr.value() % charset_attr.value()) );
|
|
} else {
|
|
response_headers.set( boost::beast::http::field::content_type, attr.value() );
|
|
}
|
|
} else {
|
|
auto assigned_mimetype = assign_mimetype_if_not_present(file, det);
|
|
response_headers.set( boost::beast::http::field::content_type, assigned_mimetype );
|
|
}
|
|
if( auto attr = file_attr(file, attr_language); attr ) {
|
|
response_headers.set( boost::beast::http::field::content_language, attr.value() );
|
|
}
|
|
if( auto attr = file_attr(file, attr_encoding); attr ) {
|
|
response_headers.set( boost::beast::http::field::content_encoding, attr.value() );
|
|
}
|
|
}
|
|
|
|
// https://stackoverflow.com/questions/56788745/how-to-convert-stdfilesystemfile-time-type-to-a-string-using-gcc-9
|
|
// ideally use C++20
|
|
template<typename TP>
|
|
std::time_t to_time_t(TP tp) {
|
|
auto system_clock_time_point = std::chrono::time_point_cast<std::chrono::system_clock::duration>(tp - TP::clock::now() + std::chrono::system_clock::now());
|
|
return std::chrono::system_clock::to_time_t(system_clock_time_point);
|
|
}
|
|
|
|
template<typename TP>
|
|
std::string http_format_date(TP ts) {
|
|
auto system_time = to_time_t(ts);
|
|
|
|
std::stringstream ss;
|
|
struct tm tm_;
|
|
ss << std::put_time(gmtime_r(&system_time, &tm_), "%a, %d %b %Y %H:%M:%S GMT");
|
|
return ss.str();
|
|
}
|
|
|
|
response response_for_path(std::filesystem::path file, bool include_body, mime_determination det) {
|
|
if( std::filesystem::exists(file) ) {
|
|
if(include_body) {
|
|
file_response ret;
|
|
boost::beast::error_code bec;
|
|
ret.body().open(file.native().c_str(), boost::beast::file_mode::read, bec);
|
|
if(bec) {
|
|
throw std::system_error(bec);
|
|
}
|
|
|
|
ret.result(200);
|
|
populate_file_headers(file, ret, det);
|
|
|
|
std::error_code ec;
|
|
auto last_modified = std::filesystem::last_write_time(file, ec);
|
|
if( !ec ) {
|
|
ret.set( boost::beast::http::field::last_modified, http_format_date(last_modified) );
|
|
}
|
|
ret.set( boost::beast::http::field::date, http_format_date(std::chrono::system_clock::now()) );
|
|
|
|
auto content_length = std::filesystem::file_size(file, ec);
|
|
if( !ec ) {
|
|
ret.set( boost::beast::http::field::content_length, std::to_string(content_length) );
|
|
}
|
|
|
|
return ret;
|
|
} else { // include_body == false
|
|
empty_response ret;
|
|
boost::beast::error_code bec;
|
|
|
|
ret.result(200);
|
|
populate_file_headers(file, ret, det);
|
|
|
|
std::error_code ec;
|
|
auto last_modified = std::filesystem::last_write_time(file, ec);
|
|
if( !ec) {
|
|
ret.set( boost::beast::http::field::last_modified, http_format_date(last_modified) );
|
|
}
|
|
ret.set( boost::beast::http::field::date, http_format_date(std::chrono::system_clock::now()) );
|
|
|
|
|
|
auto content_length = std::filesystem::file_size(file, ec);
|
|
if( !ec ) {
|
|
ret.set( boost::beast::http::field::content_length, std::to_string(content_length) );
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
} else {
|
|
empty_response ret;
|
|
ret.result(404);
|
|
ret.set( boost::beast::http::field::content_length, "0");
|
|
return ret;
|
|
}
|
|
}
|