LCOV - code coverage report
Current view: top level - src/test - httpserver_tests.cpp (source / functions) Coverage Total Hit
Test: total_coverage.info Lines: 97.8 % 650 636
Test Date: 2026-09-05 06:08:39 Functions: 95.7 % 23 22
Branches: 48.7 % 3040 1480

             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/logging.h>
       9                 :             : #include <test/util/setup_common.h>
      10                 :             : #include <util/string.h>
      11                 :             : #include <util/threadpool.h>
      12                 :             : 
      13                 :             : #include <boost/test/unit_test.hpp>
      14                 :             : 
      15                 :             : using util::LineReader;
      16                 :             : using namespace bitcoin_http;
      17                 :             : 
      18                 :             : // HTTP request captured from bitcoin-cli
      19                 :             : constexpr std::string_view full_request = "POST / HTTP/1.1\r\n"
      20                 :             :                                           "Host: 127.0.0.1\r\n"
      21                 :             :                                           "Connection: close\r\n"
      22                 :             :                                           "Content-Type: application/json\r\n"
      23                 :             :                                           "Authorization: Basic X19jb29raWVfXzo5OGQ5ODQ3MWNmNjg0NzAzYTkzN2EzNzk0ZDFlODQ1NjZmYTRkZjJiMzFkYjhhODI4ZGY4MjVjOTg5ZGI4OTVl\r\n"
      24                 :             :                                           "Content-Length: 46\r\n"
      25                 :             :                                           "\r\n"
      26                 :             :                                           R"({"method":"getblockcount","params":[],"id":1})""\n";
      27                 :             : 
      28                 :             : BOOST_FIXTURE_TEST_SUITE(httpserver_tests, SocketTestingSetup)
      29                 :             : 
      30   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(test_query_parameters)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
      31                 :             : {
      32         [ +  - ]:           1 :     std::string uri {};
      33                 :             : 
      34                 :             :     // Tolerate a URI with invalid characters (% not followed by hex digits)
      35         [ +  - ]:           1 :     uri = "/rest/endpoint/someresource.json?p1=v1&p2=v2%";
      36   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");
             +  -  +  - ]
      37   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p2"), "v2%");
             +  -  +  - ]
      38                 :             : 
      39                 :             :     // No parameters
      40         [ +  - ]:           1 :     uri = "localhost:8080/rest/headers/someresource.json";
      41   [ +  -  -  +  :           2 :     BOOST_CHECK(!GetQueryParameterFromUri(uri, "p1"));
          +  -  +  -  +  
                      - ]
      42                 :             : 
      43                 :             :     // Single parameter
      44         [ +  - ]:           1 :     uri = "localhost:8080/rest/endpoint/someresource.json?p1=v1";
      45   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");
             +  -  +  - ]
      46   [ +  -  -  +  :           2 :     BOOST_CHECK(!GetQueryParameterFromUri(uri, "p2"));
          +  -  +  -  +  
                      - ]
      47                 :             : 
      48                 :             :     // Multiple parameters
      49         [ +  - ]:           1 :     uri = "/rest/endpoint/someresource.json?p1=v1&p2=v2";
      50   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");
             +  -  +  - ]
      51   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p2"), "v2");
             +  -  +  - ]
      52                 :             : 
      53                 :             :     // If the query string contains duplicate keys, the first value is returned
      54         [ +  - ]:           1 :     uri = "/rest/endpoint/someresource.json?p1=v1&p1=v2";
      55   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");
             +  -  +  - ]
      56                 :             : 
      57                 :             :     // Invalid query string syntax is the same as not having parameters
      58         [ +  - ]:           1 :     uri = "/rest/endpoint/someresource.json&p1=v1&p2=v2";
      59   [ +  -  -  +  :           2 :     BOOST_CHECK(!GetQueryParameterFromUri(uri, "p1"));
          +  -  +  -  +  
                      - ]
      60                 :             : 
      61                 :             :     // Multiple parameters, some characters encoded
      62         [ +  - ]:           1 :     uri = "/rest/endpoint/someresource.json?p1=v1%20&p2=100%25";
      63   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1 ");
             +  -  +  - ]
      64   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p2"), "100%");
             +  -  +  - ]
      65                 :             : 
      66                 :             :     // Encoded query delimiters are part of the parameter value, not structure.
      67         [ +  - ]:           1 :     uri = "/rest/endpoint/someresource.json?p=a%26b%3Dc%23frag&other=x";
      68   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p"), "a&b=c#frag");
             +  -  +  - ]
      69   [ +  -  -  +  :           1 :     BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "other"), "x");
             +  -  +  - ]
      70                 :             : 
      71                 :             :     // An encoded question mark in the path does not introduce a query section.
      72         [ +  - ]:           1 :     uri = "/rest/endpoint/someresource.json%3Fp1%3Dv1%26p2%3D100%25";
      73   [ +  -  -  +  :           2 :     BOOST_CHECK(!GetQueryParameterFromUri(uri, "p1"));
             +  -  +  - ]
      74                 :           1 : }
      75                 :             : 
      76   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(http_headers_tests)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
      77                 :             : {
      78                 :           1 :     {
      79                 :             :         // Writing response headers
      80                 :           1 :         HTTPHeaders headers{};
      81   [ +  -  +  -  :           2 :         BOOST_CHECK(!headers.FindFirst("Cache-Control"));
             +  -  +  - ]
      82   [ +  -  +  -  :           2 :         headers.Write("Cache-Control", "no-cache");
                   +  - ]
      83                 :             :         // Check case-insensitive key matching
      84   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Cache-Control"), "no-cache");
                   +  - ]
      85   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("cache-control"), "no-cache");
                   +  - ]
      86                 :             :         // Additional values are appended, compared case-insensitive
      87   [ +  -  +  -  :           2 :         headers.Write("cache-control", "max-age=60");
                   +  - ]
      88   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Cache-Control"), "no-cache");
                   +  - ]
      89   [ +  -  +  -  :           2 :         BOOST_CHECK((headers.FindAll("Cache-Control") == std::vector<std::string_view>{"no-cache", "max-age=60"}));
          +  -  +  -  +  
                      - ]
      90                 :             :         // Add a few more
      91   [ +  -  +  -  :           2 :         headers.Write("Pie", "apple");
                   +  - ]
      92   [ +  -  +  -  :           2 :         headers.Write("Sandwich", "ham");
                   +  - ]
      93   [ +  -  +  -  :           2 :         headers.Write("Coffee", "black");
                   +  - ]
      94   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Pie"), "apple");
                   +  - ]
      95                 :             :         // Remove
      96         [ +  - ]:           1 :         headers.RemoveAll("Pie");
      97   [ +  -  +  -  :           2 :         BOOST_CHECK(!headers.FindFirst("Pie"));
             +  -  +  - ]
      98                 :             :         // Combine for transmission
      99         [ +  - ]:           1 :         std::string headers_string{headers.Stringify()};
     100   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(headers_string, "Cache-Control: no-cache\r\n"
     101                 :             :                                           "cache-control: max-age=60\r\n"
     102                 :             :                                           "Sandwich: ham\r\n"
     103                 :             :                                           "Coffee: black\r\n"
     104                 :             :                                           "\r\n");
     105                 :           1 :     }
     106                 :           1 :     {
     107                 :             :         // Reading request headers captured from bitcoin-cli
     108                 :           1 :         constexpr std::string_view bitcoin_cli_headers = "Host: 127.0.0.1\r\n"
     109                 :             :                                                          "Connection: close\r\n"
     110                 :             :                                                          "Content-Type: application/json\r\n"
     111                 :             :                                                          "Authorization: Basic X19jb29raWVfXzozYzJkNTAxNDFlMGJiYmVhMTI5ODg3NzI5MTM3NTRmNThkNjc2OWMwZTYxZjgzNTgyNzEwYTY1OGRkYjVmZGQ3\r\n"
     112                 :             :                                                          "Content-Length: 46\r\n";
     113                 :           1 :         util::LineReader reader(bitcoin_cli_headers, /*max_line_length=*/MAX_HEADERS_SIZE);
     114                 :           1 :         HTTPHeaders headers{};
     115         [ +  - ]:           1 :         headers.Read(reader);
     116   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Host"), "127.0.0.1");
                   +  - ]
     117   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Connection"), "close");
                   +  - ]
     118   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Content-Type"), "application/json");
                   +  - ]
     119   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Authorization"), "Basic X19jb29raWVfXzozYzJkNTAxNDFlMGJiYmVhMTI5ODg3NzI5MTM3NTRmNThkNjc2OWMwZTYxZjgzNTgyNzEwYTY1OGRkYjVmZGQ3");
                   +  - ]
     120   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("Content-Length"), "46");
                   +  - ]
     121   [ +  -  +  -  :           2 :         BOOST_CHECK(!headers.FindFirst("Pizza"));
                   +  - ]
     122                 :           0 :     }
     123                 :             :     // Ensure invalid headers are rejected
     124                 :           1 :     {
     125                 :             :         // missing a colon
     126                 :           1 :         util::LineReader reader{"key value\n", /*max_line_length=*/MAX_HEADERS_SIZE};
     127   [ +  -  -  +  :           3 :         BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"HTTP header missing colon (:)"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     128                 :             :     }
     129                 :           1 :     {
     130                 :             :         // missing a key
     131                 :           1 :         util::LineReader reader{":value\n", /*max_line_length=*/MAX_HEADERS_SIZE};
     132   [ +  -  -  +  :           3 :         BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Empty HTTP header name"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     133                 :             :     }
     134                 :           1 :     {
     135                 :             :         // contains NUL
     136                 :           1 :         util::LineReader reader{std::string_view{"X-Custom: foo\0bar\n", 18}, /*max_line_length=*/MAX_HEADERS_SIZE};
     137   [ +  -  -  +  :           3 :         BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Header contains invalid character"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     138                 :             :     }
     139                 :           1 :     {
     140                 :             :         // contains bare \r (not followed by \n)
     141                 :           1 :         util::LineReader reader{std::string_view{"X-Custom: foo\rbar\n"}, /*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 odd \r preceding the expected CRLF
     146                 :           1 :         util::LineReader reader{"X-Custom: foo\r\r\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                 :             :         // key contains whitespace
     151                 :           1 :         util::LineReader reader{"key : value\n", /*max_line_length=*/MAX_HEADERS_SIZE};
     152   [ +  -  -  +  :           3 :         BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Invalid header field-name contains whitespace"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     153                 :             :     }
     154                 :           1 :     {
     155                 :             :         // Individual lines are below MAX_HEADERS_SIZE but the total is excessive
     156         [ +  - ]:           1 :         std::string lines;
     157         [ +  - ]:           1 :         lines.reserve(820 * 10);
     158         [ +  + ]:         821 :         for (int i = 0; i < 820; ++i) {
     159         [ +  - ]:         820 :             lines.append("key:value\n");
     160                 :             :         }
     161         [ -  + ]:           1 :         std::string_view excessive_headers{lines};
     162   [ +  -  +  - ]:           1 :         BOOST_CHECK_GT(excessive_headers.size(), MAX_HEADERS_SIZE);
     163         [ +  - ]:           1 :         util::LineReader reader{excessive_headers, /*max_line_length=*/MAX_HEADERS_SIZE};
     164   [ +  -  -  +  :           3 :         BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"HTTP headers exceed size limit"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     165                 :           0 :     }
     166                 :           1 :     {
     167                 :             :         // Ok
     168                 :           1 :         util::LineReader reader{"key: value\n", /*max_line_length=*/MAX_HEADERS_SIZE};
     169                 :           1 :         HTTPHeaders headers{};
     170         [ +  - ]:           1 :         headers.Read(reader);
     171   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(headers.FindFirst("key"), "value");
                   +  - ]
     172                 :           1 :     }
     173                 :           1 : }
     174                 :             : 
     175   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(http_response_tests)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     176                 :             : {
     177                 :             :     // Typical HTTP 1.1 response headers
     178                 :           1 :     HTTPHeaders headers{};
     179   [ +  -  +  -  :           2 :     headers.Write("Content-Length", "41");
                   +  - ]
     180                 :             : 
     181                 :             :     // Response points to headers which already exist because some of them
     182                 :             :     // are set before we even know what the response will be.
     183                 :           1 :     HTTPResponse res;
     184                 :           1 :     res.version = {.major = 1, .minor = 1};
     185                 :           1 :     res.status = HTTP_OK;
     186                 :           1 :     res.headers = std::move(headers);
     187   [ +  -  +  -  :           1 :     BOOST_CHECK_EQUAL(
                   +  - ]
     188                 :             :         res.StringifyHeaders(),
     189                 :             :         "HTTP/1.1 200 OK\r\n"
     190                 :             :         "Content-Length: 41\r\n"
     191                 :             :         "\r\n");
     192                 :           1 : }
     193                 :             : 
     194   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(http_request_tests)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     195                 :             : {
     196                 :           1 :     {
     197                 :           1 :         HTTPRequest req;
     198         [ +  - ]:           1 :         LineReader reader(full_request, MAX_HEADERS_SIZE);
     199   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     200   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     201   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadBody(reader));
             +  -  +  - ]
     202   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.GetRequestMethod(), HTTPRequestMethod::POST);
     203   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req.GetURI(), "/");
                   +  - ]
     204   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.GetVersion().major, 1);
     205   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.GetVersion().minor, 1);
     206   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(req.GetHeader("Host"), "127.0.0.1");
                   +  - ]
     207   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(req.GetHeader("Connection"), "close");
                   +  - ]
     208   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(req.GetHeader("Content-Type"), "application/json");
                   +  - ]
     209   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(req.GetHeader("Authorization"), "Basic X19jb29raWVfXzo5OGQ5ODQ3MWNmNjg0NzAzYTkzN2EzNzk0ZDFlODQ1NjZmYTRkZjJiMzFkYjhhODI4ZGY4MjVjOTg5ZGI4OTVl");
                   +  - ]
     210   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(req.GetHeader("Content-Length"), "46");
                   +  - ]
     211   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req.ReadBody(), R"({"method":"getblockcount","params":[],"id":1})""\n");
                   +  - ]
     212                 :           1 :     }
     213                 :           1 :     {
     214                 :             :         // Malformed: no spaces between data
     215                 :           1 :         HTTPRequest req;
     216         [ +  - ]:           1 :         LineReader reader("GET/HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     217   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line too short"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     218                 :           1 :     }
     219                 :           1 :     {
     220                 :             :         // Malformed: too many spaces
     221                 :           1 :         HTTPRequest req;
     222         [ +  - ]:           1 :         LineReader reader("GET / HTTP / 1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     223   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line malformed"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     224                 :           1 :     }
     225                 :           1 :     {
     226                 :             :         // Malformed: slash missing before version
     227                 :           1 :         HTTPRequest req;
     228         [ +  - ]:           1 :         LineReader reader("GET / HTTP1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     229   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line too short"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     230                 :           1 :     }
     231                 :           1 :     {
     232                 :             :         // Malformed: no decimal in version
     233                 :           1 :         HTTPRequest req;
     234         [ +  - ]:           1 :         LineReader reader("GET / HTTP/11\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     235   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line too short"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     236                 :           1 :     }
     237                 :           1 :     {
     238                 :             :         // Malformed: version is not a number
     239                 :           1 :         HTTPRequest req;
     240         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.x\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     241   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     242                 :           1 :     }
     243                 :           1 :     {
     244                 :             :         // Malformed: version is out of range
     245                 :           1 :         HTTPRequest req;
     246         [ +  - ]:           1 :         LineReader reader("GET / HTTP/2.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     247   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     248                 :           1 :     }
     249                 :           1 :     {
     250                 :             :         // Malformed: version is out of range
     251                 :           1 :         HTTPRequest req;
     252         [ +  - ]:           1 :         LineReader reader("GET / HTTP/0.9\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     253   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     254                 :           1 :     }
     255                 :           1 :     {
     256                 :             :         // Malformed: version is out of range
     257                 :           1 :         HTTPRequest req;
     258         [ +  - ]:           1 :         LineReader reader("GET / HTTP/-1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     259   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     260                 :           1 :     }
     261                 :           1 :     {
     262                 :             :         // Malformed: version is not exactly two integers and a dot
     263                 :           1 :         HTTPRequest req;
     264         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.00\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     265   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     266                 :           1 :     }
     267                 :           1 :     {
     268                 :             :         // Malformed: contains NUL
     269                 :           1 :         HTTPRequest req;
     270         [ +  - ]:           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};
     271   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"Invalid request line contains NUL"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     272                 :           1 :     }
     273                 :           1 :     {
     274                 :             :         // Malformed: differing Content-Length values, case insensitive
     275                 :           1 :         constexpr std::string_view differing_length = "GET / HTTP/1.1\n"
     276                 :             :                                                       "Host: 127.0.0.1\n"
     277                 :             :                                                       "Content-Length: 8\n"
     278                 :             :                                                       "content-length: 9\n\n"
     279                 :             :                                                       "12345678";
     280                 :           1 :         HTTPRequest req;
     281         [ +  - ]:           1 :         util::LineReader reader{differing_length, /*max_line_length=*/MAX_HEADERS_SIZE};
     282   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     283   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     284   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadBody(reader), std::runtime_error, HasReason{"Differing Content-Length values"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     285                 :           1 :     }
     286                 :           1 :     {
     287                 :             :         // Ok: multiple same Content-Length values
     288                 :           1 :         constexpr std::string_view differing_length = "GET / HTTP/1.1\n"
     289                 :             :                                                       "Host: 127.0.0.1\n"
     290                 :             :                                                       "Content-Length: 8\n"
     291                 :             :                                                       "content-length: 8\n\n"
     292                 :             :                                                       "12345678";
     293                 :           1 :         HTTPRequest req;
     294         [ +  - ]:           1 :         util::LineReader reader{differing_length, /*max_line_length=*/MAX_HEADERS_SIZE};
     295   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     296   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     297   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadBody(reader));
                   +  - ]
     298                 :           1 :     }
     299                 :           1 :     {
     300                 :             :         // Ok
     301                 :           1 :         HTTPRequest req;
     302         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n", 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 :         BOOST_CHECK_EQUAL(req.GetRequestMethod(), HTTPRequestMethod::GET);
     307   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req.GetURI(), "/");
                   +  - ]
     308   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.GetVersion().major, 1);
     309   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req.GetVersion().minor, 0);
     310   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(req.GetHeader("Host"), "127.0.0.1");
                   +  - ]
     311                 :             :         // no body is OK
     312   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req.ReadBody(), "");
                   +  - ]
     313                 :           1 :     }
     314                 :           1 :     {
     315                 :             :         // Malformed: missing colon
     316                 :           1 :         HTTPRequest req;
     317         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.0\r\nHost=127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
     318   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     319   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadHeaders(reader), std::runtime_error, HasReason{"HTTP header missing colon (:)"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     320                 :           1 :     }
     321                 :           1 :     {
     322                 :             :         // We might not have received enough data from the client which is not
     323                 :             :         // an error. We return false so the caller can try again later when the
     324                 :             :         // buffer has more data.
     325                 :           1 :         HTTPRequest req;
     326         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.0\r\nHost: ", MAX_HEADERS_SIZE);
     327   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     328   [ +  -  +  -  :           2 :         BOOST_CHECK(!req.LoadHeaders(reader));
                   +  - ]
     329                 :           1 :     }
     330                 :           1 :     {
     331                 :             :         // No Content-Length: body is not read
     332                 :           1 :         HTTPRequest req;
     333         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.0\r\n\r\n" R"({"method":"getblockcount"})", MAX_HEADERS_SIZE);
     334   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     335   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     336   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadBody(reader));
             +  -  +  - ]
     337                 :             :         // Don't try to read request body if Content-Length is missing
     338   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req.ReadBody(), "");
                   +  - ]
     339                 :           1 :     }
     340                 :           1 :     {
     341                 :             :         // Malformed: Content-Length is not a number
     342                 :           1 :         HTTPRequest req;
     343         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.0\r\nContent-Length: eleven\r\n\r\n" R"({"method":"getblockcount"})", MAX_HEADERS_SIZE);
     344   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     345   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     346   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadBody(reader), std::runtime_error, HasReason{"Cannot parse Content-Length value"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     347                 :           1 :     }
     348                 :           1 :     {
     349                 :             :         // Malformed: Content-Length is negative
     350                 :           1 :         HTTPRequest req;
     351         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.0\r\nContent-Length: -8\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                 :             :         // Content-Length exceeds limit
     358                 :           1 :         constexpr auto excessive_size{MAX_BODY_SIZE + 1};
     359         [ +  - ]:           1 :         std::string huge_body(excessive_size, 'x');
     360   [ +  -  +  -  :           3 :         const std::string request{"GET / HTTP/1.0\r\nContent-Length: " + util::ToString(excessive_size) + "\r\n\r\n" + std::move(huge_body)};
                   +  - ]
     361                 :           1 :         HTTPRequest req;
     362   [ -  +  +  - ]:           1 :         LineReader reader(request, MAX_HEADERS_SIZE);
     363   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     364   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     365   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadBody(reader), ContentTooLargeError, HasReason{"Max body size exceeded"});
          -  -  -  -  -  
          +  +  -  +  -  
             +  -  +  - ]
     366                 :           1 :     }
     367                 :           1 :     {
     368                 :             :         // Content-Length exactly on the limit
     369         [ +  - ]:           1 :         std::string max_body(MAX_BODY_SIZE, 'x');
     370   [ +  -  +  -  :           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)};
                   +  - ]
     371                 :           1 :         HTTPRequest req;
     372   [ -  +  +  - ]:           1 :         LineReader reader(request, MAX_HEADERS_SIZE);
     373   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     374   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     375   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadBody(reader));
                   +  - ]
     376                 :           1 :     }
     377                 :           1 :     {
     378                 :             :         // Content-Length indicates more data than we have in the buffer.
     379                 :             :         // Not an error; we wait for more data before completing the body.
     380                 :           1 :         HTTPRequest req;
     381         [ +  - ]:           1 :         LineReader reader("GET / HTTP/1.0\r\nContent-Length: 1024\r\n\r\n" R"({"method":"getblockcount"})", MAX_HEADERS_SIZE);
     382   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     383   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     384   [ +  -  +  -  :           2 :         BOOST_CHECK(!req.LoadBody(reader));
                   +  - ]
     385                 :           1 :     }
     386                 :           1 :     {
     387                 :             :         // Support "chunked" transfer. Chunk lengths are ascii-encoded hex integers, whitespace ignored
     388                 :           1 :         HTTPRequest req;
     389                 :           1 :         std::string_view ok_chunked = "GET / HTTP/1.0\n"
     390                 :             :                                       "Transfer-Encoding: chunked\n"
     391                 :             :                                       "\n"
     392                 :             :                                       "10\n"
     393                 :             :                                       R"({"method":"getbl)""\n"
     394                 :             :                                       "   a    \n"
     395                 :             :                                       R"(ockcount"})""\n"
     396                 :             :                                       "0\n"
     397                 :             :                                       "\n";
     398         [ +  - ]:           1 :         LineReader reader(ok_chunked, MAX_HEADERS_SIZE);
     399   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     400   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     401   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadBody(reader));
             +  -  +  - ]
     402   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req.ReadBody(), R"({"method":"getblockcount"})");
                   +  - ]
     403                 :           1 :     }
     404                 :           1 :     {
     405                 :             :         // Prevent "chunked" transfer from exceeding size limit
     406                 :           1 :         HTTPRequest req;
     407                 :           1 :         std::string_view excessive_chunk_size = "GET / HTTP/1.0\n"
     408                 :             :                                                 "Transfer-Encoding: chunked\n"
     409                 :             :                                                 "\n"
     410                 :             :                                                 "10\n"
     411                 :             :                                                 R"({"method":"getbl)""\n"
     412                 :             :                                                 "20000000\n"
     413                 :             :                                                 R"(ockcount"})""\n"
     414                 :             :                                                 "0\n"
     415                 :             :                                                 "\n";
     416         [ +  - ]:           1 :         LineReader reader(excessive_chunk_size, MAX_HEADERS_SIZE);
     417   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     418   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     419   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadBody(reader), ContentTooLargeError, HasReason{"Chunk will exceed max body size"});
          -  -  -  -  -  
          +  +  -  +  -  
             +  -  +  - ]
     420                 :           1 :     }
     421                 :           1 :     {
     422                 :             :         // Allow (but ignore) Chunk Extensions
     423                 :           1 :         HTTPRequest req;
     424                 :           1 :         std::string_view ok_chunked = "GET / HTTP/1.0\n"
     425                 :             :                                       "Transfer-Encoding: chunked\n"
     426                 :             :                                       "\n"
     427                 :             :                                       "10;sha256=715790e8a3b09d704ac9641f42d183a5ebc5fd939663de23da548519ac2165e5\n"
     428                 :             :                                       R"({"method":"getbl)""\n"
     429                 :             :                                       "   a   ;    compressed\n"
     430                 :             :                                       R"(ockcount"})""\n"
     431                 :             :                                       "0;why;would;anyone;do;this;\n"
     432                 :             :                                       "Expires: Wed, 21 Oct 2026 07:28:00 GMT\n"
     433                 :             :                                       "\n";
     434         [ +  - ]:           1 :         LineReader reader(ok_chunked, MAX_HEADERS_SIZE);
     435   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     436   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     437   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadBody(reader));
             +  -  +  - ]
     438   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req.ReadBody(), R"({"method":"getblockcount"})");
                   +  - ]
     439                 :             :         // Chunk Trailer was parsed, but ignored
     440   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(reader.Remaining(), 0);
                   +  - ]
     441   [ +  -  +  -  :           2 :         BOOST_CHECK(!req.GetHeader("Expires"));
                   +  - ]
     442                 :           1 :     }
     443                 :           1 :     {
     444                 :             :         // Invalid "chunked" transfer, using roman numerals instead of hex for chunk length
     445                 :           1 :         HTTPRequest req;
     446                 :           1 :         std::string_view invalid_chunked = "GET / HTTP/1.0\n"
     447                 :             :                                            "Transfer-Encoding: chunked\n"
     448                 :             :                                            "\n"
     449                 :             :                                            "XVI\n"
     450                 :             :                                            R"({"method":"getbl)""\n"
     451                 :             :                                            "X\n"
     452                 :             :                                            R"(ockcount"})""\n"
     453                 :             :                                            "0\n"
     454                 :             :                                            "\n";
     455         [ +  - ]:           1 :         LineReader reader(invalid_chunked, MAX_HEADERS_SIZE);
     456   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     457   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     458   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadBody(reader), std::runtime_error, HasReason{"Cannot parse chunk length value"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     459                 :           1 :     }
     460                 :           1 :     {
     461                 :             :         // Invalid "chunked" transfer, missing chunk termination \n
     462                 :           1 :         HTTPRequest req;
     463                 :           1 :         std::string_view invalid_chunked = "GET / HTTP/1.0\n"
     464                 :             :                                            "Transfer-Encoding: chunked\n"
     465                 :             :                                            "\n"
     466                 :             :                                            "10\n"
     467                 :             :                                            R"({"method":"getbl)"
     468                 :             :                                            "a\n" // interpreted as extra data at the end of `0x10`-sized chunk
     469                 :             :                                            R"(ockcount"})"
     470                 :             :                                            "0\n"
     471                 :             :                                            "\n";
     472         [ +  - ]:           1 :         LineReader reader(invalid_chunked, MAX_HEADERS_SIZE);
     473   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadControlData(reader));
             +  -  +  - ]
     474   [ +  -  +  -  :           2 :         BOOST_CHECK(req.LoadHeaders(reader));
             +  -  +  - ]
     475   [ +  -  -  +  :           2 :         BOOST_CHECK_EXCEPTION(req.LoadBody(reader), std::runtime_error, HasReason{"Improperly terminated chunk"});
          -  -  -  -  -  
          +  +  -  +  -  
                   +  - ]
     476                 :           1 :     }
     477                 :           1 : }
     478                 :             : 
     479   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(http_request_state_tests)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     480                 :             : {
     481                 :             :     // For these tests we just need a receive buffer for the requests to read from.
     482                 :          11 :     class DummyClient : public HTTPRemoteClient
     483                 :             :     {
     484                 :             :     public:
     485   [ +  -  +  - ]:          10 :         DummyClient() : HTTPRemoteClient{/*id=*/0, /*addr=*/CService(), /*socket=*/CreateSock(0, 0, 0)} {}
     486                 :             : 
     487                 :        1665 :         void receive(std::string_view s)
     488                 :             :         {
     489   [ +  -  +  -  :        1662 :             MutateRecvBuffer().append(s);
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     490                 :        1665 :         }
     491                 :             :     };
     492                 :             : 
     493                 :           1 :     {
     494                 :             :         // Step through state machine
     495                 :           1 :         std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
     496   [ +  -  +  -  :           2 :         BOOST_CHECK(!client->GetRequest());
                   +  - ]
     497                 :             : 
     498         [ +  - ]:           1 :         client->receive("POST / HTTP/1.0\n");
     499   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     500   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsHeaders);
     501                 :             : 
     502         [ +  - ]:           1 :         client->receive("Host: 127.0.0.1\n"
     503                 :             :                         "Content-Length: 10\n\n");
     504   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     505   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
     506                 :             : 
     507         [ +  - ]:           1 :         client->receive("I miss you\n");
     508   [ +  -  +  - ]:           2 :         auto req{HTTPRemoteClient::TryReadRequest(client)};
     509   [ +  -  +  -  :           2 :         BOOST_REQUIRE(req);
                   +  - ]
     510   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req->GetState(), HTTPRequest::State::Complete);
     511         [ +  - ]:           1 :     }
     512                 :           1 :     {
     513                 :             :         // Read body over multiple data pushes, multiple requests in same push
     514                 :           1 :         std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
     515   [ +  -  +  -  :           2 :         BOOST_CHECK(!client->GetRequest());
                   +  - ]
     516                 :             : 
     517         [ +  - ]:           1 :         client->receive("POST / HTTP/1.0\n"
     518                 :             :                         "Host: 127.0.0.1\n"
     519                 :             :                         "Content-Length: 10\n\n"
     520                 :             :                         "I miss");
     521   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     522                 :             :         // Because of the Content-Length header we know the body is not complete
     523   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
     524                 :             : 
     525                 :             :         // Finish sending first request and include second request in the same buffer
     526         [ +  - ]:           1 :         client->receive(" you"
     527                 :             :                         "GET /endpoint HTTP/1.0\n\n");
     528   [ +  -  +  - ]:           2 :         auto req{HTTPRemoteClient::TryReadRequest(client)};
     529   [ +  -  +  -  :           2 :         BOOST_REQUIRE(req);
                   +  - ]
     530   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req->GetState(), HTTPRequest::State::Complete);
     531   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req->GetURI(), "/");
                   +  - ]
     532   [ +  -  +  -  :           2 :         BOOST_CHECK(!client->GetRequest());
                   +  - ]
     533   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req->ReadBody(), "I miss you");
                   +  - ]
     534         [ +  - ]:           1 :         req->WriteReply(HTTP_OK, ""); // Mark client as no longer busy
     535                 :             :         // Next request sitting in buffer
     536   [ +  -  -  +  :           1 :         BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 24);
                   +  - ]
     537                 :             : 
     538                 :             :         // Read second request
     539   [ +  -  +  -  :           3 :         req = HTTPRemoteClient::TryReadRequest(client);
                   +  - ]
     540   [ +  -  +  -  :           2 :         BOOST_REQUIRE(req);
                   +  - ]
     541   [ +  -  +  -  :           2 :         BOOST_CHECK(!client->GetRequest());
                   +  - ]
     542   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req->GetState(), HTTPRequest::State::Complete);
     543   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req->GetURI(), "/endpoint");
                   +  - ]
     544   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req->ReadBody().size(), 0);
             -  +  +  - ]
     545                 :             :         // Buffer is cleared
     546   [ +  -  -  +  :           1 :         BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 0);
                   +  - ]
     547         [ +  - ]:           1 :     }
     548                 :           1 :     {
     549                 :             :         // A Content-Length body is drained out of the receive buffer as it
     550                 :             :         // arrives, instead of accumulating there until the request is complete.
     551                 :             : 
     552                 :           1 :         std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
     553   [ +  -  +  -  :           2 :         BOOST_CHECK(!client->GetRequest());
                   +  - ]
     554                 :             : 
     555         [ +  - ]:           1 :         client->receive("POST / HTTP/1.0\n"
     556                 :             :                         "Content-Length: 30000\n\n");
     557   [ +  -  +  -  :           2 :         HTTPRemoteClient::TryReadRequest(client);
                   +  - ]
     558   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
     559                 :             : 
     560                 :             :         // Body arrives in 10kB pieces. Each one is copied onto m_body and
     561                 :             :         // erased from the receive buffer, which never holds more than one piece.
     562         [ +  + ]:           4 :         for (int i = 1; i <= 3; ++i) {
     563   [ +  -  -  +  :           6 :             client->receive(std::string(10000, 'x'));
                   +  - ]
     564   [ +  -  -  +  :           3 :             BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 10000);
                   +  - ]
     565   [ +  -  +  - ]:           6 :             auto req{HTTPRemoteClient::TryReadRequest(client)};
     566         [ +  + ]:           3 :             if (i < 3) {
     567   [ +  -  +  -  :           4 :                 BOOST_CHECK(!req.get());
                   +  - ]
     568   [ +  -  -  +  :           4 :                 BOOST_CHECK_EQUAL(client->GetRequest()->ReadBody().size(), 10000 * i);
             -  +  +  - ]
     569                 :             :             } else {
     570   [ +  -  +  -  :           2 :                 BOOST_CHECK(req.get());
                   +  - ]
     571   [ +  -  -  +  :           2 :                 BOOST_CHECK_EQUAL(req->ReadBody().size(), 10000 * i);
             -  +  +  - ]
     572   [ +  -  +  - ]:           1 :                 BOOST_CHECK_EQUAL(req->GetState(), HTTPRequest::State::Complete);
     573   [ +  -  +  - ]:           2 :                 BOOST_CHECK(!client->GetRequest());
     574                 :             :             }
     575   [ +  -  -  +  :           3 :             BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 0);
                   +  - ]
     576                 :           3 :         }
     577                 :           0 :     }
     578                 :           1 :     {
     579                 :             :         // A body sent in the same push as the next request is split correctly
     580                 :           1 :         std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
     581   [ +  -  +  -  :           2 :         BOOST_CHECK(!client->GetRequest());
                   +  - ]
     582                 :             : 
     583         [ +  - ]:           1 :         client->receive("POST / HTTP/1.0\n"
     584                 :             :                         "Content-Length: 4\n\n"
     585                 :             :                         "body"
     586                 :             :                         "GET /next HTTP/1.0\n\n");
     587   [ +  -  +  - ]:           2 :         auto req{HTTPRemoteClient::TryReadRequest(client)};
     588   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req->GetState(), HTTPRequest::State::Complete);
     589   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req->ReadBody(), "body");
                   +  - ]
     590                 :             :         // Only the second request is left over
     591   [ +  -  -  +  :           1 :         BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 20);
                   +  - ]
     592         [ +  - ]:           1 :     }
     593                 :           1 :     {
     594                 :             :         // Chunked transfer with state
     595                 :           1 :         std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
     596   [ +  -  +  -  :           2 :         BOOST_CHECK(!client->GetRequest());
                   +  - ]
     597                 :             : 
     598                 :             :         // First chunk is incomplete
     599         [ +  - ]:           1 :         client->receive("GET / HTTP/1.0\n"
     600                 :             :                         "Transfer-Encoding: chunked\n"
     601                 :             :                         "\n"
     602                 :             :                         "10\n"
     603                 :             :                         R"({"method)");
     604   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     605   [ +  -  +  -  :           2 :         BOOST_REQUIRE(client->GetRequest()->GetChunkSize());
                   +  - ]
     606   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(*client->GetRequest()->GetChunkSize(), 16);
     607   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetChunkProgress(), 8);
     608   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(client->GetRequest()->ReadBody().size(), 8);
             -  +  +  - ]
     609   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
     610                 :             : 
     611                 :             :         // More data arrives, chunk is completed.
     612         [ +  - ]:           1 :         client->receive(R"(":"getbl)""\n");
     613   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     614                 :             :         // State is reset
     615   [ +  -  +  -  :           2 :         BOOST_CHECK(!client->GetRequest()->GetChunkSize());
                   +  - ]
     616   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetChunkProgress(), 0);
     617                 :             :         // New data is added to body but body is still incomplete
     618   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(client->GetRequest()->ReadBody().size(), 16);
             -  +  +  - ]
     619   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
     620                 :             : 
     621                 :             :         // Next chunk arrives without terminal CRLF
     622         [ +  - ]:           1 :         client->receive("a\n"
     623                 :             :                         R"(ockcount"})");
     624   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     625   [ +  -  +  -  :           2 :         BOOST_CHECK(client->GetRequest()->GetChunkSize());
                   +  - ]
     626   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(*client->GetRequest()->GetChunkSize(), 10);
     627   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetChunkProgress(), 10);
     628   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(client->GetRequest()->ReadBody().size(), 26);
             -  +  +  - ]
     629   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
     630                 :             : 
     631                 :             :         // Chunk terminal CRLF arrives with final (size 0) chunk
     632         [ +  - ]:           1 :         client->receive("\n0\n\n");
     633   [ +  -  +  - ]:           2 :         auto req{HTTPRemoteClient::TryReadRequest(client)};
     634                 :             :         // Body size hasn't changed
     635   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req->ReadBody().size(), 26);
             -  +  +  - ]
     636                 :             :         // We're done
     637   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req->GetState(), HTTPRequest::State::Complete);
     638   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req->ReadBody(), R"({"method":"getblockcount"})");
                   +  - ]
     639         [ +  - ]:           1 :     }
     640                 :           1 :     {
     641                 :             :         // Invalid headers: error state stops reading
     642                 :           1 :         std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
     643                 :             : 
     644                 :             :         // Request is in the buffer
     645         [ +  - ]:           1 :         client->receive("POST / HTTP/1.0\n"
     646                 :             :                         "Host: 127.0.0.1\n");
     647   [ +  -  +  -  :           2 :         BOOST_CHECK(!client->GetRecvBuffer().empty());
                   +  - ]
     648   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     649   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsHeaders);
     650         [ +  - ]:           1 :         client->receive("Invalid header with no colon\n"
     651                 :             :                         "\n"
     652                 :             :                         "body is not read");
     653                 :             :         // Reading throws an error, sets state
     654   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     655   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::Error);
     656                 :             : 
     657                 :             :         // We read up to the invalid line
     658   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetHeader("Host"), "127.0.0.1");
                   +  - ]
     659                 :             :         // Buffer was cleared, client should just be disconnected now
     660   [ +  -  +  -  :           2 :         BOOST_CHECK(client->GetRecvBuffer().empty());
                   +  - ]
     661                 :             : 
     662                 :             :         // Even if more data comes in, trying to read again in error state is a no-op
     663         [ +  - ]:           1 :         client->receive("Content-Length: 2\n\nok");
     664   [ +  -  -  +  :           1 :         BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 21);
                   +  - ]
     665   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     666   [ +  -  -  +  :           1 :         BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 21);
             +  -  +  - ]
     667                 :           0 :     }
     668                 :           1 :     {
     669                 :             :         // Headers sent in batches that are below MAX_HEADERS_SIZE but the total is excessive
     670                 :           1 :         std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
     671   [ +  -  +  -  :           2 :         BOOST_CHECK(!client->GetRequest());
                   +  - ]
     672   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     673   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::Init);
     674                 :             : 
     675         [ +  - ]:           1 :         client->receive("POST /huge HTTP/1.0\n");
     676   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     677   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsHeaders);
     678                 :             : 
     679         [ +  + ]:         411 :         for (int i = 0; i < 410; ++i) {
     680         [ +  - ]:         820 :             client->receive("key:value\n");
     681                 :             :         }
     682   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     683   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsHeaders);
     684                 :             : 
     685         [ +  + ]:         410 :         for (int i = 0; i < 409; ++i) {
     686         [ +  - ]:         818 :             client->receive("key:value\n");
     687                 :             :         }
     688   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     689   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsHeaders);
     690                 :             : 
     691                 :             :         // We're at 819 x 10-byte headers
     692                 :             :         // The limit is 8192, three more bytes should throw.
     693         [ +  - ]:           1 :         client->receive("k:\n");
     694   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     695   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::Error);
                   +  - ]
     696                 :           0 :     }
     697                 :           1 :     {
     698                 :             :         // Client sends chunks that are below the limit but the total is excessive
     699                 :           1 :         std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
     700   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     701   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::Init);
     702                 :             : 
     703         [ +  - ]:           1 :         client->receive("POST /huge HTTP/1.0\n");
     704   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     705   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsHeaders);
     706                 :             : 
     707         [ +  - ]:           1 :         client->receive("Transfer-Encoding: chunked\n\n");
     708   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     709   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
     710                 :             : 
     711                 :             :         // Send 16-byte chunk
     712         [ +  - ]:           1 :         client->receive("10\nno auto updates!\n");
     713   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     714   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
     715                 :             : 
     716                 :             :         // The next chunk will be of size 32MiB - 16 + 1, below the limit
     717                 :             :         // on its own but not if it were added to the total cumulative body so far.
     718                 :             :         // We don't need to actually send or prepare this amount of data.
     719         [ +  - ]:           1 :         client->receive("1fffff1\n");
     720   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     721   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::Error);
                   +  - ]
     722                 :           0 :     }
     723                 :           1 :     {
     724                 :             :         // Ensure chunk trailer is parsed over state lines
     725                 :           1 :         std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
     726   [ +  -  +  -  :           2 :         BOOST_CHECK(!client->GetRequest());
                   +  - ]
     727                 :             : 
     728                 :             :         // Send a 1-byte chunk then send the 0-chunk with a trailer but no terminal CRLF
     729         [ +  - ]:           1 :         client->receive("GET / HTTP/1.0\n"
     730                 :             :                         "Transfer-Encoding: chunked\n"
     731                 :             :                         "\n"
     732                 :             :                         "1\n"
     733                 :             :                         "x\n"
     734                 :             :                         "0\n"
     735                 :             :                         "Digest: sha-4=deadbeef\n");
     736   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     737   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
     738                 :             : 
     739                 :             :         // Send first part of another trailer line
     740         [ +  - ]:           1 :         client->receive("Expires:");
     741   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     742   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
     743                 :             : 
     744                 :             :         // Finish the trailer line
     745         [ +  - ]:           1 :         client->receive("never\n");
     746   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     747   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
     748                 :             : 
     749                 :             :         // Terminate
     750         [ +  - ]:           1 :         client->receive("\n");
     751   [ +  -  +  - ]:           2 :         auto req{HTTPRemoteClient::TryReadRequest(client)};
     752   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(req->GetState(), HTTPRequest::State::Complete);
     753   [ +  -  -  +  :           2 :         BOOST_CHECK_EQUAL(req->ReadBody(), "x");
                   +  - ]
     754         [ +  - ]:           1 :     }
     755                 :           1 :     {
     756                 :             :         // Ensure chunk trailer counts towards the headers size limit
     757                 :           1 :         std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
     758   [ +  -  +  -  :           2 :         BOOST_CHECK(!client->GetRequest());
                   +  - ]
     759                 :             : 
     760         [ +  - ]:           1 :         client->receive("POST /huge HTTP/1.0\n"
     761                 :             :                         "Transfer-Encoding: chunked\n"); //   27 bytes
     762         [ +  + ]:         817 :         for (int i = 0; i < 816; ++i) {
     763         [ +  - ]:        1632 :             client->receive("key:value\n");              // 8160
     764                 :             :         }
     765         [ +  - ]:           1 :         client->receive("\n"                             //    1
     766                 :             :                         "1\n"
     767                 :             :                         "x\n"
     768                 :             :                         "0\n");
     769   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     770   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
     771                 :             : 
     772                 :             :         // We're in the trailer section with a total of 8188 bytes of headers.
     773                 :             :         // The limit is 8192, five more bytes should throw.
     774         [ +  - ]:           1 :         client->receive("k:vv\n");
     775   [ +  -  +  -  :           4 :         BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
          +  -  +  -  +  
                -  +  - ]
     776   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::Error);
                   +  - ]
     777                 :           1 :     }
     778                 :           1 : }
     779                 :             : 
     780   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(http_server_socket_tests)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     781                 :             : {
     782                 :             :     // Hard code the timestamp for the Date header in the HTTP response
     783                 :             :     // Wed Dec 11 00:47:09 2024 UTC
     784                 :           1 :     FakeNodeClock clock{1733878029s};
     785                 :             : 
     786                 :             :     // Prepare a request handler that just stores received requests so we can examine them.
     787                 :             :     // Mutex is required to prevent a race between this test's main thread and the server's I/O loop.
     788                 :           1 :     Mutex requests_mutex;
     789         [ +  - ]:           1 :     std::deque<std::unique_ptr<HTTPRequest>> requests;
     790                 :           2 :     auto StoreRequest = [&](std::unique_ptr<HTTPRequest>&& req) {
     791                 :           1 :         LOCK(requests_mutex);
     792   [ +  -  +  - ]:           1 :         requests.push_back(std::move(req));
     793                 :           2 :     };
     794                 :             : 
     795         [ +  - ]:           1 :     HTTPServer server{StoreRequest};
     796         [ +  - ]:           1 :     server.InitHTTPAllowList();
     797                 :             : 
     798                 :           1 :     {
     799                 :             :         // We can only bind to NET_IPV4 and NET_IPV6
     800   [ +  -  +  -  :           2 :         CService onion_address{Lookup("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaam2dqd.onion", /*portDefault=*/0, /*fAllowLookup=*/false).value()};
                   +  - ]
     801         [ +  - ]:           1 :         auto result{server.BindAndStartListening(onion_address)};
     802   [ +  -  +  -  :           2 :         BOOST_REQUIRE(!result);
                   +  - ]
     803   [ +  -  +  - ]:           1 :         BOOST_CHECK_EQUAL(result.error(), "Bind address family for aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaam2dqd.onion:0 not supported");
     804                 :           1 :     }
     805                 :             : 
     806                 :             :     // This VALID address won't actually get used because we stubbed CreateSock()
     807   [ +  -  +  -  :           2 :     CService addr_bind{Lookup("0.0.0.0", /*portDefault=*/0, /*fAllowLookup=*/false).value()};
                   +  - ]
     808                 :             : 
     809                 :             :     // Init state
     810   [ +  -  -  +  :           1 :     BOOST_REQUIRE_EQUAL(server.GetListeningSocketCount(), 0);
                   +  - ]
     811                 :             :     // Bind to mock Listening Socket
     812   [ +  -  +  -  :           2 :     BOOST_REQUIRE(server.BindAndStartListening(addr_bind));
             +  -  +  - ]
     813                 :             :     // We are bound and listening
     814   [ +  -  -  +  :           1 :     BOOST_REQUIRE_EQUAL(server.GetListeningSocketCount(), 1);
                   +  - ]
     815                 :             : 
     816                 :             :     // Start the I/O loop
     817         [ +  - ]:           1 :     server.StartSocketsThreads();
     818                 :             : 
     819                 :             :     // No connections yet
     820   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(server.GetConnectionsCount(), 0);
     821                 :             : 
     822                 :             :     // Create a mock client with pre-loaded request data and add it to the local CreateSock queue.
     823                 :             :     // Keep a handle for the mock client's send and receive pipes so we can examine
     824                 :             :     // the data it "receives".
     825         [ +  - ]:           1 :     std::shared_ptr<DynSock::Pipes> mock_client_socket_pipes{ConnectClient(std::as_bytes(std::span(full_request)))};
     826                 :             : 
     827                 :             :     // Wait up to a minute to find and connect the client in the I/O loop
     828                 :             :     int attempts{6000};
     829                 :           2 :     while (server.GetConnectionsCount() < 1) {
     830         [ +  - ]:           1 :         std::this_thread::sleep_for(10ms);
     831   [ +  -  +  -  :           3 :         BOOST_REQUIRE(--attempts > 0);
                   +  + ]
     832                 :             :     }
     833                 :             : 
     834                 :             :     // Prepare a pointer to the client, we'll assign it from the request itself.
     835                 :           1 :     std::shared_ptr<HTTPRemoteClient> client;
     836                 :             : 
     837                 :             :     // Wait up to a minute to read the request from the client.
     838                 :             :     // Given that the mock client is itself a mock socket
     839                 :             :     // with hard-coded data it should only take a fraction of that.
     840                 :           1 :     attempts = 6000;
     841                 :           0 :     while (true) {
     842                 :           1 :         {
     843         [ +  - ]:           1 :             LOCK(requests_mutex);
     844                 :             :             // Connected client should have one request already from the static content.
     845   [ -  +  +  - ]:           1 :             if (requests.size() == 1) {
     846                 :             :                 // Check the received request
     847   [ +  -  -  +  :           2 :                 BOOST_CHECK_EQUAL(requests.front()->ReadBody(), R"({"method":"getblockcount","params":[],"id":1})""\n");
                   +  - ]
     848   [ +  -  +  -  :           1 :                 BOOST_CHECK_EQUAL(requests.front()->GetPeer().ToStringAddrPort(), "5.5.5.5:6789");
             +  -  +  - ]
     849                 :             : 
     850                 :             :                 // Inspect the connection pointed to from the request
     851         [ -  + ]:           1 :                 client = requests.front()->GetClient();
     852   [ +  -  +  -  :           2 :                 BOOST_REQUIRE(client);
                   +  - ]
     853   [ +  -  +  - ]:           1 :                 BOOST_CHECK_EQUAL(client->GetOrigin(), "5.5.5.5:6789");
     854                 :             : 
     855                 :             :                 // Respond to request
     856         [ +  - ]:           1 :                 requests.front()->WriteReply(HTTP_OK, "874140\n");
     857                 :             : 
     858         [ +  - ]:           1 :                 break;
     859                 :             :             }
     860                 :           0 :         }
     861         [ #  # ]:           0 :         std::this_thread::sleep_for(10ms);
     862   [ #  #  #  # ]:           0 :         BOOST_REQUIRE(--attempts > 0);
     863                 :             :     }
     864                 :             : 
     865                 :             :     // Check the sent response from the mock client at the other end of the mock socket
     866                 :           1 :     std::string actual;
     867                 :             :     // Wait up to one minute for all the bytes to appear in the "send" pipe.
     868                 :           1 :     char buf[0x10000] = {};
     869                 :           1 :     attempts = 6000;
     870         [ +  - ]:           1 :     while (attempts > 0)
     871                 :             :     {
     872         [ +  - ]:           1 :         ssize_t bytes_read = mock_client_socket_pipes->send.GetBytes(buf, sizeof(buf), 0);
     873         [ +  - ]:           1 :         if (bytes_read > 0) {
     874         [ +  - ]:           1 :             actual.append(buf, bytes_read);
     875   [ -  +  -  + ]:           1 :             if (actual.length() == 146) {
     876                 :             :                 break;
     877                 :             :             }
     878                 :             :         }
     879         [ #  # ]:           0 :         std::this_thread::sleep_for(10ms);
     880                 :           0 :         --attempts;
     881                 :             :     }
     882   [ +  -  -  +  :           2 :     BOOST_CHECK(actual.starts_with("HTTP/1.1 200 OK\r\n"));
             +  -  +  - ]
     883   [ +  -  -  +  :           2 :     BOOST_CHECK(actual.ends_with("\r\n874140\n"));
             +  -  +  - ]
     884                 :             :     // Headers can be sorted in any order, and will be, since we use unordered_map
     885   [ +  -  +  -  :           2 :     BOOST_CHECK(actual.find("Connection: close\r\n") != std::string::npos);
                   +  - ]
     886   [ +  -  +  -  :           2 :     BOOST_CHECK(actual.find("Content-Length: 7\r\n") != std::string::npos);
                   +  - ]
     887   [ +  -  +  -  :           2 :     BOOST_CHECK(actual.find("Content-Type: text/html; charset=ISO-8859-1\r\n") != std::string::npos);
                   +  - ]
     888   [ +  -  +  - ]:           2 :     BOOST_CHECK(actual.find("Date: Wed, 11 Dec 2024 00:47:09 GMT\r\n") != std::string::npos);
     889                 :             : 
     890                 :             :     // Wait up to one minute for connection to be automatically closed, because
     891                 :             :     // keep-alive was not set by the client and we are done responding to their request.
     892                 :           1 :     attempts = 6000;
     893                 :           1 :     while (server.GetConnectionsCount() != 0) {
     894         [ +  - ]:           5 :         std::this_thread::sleep_for(10ms);
     895   [ +  -  +  -  :          11 :         BOOST_REQUIRE(--attempts > 0);
                   +  + ]
     896                 :             :     }
     897                 :             : 
     898                 :             :     // Stop the I/O loop and shutdown
     899         [ +  - ]:           1 :     server.InterruptNet();
     900                 :             :     // Wait for I/O loop to finish, after all connected sockets are closed
     901         [ +  - ]:           1 :     server.JoinSocketsThreads();
     902                 :             :     // Close all listening sockets
     903         [ +  - ]:           1 :     server.StopListening();
     904   [ +  -  +  - ]:           3 : }
     905                 :             : 
     906   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(http_socket_error_tests)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
     907                 :             : {
     908                 :             :     // Create a tiny threadpool for the HTTPRequest handler
     909         [ +  - ]:           1 :     ThreadPool workers("http");
     910         [ +  - ]:           1 :     workers.Start(1);
     911                 :             : 
     912                 :             :     // Hard-code the server's request handler to respond to each request with
     913                 :             :     // an incremented block count. Handle the replies in the worker thread.
     914                 :           1 :     std::atomic<int> height{0};
     915         [ +  - ]:           4 :     HTTPServer server{[&](std::shared_ptr<HTTPRequest> req) {
     916   [ +  -  -  - ]:           3 :         auto item = [req, &height]() {
     917                 :           3 :             const int h = height.fetch_add(1);
     918   [ -  +  +  - ]:           3 :             req->WriteReply(HTTP_OK, strprintf("height: %d\n", h));
     919                 :           3 :         };
     920                 :             :         // Can't call BOOST_REQUIRE from worker thread
     921   [ -  +  -  + ]:           3 :         Assert(workers.Submit(std::move(item)));
     922         [ +  - ]:           4 :     }};
     923         [ +  - ]:           1 :     server.InitHTTPAllowList();
     924                 :             : 
     925                 :             :     // All replies will be the same size
     926                 :           1 :     static constexpr std::size_t reply_length = std::string_view{
     927                 :             :         "HTTP/1.1 200 OK\r\n"
     928                 :             :         "Date: Thu, 01 Jan 2026 00:00:00 GMT\r\n"           // All RFC1123 dates are 29 characters
     929                 :             :         "Content-Length: 10\r\n"
     930                 :             :         "Content-Type: text/html; charset=ISO-8859-1\r\n"
     931                 :             :         "\r\n"
     932                 :             :         "height: 0\n"
     933                 :             :     }.size();
     934                 :             : 
     935                 :             :     /**
     936                 :             :      * A mocked Sock derived from DynSock whose Send() only succeeds when there is more than
     937                 :             :      * one reply being sent (send buffer length > reply_length). Otherwise it returns
     938                 :             :      * a recoverable error (WSAEAGAIN).
     939                 :             :      *
     940                 :             :      * After it sends successfully once, it continues to always succeed.
     941                 :             :      *
     942                 :             :      * Useful for testing "try again" logic around non-blocking socket Send() failures.
     943                 :             :      */
     944                 :           1 :     class ErrorSock : public DynSock
     945                 :             :     {
     946                 :             :     public:
     947         [ +  - ]:           2 :         explicit ErrorSock(std::shared_ptr<Pipes> pipes) : DynSock{std::move(pipes)} {}
     948                 :           0 :         DynSock& operator=(Sock&&) override { assert(false); return *this; }
     949                 :             : 
     950                 :           3 :         ssize_t Send(const void* buf, size_t len, int flags) const override
     951                 :             :         {
     952   [ +  +  +  + ]:           3 :             if (len <= reply_length && !m_have_sent) {
     953                 :             :                 #ifdef WIN32
     954                 :             :                 WSASetLastError(WSAEWOULDBLOCK);
     955                 :             :                 #else
     956                 :           1 :                 errno = WSAEAGAIN;
     957                 :             :                 #endif
     958                 :           1 :                 return -1;
     959                 :             :             } else {
     960                 :           2 :                 m_have_sent = true;
     961                 :           2 :                 return DynSock::Send(buf, len, flags);
     962                 :             :             }
     963                 :             :         }
     964                 :             : 
     965                 :             :         mutable bool m_have_sent{false};
     966                 :             :     };
     967                 :             : 
     968                 :             :     // Simpler server startup than the last test
     969   [ +  -  +  -  :           2 :     CService addr_bind{Lookup("0.0.0.0", /*portDefault=*/0, /*fAllowLookup=*/false).value()};
                   +  - ]
     970   [ +  -  +  -  :           2 :     BOOST_REQUIRE(server.BindAndStartListening(addr_bind));
             +  -  +  - ]
     971         [ +  - ]:           1 :     server.StartSocketsThreads();
     972                 :             : 
     973                 :             :     // Prepare initial requests
     974                 :           1 :     int num_requests = 2;
     975                 :             :     // Use keep-alive so the server holds the connection open for all requests.
     976         [ +  - ]:           1 :     std::string keepalive_request{full_request};
     977         [ +  - ]:           1 :     keepalive_request.replace(keepalive_request.find("Connection: close"), 17, "Connection: keep-alive");
     978                 :             :     // Combine all requests so they are read from the socket on a single iteration of the I/O loop
     979                 :           1 :     std::string all_requests;
     980         [ +  + ]:           3 :     for (int i = 0; i < num_requests; i++) {
     981         [ -  + ]:           4 :         all_requests += keepalive_request;
     982                 :             :     }
     983                 :             : 
     984                 :             :     // Watch the log messages to ensure that the first two replies were sent
     985                 :             :     // together. This indicates the non-optimistic send path was used
     986                 :             :     // because a reply was already sitting in the send buffer when a second reply
     987                 :             :     // was added.
     988         [ +  - ]:           1 :     DebugLogHelper find_two_replies{strprintf("Sent %d bytes to client", reply_length * 2),
     989                 :           1 :                                     [&](const std::string* s) {
     990                 :             :                                         return true;
     991         [ +  - ]:           1 :                                     }};
     992                 :             :     // Last reply should be sent on its own by optimistic send path, because
     993                 :             :     // the send buffer was empty when the reply was written.
     994         [ +  - ]:           1 :     DebugLogHelper find_one_reply{strprintf("Sent %d bytes to client", reply_length),
     995                 :           1 :                                   [&](const std::string* s) {
     996                 :             :                                       return true;
     997         [ +  - ]:           1 :                                    }};
     998                 :             : 
     999                 :             :     // Connect the ErrorSock as mock client with the preloaded data and get a handle on the I/O pipes
    1000                 :           1 :     std::shared_ptr<ErrorSock::Pipes> mock_client_socket_pipes{
    1001         [ +  - ]:           1 :         ConnectClient<ErrorSock>(std::as_bytes(std::span(all_requests)))
    1002   [ -  +  +  - ]:           1 :     };
    1003                 :             : 
    1004                 :             :     // Wait up to one minute for the last reply from the server
    1005                 :           1 :     std::string actual;
    1006                 :           1 :     char buf[0x10000] = {};
    1007                 :           1 :     int attempts = 6000;
    1008         [ +  - ]:           7 :     while (attempts > 0)
    1009                 :             :     {
    1010         [ +  - ]:           7 :         ssize_t bytes_read = mock_client_socket_pipes->send.GetBytes(buf, sizeof(buf), 0);
    1011         [ +  + ]:           7 :         if (bytes_read > 0) {
    1012         [ +  - ]:           1 :             actual.append(buf, bytes_read);
    1013   [ +  -  -  + ]:           2 :             if (actual.find(strprintf("height: %d", num_requests - 1)) != std::string::npos) {
    1014                 :             :                 break;
    1015                 :             :             }
    1016                 :             :         }
    1017         [ +  - ]:           6 :         std::this_thread::sleep_for(10ms);
    1018                 :           6 :         --attempts;
    1019                 :             :     }
    1020                 :             : 
    1021                 :             :     // Send the third request.
    1022                 :             :     // If there was a race between WriteReply() in the worker thread setting m_send_ready=true
    1023                 :             :     // and SocketHandlerConnected() in the I/O thread flushing the send buffer,
    1024                 :             :     // then the socket would be stuck in write mode with nothing to write,
    1025                 :             :     // the server would never read from the socket, and this request would time out.
    1026                 :             :     // Wait a second to ensure both the worker thread and I/O thread are idle.
    1027                 :             :     // If we send the next request too soon it might get accepted by the server before
    1028                 :             :     // it gets wedged shut.
    1029         [ +  - ]:           1 :     std::this_thread::sleep_for(1000ms);
    1030   [ -  +  +  - ]:           1 :     mock_client_socket_pipes->recv.PushBytes(keepalive_request.data(), keepalive_request.size());
    1031                 :           2 :     num_requests++;
    1032                 :             : 
    1033                 :             :     // Wait up to one minute for reply
    1034                 :             :     attempts = 6000;
    1035         [ +  - ]:           2 :     while (attempts > 0)
    1036                 :             :     {
    1037         [ +  - ]:           2 :         ssize_t bytes_read = mock_client_socket_pipes->send.GetBytes(buf, sizeof(buf), 0);
    1038         [ +  + ]:           2 :         if (bytes_read > 0) {
    1039         [ +  - ]:           1 :             actual.append(buf, bytes_read);
    1040   [ +  -  -  + ]:           2 :             if (actual.find(strprintf("height: %d", num_requests - 1)) != std::string::npos) {
    1041                 :             :                 break;
    1042                 :             :             }
    1043                 :             :         }
    1044         [ +  - ]:           1 :         std::this_thread::sleep_for(10ms);
    1045                 :           1 :         --attempts;
    1046                 :             :     }
    1047                 :             : 
    1048                 :             :     // All replies were received
    1049         [ +  + ]:           4 :     for (int i = 0; i < num_requests; i++) {
    1050   [ +  -  +  -  :           9 :         BOOST_REQUIRE(actual.find(strprintf("height: %d", i)) != std::string::npos);
                   +  - ]
    1051                 :             :     }
    1052                 :             : 
    1053                 :             :     // Close the keep-alive connection
    1054         [ +  - ]:           1 :     server.DisconnectAllClients();
    1055                 :             : 
    1056         [ +  - ]:           1 :     workers.Stop();
    1057                 :             : 
    1058         [ +  - ]:           1 :     server.InterruptNet();
    1059         [ +  - ]:           1 :     server.JoinSocketsThreads();
    1060         [ +  - ]:           1 :     server.StopListening();
    1061         [ +  - ]:           2 : }
    1062                 :             : 
    1063   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(http_server_rejects_disallowed_client_before_read)
          +  -  +  -  -  
          +  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  -  +  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  -  +  +  
                      - ]
    1064                 :             : {
    1065                 :             :     // DynSock reports accepted connections as coming from 5.5.5.5.
    1066   [ +  -  +  - ]:           2 :     gArgs.ForceSetArg("-rpcallowip", "4.4.4.4");
    1067                 :             : 
    1068                 :           1 :     std::atomic_bool request_dispatched{false};
    1069         [ +  - ]:           1 :     HTTPServer server{[&request_dispatched](std::unique_ptr<HTTPRequest>&&) {
    1070                 :           0 :         request_dispatched = true;
    1071         [ +  - ]:           1 :     }};
    1072   [ +  -  +  -  :           2 :     BOOST_REQUIRE(server.InitHTTPAllowList());
             +  -  +  - ]
    1073                 :             : 
    1074   [ +  -  +  -  :           2 :     CService addr_bind{Lookup("0.0.0.0", /*portDefault=*/0, /*fAllowLookup=*/false).value()};
                   +  - ]
    1075   [ +  -  +  -  :           2 :     BOOST_REQUIRE(server.BindAndStartListening(addr_bind));
             +  -  +  - ]
    1076         [ +  - ]:           1 :     server.StartSocketsThreads();
    1077                 :             : 
    1078                 :             :     // Queue a complete request; the server should never read from it.
    1079                 :           1 :     std::shared_ptr<DynSock::Pipes> client_pipes{
    1080         [ +  - ]:           1 :         ConnectClient(std::as_bytes(std::span(full_request)))};
    1081                 :             : 
    1082                 :             :     // Wait for the socket to close with an EOF (bytes_read == 0)
    1083                 :             :     // 'bytes_read > 0' means the server replied to the prohibited client
    1084                 :             :     // 'bytes_read < 0' is an error, expected until the connection is fully processed by the I/O loop
    1085                 :           1 :     ssize_t bytes_read{};
    1086                 :           1 :     char buf[0x10000]{};
    1087         [ +  - ]:           2 :     for (int attempts{0}; attempts != 1'000; ++attempts) {
    1088         [ +  - ]:           2 :         bytes_read = client_pipes->send.GetBytes(&buf, sizeof(buf), MSG_PEEK);
    1089         [ +  + ]:           2 :         if (bytes_read >= 0) break;
    1090         [ +  - ]:           1 :         std::this_thread::sleep_for(10ms);
    1091                 :             :     }
    1092                 :             : 
    1093   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(bytes_read, 0);
    1094   [ +  -  +  -  :           2 :     BOOST_CHECK(!request_dispatched);
                   +  - ]
    1095   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(server.GetConnectionsCount(), 0);
    1096                 :             : 
    1097         [ +  - ]:           1 :     server.InterruptNet();
    1098         [ +  - ]:           1 :     server.JoinSocketsThreads();
    1099         [ +  - ]:           1 :     server.StopListening();
    1100                 :             : 
    1101                 :             :     // 'recv' buffer still holds the client's request untouched which
    1102                 :             :     // proves the server never called Recv()
    1103         [ +  - ]:           1 :     const ssize_t recv_bytes{client_pipes->recv.GetBytes(&buf, sizeof(buf))};
    1104   [ +  -  +  - ]:           1 :     BOOST_REQUIRE_EQUAL(recv_bytes, static_cast<ssize_t>(full_request.size()));
    1105   [ +  -  +  -  :           1 :     BOOST_CHECK_EQUAL(std::string_view(buf, recv_bytes), full_request);
                   +  - ]
    1106                 :           1 : }
    1107                 :             : 
    1108                 :             : BOOST_AUTO_TEST_SUITE_END()
        

Generated by: LCOV version 2.0-1