]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/boost/libs/beast/example/http/server/stackless-ssl/http_server_stackless_ssl.cpp
import new upstream nautilus stable release 14.2.8
[ceph.git] / ceph / src / boost / libs / beast / example / http / server / stackless-ssl / http_server_stackless_ssl.cpp
index a0fbac4e73289225e002acfb74ad23b1c14258b1..7786cbb42d0d439b08205718352015df61e14573 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 <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/coroutine.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 <thread>
 #include <vector>
 
-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";
@@ -77,13 +78,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);
@@ -109,45 +110,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;
     };
@@ -160,7 +161,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
@@ -169,12 +170,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
@@ -211,8 +212,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";
 }
 
@@ -249,25 +270,20 @@ class session
 
             // Write the response
             http::async_write(
-                self_.socket_,
+                self_.stream_,
                 *sp,
-                boost::asio::bind_executor(
-                    self_.strand_,
-                    std::bind(
-                        &session::loop,
-                        self_.shared_from_this(),
-                        std::placeholders::_1,
-                        std::placeholders::_2,
-                        sp->need_eof())));
+                std::bind(
+                    &session::loop,
+                    self_.shared_from_this(),
+                    std::placeholders::_1,
+                    std::placeholders::_2,
+                    sp->need_eof()));
         }
     };
 
-    tcp::socket socket_;
-    ssl::stream<tcp::socket&> stream_;
-    boost::asio::strand<
-        boost::asio::io_context::executor_type> strand_;
-    boost::beast::flat_buffer buffer_;
-    std::string const& doc_root_;
+    beast::ssl_stream<beast::tcp_stream> stream_;
+    beast::flat_buffer buffer_;
+    std::shared_ptr<std::string const> doc_root_;
     http::request<http::string_body> req_;
     std::shared_ptr<void> res_;
     send_lambda lambda_;
@@ -276,12 +292,10 @@ public:
     // Take ownership of the socket
     explicit
     session(
-        tcp::socket socket,
+        tcp::socket&& socket,
         ssl::context& ctx,
-        std::string const& doc_root)
-        : socket_(std::move(socket))
-        , stream_(socket_, ctx)
-        , strand_(socket_.get_executor())
+        std::shared_ptr<std::string const> const& doc_root)
+        : stream_(std::move(socket), ctx)
         , doc_root_(doc_root)
         , lambda_(*this)
     {
@@ -291,49 +305,61 @@ public:
     void
     run()
     {
-        loop({}, 0, false);
+        // 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::loop,
+                                                shared_from_this(),
+                                                beast::error_code{},
+                                                0,
+                                                false));
     }
 
-#include <boost/asio/yield.hpp>
+    #include <boost/asio/yield.hpp>
+
     void
     loop(
-        boost::system::error_code ec,
+        beast::error_code ec,
         std::size_t bytes_transferred,
         bool close)
     {
         boost::ignore_unused(bytes_transferred);
         reenter(*this)
         {
+            // Set the timeout.
+            beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
+
             // Perform the SSL handshake
             yield stream_.async_handshake(
                 ssl::stream_base::server,
-                boost::asio::bind_executor(
-                    strand_,
-                    std::bind(
-                        &session::loop,
-                        shared_from_this(),
-                        std::placeholders::_1,
-                        0,
-                        false)));
+                std::bind(
+                    &session::loop,
+                    shared_from_this(),
+                    std::placeholders::_1,
+                    0,
+                    false));
             if(ec)
                 return fail(ec, "handshake");
 
             for(;;)
             {
+                // Set the timeout.
+                beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
+
                 // Make the request empty before reading,
                 // otherwise the operation behavior is undefined.
                 req_ = {};
 
                 // Read a request
                 yield http::async_read(stream_, buffer_, req_,
-                    boost::asio::bind_executor(
-                        strand_,
-                        std::bind(
-                            &session::loop,
-                            shared_from_this(),
-                            std::placeholders::_1,
-                            std::placeholders::_2,
-                            false)));
+                    std::bind(
+                        &session::loop,
+                        shared_from_this(),
+                        std::placeholders::_1,
+                        std::placeholders::_2,
+                        false));
                 if(ec == http::error::end_of_stream)
                 {
                     // The remote host closed the connection
@@ -343,7 +369,7 @@ public:
                     return fail(ec, "read");
 
                 // Send the response
-                yield handle_request(doc_root_, std::move(req_), lambda_);
+                yield handle_request(*doc_root_, std::move(req_), lambda_);
                 if(ec)
                     return fail(ec, "write");
                 if(close)
@@ -357,23 +383,25 @@ public:
                 res_ = nullptr;
             }
 
+            // Set the timeout.
+            beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));
+
             // Perform the SSL shutdown
             yield stream_.async_shutdown(
-                boost::asio::bind_executor(
-                    strand_,
-                    std::bind(
-                        &session::loop,
-                        shared_from_this(),
-                        std::placeholders::_1,
-                        0,
-                        false)));
+                std::bind(
+                    &session::loop,
+                    shared_from_this(),
+                    std::placeholders::_1,
+                    0,
+                    false));
             if(ec)
                 return fail(ec, "shutdown");
 
             // At this point the connection is closed gracefully
         }
     }
-#include <boost/asio/unyield.hpp>
+
+    #include <boost/asio/unyield.hpp>
 };
 
 //------------------------------------------------------------------------------
@@ -383,23 +411,25 @@ class listener
     : public boost::asio::coroutine
     , public std::enable_shared_from_this<listener>
 {
+    net::io_context& ioc_;
     ssl::context& ctx_;
     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)
-        , acceptor_(ioc)
-        , socket_(ioc)
+        std::shared_ptr<std::string const> const& doc_root)
+        : ioc_(ioc)
+        , ctx_(ctx)
+        , acceptor_(net::make_strand(ioc))
+        , socket_(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);
@@ -410,7 +440,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");
@@ -427,7 +457,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");
@@ -439,14 +469,15 @@ public:
     void
     run()
     {
-        if(! acceptor_.is_open())
-            return;
         loop();
     }
 
-#include <boost/asio/yield.hpp>
+private:
+
+    #include <boost/asio/yield.hpp>
+
     void
-    loop(boost::system::error_code ec = {})
+    loop(beast::error_code ec = {})
     {
         reenter(*this)
         {
@@ -470,10 +501,14 @@ public:
                         ctx_,
                         doc_root_)->run();
                 }
+
+                // Make sure each session gets its own strand
+                socket_ = tcp::socket(net::make_strand(ioc_));
             }
         }
     }
-#include <boost/asio/unyield.hpp>
+
+    #include <boost/asio/unyield.hpp>
 };
 
 //------------------------------------------------------------------------------
@@ -489,16 +524,16 @@ int main(int argc, char* argv[])
             "    http-server-stackless-ssl 0.0.0.0 8080 . 1\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);