]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/boost/libs/beast/example/http/server/flex/http_server_flex.cpp
import new upstream nautilus stable release 14.2.8
[ceph.git] / ceph / src / boost / libs / beast / example / http / server / flex / http_server_flex.cpp
index f13f7a17061bedc29a1b6f659c17c72447ed09dc..dd4b4ccd0d93c786b9d93e8568ae87d2aea70edc 100644 (file)
@@ -1,5 +1,5 @@
 //
-// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
+// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
 //
 // Distributed under the Boost Software License, Version 1.0. (See accompanying
 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 //
 //------------------------------------------------------------------------------
 
-#include "example/common/detect_ssl.hpp"
 #include "example/common/server_certificate.hpp"
 
 #include <boost/beast/core.hpp>
 #include <boost/beast/http.hpp>
+#include <boost/beast/ssl.hpp>
 #include <boost/beast/version.hpp>
-#include <boost/asio/bind_executor.hpp>
-#include <boost/asio/ip/tcp.hpp>
-#include <boost/asio/ssl/stream.hpp>
+#include <boost/asio/dispatch.hpp>
 #include <boost/asio/strand.hpp>
 #include <boost/config.hpp>
 #include <algorithm>
 #include <string>
 #include <thread>
 
-using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>
+namespace beast = boost::beast;         // from <boost/beast.hpp>
+namespace http = beast::http;           // from <boost/beast/http.hpp>
+namespace net = boost::asio;            // from <boost/asio.hpp>
 namespace ssl = boost::asio::ssl;       // from <boost/asio/ssl.hpp>
-namespace http = boost::beast::http;    // from <boost/beast/http.hpp>
+using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>
 
 // Return a reasonable mime type based on the extension of a file.
-boost::beast::string_view
-mime_type(boost::beast::string_view path)
+beast::string_view
+mime_type(beast::string_view path)
 {
-    using boost::beast::iequals;
+    using beast::iequals;
     auto const ext = [&path]
     {
         auto const pos = path.rfind(".");
-        if(pos == boost::beast::string_view::npos)
-            return boost::beast::string_view{};
+        if(pos == beast::string_view::npos)
+            return beast::string_view{};
         return path.substr(pos);
     }();
     if(iequals(ext, ".htm"))  return "text/html";
@@ -76,13 +76,13 @@ mime_type(boost::beast::string_view path)
 // The returned path is normalized for the platform.
 std::string
 path_cat(
-    boost::beast::string_view base,
-    boost::beast::string_view path)
+    beast::string_view base,
+    beast::string_view path)
 {
     if(base.empty())
-        return path.to_string();
-    std::string result = base.to_string();
-#if BOOST_MSVC
+        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);
@@ -108,45 +108,45 @@ template<
     class Send>
 void
 handle_request(
-    boost::beast::string_view doc_root,
+    beast::string_view doc_root,
     http::request<Body, http::basic_fields<Allocator>>&& req,
     Send&& send)
 {
     // Returns a bad request response
     auto const bad_request =
-    [&req](boost::beast::string_view why)
+    [&req](beast::string_view why)
     {
         http::response<http::string_body> res{http::status::bad_request, req.version()};
         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
         res.set(http::field::content_type, "text/html");
         res.keep_alive(req.keep_alive());
-        res.body() = why.to_string();
+        res.body() = std::string(why);
         res.prepare_payload();
         return res;
     };
 
     // Returns a not found response
     auto const not_found =
-    [&req](boost::beast::string_view target)
+    [&req](beast::string_view target)
     {
         http::response<http::string_body> res{http::status::not_found, req.version()};
         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
         res.set(http::field::content_type, "text/html");
         res.keep_alive(req.keep_alive());
-        res.body() = "The resource '" + target.to_string() + "' was not found.";
+        res.body() = "The resource '" + std::string(target) + "' was not found.";
         res.prepare_payload();
         return res;
     };
 
     // Returns a server error response
     auto const server_error =
-    [&req](boost::beast::string_view what)
+    [&req](beast::string_view what)
     {
         http::response<http::string_body> res{http::status::internal_server_error, req.version()};
         res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
         res.set(http::field::content_type, "text/html");
         res.keep_alive(req.keep_alive());
-        res.body() = "An error occurred: '" + what.to_string() + "'";
+        res.body() = "An error occurred: '" + std::string(what) + "'";
         res.prepare_payload();
         return res;
     };
@@ -159,7 +159,7 @@ handle_request(
     // Request path must be absolute and not contain "..".
     if( req.target().empty() ||
         req.target()[0] != '/' ||
-        req.target().find("..") != boost::beast::string_view::npos)
+        req.target().find("..") != beast::string_view::npos)
         return send(bad_request("Illegal request-target"));
 
     // Build the path to the requested file
@@ -168,12 +168,12 @@ handle_request(
         path.append("index.html");
 
     // Attempt to open the file
-    boost::beast::error_code ec;
+    beast::error_code ec;
     http::file_body::value_type body;
-    body.open(path.c_str(), boost::beast::file_mode::scan, ec);
+    body.open(path.c_str(), beast::file_mode::scan, ec);
 
     // Handle the case where the file doesn't exist
-    if(ec == boost::system::errc::no_such_file_or_directory)
+    if(ec == beast::errc::no_such_file_or_directory)
         return send(not_found(req.target()));
 
     // Handle an unknown error
@@ -210,8 +210,28 @@ handle_request(
 
 // Report a failure
 void
-fail(boost::system::error_code ec, char const* what)
+fail(beast::error_code ec, char const* what)
 {
+    // ssl::error::stream_truncated, also known as an SSL "short read",
+    // indicates the peer closed the connection without performing the
+    // required closing handshake (for example, Google does this to
+    // improve performance). Generally this can be a security issue,
+    // but if your communication protocol is self-terminated (as
+    // it is with both HTTP and WebSocket) then you may simply
+    // ignore the lack of close_notify.
+    //
+    // https://github.com/boostorg/beast/issues/38
+    //
+    // https://security.stackexchange.com/questions/91435/how-to-handle-a-malicious-ssl-tls-shutdown
+    //
+    // When a short read would cut off the end of an HTTP message,
+    // Beast returns the error beast::http::error::partial_message.
+    // Therefore, if we see a short read here, it has occurred
+    // after the message has been completed, so it is safe to ignore it.
+
+    if(ec == net::ssl::error::stream_truncated)
+        return;
+
     std::cerr << what << ": " << ec.message() << "\n";
 }
 
@@ -259,37 +279,28 @@ class session
             http::async_write(
                 self_.derived().stream(),
                 *sp,
-                boost::asio::bind_executor(
-                    self_.strand_,
-                    std::bind(
-                        &session::on_write,
-                        self_.derived().shared_from_this(),
-                        std::placeholders::_1,
-                        std::placeholders::_2,
-                        sp->need_eof())));
+                beast::bind_front_handler(
+                    &session::on_write,
+                    self_.derived().shared_from_this(),
+                    sp->need_eof()));
         }
     };
 
-    std::string const& doc_root_;
+    std::shared_ptr<std::string const> doc_root_;
     http::request<http::string_body> req_;
     std::shared_ptr<void> res_;
     send_lambda lambda_;
 
 protected:
-    boost::asio::strand<
-        boost::asio::io_context::executor_type> strand_;
-    boost::beast::flat_buffer buffer_;
+    beast::flat_buffer buffer_;
 
 public:
     // Take ownership of the buffer
-    explicit
     session(
-        boost::asio::io_context& ioc,
-        boost::beast::flat_buffer buffer,
-        std::string const& doc_root)
+        beast::flat_buffer buffer,
+        std::shared_ptr<std::string const> const& doc_root)
         : doc_root_(doc_root)
         , lambda_(*this)
-        , strand_(ioc.get_executor())
         , buffer_(std::move(buffer))
     {
     }
@@ -297,23 +308,23 @@ public:
     void
     do_read()
     {
+        // Set the timeout.
+        beast::get_lowest_layer(
+            derived().stream()).expires_after(std::chrono::seconds(30));
+
         // Read a request
         http::async_read(
             derived().stream(),
             buffer_,
             req_,
-            boost::asio::bind_executor(
-                strand_,
-                std::bind(
-                    &session::on_read,
-                    derived().shared_from_this(),
-                    std::placeholders::_1,
-                    std::placeholders::_2)));
+            beast::bind_front_handler(
+                &session::on_read,
+                derived().shared_from_this()));
     }
 
     void
     on_read(
-        boost::system::error_code ec,
+        beast::error_code ec,
         std::size_t bytes_transferred)
     {
         boost::ignore_unused(bytes_transferred);
@@ -326,14 +337,14 @@ public:
             return fail(ec, "read");
 
         // Send the response
-        handle_request(doc_root_, std::move(req_), lambda_);
+        handle_request(*doc_root_, std::move(req_), lambda_);
     }
 
     void
     on_write(
-        boost::system::error_code ec,
-        std::size_t bytes_transferred,
-        bool close)
+        bool close,
+        beast::error_code ec,
+        std::size_t bytes_transferred)
     {
         boost::ignore_unused(bytes_transferred);
 
@@ -360,45 +371,48 @@ class plain_session
     : public session<plain_session>
     , public std::enable_shared_from_this<plain_session>
 {
-    tcp::socket socket_;
-    boost::asio::strand<
-        boost::asio::io_context::executor_type> strand_;
+    beast::tcp_stream stream_;
 
 public:
     // Create the session
     plain_session(
-        tcp::socket socket,
-        boost::beast::flat_buffer buffer,
-        std::string const& doc_root)
+        tcp::socket&& socket,
+        beast::flat_buffer buffer,
+        std::shared_ptr<std::string const> const& doc_root)
         : session<plain_session>(
-            socket.get_executor().context(),
             std::move(buffer),
             doc_root)
-        , socket_(std::move(socket))
-        , strand_(socket_.get_executor())
+        , stream_(std::move(socket))
     {
     }
 
     // Called by the base class
-    tcp::socket&
+    beast::tcp_stream&
     stream()
     {
-        return socket_;
+        return stream_;
     }
 
     // Start the asynchronous operation
     void
     run()
     {
-        do_read();
+        // We need to be executing within a strand to perform async operations
+        // on the I/O objects in this session. Although not strictly necessary
+        // for single-threaded contexts, this example code is written to be
+        // thread-safe by default.
+        net::dispatch(stream_.get_executor(),
+                      beast::bind_front_handler(
+                          &session::do_read,
+                          shared_from_this()));
     }
 
     void
     do_eof()
     {
         // Send a TCP shutdown
-        boost::system::error_code ec;
-        socket_.shutdown(tcp::socket::shutdown_send, ec);
+        beast::error_code ec;
+        stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
 
         // At this point the connection is closed gracefully
     }
@@ -409,30 +423,24 @@ class ssl_session
     : public session<ssl_session>
     , public std::enable_shared_from_this<ssl_session>
 {
-    tcp::socket socket_;
-    ssl::stream<tcp::socket&> stream_;
-    boost::asio::strand<
-        boost::asio::io_context::executor_type> strand_;
+    beast::ssl_stream<beast::tcp_stream> stream_;
 
 public:
     // Create the session
     ssl_session(
-        tcp::socket socket,
+        tcp::socket&& socket,
         ssl::context& ctx,
-        boost::beast::flat_buffer buffer,
-        std::string const& doc_root)
+        beast::flat_buffer buffer,
+        std::shared_ptr<std::string const> const& doc_root)
         : session<ssl_session>(
-            socket.get_executor().context(),
             std::move(buffer),
             doc_root)
-        , socket_(std::move(socket))
-        , stream_(socket_, ctx)
-        , strand_(stream_.get_executor())
+        , stream_(std::move(socket), ctx)
     {
     }
 
     // Called by the base class
-    ssl::stream<tcp::socket&>&
+    beast::ssl_stream<beast::tcp_stream>&
     stream()
     {
         return stream_;
@@ -442,22 +450,28 @@ public:
     void
     run()
     {
-        // Perform the SSL handshake
-        // Note, this is the buffered version of the handshake.
-        stream_.async_handshake(
-            ssl::stream_base::server,
-            buffer_.data(),
-            boost::asio::bind_executor(
-                strand_,
-                std::bind(
+        auto self = shared_from_this();
+        // We need to be executing within a strand to perform async operations
+        // on the I/O objects in this session.
+        net::dispatch(stream_.get_executor(), [self]() {
+            // Set the timeout.
+            beast::get_lowest_layer(self->stream_).expires_after(
+                std::chrono::seconds(30));
+
+            // Perform the SSL handshake
+            // Note, this is the buffered version of the handshake.
+            self->stream_.async_handshake(
+                ssl::stream_base::server,
+                self->buffer_.data(),
+                beast::bind_front_handler(
                     &ssl_session::on_handshake,
-                    shared_from_this(),
-                    std::placeholders::_1,
-                    std::placeholders::_2)));
+                    self));
+        });
     }
+
     void
     on_handshake(
-        boost::system::error_code ec,
+        beast::error_code ec,
         std::size_t bytes_used)
     {
         if(ec)
@@ -472,18 +486,18 @@ public:
     void
     do_eof()
     {
+        // Set the timeout.
+        beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
+
         // Perform the SSL shutdown
         stream_.async_shutdown(
-            boost::asio::bind_executor(
-                strand_,
-                std::bind(
-                    &ssl_session::on_shutdown,
-                    shared_from_this(),
-                    std::placeholders::_1)));
+            beast::bind_front_handler(
+                &ssl_session::on_shutdown,
+                shared_from_this()));
     }
 
     void
-    on_shutdown(boost::system::error_code ec)
+    on_shutdown(beast::error_code ec)
     {
         if(ec)
             return fail(ec, "shutdown");
@@ -497,22 +511,18 @@ public:
 // Detects SSL handshakes
 class detect_session : public std::enable_shared_from_this<detect_session>
 {
-    tcp::socket socket_;
+    beast::tcp_stream stream_;
     ssl::context& ctx_;
-    boost::asio::strand<
-        boost::asio::io_context::executor_type> strand_;
-    std::string const& doc_root_;
-    boost::beast::flat_buffer buffer_;
+    std::shared_ptr<std::string const> doc_root_;
+    beast::flat_buffer buffer_;
 
 public:
-    explicit
     detect_session(
-        tcp::socket socket,
+        tcp::socket&& socket,
         ssl::context& ctx,
-        std::string const& doc_root)
-        : socket_(std::move(socket))
+        std::shared_ptr<std::string const> const& doc_root)
+        : stream_(std::move(socket))
         , ctx_(ctx)
-        , strand_(socket_.get_executor())
         , doc_root_(doc_root)
     {
     }
@@ -521,21 +531,20 @@ public:
     void
     run()
     {
+        // Set the timeout.
+        beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
+
+        // Detect a TLS handshake
         async_detect_ssl(
-            socket_,
+            stream_,
             buffer_,
-            boost::asio::bind_executor(
-                strand_,
-                std::bind(
-                    &detect_session::on_detect,
-                    shared_from_this(),
-                    std::placeholders::_1,
-                    std::placeholders::_2)));
-
+            beast::bind_front_handler(
+                &detect_session::on_detect,
+                shared_from_this()));
     }
 
     void
-    on_detect(boost::system::error_code ec, boost::tribool result)
+    on_detect(beast::error_code ec, bool result)
     {
         if(ec)
             return fail(ec, "detect");
@@ -544,7 +553,7 @@ public:
         {
             // Launch SSL session
             std::make_shared<ssl_session>(
-                std::move(socket_),
+                stream_.release_socket(),
                 ctx_,
                 std::move(buffer_),
                 doc_root_)->run();
@@ -553,7 +562,7 @@ public:
 
         // Launch plain session
         std::make_shared<plain_session>(
-            std::move(socket_),
+            stream_.release_socket(),
             std::move(buffer_),
             doc_root_)->run();
     }
@@ -562,26 +571,23 @@ public:
 // Accepts incoming connections and launches the sessions
 class listener : public std::enable_shared_from_this<listener>
 {
+    net::io_context& ioc_;
     ssl::context& ctx_;
-    boost::asio::strand<
-        boost::asio::io_context::executor_type> strand_;
     tcp::acceptor acceptor_;
-    tcp::socket socket_;
-    std::string const& doc_root_;
+    std::shared_ptr<std::string const> doc_root_;
 
 public:
     listener(
-        boost::asio::io_context& ioc,
+        net::io_context& ioc,
         ssl::context& ctx,
         tcp::endpoint endpoint,
-        std::string const& doc_root)
-        : ctx_(ctx)
-        , strand_(ioc.get_executor())
-        , acceptor_(ioc)
-        , socket_(ioc)
+        std::shared_ptr<std::string const> const& doc_root)
+        : ioc_(ioc)
+        , ctx_(ctx)
+        , acceptor_(net::make_strand(ioc))
         , doc_root_(doc_root)
     {
-        boost::system::error_code ec;
+        beast::error_code ec;
 
         // Open the acceptor
         acceptor_.open(endpoint.protocol(), ec);
@@ -592,7 +598,7 @@ public:
         }
 
         // Allow address reuse
-        acceptor_.set_option(boost::asio::socket_base::reuse_address(true));
+        acceptor_.set_option(net::socket_base::reuse_address(true), ec);
         if(ec)
         {
             fail(ec, "set_option");
@@ -609,7 +615,7 @@ public:
 
         // Start listening for connections
         acceptor_.listen(
-            boost::asio::socket_base::max_listen_connections, ec);
+            net::socket_base::max_listen_connections, ec);
         if(ec)
         {
             fail(ec, "listen");
@@ -621,24 +627,23 @@ public:
     void
     run()
     {
-        if(! acceptor_.is_open())
-            return;
         do_accept();
     }
 
+private:
     void
     do_accept()
     {
+        // The new connection gets its own strand
         acceptor_.async_accept(
-            socket_,
-            std::bind(
+            net::make_strand(ioc_),
+            beast::bind_front_handler(
                 &listener::on_accept,
-                shared_from_this(),
-                std::placeholders::_1));
+                shared_from_this()));
     }
 
     void
-    on_accept(boost::system::error_code ec)
+    on_accept(beast::error_code ec, tcp::socket socket)
     {
         if(ec)
         {
@@ -648,7 +653,7 @@ public:
         {
             // Create the detector session and run it
             std::make_shared<detect_session>(
-                std::move(socket_),
+                std::move(socket),
                 ctx_,
                 doc_root_)->run();
         }
@@ -671,16 +676,16 @@ int main(int argc, char* argv[])
             "    http-server-flex 0.0.0.0 8080 .\n";
         return EXIT_FAILURE;
     }
-    auto const address = boost::asio::ip::make_address(argv[1]);
+    auto const address = net::ip::make_address(argv[1]);
     auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
-    std::string const doc_root = argv[3];
+    auto const doc_root = std::make_shared<std::string>(argv[3]);
     auto const threads = std::max<int>(1, std::atoi(argv[4]));
 
     // The io_context is required for all I/O
-    boost::asio::io_context ioc{threads};
+    net::io_context ioc{threads};
 
     // The SSL context is required, and holds certificates
-    ssl::context ctx{ssl::context::sslv23};
+    ssl::context ctx{ssl::context::tlsv12};
 
     // This holds the self-signed certificate used by the server
     load_server_certificate(ctx);