From 917d4574153fa57ea43e7410006f58aa5b1bbb0b Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Sun, 5 Apr 2020 19:15:25 +0200 Subject: Separate out response handling --- Makefile | 1 + http.cpp | 94 +++++++++++------------------------------------------------- https.cpp | 94 +++++++++++------------------------------------------------- response.cpp | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ response.h | 40 ++++++++++++++++++++++++++ 5 files changed, 165 insertions(+), 156 deletions(-) create mode 100644 response.cpp create mode 100644 response.h diff --git a/Makefile b/Makefile index bc7d147..06385de 100644 --- a/Makefile +++ b/Makefile @@ -64,6 +64,7 @@ PROGSRC=\ http_debian10.cpp \ plugin.cpp \ privileges.cpp \ + response.cpp \ server.cpp \ stringutil.cpp diff --git a/http.cpp b/http.cpp index 2203ffe..eeff552 100644 --- a/http.cpp +++ b/http.cpp @@ -1,6 +1,7 @@ #include "http.h" #include "server.h" +#include "response.h" #include #include @@ -61,33 +62,6 @@ mime_type(beast::string_view path) return "application/text"; } -// Append an HTTP rel-path to a local filesystem path. -// The returned path is normalized for the platform. -std::string -path_cat( - beast::string_view base, - beast::string_view path) -{ - if(base.empty()) - return std::string(path); - std::string result(base); -#ifdef BOOST_MSVC - char constexpr path_separator = '\\'; - if(result.back() == path_separator) - result.resize(result.size() - 1); - result.append(path.data(), path.size()); - for(auto& c : result) - if(c == '/') - c = path_separator; -#else - char constexpr path_separator = '/'; - if(result.back() == path_separator) - result.resize(result.size() - 1); - result.append(path.data(), path.size()); -#endif - return result; -} - // This function produces an HTTP response for the given // request. The type of the response object depends on the // contents of the request, so the interface requires the @@ -141,60 +115,24 @@ handle_request( return res; }; - // Make sure we can handle the method - if( req.method() != http::verb::get && - req.method() != http::verb::head) - return send(bad_request("Unknown HTTP-method")); - - // Request path must be absolute and not contain "..". - if( req.target().empty() || - req.target()[0] != '/' || - req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); - - // Build the path to the requested file - std::string host{req["host"]}; // TODO: just use string_view - std::string target{req.target()}; - std::string path = path_cat(config.DocRoot(socket, host, target), req.target()); - if(req.target().back() == '/') - path.append("index.html"); - - // Attempt to open the file - beast::error_code ec; - http::file_body::value_type body; - body.open(path.c_str(), beast::file_mode::scan, ec); - - // Handle the case where the file doesn't exist - if(ec == beast::errc::no_such_file_or_directory) - return send(not_found(req.target())); - - // Handle an unknown error - if(ec) - return send(server_error(ec.message())); - - // Cache the size since we need it after the move - auto const size = body.size(); - - // Respond to HEAD request - if(req.method() == http::verb::head) - { - http::response res{http::status::ok, req.version()}; - res.set(http::field::server, VersionString); - res.set(http::field::content_type, mime_type(path)); - res.content_length(size); - res.keep_alive(req.keep_alive()); - return send(std::move(res)); + std::string res_data; + try { + res_data = generate_response(req, config, socket); + } catch(const bad_request_exception& ex) { + return send(bad_request(ex.what())); + } catch(const not_found_exception& ex) { + return send(not_found(ex.what())); + } catch(const server_error_exception& ex) { + return send(server_error(ex.what())); } - - // Respond to GET request - http::response res{ - std::piecewise_construct, - std::make_tuple(std::move(body)), - std::make_tuple(http::status::ok, req.version())}; + + http::response res{http::status::ok, req.version()}; res.set(http::field::server, VersionString); - res.set(http::field::content_type, mime_type(path)); - res.content_length(size); + res.set(http::field::content_type, mime_type(extend_index_html(std::string(req.target())))); + res.content_length(res_data.size()); res.keep_alive(req.keep_alive()); + if (req.method() != http::verb::head) + res.body() = res_data; return send(std::move(res)); } diff --git a/https.cpp b/https.cpp index 0e45272..9be69a8 100644 --- a/https.cpp +++ b/https.cpp @@ -3,6 +3,7 @@ #include "config.h" #include "file.h" #include "server.h" +#include "response.h" #include @@ -74,33 +75,6 @@ mime_type(beast::string_view path) return "application/text"; } -// Append an HTTP rel-path to a local filesystem path. -// The returned path is normalized for the platform. -std::string -path_cat( - beast::string_view base, - beast::string_view path) -{ - if(base.empty()) - return std::string(path); - std::string result(base); -#ifdef BOOST_MSVC - char constexpr path_separator = '\\'; - if(result.back() == path_separator) - result.resize(result.size() - 1); - result.append(path.data(), path.size()); - for(auto& c : result) - if(c == '/') - c = path_separator; -#else - char constexpr path_separator = '/'; - if(result.back() == path_separator) - result.resize(result.size() - 1); - result.append(path.data(), path.size()); -#endif - return result; -} - // This function produces an HTTP response for the given // request. The type of the response object depends on the // contents of the request, so the interface requires the @@ -154,60 +128,24 @@ handle_request( return res; }; - // Make sure we can handle the method - if( req.method() != http::verb::get && - req.method() != http::verb::head) - return send(bad_request("Unknown HTTP-method")); - - // Request path must be absolute and not contain "..". - if( req.target().empty() || - req.target()[0] != '/' || - req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); - - // Build the path to the requested file - std::string host{req["host"]}; // TODO: just use string_view - std::string target{req.target()}; - std::string path = path_cat(config.DocRoot(socket, host, target), req.target()); - if(req.target().back() == '/') - path.append("index.html"); - - // Attempt to open the file - beast::error_code ec; - http::file_body::value_type body; - body.open(path.c_str(), beast::file_mode::scan, ec); - - // Handle the case where the file doesn't exist - if(ec == beast::errc::no_such_file_or_directory) - return send(not_found(req.target())); - - // Handle an unknown error - if(ec) - return send(server_error(ec.message())); - - // Cache the size since we need it after the move - auto const size = body.size(); - - // Respond to HEAD request - if(req.method() == http::verb::head) - { - http::response res{http::status::ok, req.version()}; - res.set(http::field::server, VersionString); - res.set(http::field::content_type, mime_type(path)); - res.content_length(size); - res.keep_alive(req.keep_alive()); - return send(std::move(res)); + std::string res_data; + try { + res_data = generate_response(req, config, socket); + } catch(const bad_request_exception& ex) { + return send(bad_request(ex.what())); + } catch(const not_found_exception& ex) { + return send(not_found(ex.what())); + } catch(const server_error_exception& ex) { + return send(server_error(ex.what())); } - - // Respond to GET request - http::response res{ - std::piecewise_construct, - std::make_tuple(std::move(body)), - std::make_tuple(http::status::ok, req.version())}; + + http::response res{http::status::ok, req.version()}; res.set(http::field::server, VersionString); - res.set(http::field::content_type, mime_type(path)); - res.content_length(size); + res.set(http::field::content_type, mime_type(extend_index_html(std::string(req.target())))); + res.content_length(res_data.size()); res.keep_alive(req.keep_alive()); + if (req.method() != http::verb::head) + res.body() = res_data; return send(std::move(res)); } diff --git a/response.cpp b/response.cpp new file mode 100644 index 0000000..78368f6 --- /dev/null +++ b/response.cpp @@ -0,0 +1,92 @@ +#include "response.h" +#include "file.h" + +namespace { + +// Append an HTTP rel-path to a local filesystem path. +// The returned path is normalized for the platform. +std::string +path_cat( + beast::string_view base, + beast::string_view path) +{ + if(base.empty()) + return std::string(path); + std::string result(base); +#ifdef BOOST_MSVC + char constexpr path_separator = '\\'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); + for(auto& c : result) + if(c == '/') + c = path_separator; +#else + char constexpr path_separator = '/'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); +#endif + return result; +} + +} + +http_exception::http_exception(std::string message): m_message(message) +{ +} + +const char* http_exception::what() const noexcept +{ + return m_message.data(); +} + +bad_request_exception::bad_request_exception(std::string message): http_exception(message) +{ +} + +not_found_exception::not_found_exception(std::string message): http_exception(message) +{ +} + +server_error_exception::server_error_exception(std::string message): http_exception(message) +{ +} + +std::string extend_index_html(std::string path) +{ + if (path.size() && path.back() == '/') + path.append("index.html"); + return path; +} + +std::string generate_response(http::request& req, const Config& config, const Socket& socket) +{ + // Make sure we can handle the method + if( req.method() != http::verb::get && + req.method() != http::verb::head) + throw bad_request_exception("Unknown HTTP-method"); + + // Request path must be absolute and not contain "..". + if( req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != beast::string_view::npos) + throw bad_request_exception("Illegal request-target"); + + // Build the path to the requested file + std::string host{req["host"]}; // TODO: just use string_view + std::string target{req.target()}; + std::string path = path_cat(config.DocRoot(socket, host, target), extend_index_html(std::string(req.target()))); + + std::string result; + try { + result = File::getFile(path); + } catch (const std::runtime_error& ex) { + throw not_found_exception(std::string(req.target())); + } catch (const std::exception& ex) { + throw server_error_exception(ex.what()); + } + + return result; +} + diff --git a/response.h b/response.h new file mode 100644 index 0000000..a093320 --- /dev/null +++ b/response.h @@ -0,0 +1,40 @@ +#pragma once + +#include "config.h" + +#include + +#include +#include + +namespace beast = boost::beast; // from +namespace http = beast::http; // from + +class http_exception: public std::exception +{ + std::string m_message; +public: + http_exception(std::string message); + virtual const char* what() const noexcept; +}; + +class bad_request_exception: public http_exception +{ +public: + bad_request_exception(std::string message); +}; + +class not_found_exception: public http_exception +{ +public: + not_found_exception(std::string message); +}; + +class server_error_exception: public http_exception +{ +public: + server_error_exception(std::string message); +}; + +std::string extend_index_html(std::string path); +std::string generate_response(http::request& req, const Config& config, const Socket& socket); -- cgit v1.2.3