LCOV - code coverage report
Current view: top level - src/test - httpserver_tests.cpp (source / functions) Coverage Total Hit
Test: test_bitcoin_coverage.info Lines: 97.9 % 382 374
Test Date: 2026-06-27 07:07:37 Functions: 100.0 % 11 11
Branches: 47.2 % 1844 871

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2012-present The Bitcoin Core developers
       2                 :             : // Distributed under the MIT software license, see the accompanying
       3                 :             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4                 :             : 
       5                 :             : #include <httpserver.h>
       6                 :             : #include <rpc/protocol.h>
       7                 :             : #include <test/util/common.h>
       8                 :             : #include <test/util/setup_common.h>
       9                 :             : #include <util/string.h>
      10                 :             : 
      11                 :             : #include <boost/test/unit_test.hpp>
      12                 :             : 
      13                 :             : using http_bitcoin::GetQueryParameterFromUri;
      14                 :             : using http_bitcoin::HTTPHeaders;
      15                 :             : using http_bitcoin::HTTPRemoteClient;
      16                 :             : using http_bitcoin::HTTPRequest;
      17                 :             : using http_bitcoin::HTTPResponse;
      18                 :             : using http_bitcoin::HTTPServer;
      19                 :             : using http_bitcoin::MAX_BODY_SIZE;
      20                 :             : using http_bitcoin::MAX_HEADERS_SIZE;
      21                 :             : using util::LineReader;
      22                 :             : 
      23                 :             : // HTTP request captured from bitcoin-cli
      24                 :             : constexpr std::string_view full_request = "POST / HTTP/1.1\r\n"
      25                 :             :                                           "Host: 127.0.0.1\r\n"
      26                 :             :                                           "Connection: close\r\n"
      27                 :             :                                           "Content-Type: application/json\r\n"
      28                 :             :                                           "Authorization: Basic X19jb29raWVfXzo5OGQ5ODQ3MWNmNjg0NzAzYTkzN2EzNzk0ZDFlODQ1NjZmYTRkZjJiMzFkYjhhODI4ZGY4MjVjOTg5ZGI4OTVl\r\n"
      29                 :             :                                           "Content-Length: 46\r\n"
      30                 :             :                                           "\r\n"
      31                 :             :                                           R"({"method":"getblockcount","params":[],"id":1})""\n";
      32                 :             : 
      33                 :             : BOOST_FIXTURE_TEST_SUITE(httpserver_tests, SocketTestingSetup)
      34                 :             : 
      35   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(test_query_parameters)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
      36                 :             : {
      37         [ +  - ]:           1 :     std::string uri {};
      38                 :             : 
      39                 :             :     // Tolerate a URI with invalid characters (% not followed by hex digits)
      40         [ +  - ]:           1 :     uri = "/rest/endpoint/someresource.json?p1=v1&p2=v2%";
      41   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");
             +  -  +  - ]
      42   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p2"), "v2%");
             +  -  +  - ]
      43                 :             : 
      44                 :             :     // No parameters
      45         [ +  - ]:           1 :     uri = "localhost:8080/rest/headers/someresource.json";
      46   [ +  -  -  +  :           2 :     BOOST_CHECK(!GetQueryParameterFromUri(uri, "p1"));
          +  -  +  -  +  
                      - ]
      47                 :             : 
      48                 :             :     // Single parameter
      49         [ +  - ]:           1 :     uri = "localhost:8080/rest/endpoint/someresource.json?p1=v1";
      50   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");
             +  -  +  - ]
      51   [ +  -  -  +  :           2 :     BOOST_CHECK(!GetQueryParameterFromUri(uri, "p2"));
          +  -  +  -  +  
                      - ]
      52                 :             : 
      53                 :             :     // Multiple parameters
      54         [ +  - ]:           1 :     uri = "/rest/endpoint/someresource.json?p1=v1&p2=v2";
      55   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");
             +  -  +  - ]
      56   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p2"), "v2");
             +  -  +  - ]
      57                 :             : 
      58                 :             :     // If the query string contains duplicate keys, the first value is returned
      59         [ +  - ]:           1 :     uri = "/rest/endpoint/someresource.json?p1=v1&p1=v2";
      60   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");
             +  -  +  - ]
      61                 :             : 
      62                 :             :     // Invalid query string syntax is the same as not having parameters
      63         [ +  - ]:           1 :     uri = "/rest/endpoint/someresource.json&p1=v1&p2=v2";
      64   [ +  -  -  +  :           2 :     BOOST_CHECK(!GetQueryParameterFromUri(uri, "p1"));
          +  -  +  -  +  
                      - ]
      65                 :             : 
      66                 :             :     // Multiple parameters, some characters encoded
      67         [ +  - ]:           1 :     uri = "/rest/endpoint/someresource.json?p1=v1%20&p2=100%25";
      68   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1 ");
             +  -  +  - ]
      69   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p2"), "100%");
             +  -  +  - ]
      70                 :             : 
      71                 :             :     // Encoded query delimiters are part of the parameter value, not structure.
      72         [ +  - ]:           1 :     uri = "/rest/endpoint/someresource.json?p=a%26b%3Dc%23frag&other=x";
      73   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p"), "a&b=c#frag");
             +  -  +  - ]
      74   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "other"), "x");
             +  -  +  - ]
      75                 :             : 
      76                 :             :     // An encoded question mark in the path does not introduce a query section.
      77         [ +  - ]:           1 :     uri = "/rest/endpoint/someresource.json%3Fp1%3Dv1%26p2%3D100%25";
      78   [ +  -  -  +  :           2 :     BOOST_CHECK(!GetQueryParameterFromUri(uri, "p1"));
             +  -  +  - ]
      79                 :           1 : }
      80                 :             : 
      81   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(http_headers_tests)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
      82                 :             : {
      83                 :           1 :     {
      84                 :             :         // Writing response headers
      85                 :           1 :         HTTPHeaders headers{};
      86   [ +  -  +  -  :           2 :         BOOST_CHECK(!headers.FindFirst("Cache-Control"));
             +  -  +  - ]
      87   [ +  -  +  -  :           2 :         headers.Write("Cache-Control", "no-cache");
                   +  - ]
      88                 :             :         // Check case-insensitive key matching
      89   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Cache-Control"), "no-cache");
                   +  - ]
      90   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("cache-control"), "no-cache");
                   +  - ]
      91                 :             :         // Additional values are appended, compared case-insensitive
      92   [ +  -  +  -  :           2 :         headers.Write("cache-control", "max-age=60");
                   +  - ]
      93   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Cache-Control"), "no-cache");
                   +  - ]
      94   [ +  -  +  -  :           2 :         BOOST_CHECK((headers.FindAll("Cache-Control") == std::vector<std::string_view>{"no-cache", "max-age=60"}));
          +  -  +  -  +  
                      - ]
      95                 :             :         // Add a few more
      96   [ +  -  +  -  :           2 :         headers.Write("Pie", "apple");
                   +  - ]
      97   [ +  -  +  -  :           2 :         headers.Write("Sandwich", "ham");
                   +  - ]
      98   [ +  -  +  -  :           2 :         headers.Write("Coffee", "black");
                   +  - ]
      99   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Pie"), "apple");
                   +  - ]
     100                 :             :         // Remove
     101         [ +  - ]:           1 :         headers.RemoveAll("Pie");
     102   [ +  -  +  -  :           2 :         BOOST_CHECK(!headers.FindFirst("Pie"));
             +  -  +  - ]
     103                 :             :         // Combine for transmission
     104         [ +  - ]:           1 :         std::string headers_string{headers.Stringify()};
     105   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(headers_string, "Cache-Control: no-cache\r\n"
     106                 :             :                                           "cache-control: max-age=60\r\n"
     107                 :             :                                           "Sandwich: ham\r\n"
     108                 :             :                                           "Coffee: black\r\n"
     109                 :             :                                           "\r\n");
     110                 :           1 :     }
     111                 :           1 :     {
     112                 :             :         // Reading request headers captured from bitcoin-cli
     113                 :           1 :         constexpr std::string_view bitcoin_cli_headers = "Host: 127.0.0.1\r\n"
     114                 :             :                                                          "Connection: close\r\n"
     115                 :             :                                                          "Content-Type: application/json\r\n"
     116                 :             :                                                          "Authorization: Basic X19jb29raWVfXzozYzJkNTAxNDFlMGJiYmVhMTI5ODg3NzI5MTM3NTRmNThkNjc2OWMwZTYxZjgzNTgyNzEwYTY1OGRkYjVmZGQ3\r\n"
     117                 :             :                                                          "Content-Length: 46\r\n";
     118                 :           1 :         util::LineReader reader(bitcoin_cli_headers, /*max_line_length=*/MAX_HEADERS_SIZE);
     119                 :           1 :         HTTPHeaders headers{};
     120         [ +  - ]:           1 :         headers.Read(reader);
     121   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Host"), "127.0.0.1");
                   +  - ]
     122   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Connection"), "close");
                   +  - ]
     123   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Content-Type"), "application/json");
                   +  - ]
     124   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Authorization"), "Basic X19jb29raWVfXzozYzJkNTAxNDFlMGJiYmVhMTI5ODg3NzI5MTM3NTRmNThkNjc2OWMwZTYxZjgzNTgyNzEwYTY1OGRkYjVmZGQ3");
                   +  - ]
     125   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Content-Length"), "46");
                   +  - ]
     126   [ +  -  +  -  :           2 :         BOOST_CHECK(!headers.FindFirst("Pizza"));
                   +  - ]
     127                 :           0 :     }
     128                 :             :     // Ensure invalid headers are rejected
     129                 :           1 :     {
     130                 :             :         // missing a colon
     131                 :           1 :         util::LineReader reader{"key value\n", /*max_line_length=*/MAX_HEADERS_SIZE};
     132   [ +  -  -  +  :           3 :         BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"HTTP header missing colon (:)"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     133                 :             :     }
     134                 :           1 :     {
     135                 :             :         // missing a key
     136                 :           1 :         util::LineReader reader{":value\n", /*max_line_length=*/MAX_HEADERS_SIZE};
     137   [ +  -  -  +  :           3 :         BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Empty HTTP header name"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     138                 :             :     }
     139                 :           1 :     {
     140                 :             :         // contains NUL
     141                 :           1 :         util::LineReader reader{std::string_view{"X-Custom: foo\0bar\n", 18}, /*max_line_length=*/MAX_HEADERS_SIZE};
     142   [ +  -  -  +  :           3 :         BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Header contains invalid character"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     143                 :             :     }
     144                 :           1 :     {
     145                 :             :         // contains bare \r (not followed by \n)
     146                 :           1 :         util::LineReader reader{std::string_view{"X-Custom: foo\rbar\n"}, /*max_line_length=*/MAX_HEADERS_SIZE};
     147   [ +  -  -  +  :           3 :         BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Header contains invalid character"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     148                 :             :     }
     149                 :           1 :     {
     150                 :             :         // contains odd \r preceding the expected CRLF
     151                 :           1 :         util::LineReader reader{"X-Custom: foo\r\r\n", /*max_line_length=*/MAX_HEADERS_SIZE};
     152   [ +  -  -  +  :           3 :         BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Header contains invalid character"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     153                 :             :     }
     154                 :           1 :     {
     155                 :             :         // key contains whitespace
     156                 :           1 :         util::LineReader reader{"key : value\n", /*max_line_length=*/MAX_HEADERS_SIZE};
     157   [ +  -  -  +  :           3 :         BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Invalid header field-name contains whitespace"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     158                 :             :     }
     159                 :           1 :     {
     160                 :             :         // Individual lines are below MAX_HEADERS_SIZE but the total is excessive
     161         [ +  - ]:           1 :         std::string lines;
     162         [ +  - ]:           1 :         lines.reserve(820 * 10);
     163         [ +  + ]:         821 :         for (int i = 0; i < 820; ++i) {
     164         [ +  - ]:         820 :             lines.append("key:value\n");
     165                 :             :         }
     166         [ -  + ]:           1 :         std::string_view excessive_headers{lines};
     167   [ +  -  +  - ]:           1 :         BOOST_CHECK_GT(excessive_headers.size(), MAX_HEADERS_SIZE);
     168         [ +  - ]:           1 :         util::LineReader reader{excessive_headers, /*max_line_length=*/MAX_HEADERS_SIZE};
     169   [ +  -  -  +  :           3 :         BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"HTTP headers exceed size limit"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     170                 :           0 :     }
     171                 :           1 :     {
     172                 :             :         // Ok
     173                 :           1 :         util::LineReader reader{"key: value\n", /*max_line_length=*/MAX_HEADERS_SIZE};
     174                 :           1 :         HTTPHeaders headers{};
     175         [ +  - ]:           1 :         headers.Read(reader);
     176   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("key"), "value");
                   +  - ]
     177                 :           1 :     }
     178                 :           1 : }
     179                 :             : 
     180   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(http_response_tests)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     181                 :             : {
     182                 :             :     // Typical HTTP 1.1 response headers
     183                 :           1 :     HTTPHeaders headers{};
     184   [ +  -  +  -  :           2 :     headers.Write("Content-Length", "41");
                   +  - ]
     185                 :             : 
     186                 :             :     // Response points to headers which already exist because some of them
     187                 :             :     // are set before we even know what the response will be.
     188                 :           1 :     HTTPResponse res;
     189                 :           1 :     res.m_version = {.major = 1, .minor = 1};
     190                 :           1 :     res.m_status = HTTP_OK;
     191                 :           1 :     res.m_headers = std::move(headers);
     192   [ +  -  +  -  :           1 :     BOOST_CHECK_EQUAL(
                   +  - ]
     193                 :             :         res.StringifyHeaders(),
     194                 :             :         "HTTP/1.1 200 OK\r\n"
     195                 :             :         "Content-Length: 41\r\n"
     196                 :             :         "\r\n");
     197                 :           1 : }
     198                 :             : 
     199   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(http_request_tests)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     200                 :             : {
     201                 :           1 :     {
     202         [ +  - ]:           1 :         HTTPRequest req;
     203         [ +  - ]:           1 :         LineReader reader(full_request, MAX_HEADERS_SIZE);
     204   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     205   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     206   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadBody(reader));
             +  -  +  - ]
     207   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.m_method, HTTPRequestMethod::POST);
     208   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.GetRequestMethod(), HTTPRequestMethod::POST);
     209   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.m_target, "/");
     210   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req.GetURI(), "/");
                   +  - ]
     211   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.m_version.major, 1);
     212   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.m_version.minor, 1);
     213   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(req.m_headers.FindFirst("Host"), "127.0.0.1");
                   +  - ]
     214   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(req.m_headers.FindFirst("Connection"), "close");
                   +  - ]
     215   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(req.m_headers.FindFirst("Content-Type"), "application/json");
                   +  - ]
     216   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(req.m_headers.FindFirst("Authorization"), "Basic X19jb29raWVfXzo5OGQ5ODQ3MWNmNjg0NzAzYTkzN2EzNzk0ZDFlODQ1NjZmYTRkZjJiMzFkYjhhODI4ZGY4MjVjOTg5ZGI4OTVl");
                   +  - ]
     217   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(req.m_headers.FindFirst("Content-Length"), "46");
                   +  - ]
     218   [ +  -  -  +  :           1 :         BOOST_CHECK_EQUAL(req.m_body.size(), 46);
                   +  - ]
     219   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.m_body, R"({"method":"getblockcount","params":[],"id":1})""\n");
     220                 :           1 :     }
     221                 :           1 :     {
     222                 :             :         // Malformed: no spaces between data
     223         [ +  - ]:           1 :         HTTPRequest req;
     224         [ +  - ]:           1 :         LineReader reader("GET/HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     225   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line too short"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     226                 :           1 :     }
     227                 :           1 :     {
     228                 :             :         // Malformed: too many spaces
     229         [ +  - ]:           1 :         HTTPRequest req;
     230         [ +  - ]:           1 :         LineReader reader("GET / HTTP / 1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     231   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line malformed"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     232                 :           1 :     }
     233                 :           1 :     {
     234                 :             :         // Malformed: slash missing before version
     235         [ +  - ]:           1 :         HTTPRequest req;
     236         [ +  - ]:           1 :         LineReader reader("GET / HTTP1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     237   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line too short"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     238                 :           1 :     }
     239                 :           1 :     {
     240                 :             :         // Malformed: no decimal in version
     241         [ +  - ]:           1 :         HTTPRequest req;
     242         [ +  - ]:           1 :         LineReader reader("GET / HTTP/11\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     243   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line too short"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     244                 :           1 :     }
     245                 :           1 :     {
     246                 :             :         // Malformed: version is not a number
     247         [ +  - ]:           1 :         HTTPRequest req;
     248         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.x\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     249   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     250                 :           1 :     }
     251                 :           1 :     {
     252                 :             :         // Malformed: version is out of range
     253         [ +  - ]:           1 :         HTTPRequest req;
     254         [ +  - ]:           1 :         LineReader reader("GET / HTTP/2.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     255   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     256                 :           1 :     }
     257                 :           1 :     {
     258                 :             :         // Malformed: version is out of range
     259         [ +  - ]:           1 :         HTTPRequest req;
     260         [ +  - ]:           1 :         LineReader reader("GET / HTTP/0.9\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     261   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     262                 :           1 :     }
     263                 :           1 :     {
     264                 :             :         // Malformed: version is out of range
     265         [ +  - ]:           1 :         HTTPRequest req;
     266         [ +  - ]:           1 :         LineReader reader("GET / HTTP/-1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     267   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     268                 :           1 :     }
     269                 :           1 :     {
     270                 :             :         // Malformed: version is not exactly two integers and a dot
     271         [ +  - ]:           1 :         HTTPRequest req;
     272         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.00\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     273   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     274                 :           1 :     }
     275                 :           1 :     {
     276                 :             :         // Malformed: contains NUL
     277         [ +  - ]:           1 :         HTTPRequest req;
     278         [ +  - ]:           1 :         LineReader reader{std::string_view{"GET /safe\0/etc/passwd HTTP/1.00\r\nHost: 127.0.0.1\r\n\r\n", 50}, MAX_HEADERS_SIZE};
     279   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"Invalid request line contains NUL"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     280                 :           1 :     }
     281                 :           1 :     {
     282                 :             :         // Malformed: differing Content-Length values, case insensitive
     283                 :           1 :         constexpr std::string_view differing_length = "GET / HTTP/1.1\n"
     284                 :             :                                                       "Host: 127.0.0.1\n"
     285                 :             :                                                       "Content-Length: 8\n"
     286                 :             :                                                       "content-length: 9\n\n"
     287                 :             :                                                       "12345678";
     288         [ +  - ]:           1 :         HTTPRequest req;
     289         [ +  - ]:           1 :         util::LineReader reader{differing_length, /*max_line_length=*/MAX_HEADERS_SIZE};
     290   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     291   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     292   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadBody(reader), std::runtime_error, HasReason{"Differing Content-Length values"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     293                 :           1 :     }
     294                 :           1 :     {
     295                 :             :         // Ok: multiple same Content-Length values
     296                 :           1 :         constexpr std::string_view differing_length = "GET / HTTP/1.1\n"
     297                 :             :                                                       "Host: 127.0.0.1\n"
     298                 :             :                                                       "Content-Length: 8\n"
     299                 :             :                                                       "content-length: 8\n\n"
     300                 :             :                                                       "12345678";
     301         [ +  - ]:           1 :         HTTPRequest req;
     302         [ +  - ]:           1 :         util::LineReader reader{differing_length, /*max_line_length=*/MAX_HEADERS_SIZE};
     303   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     304   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     305   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadBody(reader));
                   +  - ]
     306                 :           1 :     }
     307                 :           1 :     {
     308                 :             :         // Ok
     309         [ +  - ]:           1 :         HTTPRequest req;
     310         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     311   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     312   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     313   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadBody(reader));
             +  -  +  - ]
     314   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.m_method, HTTPRequestMethod::GET);
     315   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.m_target, "/");
     316   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.m_version.major, 1);
     317   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.m_version.minor, 0);
     318   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(req.m_headers.FindFirst("Host"), "127.0.0.1");
                   +  - ]
     319                 :             :         // no body is OK
     320   [ +  -  -  +  :           1 :         BOOST_CHECK_EQUAL(req.m_body.size(), 0);
                   +  - ]
     321                 :           1 :     }
     322                 :           1 :     {
     323                 :             :         // Malformed: missing colon
     324         [ +  - ]:           1 :         HTTPRequest req;
     325         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.0\r\nHost=127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     326   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     327   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadHeaders(reader), std::runtime_error, HasReason{"HTTP header missing colon (:)"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     328                 :           1 :     }
     329                 :           1 :     {
     330                 :             :         // We might not have received enough data from the client which is not
     331                 :             :         // an error. We return false so the caller can try again later when the
     332                 :             :         // buffer has more data.
     333         [ +  - ]:           1 :         HTTPRequest req;
     334         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.0\r\nHost: ", MAX_HEADERS_SIZE);
     335   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     336   [ +  -  +  -  :           2 :         BOOST_CHECK(!req.LoadHeaders(reader));
                   +  - ]
     337                 :           1 :     }
     338                 :           1 :     {
     339                 :             :         // No Content-Length: body is not read
     340         [ +  - ]:           1 :         HTTPRequest req;
     341         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.0\r\n\r\n" R"({"method":"getblockcount"})", MAX_HEADERS_SIZE);
     342   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     343   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     344   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadBody(reader));
             +  -  +  - ]
     345                 :             :         // Don't try to read request body if Content-Length is missing
     346   [ +  -  -  +  :           1 :         BOOST_CHECK_EQUAL(req.m_body.size(), 0);
                   +  - ]
     347                 :           1 :     }
     348                 :           1 :     {
     349                 :             :         // Malformed: Content-Length is not a number
     350         [ +  - ]:           1 :         HTTPRequest req;
     351         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.0\r\nContent-Length: eleven\r\n\r\n" R"({"method":"getblockcount"})", MAX_HEADERS_SIZE);
     352   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     353   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     354   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadBody(reader), std::runtime_error, HasReason{"Cannot parse Content-Length value"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     355                 :           1 :     }
     356                 :           1 :     {
     357                 :             :         // Malformed: Content-Length is negative
     358         [ +  - ]:           1 :         HTTPRequest req;
     359         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.0\r\nContent-Length: -8\r\n\r\n" R"({"method":"getblockcount"})", MAX_HEADERS_SIZE);
     360   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     361   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     362   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadBody(reader), std::runtime_error, HasReason{"Cannot parse Content-Length value"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     363                 :           1 :     }
     364                 :           1 :     {
     365                 :             :         // Content-Length exceeds limit
     366                 :           1 :         constexpr auto excessive_size{MAX_BODY_SIZE + 1};
     367         [ +  - ]:           1 :         std::string huge_body(excessive_size, 'x');
     368   [ +  -  +  -  :           3 :         const std::string request{"GET / HTTP/1.0\r\nContent-Length: " + util::ToString(excessive_size) + "\r\n\r\n" + std::move(huge_body)};
                   +  - ]
     369         [ -  + ]:           1 :         HTTPRequest req;
     370   [ -  +  +  - ]:           1 :         LineReader reader(request, MAX_HEADERS_SIZE);
     371   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     372   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     373   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadBody(reader), http_bitcoin::ContentTooLargeError, HasReason{"Max body size exceeded"});
          -  -  -  -  -  
          +  +  -  +  -  
             +  -  +  - ]
     374                 :           1 :     }
     375                 :           1 :     {
     376                 :             :         // Content-Length exactly on the limit
     377         [ +  - ]:           1 :         std::string max_body(MAX_BODY_SIZE, 'x');
     378   [ +  -  +  -  :           3 :         const std::string request{"GET / HTTP/1.0\r\nContent-Length: " + util::ToString(MAX_BODY_SIZE) + "\r\n\r\n" + std::move(max_body)};
                   +  - ]
     379         [ -  + ]:           1 :         HTTPRequest req;
     380   [ -  +  +  - ]:           1 :         LineReader reader(request, MAX_HEADERS_SIZE);
     381   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     382   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     383   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadBody(reader));
                   +  - ]
     384                 :           1 :     }
     385                 :           1 :     {
     386                 :             :         // Content-Length indicates more data than we have in the buffer.
     387                 :             :         // Not an error; we wait for more data before completing the body.
     388         [ +  - ]:           1 :         HTTPRequest req;
     389         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.0\r\nContent-Length: 1024\r\n\r\n" R"({"method":"getblockcount"})", MAX_HEADERS_SIZE);
     390   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     391   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     392   [ +  -  +  -  :           2 :         BOOST_CHECK(!req.LoadBody(reader));
                   +  - ]
     393                 :           1 :     }
     394                 :           1 :     {
     395                 :             :         // Support "chunked" transfer. Chunk lengths are ascii-encoded hex integers, whitespace ignored
     396         [ +  - ]:           1 :         HTTPRequest req;
     397                 :           1 :         std::string_view ok_chunked = "GET / HTTP/1.0\n"
     398                 :             :                                       "Transfer-Encoding: chunked\n"
     399                 :             :                                       "\n"
     400                 :             :                                       "10\n"
     401                 :             :                                       R"({"method":"getbl)""\n"
     402                 :             :                                       "   a    \n"
     403                 :             :                                       R"(ockcount"})""\n"
     404                 :             :                                       "0\n"
     405                 :             :                                       "\n";
     406         [ +  - ]:           1 :         LineReader reader(ok_chunked, MAX_HEADERS_SIZE);
     407   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     408   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     409   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadBody(reader));
             +  -  +  - ]
     410   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.m_body, R"({"method":"getblockcount"})");
     411                 :           1 :     }
     412                 :           1 :     {
     413                 :             :         // Prevent "chunked" transfer from exceeding size limit
     414         [ +  - ]:           1 :         HTTPRequest req;
     415                 :           1 :         std::string_view excessive_chunk_size = "GET / HTTP/1.0\n"
     416                 :             :                                                 "Transfer-Encoding: chunked\n"
     417                 :             :                                                 "\n"
     418                 :             :                                                 "10\n"
     419                 :             :                                                 R"({"method":"getbl)""\n"
     420                 :             :                                                 "20000000\n"
     421                 :             :                                                 R"(ockcount"})""\n"
     422                 :             :                                                 "0\n"
     423                 :             :                                                 "\n";
     424         [ +  - ]:           1 :         LineReader reader(excessive_chunk_size, MAX_HEADERS_SIZE);
     425   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     426   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     427   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadBody(reader), http_bitcoin::ContentTooLargeError, HasReason{"Chunk will exceed max body size"});
          -  -  -  -  -  
          +  +  -  +  -  
             +  -  +  - ]
     428                 :           1 :     }
     429                 :           1 :     {
     430                 :             :         // Allow (but ignore) Chunk Extensions
     431         [ +  - ]:           1 :         HTTPRequest req;
     432                 :           1 :         std::string_view ok_chunked = "GET / HTTP/1.0\n"
     433                 :             :                                       "Transfer-Encoding: chunked\n"
     434                 :             :                                       "\n"
     435                 :             :                                       "10;sha256=715790e8a3b09d704ac9641f42d183a5ebc5fd939663de23da548519ac2165e5\n"
     436                 :             :                                       R"({"method":"getbl)""\n"
     437                 :             :                                       "   a   ;    compressed\n"
     438                 :             :                                       R"(ockcount"})""\n"
     439                 :             :                                       "0;why;would;anyone;do;this;\n"
     440                 :             :                                       "Expires: Wed, 21 Oct 2026 07:28:00 GMT\n"
     441                 :             :                                       "\n";
     442         [ +  - ]:           1 :         LineReader reader(ok_chunked, MAX_HEADERS_SIZE);
     443   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     444   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     445   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadBody(reader));
             +  -  +  - ]
     446   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.m_body, R"({"method":"getblockcount"})");
     447                 :             :         // Chunk Trailer was cleared
     448   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(reader.Remaining(), 0);
                   +  - ]
     449                 :           1 :     }
     450                 :           1 :     {
     451                 :             :         // Invalid "chunked" transfer, using roman numerals instead of hex for chunk length
     452         [ +  - ]:           1 :         HTTPRequest req;
     453                 :           1 :         std::string_view invalid_chunked = "GET / HTTP/1.0\n"
     454                 :             :                                            "Transfer-Encoding: chunked\n"
     455                 :             :                                            "\n"
     456                 :             :                                            "XVI\n"
     457                 :             :                                            R"({"method":"getbl)""\n"
     458                 :             :                                            "X\n"
     459                 :             :                                            R"(ockcount"})""\n"
     460                 :             :                                            "0\n"
     461                 :             :                                            "\n";
     462         [ +  - ]:           1 :         LineReader reader(invalid_chunked, MAX_HEADERS_SIZE);
     463   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     464   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     465   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadBody(reader), std::runtime_error, HasReason{"Cannot parse chunk length value"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     466                 :           1 :     }
     467                 :           1 :     {
     468                 :             :         // Invalid "chunked" transfer, missing chunk termination \n
     469         [ +  - ]:           1 :         HTTPRequest req;
     470                 :           1 :         std::string_view invalid_chunked = "GET / HTTP/1.0\n"
     471                 :             :                                            "Transfer-Encoding: chunked\n"
     472                 :             :                                            "\n"
     473                 :             :                                            "10\n"
     474                 :             :                                            R"({"method":"getbl)"
     475                 :             :                                            "a\n" // interpreted as extra data at the end of `0x10`-sized chunk
     476                 :             :                                            R"(ockcount"})"
     477                 :             :                                            "0\n"
     478                 :             :                                            "\n";
     479         [ +  - ]:           1 :         LineReader reader(invalid_chunked, MAX_HEADERS_SIZE);
     480   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     481   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     482   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadBody(reader), std::runtime_error, HasReason{"Improperly terminated chunk"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     483                 :           1 :     }
     484                 :           1 :     {
     485                 :             :         // End of buffer reached without chunk termination, caller must wait for more data to arrive
     486         [ +  - ]:           1 :         HTTPRequest req;
     487                 :           1 :         std::string delayed_chunked = "GET / HTTP/1.0\n"
     488                 :             :                                       "Transfer-Encoding: chunked\n"
     489                 :             :                                       "\n"
     490                 :             :                                       "10\n"
     491                 :             :                                       R"({"method":"getbl)""\n"
     492                 :             :                                       "a\n"
     493         [ +  - ]:           1 :                                       R"(ockcount"})";
     494   [ -  +  +  - ]:           1 :         LineReader reader1(delayed_chunked, MAX_HEADERS_SIZE);
     495   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader1));
             +  -  +  - ]
     496   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader1));
             +  -  +  - ]
     497   [ +  -  +  -  :           2 :         BOOST_CHECK(!req.LoadBody(reader1));
             +  -  +  - ]
     498                 :             :         // more data arrives!
     499         [ +  - ]:           1 :         delayed_chunked += "\n0\n\n";
     500   [ -  +  +  - ]:           1 :         LineReader reader2(delayed_chunked, MAX_HEADERS_SIZE);
     501   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader2));
             +  -  +  - ]
     502   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader2));
             +  -  +  - ]
     503   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadBody(reader2));
                   +  - ]
     504                 :           1 :     }
     505                 :           1 : }
     506                 :             : 
     507   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(http_server_socket_tests)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     508                 :             : {
     509                 :             :     // Hard code the timestamp for the Date header in the HTTP response
     510                 :             :     // Wed Dec 11 00:47:09 2024 UTC
     511                 :           1 :     SetMockTime(1733878029);
     512                 :             : 
     513                 :             :     // Prepare a request handler that just stores received requests so we can examine them.
     514                 :             :     // Mutex is required to prevent a race between this test's main thread and the server's I/O loop.
     515                 :           1 :     Mutex requests_mutex;
     516                 :           1 :     std::deque<std::unique_ptr<HTTPRequest>> requests;
     517                 :           2 :     auto StoreRequest = [&](std::unique_ptr<HTTPRequest>&& req) {
     518                 :           1 :         LOCK(requests_mutex);
     519   [ +  -  +  - ]:           1 :         requests.push_back(std::move(req));
     520                 :           2 :     };
     521                 :             : 
     522         [ +  - ]:           1 :     HTTPServer server{StoreRequest};
     523                 :             : 
     524                 :           1 :     {
     525                 :             :         // We can only bind to NET_IPV4 and NET_IPV6
     526   [ +  -  +  -  :           2 :         CService onion_address{Lookup("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaam2dqd.onion", /*portDefault=*/0, /*fAllowLookup=*/false).value()};
                   +  - ]
     527         [ +  - ]:           1 :         auto result{server.BindAndStartListening(onion_address)};
     528   [ +  -  +  -  :           2 :         BOOST_REQUIRE(!result);
                   +  - ]
     529   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.error(), "Bind address family for aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaam2dqd.onion:0 not supported");
     530                 :           1 :     }
     531                 :             : 
     532                 :             :     // This VALID address won't actually get used because we stubbed CreateSock()
     533   [ +  -  +  -  :           2 :     CService addr_bind{Lookup("0.0.0.0", /*portDefault=*/0, /*fAllowLookup=*/false).value()};
                   +  - ]
     534                 :             : 
     535                 :             :     // Init state
     536   [ +  -  -  +  :           1 :     BOOST_REQUIRE_EQUAL(server.GetListeningSocketCount(), 0);
                   +  - ]
     537                 :             :     // Bind to mock Listening Socket
     538   [ +  -  +  -  :           2 :     BOOST_REQUIRE(server.BindAndStartListening(addr_bind));
             +  -  +  - ]
     539                 :             :     // We are bound and listening
     540   [ +  -  -  +  :           1 :     BOOST_REQUIRE_EQUAL(server.GetListeningSocketCount(), 1);
                   +  - ]
     541                 :             : 
     542                 :             :     // Start the I/O loop
     543         [ +  - ]:           1 :     server.StartSocketsThreads();
     544                 :             : 
     545                 :             :     // No connections yet
     546   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(server.GetConnectionsCount(), 0);
     547                 :             : 
     548                 :             :     // Create a mock client with pre-loaded request data and add it to the local CreateSock queue.
     549                 :             :     // Keep a handle for the mock client's send and receive pipes so we can examine
     550                 :             :     // the data it "receives".
     551         [ +  - ]:           1 :     std::shared_ptr<DynSock::Pipes> mock_client_socket_pipes{ConnectClient(std::as_bytes(std::span(full_request)))};
     552                 :             : 
     553                 :             :     // Wait up to a minute to find and connect the client in the I/O loop
     554                 :             :     int attempts{6000};
     555                 :           2 :     while (server.GetConnectionsCount() < 1) {
     556         [ +  - ]:           1 :         std::this_thread::sleep_for(10ms);
     557   [ +  -  +  -  :           3 :         BOOST_REQUIRE(--attempts > 0);
                   +  + ]
     558                 :             :     }
     559                 :             : 
     560                 :             :     // Prepare a pointer to the client, we'll assign it from the request itself.
     561                 :           1 :     std::shared_ptr<HTTPRemoteClient> client;
     562                 :             : 
     563                 :             :     // Wait up to a minute to read the request from the client.
     564                 :             :     // Given that the mock client is itself a mock socket
     565                 :             :     // with hard-coded data it should only take a fraction of that.
     566                 :           1 :     attempts = 6000;
     567                 :           0 :     while (true) {
     568                 :           1 :         {
     569         [ +  - ]:           1 :             LOCK(requests_mutex);
     570                 :             :             // Connected client should have one request already from the static content.
     571   [ -  +  +  - ]:           1 :             if (requests.size() == 1) {
     572                 :             :                 // Check the received request
     573   [ +  -  +  - ]:           1 :                 BOOST_CHECK_EQUAL(requests.front()->m_body, R"({"method":"getblockcount","params":[],"id":1})""\n");
     574   [ +  -  +  -  :           1 :                 BOOST_CHECK_EQUAL(requests.front()->GetPeer().ToStringAddrPort(), "5.5.5.5:6789");
             +  -  +  - ]
     575                 :             : 
     576                 :             :                 // Inspect the connection pointed to from the request
     577                 :           1 :                 client = requests.front()->m_client;
     578   [ +  -  +  - ]:           1 :                 BOOST_CHECK_EQUAL(client->m_origin, "5.5.5.5:6789");
     579                 :             : 
     580                 :             :                 // Respond to request
     581         [ +  - ]:           1 :                 requests.front()->WriteReply(HTTP_OK, "874140\n");
     582                 :             : 
     583         [ +  - ]:           1 :                 break;
     584                 :             :             }
     585                 :           0 :         }
     586         [ #  # ]:           0 :         std::this_thread::sleep_for(10ms);
     587   [ #  #  #  # ]:           0 :         BOOST_REQUIRE(--attempts > 0);
     588                 :             :     }
     589                 :             : 
     590                 :             :     // Check the sent response from the mock client at the other end of the mock socket
     591                 :           1 :     std::string actual;
     592                 :             :     // Wait up to one minute for all the bytes to appear in the "send" pipe.
     593                 :             :     char buf[0x10000] = {};
     594                 :           1 :     attempts = 6000;
     595         [ +  - ]:           1 :     while (attempts > 0)
     596                 :             :     {
     597         [ +  - ]:           1 :         ssize_t bytes_read = mock_client_socket_pipes->send.GetBytes(buf, sizeof(buf), 0);
     598         [ +  - ]:           1 :         if (bytes_read > 0) {
     599         [ +  - ]:           1 :             actual.append(buf, bytes_read);
     600   [ -  +  -  + ]:           1 :             if (actual.length() == 146) {
     601                 :             :                 break;
     602                 :             :             }
     603                 :             :         }
     604         [ #  # ]:           0 :         std::this_thread::sleep_for(10ms);
     605                 :           0 :         --attempts;
     606                 :             :     }
     607   [ +  -  -  +  :           2 :     BOOST_CHECK(actual.starts_with("HTTP/1.1 200 OK\r\n"));
             +  -  +  - ]
     608   [ +  -  -  +  :           2 :     BOOST_CHECK(actual.ends_with("\r\n874140\n"));
             +  -  +  - ]
     609                 :             :     // Headers can be sorted in any order, and will be, since we use unordered_map
     610   [ +  -  +  -  :           2 :     BOOST_CHECK(actual.find("Connection: close\r\n") != std::string::npos);
                   +  - ]
     611   [ +  -  +  -  :           2 :     BOOST_CHECK(actual.find("Content-Length: 7\r\n") != std::string::npos);
                   +  - ]
     612   [ +  -  +  -  :           2 :     BOOST_CHECK(actual.find("Content-Type: text/html; charset=ISO-8859-1\r\n") != std::string::npos);
                   +  - ]
     613   [ +  -  +  - ]:           2 :     BOOST_CHECK(actual.find("Date: Wed, 11 Dec 2024 00:47:09 GMT\r\n") != std::string::npos);
     614                 :             : 
     615                 :             :     // Wait up to one minute for connection to be automatically closed, because
     616                 :             :     // keep-alive was not set by the client and we are done responding to their request.
     617                 :           1 :     attempts = 6000;
     618                 :           1 :     while (server.GetConnectionsCount() != 0) {
     619         [ +  - ]:           5 :         std::this_thread::sleep_for(10ms);
     620   [ +  -  +  -  :          11 :         BOOST_REQUIRE(--attempts > 0);
                   +  + ]
     621                 :             :     }
     622                 :             : 
     623                 :             :     // Stop the I/O loop and shutdown
     624         [ +  - ]:           1 :     server.InterruptNet();
     625                 :             :     // Wait for I/O loop to finish, after all connected sockets are closed
     626         [ +  - ]:           1 :     server.JoinSocketsThreads();
     627                 :             :     // Close all listening sockets
     628         [ +  - ]:           1 :     server.StopListening();
     629   [ +  -  +  - ]:           3 : }
     630                 :             : 
     631                 :             : BOOST_AUTO_TEST_SUITE_END()
        

Generated by: LCOV version 2.0-1