Bitcoin Core 32.99.0
P2P Digital Currency
httpserver_tests.cpp
Go to the documentation of this file.
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>
10#include <util/string.h>
11#include <util/threadpool.h>
12
13#include <boost/test/unit_test.hpp>
14
16using namespace bitcoin_http;
17
18// HTTP request captured from bitcoin-cli
19constexpr 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
28BOOST_FIXTURE_TEST_SUITE(httpserver_tests, SocketTestingSetup)
29
30BOOST_AUTO_TEST_CASE(test_query_parameters)
31{
32 std::string uri {};
33
34 // Tolerate a URI with invalid characters (% not followed by hex digits)
35 uri = "/rest/endpoint/someresource.json?p1=v1&p2=v2%";
36 BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");
37 BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p2"), "v2%");
38
39 // No parameters
40 uri = "localhost:8080/rest/headers/someresource.json";
41 BOOST_CHECK(!GetQueryParameterFromUri(uri, "p1"));
42
43 // Single parameter
44 uri = "localhost:8080/rest/endpoint/someresource.json?p1=v1";
45 BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");
46 BOOST_CHECK(!GetQueryParameterFromUri(uri, "p2"));
47
48 // Multiple parameters
49 uri = "/rest/endpoint/someresource.json?p1=v1&p2=v2";
50 BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");
51 BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p2"), "v2");
52
53 // If the query string contains duplicate keys, the first value is returned
54 uri = "/rest/endpoint/someresource.json?p1=v1&p1=v2";
55 BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");
56
57 // Invalid query string syntax is the same as not having parameters
58 uri = "/rest/endpoint/someresource.json&p1=v1&p2=v2";
59 BOOST_CHECK(!GetQueryParameterFromUri(uri, "p1"));
60
61 // Multiple parameters, some characters encoded
62 uri = "/rest/endpoint/someresource.json?p1=v1%20&p2=100%25";
63 BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1 ");
64 BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p2"), "100%");
65
66 // Encoded query delimiters are part of the parameter value, not structure.
67 uri = "/rest/endpoint/someresource.json?p=a%26b%3Dc%23frag&other=x";
68 BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p"), "a&b=c#frag");
69 BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "other"), "x");
70
71 // An encoded question mark in the path does not introduce a query section.
72 uri = "/rest/endpoint/someresource.json%3Fp1%3Dv1%26p2%3D100%25";
73 BOOST_CHECK(!GetQueryParameterFromUri(uri, "p1"));
74}
75
76BOOST_AUTO_TEST_CASE(http_headers_tests)
77{
78 {
79 // Writing response headers
80 HTTPHeaders headers{};
81 BOOST_CHECK(!headers.FindFirst("Cache-Control"));
82 headers.Write("Cache-Control", "no-cache");
83 // Check case-insensitive key matching
84 BOOST_CHECK_EQUAL(headers.FindFirst("Cache-Control"), "no-cache");
85 BOOST_CHECK_EQUAL(headers.FindFirst("cache-control"), "no-cache");
86 // Additional values are appended, compared case-insensitive
87 headers.Write("cache-control", "max-age=60");
88 BOOST_CHECK_EQUAL(headers.FindFirst("Cache-Control"), "no-cache");
89 BOOST_CHECK((headers.FindAll("Cache-Control") == std::vector<std::string_view>{"no-cache", "max-age=60"}));
90 // Add a few more
91 headers.Write("Pie", "apple");
92 headers.Write("Sandwich", "ham");
93 headers.Write("Coffee", "black");
94 BOOST_CHECK_EQUAL(headers.FindFirst("Pie"), "apple");
95 // Remove
96 headers.RemoveAll("Pie");
97 BOOST_CHECK(!headers.FindFirst("Pie"));
98 // Combine for transmission
99 std::string headers_string{headers.Stringify()};
100 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 }
106 {
107 // Reading request headers captured from bitcoin-cli
108 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 util::LineReader reader(bitcoin_cli_headers, /*max_line_length=*/MAX_HEADERS_SIZE);
114 HTTPHeaders headers{};
115 headers.Read(reader);
116 BOOST_CHECK_EQUAL(headers.FindFirst("Host"), "127.0.0.1");
117 BOOST_CHECK_EQUAL(headers.FindFirst("Connection"), "close");
118 BOOST_CHECK_EQUAL(headers.FindFirst("Content-Type"), "application/json");
119 BOOST_CHECK_EQUAL(headers.FindFirst("Authorization"), "Basic X19jb29raWVfXzozYzJkNTAxNDFlMGJiYmVhMTI5ODg3NzI5MTM3NTRmNThkNjc2OWMwZTYxZjgzNTgyNzEwYTY1OGRkYjVmZGQ3");
120 BOOST_CHECK_EQUAL(headers.FindFirst("Content-Length"), "46");
121 BOOST_CHECK(!headers.FindFirst("Pizza"));
122 }
123 // Ensure invalid headers are rejected
124 {
125 // missing a colon
126 util::LineReader reader{"key value\n", /*max_line_length=*/MAX_HEADERS_SIZE};
127 BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"HTTP header missing colon (:)"});
128 }
129 {
130 // missing a key
131 util::LineReader reader{":value\n", /*max_line_length=*/MAX_HEADERS_SIZE};
132 BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Empty HTTP header name"});
133 }
134 {
135 // contains NUL
136 util::LineReader reader{std::string_view{"X-Custom: foo\0bar\n", 18}, /*max_line_length=*/MAX_HEADERS_SIZE};
137 BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Header contains invalid character"});
138 }
139 {
140 // contains bare \r (not followed by \n)
141 util::LineReader reader{std::string_view{"X-Custom: foo\rbar\n"}, /*max_line_length=*/MAX_HEADERS_SIZE};
142 BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Header contains invalid character"});
143 }
144 {
145 // contains odd \r preceding the expected CRLF
146 util::LineReader reader{"X-Custom: foo\r\r\n", /*max_line_length=*/MAX_HEADERS_SIZE};
147 BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Header contains invalid character"});
148 }
149 {
150 // key contains whitespace
151 util::LineReader reader{"key : value\n", /*max_line_length=*/MAX_HEADERS_SIZE};
152 BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Invalid header field-name contains whitespace"});
153 }
154 {
155 // Individual lines are below MAX_HEADERS_SIZE but the total is excessive
156 std::string lines;
157 lines.reserve(820 * 10);
158 for (int i = 0; i < 820; ++i) {
159 lines.append("key:value\n");
160 }
161 std::string_view excessive_headers{lines};
164 BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"HTTP headers exceed size limit"});
165 }
166 {
167 // Ok
168 util::LineReader reader{"key: value\n", /*max_line_length=*/MAX_HEADERS_SIZE};
172 }
173}
174
175BOOST_AUTO_TEST_CASE(http_response_tests)
176{
177 // Typical HTTP 1.1 response headers
179 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 HTTPResponse res;
184 res.version = {.major = 1, .minor = 1};
185 res.status = HTTP_OK;
186 res.headers = std::move(headers);
188 res.StringifyHeaders(),
189 "HTTP/1.1 200 OK\r\n"
190 "Content-Length: 41\r\n"
191 "\r\n");
192}
193
194BOOST_AUTO_TEST_CASE(http_request_tests)
195{
196 {
197 HTTPRequest req;
203 BOOST_CHECK_EQUAL(req.GetURI(), "/");
206 BOOST_CHECK_EQUAL(req.GetHeader("Host"), "127.0.0.1");
207 BOOST_CHECK_EQUAL(req.GetHeader("Connection"), "close");
208 BOOST_CHECK_EQUAL(req.GetHeader("Content-Type"), "application/json");
209 BOOST_CHECK_EQUAL(req.GetHeader("Authorization"), "Basic X19jb29raWVfXzo5OGQ5ODQ3MWNmNjg0NzAzYTkzN2EzNzk0ZDFlODQ1NjZmYTRkZjJiMzFkYjhhODI4ZGY4MjVjOTg5ZGI4OTVl");
210 BOOST_CHECK_EQUAL(req.GetHeader("Content-Length"), "46");
211 BOOST_CHECK_EQUAL(req.ReadBody(), R"({"method":"getblockcount","params":[],"id":1})""\n");
212 }
213 {
214 // Malformed: no spaces between data
215 HTTPRequest req;
216 LineReader reader("GET/HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
217 BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line too short"});
218 }
219 {
220 // Malformed: too many spaces
221 HTTPRequest req;
222 LineReader reader("GET / HTTP / 1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
223 BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line malformed"});
224 }
225 {
226 // Malformed: slash missing before version
227 HTTPRequest req;
228 LineReader reader("GET / HTTP1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
229 BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line too short"});
230 }
231 {
232 // Malformed: no decimal in version
233 HTTPRequest req;
234 LineReader reader("GET / HTTP/11\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
235 BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line too short"});
236 }
237 {
238 // Malformed: version is not a number
239 HTTPRequest req;
240 LineReader reader("GET / HTTP/1.x\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
241 BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});
242 }
243 {
244 // Malformed: version is out of range
245 HTTPRequest req;
246 LineReader reader("GET / HTTP/2.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
247 BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});
248 }
249 {
250 // Malformed: version is out of range
251 HTTPRequest req;
252 LineReader reader("GET / HTTP/0.9\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
253 BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});
254 }
255 {
256 // Malformed: version is out of range
257 HTTPRequest req;
258 LineReader reader("GET / HTTP/-1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
259 BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});
260 }
261 {
262 // Malformed: version is not exactly two integers and a dot
263 HTTPRequest req;
264 LineReader reader("GET / HTTP/1.00\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
265 BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});
266 }
267 {
268 // Malformed: contains NUL
269 HTTPRequest req;
270 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 BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"Invalid request line contains NUL"});
272 }
273 {
274 // Malformed: differing Content-Length values, case insensitive
275 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 HTTPRequest req;
281 util::LineReader reader{differing_length, /*max_line_length=*/MAX_HEADERS_SIZE};
282 BOOST_CHECK(req.LoadControlData(reader));
283 BOOST_CHECK(req.LoadHeaders(reader));
284 BOOST_CHECK_EXCEPTION(req.LoadBody(reader), std::runtime_error, HasReason{"Differing Content-Length values"});
285 }
286 {
287 // Ok: multiple same Content-Length values
288 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 HTTPRequest req;
294 util::LineReader reader{differing_length, /*max_line_length=*/MAX_HEADERS_SIZE};
295 BOOST_CHECK(req.LoadControlData(reader));
296 BOOST_CHECK(req.LoadHeaders(reader));
297 BOOST_CHECK(req.LoadBody(reader));
298 }
299 {
300 // Ok
301 HTTPRequest req;
302 LineReader reader("GET / HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
303 BOOST_CHECK(req.LoadControlData(reader));
304 BOOST_CHECK(req.LoadHeaders(reader));
305 BOOST_CHECK(req.LoadBody(reader));
306 BOOST_CHECK_EQUAL(req.GetRequestMethod(), HTTPRequestMethod::GET);
307 BOOST_CHECK_EQUAL(req.GetURI(), "/");
308 BOOST_CHECK_EQUAL(req.GetVersion().major, 1);
309 BOOST_CHECK_EQUAL(req.GetVersion().minor, 0);
310 BOOST_CHECK_EQUAL(req.GetHeader("Host"), "127.0.0.1");
311 // no body is OK
312 BOOST_CHECK_EQUAL(req.ReadBody(), "");
313 }
314 {
315 // Malformed: missing colon
316 HTTPRequest req;
317 LineReader reader("GET / HTTP/1.0\r\nHost=127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);
318 BOOST_CHECK(req.LoadControlData(reader));
319 BOOST_CHECK_EXCEPTION(req.LoadHeaders(reader), std::runtime_error, HasReason{"HTTP header missing colon (:)"});
320 }
321 {
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 HTTPRequest req;
326 LineReader reader("GET / HTTP/1.0\r\nHost: ", MAX_HEADERS_SIZE);
329 }
330 {
331 // No Content-Length: body is not read
332 HTTPRequest req;
333 LineReader reader("GET / HTTP/1.0\r\n\r\n" R"({"method":"getblockcount"})", MAX_HEADERS_SIZE);
337 // Don't try to read request body if Content-Length is missing
338 BOOST_CHECK_EQUAL(req.ReadBody(), "");
339 }
340 {
341 // Malformed: Content-Length is not a number
342 HTTPRequest req;
343 LineReader reader("GET / HTTP/1.0\r\nContent-Length: eleven\r\n\r\n" R"({"method":"getblockcount"})", MAX_HEADERS_SIZE);
346 BOOST_CHECK_EXCEPTION(req.LoadBody(reader), std::runtime_error, HasReason{"Cannot parse Content-Length value"});
347 }
348 {
349 // Malformed: Content-Length is negative
350 HTTPRequest req;
351 LineReader reader("GET / HTTP/1.0\r\nContent-Length: -8\r\n\r\n" R"({"method":"getblockcount"})", MAX_HEADERS_SIZE);
354 BOOST_CHECK_EXCEPTION(req.LoadBody(reader), std::runtime_error, HasReason{"Cannot parse Content-Length value"});
355 }
356 {
357 // Content-Length exceeds limit
358 constexpr auto excessive_size{MAX_BODY_SIZE + 1};
359 std::string huge_body(excessive_size, 'x');
360 const std::string request{"GET / HTTP/1.0\r\nContent-Length: " + util::ToString(excessive_size) + "\r\n\r\n" + std::move(huge_body)};
361 HTTPRequest req;
365 BOOST_CHECK_EXCEPTION(req.LoadBody(reader), ContentTooLargeError, HasReason{"Max body size exceeded"});
366 }
367 {
368 // Content-Length exactly on the limit
369 std::string max_body(MAX_BODY_SIZE, 'x');
370 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 HTTPRequest req;
376 }
377 {
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 HTTPRequest req;
381 LineReader reader("GET / HTTP/1.0\r\nContent-Length: 1024\r\n\r\n" R"({"method":"getblockcount"})", MAX_HEADERS_SIZE);
385 }
386 {
387 // Support "chunked" transfer. Chunk lengths are ascii-encoded hex integers, whitespace ignored
388 HTTPRequest req;
389 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 LineReader reader(ok_chunked, MAX_HEADERS_SIZE);
399 BOOST_CHECK(req.LoadControlData(reader));
400 BOOST_CHECK(req.LoadHeaders(reader));
401 BOOST_CHECK(req.LoadBody(reader));
402 BOOST_CHECK_EQUAL(req.ReadBody(), R"({"method":"getblockcount"})");
403 }
404 {
405 // Prevent "chunked" transfer from exceeding size limit
406 HTTPRequest req;
407 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 LineReader reader(excessive_chunk_size, MAX_HEADERS_SIZE);
417 BOOST_CHECK(req.LoadControlData(reader));
418 BOOST_CHECK(req.LoadHeaders(reader));
419 BOOST_CHECK_EXCEPTION(req.LoadBody(reader), ContentTooLargeError, HasReason{"Chunk will exceed max body size"});
420 }
421 {
422 // Allow (but ignore) Chunk Extensions
423 HTTPRequest req;
424 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 LineReader reader(ok_chunked, MAX_HEADERS_SIZE);
435 BOOST_CHECK(req.LoadControlData(reader));
436 BOOST_CHECK(req.LoadHeaders(reader));
437 BOOST_CHECK(req.LoadBody(reader));
438 BOOST_CHECK_EQUAL(req.ReadBody(), R"({"method":"getblockcount"})");
439 // Chunk Trailer was parsed, but ignored
441 BOOST_CHECK(!req.GetHeader("Expires"));
442 }
443 {
444 // Invalid "chunked" transfer, using roman numerals instead of hex for chunk length
445 HTTPRequest req;
446 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 LineReader reader(invalid_chunked, MAX_HEADERS_SIZE);
456 BOOST_CHECK(req.LoadControlData(reader));
457 BOOST_CHECK(req.LoadHeaders(reader));
458 BOOST_CHECK_EXCEPTION(req.LoadBody(reader), std::runtime_error, HasReason{"Cannot parse chunk length value"});
459 }
460 {
461 // Invalid "chunked" transfer, missing chunk termination \n
462 HTTPRequest req;
463 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 LineReader reader(invalid_chunked, MAX_HEADERS_SIZE);
475 BOOST_CHECK_EXCEPTION(req.LoadBody(reader), std::runtime_error, HasReason{"Improperly terminated chunk"});
476 }
477}
478
479BOOST_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 class DummyClient : public HTTPRemoteClient
483 {
484 public:
485 DummyClient() : HTTPRemoteClient{/*id=*/0, /*addr=*/CService(), /*socket=*/CreateSock(0, 0, 0)} {}
486
487 void receive(std::string_view s)
488 {
489 MutateRecvBuffer().append(s);
490 }
491 };
492
493 {
494 // Step through state machine
495 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
496 BOOST_CHECK(!client->GetRequest());
497
498 client->receive("POST / HTTP/1.0\n");
501
502 client->receive("Host: 127.0.0.1\n"
503 "Content-Length: 10\n\n");
505 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
506
507 client->receive("I miss you\n");
509 BOOST_REQUIRE(req);
511 }
512 {
513 // Read body over multiple data pushes, multiple requests in same push
514 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
515 BOOST_CHECK(!client->GetRequest());
516
517 client->receive("POST / HTTP/1.0\n"
518 "Host: 127.0.0.1\n"
519 "Content-Length: 10\n\n"
520 "I miss");
522 // Because of the Content-Length header we know the body is not complete
523 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
524
525 // Finish sending first request and include second request in the same buffer
526 client->receive(" you"
527 "GET /endpoint HTTP/1.0\n\n");
529 BOOST_REQUIRE(req);
531 BOOST_CHECK_EQUAL(req->GetURI(), "/");
532 BOOST_CHECK(!client->GetRequest());
533 BOOST_CHECK_EQUAL(req->ReadBody(), "I miss you");
534 req->WriteReply(HTTP_OK, ""); // Mark client as no longer busy
535 // Next request sitting in buffer
536 BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 24);
537
538 // Read second request
540 BOOST_REQUIRE(req);
541 BOOST_CHECK(!client->GetRequest());
543 BOOST_CHECK_EQUAL(req->GetURI(), "/endpoint");
544 BOOST_CHECK_EQUAL(req->ReadBody().size(), 0);
545 // Buffer is cleared
546 BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 0);
547 }
548 {
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 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
553 BOOST_CHECK(!client->GetRequest());
554
555 client->receive("POST / HTTP/1.0\n"
556 "Content-Length: 30000\n\n");
558 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 for (int i = 1; i <= 3; ++i) {
563 client->receive(std::string(10000, 'x'));
564 BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 10000);
566 if (i < 3) {
567 BOOST_CHECK(!req.get());
568 BOOST_CHECK_EQUAL(client->GetRequest()->ReadBody().size(), 10000 * i);
569 } else {
570 BOOST_CHECK(req.get());
571 BOOST_CHECK_EQUAL(req->ReadBody().size(), 10000 * i);
573 BOOST_CHECK(!client->GetRequest());
574 }
575 BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 0);
576 }
577 }
578 {
579 // A body sent in the same push as the next request is split correctly
580 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
581 BOOST_CHECK(!client->GetRequest());
582
583 client->receive("POST / HTTP/1.0\n"
584 "Content-Length: 4\n\n"
585 "body"
586 "GET /next HTTP/1.0\n\n");
589 BOOST_CHECK_EQUAL(req->ReadBody(), "body");
590 // Only the second request is left over
591 BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 20);
592 }
593 {
594 // Chunked transfer with state
595 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
596 BOOST_CHECK(!client->GetRequest());
597
598 // First chunk is incomplete
599 client->receive("GET / HTTP/1.0\n"
600 "Transfer-Encoding: chunked\n"
601 "\n"
602 "10\n"
603 R"({"method)");
605 BOOST_REQUIRE(client->GetRequest()->GetChunkSize());
606 BOOST_CHECK_EQUAL(*client->GetRequest()->GetChunkSize(), 16);
607 BOOST_CHECK_EQUAL(client->GetRequest()->GetChunkProgress(), 8);
608 BOOST_CHECK_EQUAL(client->GetRequest()->ReadBody().size(), 8);
609 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
610
611 // More data arrives, chunk is completed.
612 client->receive(R"(":"getbl)""\n");
613 BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
614 // State is reset
615 BOOST_CHECK(!client->GetRequest()->GetChunkSize());
616 BOOST_CHECK_EQUAL(client->GetRequest()->GetChunkProgress(), 0);
617 // New data is added to body but body is still incomplete
618 BOOST_CHECK_EQUAL(client->GetRequest()->ReadBody().size(), 16);
619 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
620
621 // Next chunk arrives without terminal CRLF
622 client->receive("a\n"
623 R"(ockcount"})");
625 BOOST_CHECK(client->GetRequest()->GetChunkSize());
626 BOOST_CHECK_EQUAL(*client->GetRequest()->GetChunkSize(), 10);
627 BOOST_CHECK_EQUAL(client->GetRequest()->GetChunkProgress(), 10);
628 BOOST_CHECK_EQUAL(client->GetRequest()->ReadBody().size(), 26);
629 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
630
631 // Chunk terminal CRLF arrives with final (size 0) chunk
632 client->receive("\n0\n\n");
634 // Body size hasn't changed
635 BOOST_CHECK_EQUAL(req->ReadBody().size(), 26);
636 // We're done
638 BOOST_CHECK_EQUAL(req->ReadBody(), R"({"method":"getblockcount"})");
639 }
640 {
641 // Invalid headers: error state stops reading
642 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
643
644 // Request is in the buffer
645 client->receive("POST / HTTP/1.0\n"
646 "Host: 127.0.0.1\n");
647 BOOST_CHECK(!client->GetRecvBuffer().empty());
650 client->receive("Invalid header with no colon\n"
651 "\n"
652 "body is not read");
653 // Reading throws an error, sets state
655 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::Error);
656
657 // We read up to the invalid line
658 BOOST_CHECK_EQUAL(client->GetRequest()->GetHeader("Host"), "127.0.0.1");
659 // Buffer was cleared, client should just be disconnected now
660 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 client->receive("Content-Length: 2\n\nok");
664 BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 21);
666 BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 21);
667 }
668 {
669 // Headers sent in batches that are below MAX_HEADERS_SIZE but the total is excessive
670 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
671 BOOST_CHECK(!client->GetRequest());
673 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::Init);
674
675 client->receive("POST /huge HTTP/1.0\n");
678
679 for (int i = 0; i < 410; ++i) {
680 client->receive("key:value\n");
681 }
684
685 for (int i = 0; i < 409; ++i) {
686 client->receive("key:value\n");
687 }
690
691 // We're at 819 x 10-byte headers
692 // The limit is 8192, three more bytes should throw.
693 client->receive("k:\n");
695 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::Error);
696 }
697 {
698 // Client sends chunks that are below the limit but the total is excessive
699 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
701 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::Init);
702
703 client->receive("POST /huge HTTP/1.0\n");
706
707 client->receive("Transfer-Encoding: chunked\n\n");
709 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
710
711 // Send 16-byte chunk
712 client->receive("10\nno auto updates!\n");
714 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 client->receive("1fffff1\n");
721 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::Error);
722 }
723 {
724 // Ensure chunk trailer is parsed over state lines
725 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
726 BOOST_CHECK(!client->GetRequest());
728 // Send a 1-byte chunk then send the 0-chunk with a trailer but no terminal CRLF
729 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");
737 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
738
739 // Send first part of another trailer line
740 client->receive("Expires:");
742 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
743
744 // Finish the trailer line
745 client->receive("never\n");
747 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::NeedsBody);
748
749 // Terminate
750 client->receive("\n");
753 BOOST_CHECK_EQUAL(req->ReadBody(), "x");
754 }
755 {
756 // Ensure chunk trailer counts towards the headers size limit
757 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
758 BOOST_CHECK(!client->GetRequest());
759
760 client->receive("POST /huge HTTP/1.0\n"
761 "Transfer-Encoding: chunked\n"); // 27 bytes
762 for (int i = 0; i < 816; ++i) {
763 client->receive("key:value\n"); // 8160
764 }
765 client->receive("\n" // 1
766 "1\n"
767 "x\n"
768 "0\n");
770 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 client->receive("k:vv\n");
776 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), HTTPRequest::State::Error);
777 }
778}
779
780BOOST_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 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 Mutex requests_mutex;
789 std::deque<std::unique_ptr<HTTPRequest>> requests;
790 auto StoreRequest = [&](std::unique_ptr<HTTPRequest>&& req) {
791 LOCK(requests_mutex);
792 requests.push_back(std::move(req));
793 };
794
795 HTTPServer server{StoreRequest};
796 server.InitHTTPAllowList();
797
798 {
799 // We can only bind to NET_IPV4 and NET_IPV6
800 CService onion_address{Lookup("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaam2dqd.onion", /*portDefault=*/0, /*fAllowLookup=*/false).value()};
801 auto result{server.BindAndStartListening(onion_address)};
802 BOOST_REQUIRE(!result);
803 BOOST_CHECK_EQUAL(result.error(), "Bind address family for aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaam2dqd.onion:0 not supported");
804 }
805
806 // This VALID address won't actually get used because we stubbed CreateSock()
807 CService addr_bind{Lookup("0.0.0.0", /*portDefault=*/0, /*fAllowLookup=*/false).value()};
808
809 // Init state
810 BOOST_REQUIRE_EQUAL(server.GetListeningSocketCount(), 0);
811 // Bind to mock Listening Socket
812 BOOST_REQUIRE(server.BindAndStartListening(addr_bind));
813 // We are bound and listening
814 BOOST_REQUIRE_EQUAL(server.GetListeningSocketCount(), 1);
815
816 // Start the I/O loop
817 server.StartSocketsThreads();
818
819 // No connections yet
820 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 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 while (server.GetConnectionsCount() < 1) {
830 std::this_thread::sleep_for(10ms);
831 BOOST_REQUIRE(--attempts > 0);
832 }
833
834 // Prepare a pointer to the client, we'll assign it from the request itself.
835 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 attempts = 6000;
841 while (true) {
842 {
843 LOCK(requests_mutex);
844 // Connected client should have one request already from the static content.
845 if (requests.size() == 1) {
846 // Check the received request
847 BOOST_CHECK_EQUAL(requests.front()->ReadBody(), R"({"method":"getblockcount","params":[],"id":1})""\n");
848 BOOST_CHECK_EQUAL(requests.front()->GetPeer().ToStringAddrPort(), "5.5.5.5:6789");
849
850 // Inspect the connection pointed to from the request
851 client = requests.front()->GetClient();
852 BOOST_REQUIRE(client);
853 BOOST_CHECK_EQUAL(client->GetOrigin(), "5.5.5.5:6789");
854
855 // Respond to request
856 requests.front()->WriteReply(HTTP_OK, "874140\n");
857
858 break;
859 }
860 }
861 std::this_thread::sleep_for(10ms);
862 BOOST_REQUIRE(--attempts > 0);
863 }
864
865 // Check the sent response from the mock client at the other end of the mock socket
866 std::string actual;
867 // Wait up to one minute for all the bytes to appear in the "send" pipe.
868 char buf[0x10000] = {};
869 attempts = 6000;
870 while (attempts > 0)
871 {
872 ssize_t bytes_read = mock_client_socket_pipes->send.GetBytes(buf, sizeof(buf), 0);
873 if (bytes_read > 0) {
874 actual.append(buf, bytes_read);
875 if (actual.length() == 146) {
876 break;
877 }
878 }
879 std::this_thread::sleep_for(10ms);
880 --attempts;
881 }
882 BOOST_CHECK(actual.starts_with("HTTP/1.1 200 OK\r\n"));
883 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 BOOST_CHECK(actual.find("Connection: close\r\n") != std::string::npos);
886 BOOST_CHECK(actual.find("Content-Length: 7\r\n") != std::string::npos);
887 BOOST_CHECK(actual.find("Content-Type: text/html; charset=ISO-8859-1\r\n") != std::string::npos);
888 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 attempts = 6000;
893 while (server.GetConnectionsCount() != 0) {
894 std::this_thread::sleep_for(10ms);
895 BOOST_REQUIRE(--attempts > 0);
896 }
897
898 // Stop the I/O loop and shutdown
899 server.InterruptNet();
900 // Wait for I/O loop to finish, after all connected sockets are closed
901 server.JoinSocketsThreads();
902 // Close all listening sockets
903 server.StopListening();
904}
905
906BOOST_AUTO_TEST_CASE(http_socket_error_tests)
907{
908 // Create a tiny threadpool for the HTTPRequest handler
909 ThreadPool workers("http");
910 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 std::atomic<int> height{0};
915 HTTPServer server{[&](std::shared_ptr<HTTPRequest> req) {
916 auto item = [req, &height]() {
917 const int h = height.fetch_add(1);
918 req->WriteReply(HTTP_OK, strprintf("height: %d\n", h));
919 };
920 // Can't call BOOST_REQUIRE from worker thread
921 Assert(workers.Submit(std::move(item)));
922 }};
923 server.InitHTTPAllowList();
924
925 // All replies will be the same size
926 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
944 class ErrorSock : public DynSock
945 {
946 public:
947 explicit ErrorSock(std::shared_ptr<Pipes> pipes) : DynSock{std::move(pipes)} {}
948 DynSock& operator=(Sock&&) override { assert(false); return *this; }
949
950 ssize_t Send(const void* buf, size_t len, int flags) const override
951 {
952 if (len <= reply_length && !m_have_sent) {
953 #ifdef WIN32
954 WSASetLastError(WSAEWOULDBLOCK);
955 #else
956 errno = WSAEAGAIN;
957 #endif
958 return -1;
959 } else {
960 m_have_sent = true;
961 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 CService addr_bind{Lookup("0.0.0.0", /*portDefault=*/0, /*fAllowLookup=*/false).value()};
970 BOOST_REQUIRE(server.BindAndStartListening(addr_bind));
971 server.StartSocketsThreads();
972
973 // Prepare initial requests
974 int num_requests = 2;
975 // Use keep-alive so the server holds the connection open for all requests.
976 std::string keepalive_request{full_request};
977 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 std::string all_requests;
980 for (int i = 0; i < num_requests; i++) {
981 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 DebugLogHelper find_two_replies{strprintf("Sent %d bytes to client", reply_length * 2),
989 [&](const std::string* s) {
990 return true;
991 }};
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 DebugLogHelper find_one_reply{strprintf("Sent %d bytes to client", reply_length),
995 [&](const std::string* s) {
996 return true;
997 }};
998
999 // Connect the ErrorSock as mock client with the preloaded data and get a handle on the I/O pipes
1000 std::shared_ptr<ErrorSock::Pipes> mock_client_socket_pipes{
1001 ConnectClient<ErrorSock>(std::as_bytes(std::span(all_requests)))
1002 };
1003
1004 // Wait up to one minute for the last reply from the server
1005 std::string actual;
1006 char buf[0x10000] = {};
1007 int attempts = 6000;
1008 while (attempts > 0)
1009 {
1010 ssize_t bytes_read = mock_client_socket_pipes->send.GetBytes(buf, sizeof(buf), 0);
1011 if (bytes_read > 0) {
1012 actual.append(buf, bytes_read);
1013 if (actual.find(strprintf("height: %d", num_requests - 1)) != std::string::npos) {
1014 break;
1015 }
1016 }
1017 std::this_thread::sleep_for(10ms);
1018 --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 std::this_thread::sleep_for(1000ms);
1030 mock_client_socket_pipes->recv.PushBytes(keepalive_request.data(), keepalive_request.size());
1031 num_requests++;
1032
1033 // Wait up to one minute for reply
1034 attempts = 6000;
1035 while (attempts > 0)
1036 {
1037 ssize_t bytes_read = mock_client_socket_pipes->send.GetBytes(buf, sizeof(buf), 0);
1038 if (bytes_read > 0) {
1039 actual.append(buf, bytes_read);
1040 if (actual.find(strprintf("height: %d", num_requests - 1)) != std::string::npos) {
1041 break;
1042 }
1043 }
1044 std::this_thread::sleep_for(10ms);
1045 --attempts;
1046 }
1047
1048 // All replies were received
1049 for (int i = 0; i < num_requests; i++) {
1050 BOOST_REQUIRE(actual.find(strprintf("height: %d", i)) != std::string::npos);
1051 }
1052
1053 // Close the keep-alive connection
1054 server.DisconnectAllClients();
1055
1056 workers.Stop();
1057
1058 server.InterruptNet();
1059 server.JoinSocketsThreads();
1060 server.StopListening();
1061}
1062
1063BOOST_AUTO_TEST_CASE(http_server_rejects_disallowed_client_before_read)
1064{
1065 // DynSock reports accepted connections as coming from 5.5.5.5.
1066 gArgs.ForceSetArg("-rpcallowip", "4.4.4.4");
1067
1068 std::atomic_bool request_dispatched{false};
1069 HTTPServer server{[&request_dispatched](std::unique_ptr<HTTPRequest>&&) {
1070 request_dispatched = true;
1071 }};
1072 BOOST_REQUIRE(server.InitHTTPAllowList());
1073
1074 CService addr_bind{Lookup("0.0.0.0", /*portDefault=*/0, /*fAllowLookup=*/false).value()};
1075 BOOST_REQUIRE(server.BindAndStartListening(addr_bind));
1076 server.StartSocketsThreads();
1077
1078 // Queue a complete request; the server should never read from it.
1079 std::shared_ptr<DynSock::Pipes> client_pipes{
1080 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 ssize_t bytes_read{};
1086 char buf[0x10000]{};
1087 for (int attempts{0}; attempts != 1'000; ++attempts) {
1088 bytes_read = client_pipes->send.GetBytes(&buf, sizeof(buf), MSG_PEEK);
1089 if (bytes_read >= 0) break;
1090 std::this_thread::sleep_for(10ms);
1091 }
1092
1093 BOOST_CHECK_EQUAL(bytes_read, 0);
1094 BOOST_CHECK(!request_dispatched);
1095 BOOST_CHECK_EQUAL(server.GetConnectionsCount(), 0);
1096
1097 server.InterruptNet();
1098 server.JoinSocketsThreads();
1099 server.StopListening();
1100
1101 // 'recv' buffer still holds the client's request untouched which
1102 // proves the server never called Recv()
1103 const ssize_t recv_bytes{client_pipes->recv.GetBytes(&buf, sizeof(buf))};
1104 BOOST_REQUIRE_EQUAL(recv_bytes, static_cast<ssize_t>(full_request.size()));
1105 BOOST_CHECK_EQUAL(std::string_view(buf, recv_bytes), full_request);
1106}
1107
1108BOOST_AUTO_TEST_SUITE_END()
A combination of a network address (CNetAddr) and a (TCP) port.
Definition: netaddress.h:531
Helper to initialize the global NodeClock, let a duration elapse, and reset it after use in a test.
Definition: time.h:54
std::optional< std::string > FindFirst(std::string_view key) const
Definition: httpserver.cpp:265
bool Read(util::LineReader &reader, bool write=true)
Definition: httpserver.cpp:299
void Write(std::string &&key, std::string &&value)
Definition: httpserver.cpp:286
std::string & MutateRecvBuffer()
Used for tests.
Definition: httpserver.h:539
static std::unique_ptr< HTTPRequest > TryReadRequest(const std::shared_ptr< HTTPRemoteClient > &client) EXCLUSIVE_LOCKS_REQUIRED(!client -> m_send_mutex)
Try to read an HTTPRequest from a client's receive buffer.
const HTTPVersion & GetVersion() const
Definition: httpserver.h:181
std::string GetURI() const
Definition: httpserver.h:186
bool LoadHeaders(util::LineReader &reader)
Definition: httpserver.cpp:426
bool LoadControlData(util::LineReader &reader)
Methods that attempt to parse HTTP request fields line-by-line from a receive buffer.
Definition: httpserver.cpp:378
std::optional< std::string > GetHeader(std::string_view hdr) const
Definition: httpserver.cpp:698
HTTPRequestMethod GetRequestMethod() const
Definition: httpserver.h:188
std::string ReadBody() const
Definition: httpserver.h:191
bool LoadBody(util::LineReader &reader)
Definition: httpserver.cpp:431
bool InitHTTPAllowList()
Parse the user's -rpcallowip settings and populate m_allow_subnets.
Definition: httpserver.cpp:90
BOOST_CHECK_EXCEPTION predicates to check the specific validation error.
Definition: common.h:19
size_t Remaining() const
Returns remaining size of bytes in buffer.
Definition: string.cpp:80
BOOST_AUTO_TEST_CASE(http_response_tests)
std::string_view excessive_headers
BOOST_CHECK_GT(excessive_headers.size(), MAX_HEADERS_SIZE)
BOOST_CHECK_EQUAL(headers.FindFirst("key"), "value")
BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Empty HTTP header name"})
util::LineReader reader
constexpr std::string_view full_request
HTTPHeaders headers
std::unique_ptr< ProxyClient< messages::FooInterface > > client
constexpr uint64_t MAX_BODY_SIZE
Maximum size of an HTTP request body received from a client.
Definition: httpserver.h:82
constexpr size_t MAX_HEADERS_SIZE
Maximum size of each headers line in an HTTP request, also the maximum size of all headers total.
Definition: httpserver.h:78
std::string ToString(const T &t)
Locale-independent version of std::to_string.
Definition: string.h:250
std::vector< CService > Lookup(const std::string &name, uint16_t portDefault, bool fAllowLookup, unsigned int nMaxSolutions, DNSLookupFn dns_lookup_function)
Resolve a service string to its corresponding service.
Definition: netbase.cpp:191
std::function< std::unique_ptr< Sock >(int, int, int)> CreateSock
Socket factory.
Definition: netbase.cpp:577
#define BOOST_CHECK(expr)
Definition: object.cpp:16
@ HTTP_OK
Definition: protocol.h:14
HTTPVersion version
Definition: httpserver.h:145
std::string StringifyHeaders() const
Definition: httpserver.cpp:368
HTTPHeaders headers
Definition: httpserver.h:147
uint8_t minor
Definition: httpserver.h:140
uint8_t major
Default HTTP protocol version 1.1 is used by error responses when a request is unreadable.
Definition: httpserver.h:139
Thrown when a request body exceeds MAX_BODY_SIZE (or will exceed, in chunked transfer) so the server ...
Definition: httpserver.h:86
#define LOCK(cs)
Definition: sync.h:268