]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3490] Moved the HTTPS variant
authorFrancis Dupont <fdupont@isc.org>
Fri, 19 Jul 2024 17:29:33 +0000 (19:29 +0200)
committerRazvan Becheriu <razvan@isc.org>
Thu, 8 Aug 2024 19:39:03 +0000 (19:39 +0000)
src/lib/http/tests/tls_server_unittests.cc
src/lib/http/testutils/test_http_client.h

index 81f758d6075b4730c8d9ec92dbd4c993c0f1c92b..10b82641f97d49e8bd9f0dfb6c6b6d828e155cf4 100644 (file)
@@ -19,6 +19,7 @@
 #include <http/response_creator_factory.h>
 #include <http/response_json.h>
 #include <http/tests/response_test.h>
+#include <http/testutils/test_http_client.h>
 #include <http/url.h>
 #include <util/multi_threading_mgr.h>
 
@@ -386,247 +387,6 @@ public:
     }
 };
 
-
-/// @brief Entity which can connect to the HTTP server endpoint.
-class TestHttpsClient : public boost::noncopyable {
-public:
-
-    /// @brief Constructor.
-    ///
-    /// This constructor creates new socket instance. It doesn't connect. Call
-    /// connect() to connect to the server.
-    ///
-    /// @param io_service IO service to be stopped on error.
-    /// @param tls_context TLS context.
-    TestHttpsClient(const IOServicePtr& io_service, TlsContextPtr tls_context)
-        : io_service_(io_service), stream_(io_service_->getInternalIOService(),
-          tls_context->getContext()), buf_(), response_() {
-    }
-
-    /// @brief Destructor.
-    ///
-    /// Closes the underlying socket if it is open.
-    ~TestHttpsClient() {
-        close();
-    }
-
-    /// @brief Send HTTP request specified in textual format.
-    ///
-    /// @param request HTTP request in the textual format.
-    void startRequest(const std::string& request) {
-        tcp::endpoint endpoint(address::from_string(SERVER_ADDRESS),
-                               SERVER_PORT);
-        stream_.lowest_layer().async_connect(endpoint,
-        [this, request](const boost::system::error_code& ec) {
-            if (ec) {
-                // One would expect that async_connect wouldn't return
-                // EINPROGRESS error code, but simply wait for the connection
-                // to get established before the handler is invoked. It turns out,
-                // however, that on some OSes the connect handler may receive this
-                // error code which doesn't necessarily indicate a problem.
-                // Making an attempt to write and read from this socket will
-                // typically succeed. So, we ignore this error.
-                if (ec.value() != boost::asio::error::in_progress) {
-                    ADD_FAILURE() << "error occurred while connecting: "
-                                  << ec.message();
-                    io_service_->stop();
-                    return;
-                }
-            }
-            stream_.async_handshake(roleToImpl(TlsRole::CLIENT),
-            [this, request](const boost::system::error_code& ec) {
-                if (ec) {
-                    ADD_FAILURE() << "error occurred during handshake: "
-                                  << ec.message();
-                    io_service_->stop();
-                    return;
-                }
-                sendRequest(request);
-            });
-        });
-    }
-
-    /// @brief Send HTTP request.
-    ///
-    /// @param request HTTP request in the textual format.
-    void sendRequest(const std::string& request) {
-        sendPartialRequest(request);
-    }
-
-    /// @brief Send part of the HTTP request.
-    ///
-    /// @param request part of the HTTP request to be sent.
-    void sendPartialRequest(std::string request) {
-        boost::asio::async_write(stream_,
-                boost::asio::buffer(request.data(), request.size()),
-                [this, request](const boost::system::error_code& ec,
-                                std::size_t bytes_transferred) mutable {
-            if (ec) {
-                if (ec.value() == boost::asio::error::operation_aborted) {
-                    return;
-
-                } else if ((ec.value() == boost::asio::error::try_again) ||
-                           (ec.value() == boost::asio::error::would_block)) {
-                    // If we should try again make sure there is no garbage in the
-                    // bytes_transferred.
-                    bytes_transferred = 0;
-
-                } else {
-                    ADD_FAILURE() << "error occurred while connecting: "
-                                  << ec.message();
-                    io_service_->stop();
-                    return;
-                }
-            }
-
-            // Remove the part of the request which has been sent.
-            if (bytes_transferred > 0 && (request.size() <= bytes_transferred)) {
-                request.erase(0, bytes_transferred);
-            }
-
-            // Continue sending request data if there are still some data to be
-            // sent.
-            if (!request.empty()) {
-                sendPartialRequest(request);
-
-            } else {
-                // Request has been sent. Start receiving response.
-                response_.clear();
-                receivePartialResponse();
-            }
-       });
-    }
-
-    /// @brief Receive response from the server.
-    void receivePartialResponse() {
-        stream_.async_read_some(boost::asio::buffer(buf_.data(), buf_.size()),
-                                [this](const boost::system::error_code& ec,
-                                       std::size_t bytes_transferred) {
-            if (ec) {
-                // IO service stopped so simply return.
-                if (ec.value() == boost::asio::error::operation_aborted) {
-                    return;
-
-                } else if ((ec.value() == boost::asio::error::try_again) ||
-                           (ec.value() == boost::asio::error::would_block)) {
-                    // If we should try again, make sure that there is no garbage
-                    // in the bytes_transferred.
-                    bytes_transferred = 0;
-
-                } else {
-                    // Error occurred, bail...
-                    ADD_FAILURE() << "error occurred while receiving HTTP"
-                        " response from the server: " << ec.message();
-                    io_service_->stop();
-                }
-            }
-
-            if (bytes_transferred > 0) {
-                response_.insert(response_.end(), buf_.data(),
-                                 buf_.data() + bytes_transferred);
-            }
-
-            // Two consecutive new lines end the part of the response we're
-            // expecting.
-            if (response_.find("\r\n\r\n", 0) != std::string::npos) {
-                io_service_->stop();
-
-            } else {
-                receivePartialResponse();
-            }
-
-        });
-    }
-
-    /// @brief Checks if the TCP connection is still open.
-    ///
-    /// Tests the TCP connection by trying to read from the socket.
-    /// Unfortunately expected failure depends on a race between the read
-    /// and the other side close so to check if the connection is closed
-    /// please use @c isConnectionClosed instead.
-    ///
-    /// @return true if the TCP connection is open.
-    bool isConnectionAlive() {
-        // Remember the current non blocking setting.
-        const bool non_blocking_orig = stream_.lowest_layer().non_blocking();
-        // Set the socket to non blocking mode. We're going to test if the socket
-        // returns would_block status on the attempt to read from it.
-        stream_.lowest_layer().non_blocking(true);
-
-        // We need to provide a buffer for a call to read.
-        char data[2];
-        boost::system::error_code ec;
-        boost::asio::read(stream_, boost::asio::buffer(data, sizeof(data)), ec);
-
-        // Revert the original non_blocking flag on the socket.
-        stream_.lowest_layer().non_blocking(non_blocking_orig);
-
-        // If the connection is alive we'd typically get would_block status code.
-        // If there are any data that haven't been read we may also get success
-        // status. We're guessing that try_again may also be returned by some
-        // implementations in some situations. Any other error code indicates a
-        // problem with the connection so we assume that the connection has been
-        // closed.
-        return (!ec || (ec.value() == boost::asio::error::try_again) ||
-                (ec.value() == boost::asio::error::would_block));
-    }
-
-    /// @brief Checks if the TCP connection is already closed.
-    ///
-    /// Tests the TCP connection by trying to read from the socket.
-    /// The read can block so this must be used to check if a connection
-    /// is alive so to check if the connection is alive please always
-    /// use @c isConnectionAlive.
-    ///
-    /// @return true if the TCP connection is closed.
-    bool isConnectionClosed() {
-        // Remember the current non blocking setting.
-        const bool non_blocking_orig = stream_.lowest_layer().non_blocking();
-        // Set the socket to blocking mode. We're going to test if the socket
-        // returns eof status on the attempt to read from it.
-        stream_.lowest_layer().non_blocking(false);
-
-        // We need to provide a buffer for a call to read.
-        char data[2];
-        boost::system::error_code ec;
-        boost::asio::read(stream_, boost::asio::buffer(data, sizeof(data)), ec);
-
-        // Revert the original non_blocking flag on the socket.
-        stream_.lowest_layer().non_blocking(non_blocking_orig);
-
-        // If the connection is closed we'd typically get eof or
-        // stream_truncated status code.
-        return ((ec.value() == boost::asio::error::eof) ||
-                (ec.value() == STREAM_TRUNCATED));
-    }
-
-    /// @brief Close connection.
-    void close() {
-        stream_.lowest_layer().close();
-    }
-
-    std::string getResponse() const {
-        return (response_);
-    }
-
-private:
-
-    /// @brief Holds pointer to the IO service.
-    isc::asiolink::IOServicePtr io_service_;
-
-    /// @brief A socket used for the connection.
-    TlsStreamImpl stream_;
-
-    /// @brief Buffer into which response is written.
-    std::array<char, 8192> buf_;
-
-    /// @brief Response in the textual format.
-    std::string response_;
-};
-
-/// @brief Pointer to the TestHttpsClient.
-typedef boost::shared_ptr<TestHttpsClient> TestHttpsClientPtr;
-
 /// @brief Test fixture class for @ref HttpListener.
 class HttpsListenerTest : public ::testing::Test {
 public:
index 64db858db3b34961f4e58c6bd99fcb3a1fecb9e0..eaf2d60e77fe4681677264ebfe1a846bd4c5f8dd 100644 (file)
 using namespace boost::asio::ip;
 using namespace isc::asiolink;
 
+/// @brief Common base for test HTTP/HTTPS clients.
+class BaseTestHttpClient : public boost::noncopyable {
+public:
+
+    /// @brief Destructor.
+    virtual ~BaseTestHttpClient() = default;
+
+    /// @brief Send HTTP request specified in textual format.
+    ///
+    /// @param request HTTP request in the textual format.
+    virtual void startRequest(const std::string& request) = 0;
+
+    /// @brief Send HTTP request.
+    ///
+    /// @param request HTTP request in the textual format.
+    virtual void sendRequest(const std::string& request) = 0;
+
+    /// @brief Send part of the HTTP request.
+    ///
+    /// @param request part of the HTTP request to be sent.
+    virtual void sendPartialRequest(std::string request) = 0;
+
+    /// @brief Receive response from the server.
+    virtual void receivePartialResponse() = 0;
+
+    /// @brief Checks if the TCP connection is still open.
+    ///
+    /// Tests the TCP connection by trying to read from the socket.
+    /// Unfortunately expected failure depends on a race between the read
+    /// and the other side close so to check if the connection is closed
+    /// please use @c isConnectionClosed instead.
+    ///
+    /// @return true if the TCP connection is open.
+    virtual bool isConnectionAlive() = 0;
+
+    /// @brief Checks if the TCP connection is already closed.
+    ///
+    /// Tests the TCP connection by trying to read from the socket.
+    /// The read can block so this must be used to check if a connection
+    /// is alive so to check if the connection is alive please always
+    /// use @c isConnectionAlive.
+    ///
+    /// @return true if the TCP connection is closed.
+    virtual bool isConnectionClosed() = 0;
+
+    /// @brief Close connection.
+    virtual void close() = 0;
+
+    /// @brief Returns the HTTP response string.
+    ///
+    /// @return string containing the response.
+    virtual std::string getResponse() const = 0;
+
+    /// @brief Returns true if the receive completed without error.
+    ///
+    /// @return True if the receive completed successfully, false
+    /// otherwise.
+    virtual bool receiveDone() const = 0;
+};
+
 /// @brief Entity which can connect to the HTTP server endpoint.
-class TestHttpClient : public boost::noncopyable {
+class TestHttpClient : public BaseTestHttpClient {
 public:
 
     /// @brief Constructor.
@@ -42,14 +102,14 @@ public:
     /// @brief Destructor.
     ///
     /// Closes the underlying socket if it is open.
-    ~TestHttpClient() {
+    virtual ~TestHttpClient() {
         close();
     }
 
     /// @brief Send HTTP request specified in textual format.
     ///
     /// @param request HTTP request in the textual format.
-    void startRequest(const std::string& request) {
+    virtual void startRequest(const std::string& request) {
         tcp::endpoint endpoint(address::from_string(server_address_), server_port_);
         socket_.async_connect(endpoint,
         [this, request](const boost::system::error_code& ec) {
@@ -76,14 +136,14 @@ public:
     /// @brief Send HTTP request.
     ///
     /// @param request HTTP request in the textual format.
-    void sendRequest(const std::string& request) {
+    virtual void sendRequest(const std::string& request) {
         sendPartialRequest(request);
     }
 
     /// @brief Send part of the HTTP request.
     ///
     /// @param request part of the HTTP request to be sent.
-    void sendPartialRequest(std::string request) {
+    virtual void sendPartialRequest(std::string request) {
         socket_.async_send(boost::asio::buffer(request.data(), request.size()),
                            [this, request](const boost::system::error_code& ec,
                                            std::size_t bytes_transferred) mutable {
@@ -124,7 +184,7 @@ public:
     }
 
     /// @brief Receive response from the server.
-    void receivePartialResponse() {
+    virtual void receivePartialResponse() {
         socket_.async_read_some(boost::asio::buffer(buf_.data(), buf_.size()),
                                 [this](const boost::system::error_code& ec,
                                        std::size_t bytes_transferred) {
@@ -171,7 +231,7 @@ public:
     /// please use @c isConnectionClosed instead.
     ///
     /// @return true if the TCP connection is open.
-    bool isConnectionAlive() {
+    virtual bool isConnectionAlive() {
         // Remember the current non blocking setting.
         const bool non_blocking_orig = socket_.non_blocking();
         // Set the socket to non blocking mode. We're going to test if the socket
@@ -204,7 +264,7 @@ public:
     /// use @c isConnectionAlive.
     ///
     /// @return true if the TCP connection is closed.
-    bool isConnectionClosed() {
+    virtual bool isConnectionClosed() {
         // Remember the current non blocking setting.
         const bool non_blocking_orig = socket_.non_blocking();
         // Set the socket to blocking mode. We're going to test if the socket
@@ -224,14 +284,14 @@ public:
     }
 
     /// @brief Close connection.
-    void close() {
+    virtual void close() {
         socket_.close();
     }
 
     /// @brief Returns the HTTP response string.
     ///
     /// @return string containing the response.
-    std::string getResponse() const {
+    virtual std::string getResponse() const {
         return (response_);
     }
 
@@ -239,7 +299,7 @@ public:
     ///
     /// @return True if the receive completed successfully, false
     /// otherwise.
-    bool receiveDone() {
+    virtual bool receiveDone() const {
         return (receive_done_);
     }
 
@@ -270,4 +330,268 @@ private:
 /// @brief Pointer to the TestHttpClient.
 typedef boost::shared_ptr<TestHttpClient> TestHttpClientPtr;
 
+/// @brief Entity which can connect to the HTTPS server endpoint.
+class TestHttpsClient : public boost::noncopyable {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// This constructor creates new socket instance. It doesn't connect. Call
+    /// connect() to connect to the server.
+    ///
+    /// @param io_service IO service to be stopped on error.
+    /// @param tls_context TLS context.
+    /// @param server_address string containing the IP address of the server.
+    /// @param port port number of the server.
+    TestHttpsClient(const IOServicePtr& io_service, TlsContextPtr tls_context,
+                    const std::string& server_address = "127.0.0.1",
+                    uint16_t port = 18123)
+        : io_service_(io_service), stream_(io_service_->getInternalIOService(),
+          tls_context->getContext()), buf_(), response_(),
+          server_address_(server_address), server_port_(port),
+          receive_done_(false) {
+    }
+
+    /// @brief Destructor.
+    ///
+    /// Closes the underlying socket if it is open.
+    virtual ~TestHttpsClient() {
+        close();
+    }
+
+    /// @brief Send HTTP request specified in textual format.
+    ///
+    /// @param request HTTP request in the textual format.
+    virtual void startRequest(const std::string& request) {
+        tcp::endpoint endpoint(address::from_string(server_address_),
+                               server_port_);
+        stream_.lowest_layer().async_connect(endpoint,
+        [this, request](const boost::system::error_code& ec) {
+            receive_done_ = false;
+            if (ec) {
+                // One would expect that async_connect wouldn't return
+                // EINPROGRESS error code, but simply wait for the connection
+                // to get established before the handler is invoked. It turns out,
+                // however, that on some OSes the connect handler may receive this
+                // error code which doesn't necessarily indicate a problem.
+                // Making an attempt to write and read from this socket will
+                // typically succeed. So, we ignore this error.
+                if (ec.value() != boost::asio::error::in_progress) {
+                    ADD_FAILURE() << "error occurred while connecting: "
+                                  << ec.message();
+                    io_service_->stop();
+                    return;
+                }
+            }
+            stream_.async_handshake(roleToImpl(TlsRole::CLIENT),
+            [this, request](const boost::system::error_code& ec) {
+                if (ec) {
+                    ADD_FAILURE() << "error occurred during handshake: "
+                                  << ec.message();
+                    io_service_->stop();
+                    return;
+                }
+                sendRequest(request);
+            });
+        });
+    }
+
+    /// @brief Send HTTP request.
+    ///
+    /// @param request HTTP request in the textual format.
+    virtual void sendRequest(const std::string& request) {
+        sendPartialRequest(request);
+    }
+
+    /// @brief Send part of the HTTP request.
+    ///
+    /// @param request part of the HTTP request to be sent.
+    virtual void sendPartialRequest(std::string request) {
+        boost::asio::async_write(stream_,
+                boost::asio::buffer(request.data(), request.size()),
+                [this, request](const boost::system::error_code& ec,
+                                std::size_t bytes_transferred) mutable {
+            if (ec) {
+                if (ec.value() == boost::asio::error::operation_aborted) {
+                    return;
+
+                } else if ((ec.value() == boost::asio::error::try_again) ||
+                           (ec.value() == boost::asio::error::would_block)) {
+                    // If we should try again make sure there is no garbage in the
+                    // bytes_transferred.
+                    bytes_transferred = 0;
+
+                } else {
+                    ADD_FAILURE() << "error occurred while connecting: "
+                                  << ec.message();
+                    io_service_->stop();
+                    return;
+                }
+            }
+
+            // Remove the part of the request which has been sent.
+            if (bytes_transferred > 0 && (request.size() <= bytes_transferred)) {
+                request.erase(0, bytes_transferred);
+            }
+
+            // Continue sending request data if there are still some data to be
+            // sent.
+            if (!request.empty()) {
+                sendPartialRequest(request);
+
+            } else {
+                // Request has been sent. Start receiving response.
+                response_.clear();
+                receivePartialResponse();
+            }
+       });
+    }
+
+    /// @brief Receive response from the server.
+    virtual void receivePartialResponse() {
+        stream_.async_read_some(boost::asio::buffer(buf_.data(), buf_.size()),
+                                [this](const boost::system::error_code& ec,
+                                       std::size_t bytes_transferred) {
+            if (ec) {
+                // IO service stopped so simply return.
+                if (ec.value() == boost::asio::error::operation_aborted) {
+                    return;
+
+                } else if ((ec.value() == boost::asio::error::try_again) ||
+                           (ec.value() == boost::asio::error::would_block)) {
+                    // If we should try again, make sure that there is no garbage
+                    // in the bytes_transferred.
+                    bytes_transferred = 0;
+
+                } else {
+                    // Error occurred, bail...
+                    ADD_FAILURE() << "error occurred while receiving HTTP"
+                        " response from the server: " << ec.message();
+                    io_service_->stop();
+                }
+            }
+
+            if (bytes_transferred > 0) {
+                response_.insert(response_.end(), buf_.data(),
+                                 buf_.data() + bytes_transferred);
+            }
+
+            // Two consecutive new lines end the part of the response we're
+            // expecting.
+            if (response_.find("\r\n\r\n", 0) != std::string::npos) {
+                receive_done_ = true;
+                io_service_->stop();
+            } else {
+                receivePartialResponse();
+            }
+
+        });
+    }
+
+    /// @brief Checks if the TCP connection is still open.
+    ///
+    /// Tests the TCP connection by trying to read from the socket.
+    /// Unfortunately expected failure depends on a race between the read
+    /// and the other side close so to check if the connection is closed
+    /// please use @c isConnectionClosed instead.
+    ///
+    /// @return true if the TCP connection is open.
+    virtual bool isConnectionAlive() {
+        // Remember the current non blocking setting.
+        const bool non_blocking_orig = stream_.lowest_layer().non_blocking();
+        // Set the socket to non blocking mode. We're going to test if the socket
+        // returns would_block status on the attempt to read from it.
+        stream_.lowest_layer().non_blocking(true);
+
+        // We need to provide a buffer for a call to read.
+        char data[2];
+        boost::system::error_code ec;
+        boost::asio::read(stream_, boost::asio::buffer(data, sizeof(data)), ec);
+
+        // Revert the original non_blocking flag on the socket.
+        stream_.lowest_layer().non_blocking(non_blocking_orig);
+
+        // If the connection is alive we'd typically get would_block status code.
+        // If there are any data that haven't been read we may also get success
+        // status. We're guessing that try_again may also be returned by some
+        // implementations in some situations. Any other error code indicates a
+        // problem with the connection so we assume that the connection has been
+        // closed.
+        return (!ec || (ec.value() == boost::asio::error::try_again) ||
+                (ec.value() == boost::asio::error::would_block));
+    }
+
+    /// @brief Checks if the TCP connection is already closed.
+    ///
+    /// Tests the TCP connection by trying to read from the socket.
+    /// The read can block so this must be used to check if a connection
+    /// is alive so to check if the connection is alive please always
+    /// use @c isConnectionAlive.
+    ///
+    /// @return true if the TCP connection is closed.
+    virtual bool isConnectionClosed() {
+        // Remember the current non blocking setting.
+        const bool non_blocking_orig = stream_.lowest_layer().non_blocking();
+        // Set the socket to blocking mode. We're going to test if the socket
+        // returns eof status on the attempt to read from it.
+        stream_.lowest_layer().non_blocking(false);
+
+        // We need to provide a buffer for a call to read.
+        char data[2];
+        boost::system::error_code ec;
+        boost::asio::read(stream_, boost::asio::buffer(data, sizeof(data)), ec);
+
+        // Revert the original non_blocking flag on the socket.
+        stream_.lowest_layer().non_blocking(non_blocking_orig);
+
+        // If the connection is closed we'd typically get eof or
+        // stream_truncated status code.
+        return ((ec.value() == boost::asio::error::eof) ||
+                (ec.value() == STREAM_TRUNCATED));
+    }
+
+    /// @brief Close connection.
+    virtual void close() {
+        stream_.lowest_layer().close();
+    }
+
+    virtual std::string getResponse() const {
+        return (response_);
+    }
+
+    /// @brief Returns true if the receive completed without error.
+    ///
+    /// @return True if the receive completed successfully, false
+    /// otherwise.
+    virtual bool receiveDone() const {
+        return (receive_done_);
+    }
+
+private:
+
+    /// @brief Holds pointer to the IO service.
+    isc::asiolink::IOServicePtr io_service_;
+
+    /// @brief A socket used for the connection.
+    TlsStreamImpl stream_;
+
+    /// @brief Buffer into which response is written.
+    std::array<char, 8192> buf_;
+
+    /// @brief Response in the textual format.
+    std::string response_;
+
+    /// @brief IP address of the server.
+    std::string server_address_;
+
+    /// @brief IP port of the server.
+    uint16_t server_port_;
+
+    /// @brief Set to true when the receive has completed successfully.
+    bool receive_done_;
+};
+
+/// @brief Pointer to the TestHttpsClient.
+typedef boost::shared_ptr<TestHttpsClient> TestHttpsClientPtr;
+
 #endif