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 using enum HTTPRequest::State;
493
494 {
495 // Step through state machine
496 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
497 BOOST_CHECK(!client->GetRequest());
498
499 client->Receive("POST / HTTP/1.0\n");
501 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsHeaders);
502
503 client->Receive("Host: 127.0.0.1\n"
504 "Content-Length: 10\n\n");
506 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsBody);
507
508 client->Receive("I miss you\n");
510 BOOST_REQUIRE(req);
511 BOOST_CHECK_EQUAL(req->GetState(), Complete);
512 }
513 {
514 // Read body over multiple data pushes, multiple requests in same push
515 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
516 BOOST_CHECK(!client->GetRequest());
517
518 client->Receive("POST / HTTP/1.0\n"
519 "Host: 127.0.0.1\n"
520 "Content-Length: 10\n\n"
521 "I miss");
523 // Because of the Content-Length header we know the body is not complete
524 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsBody);
525
526 // Finish sending first request and include second request in the same buffer
527 client->Receive(" you"
528 "GET /endpoint HTTP/1.0\n\n");
530 BOOST_REQUIRE(req);
531 BOOST_CHECK_EQUAL(req->GetState(), Complete);
532 BOOST_CHECK_EQUAL(req->GetURI(), "/");
533 BOOST_CHECK(!client->GetRequest());
534 BOOST_CHECK_EQUAL(req->ReadBody(), "I miss you");
535 req->WriteReply(HTTP_OK, ""); // Mark client as no longer busy
536 // Next request sitting in buffer
537 BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 24);
538
539 // Read second request
541 BOOST_REQUIRE(req);
542 BOOST_CHECK(!client->GetRequest());
543 BOOST_CHECK_EQUAL(req->GetState(), Complete);
544 BOOST_CHECK_EQUAL(req->GetURI(), "/endpoint");
545 BOOST_CHECK_EQUAL(req->ReadBody().size(), 0);
546 // Buffer is cleared
547 BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 0);
548 }
549 {
550 // A Content-Length body is drained out of the receive buffer as it
551 // arrives, instead of accumulating there until the request is complete.
552
553 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
554 BOOST_CHECK(!client->GetRequest());
555
556 client->Receive("POST / HTTP/1.0\n"
557 "Content-Length: 30000\n\n");
559 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsBody);
560
561 // Body arrives in 10kB pieces. Each one is copied onto m_body and
562 // erased from the receive buffer, which never holds more than one piece.
563 for (int i = 1; i <= 3; ++i) {
564 client->Receive(std::string(10000, 'x'));
565 BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 10000);
567 if (i < 3) {
568 BOOST_CHECK(!req.get());
569 BOOST_CHECK_EQUAL(client->GetRequest()->ReadBody().size(), 10000 * i);
570 } else {
571 BOOST_CHECK(req.get());
572 BOOST_CHECK_EQUAL(req->ReadBody().size(), 10000 * i);
573 BOOST_CHECK_EQUAL(req->GetState(), Complete);
574 BOOST_CHECK(!client->GetRequest());
575 }
576 BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 0);
577 }
578 }
579 {
580 // A body sent in the same push as the next request is split correctly
581 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
582 BOOST_CHECK(!client->GetRequest());
583
584 client->Receive("POST / HTTP/1.0\n"
585 "Content-Length: 4\n\n"
586 "body"
587 "GET /next HTTP/1.0\n\n");
589 BOOST_CHECK_EQUAL(req->GetState(), Complete);
590 BOOST_CHECK_EQUAL(req->ReadBody(), "body");
591 // Only the second request is left over
592 BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 20);
593 }
594 {
595 // Chunked transfer with state
596 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
597 BOOST_CHECK(!client->GetRequest());
598
599 // First chunk is incomplete
600 client->Receive("GET / HTTP/1.0\n"
601 "Transfer-Encoding: chunked\n"
602 "\n"
603 "10\n"
604 R"({"method)");
606 BOOST_REQUIRE(client->GetRequest()->GetChunkSize());
607 BOOST_CHECK_EQUAL(*client->GetRequest()->GetChunkSize(), 16);
608 BOOST_CHECK_EQUAL(client->GetRequest()->GetChunkProgress(), 8);
609 BOOST_CHECK_EQUAL(client->GetRequest()->ReadBody().size(), 8);
610 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsBody);
611
612 // More data arrives, chunk is completed.
613 client->Receive(R"(":"getbl)""\n");
614 BOOST_CHECK(!HTTPRemoteClient::TryReadRequest(client));
615 // State is reset
616 BOOST_CHECK(!client->GetRequest()->GetChunkSize());
617 BOOST_CHECK_EQUAL(client->GetRequest()->GetChunkProgress(), 0);
618 // New data is added to body but body is still incomplete
619 BOOST_CHECK_EQUAL(client->GetRequest()->ReadBody().size(), 16);
620 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsBody);
621
622 // Next chunk arrives without terminal CRLF
623 client->Receive("a\n"
624 R"(ockcount"})");
626 BOOST_CHECK(client->GetRequest()->GetChunkSize());
627 BOOST_CHECK_EQUAL(*client->GetRequest()->GetChunkSize(), 10);
628 BOOST_CHECK_EQUAL(client->GetRequest()->GetChunkProgress(), 10);
629 BOOST_CHECK_EQUAL(client->GetRequest()->ReadBody().size(), 26);
630 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsBody);
631
632 // Chunk terminal CRLF arrives with final (size 0) chunk
633 client->Receive("\n0\n\n");
635 // Body size hasn't changed
636 BOOST_CHECK_EQUAL(req->ReadBody().size(), 26);
637 // We're done
638 BOOST_CHECK_EQUAL(req->GetState(), Complete);
639 BOOST_CHECK_EQUAL(req->ReadBody(), R"({"method":"getblockcount"})");
640 }
641 {
642 // Invalid headers: error state stops reading
643 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
644
645 // Request is in the buffer
646 client->Receive("POST / HTTP/1.0\n"
647 "Host: 127.0.0.1\n");
648 BOOST_CHECK(!client->GetRecvBuffer().empty());
650 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsHeaders);
651 client->Receive("Invalid header with no colon\n"
652 "\n"
653 "body is not read");
654 // Reading throws an error, sets state
656 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), Error);
657
658 // We read up to the invalid line
659 BOOST_CHECK_EQUAL(client->GetRequest()->GetHeader("Host"), "127.0.0.1");
660 // Buffer was cleared, client should just be disconnected now
661 BOOST_CHECK(client->GetRecvBuffer().empty());
662
663 // Even if more data comes in, trying to read again in error state is a no-op
664 client->Receive("Content-Length: 2\n\nok");
665 BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 21);
667 BOOST_CHECK_EQUAL(client->GetRecvBuffer().size(), 21);
668 }
669 {
670 // Headers sent in batches that are below MAX_HEADERS_SIZE but the total is excessive
671 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
672 BOOST_CHECK(!client->GetRequest());
674 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), Init);
675
676 client->Receive("POST /huge HTTP/1.0\n");
678 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsHeaders);
679
680 for (int i = 0; i < 410; ++i) {
681 client->Receive("key:value\n");
682 }
684 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsHeaders);
685
686 for (int i = 0; i < 409; ++i) {
687 client->Receive("key:value\n");
688 }
690 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsHeaders);
691
692 // We're at 819 x 10-byte headers
693 // The limit is 8192, three more bytes should throw.
694 client->Receive("k:\n");
696 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), Error);
697 }
698 {
699 // Client sends chunks that are below the limit but the total is excessive
700 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
702 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), Init);
703
704 client->Receive("POST /huge HTTP/1.0\n");
706 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsHeaders);
707
708 client->Receive("Transfer-Encoding: chunked\n\n");
710 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsBody);
711
712 // Send 16-byte chunk
713 client->Receive("10\nno auto updates!\n");
715 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsBody);
716
717 // The next chunk will be of size 32MiB - 16 + 1, below the limit
718 // on its own but not if it were added to the total cumulative body so far.
719 // We don't need to actually send or prepare this amount of data.
720 client->Receive("1fffff1\n");
722 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), Error);
723 }
724 {
725 // Ensure chunk trailer is parsed over state lines
726 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
727 BOOST_CHECK(!client->GetRequest());
729 // Send a 1-byte chunk then send the 0-chunk with a trailer but no terminal CRLF
730 client->Receive("GET / HTTP/1.0\n"
731 "Transfer-Encoding: chunked\n"
732 "\n"
733 "1\n"
734 "x\n"
735 "0\n"
736 "Digest: sha-4=deadbeef\n");
738 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsBody);
739
740 // Send first part of another trailer line
741 client->Receive("Expires:");
743 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsBody);
744
745 // Finish the trailer line
746 client->Receive("never\n");
748 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsBody);
749
750 // Terminate
751 client->Receive("\n");
753 BOOST_CHECK_EQUAL(req->GetState(), Complete);
754 BOOST_CHECK_EQUAL(req->ReadBody(), "x");
755 }
756 {
757 // Ensure chunk trailer counts towards the headers size limit
758 std::shared_ptr<DummyClient> client{std::make_shared<DummyClient>()};
759 BOOST_CHECK(!client->GetRequest());
760
761 client->Receive("POST /huge HTTP/1.0\n"
762 "Transfer-Encoding: chunked\n"); // 27 bytes
763 for (int i = 0; i < 816; ++i) {
764 client->Receive("key:value\n"); // 8160
765 }
766 client->Receive("\n" // 1
767 "1\n"
768 "x\n"
769 "0\n");
771 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), NeedsBody);
772
773 // We're in the trailer section with a total of 8188 bytes of headers.
774 // The limit is 8192, five more bytes should throw.
775 client->Receive("k:vv\n");
777 BOOST_CHECK_EQUAL(client->GetRequest()->GetState(), Error);
778 }
779}
780
781BOOST_AUTO_TEST_CASE(http_server_socket_tests)
782{
783 // Hard code the timestamp for the Date header in the HTTP response
784 // Wed Dec 11 00:47:09 2024 UTC
785 FakeNodeClock clock{1733878029s};
786
787 // Prepare a request handler that just stores received requests so we can examine them.
788 // Mutex is required to prevent a race between this test's main thread and the server's I/O loop.
789 Mutex requests_mutex;
790 std::deque<std::unique_ptr<HTTPRequest>> requests;
791 auto StoreRequest = [&](std::unique_ptr<HTTPRequest>&& req) {
792 LOCK(requests_mutex);
793 requests.push_back(std::move(req));
794 };
795
796 HTTPServer server{StoreRequest};
797 server.InitHTTPAllowList();
798
799 {
800 // We can only bind to NET_IPV4 and NET_IPV6
801 CService onion_address{Lookup("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaam2dqd.onion", /*portDefault=*/0, /*fAllowLookup=*/false).value()};
802 auto result{server.BindAndStartListening(onion_address)};
803 BOOST_REQUIRE(!result);
804 BOOST_CHECK_EQUAL(result.error(), "Bind address family for aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaam2dqd.onion:0 not supported");
805 }
806
807 // This VALID address won't actually get used because we stubbed CreateSock()
808 CService addr_bind{Lookup("0.0.0.0", /*portDefault=*/0, /*fAllowLookup=*/false).value()};
809
810 // Init state
811 BOOST_REQUIRE_EQUAL(server.GetListeningSocketCount(), 0);
812 // Bind to mock Listening Socket
813 BOOST_REQUIRE(server.BindAndStartListening(addr_bind));
814 // We are bound and listening
815 BOOST_REQUIRE_EQUAL(server.GetListeningSocketCount(), 1);
816
817 // Start the I/O loop
818 server.StartSocketsThreads();
819
820 // No connections yet
821 BOOST_CHECK_EQUAL(server.GetConnectionsCount(), 0);
822
823 // Create a mock client with pre-loaded request data and add it to the local CreateSock queue.
824 // Keep a handle for the mock client's send and receive pipes so we can examine
825 // the data it "receives".
826 std::shared_ptr<DynSock::Pipes> mock_client_socket_pipes{ConnectClient(std::as_bytes(std::span(full_request)))};
827
828 // Wait up to a minute to find and connect the client in the I/O loop
829 int attempts{6000};
830 while (server.GetConnectionsCount() < 1) {
831 std::this_thread::sleep_for(10ms);
832 BOOST_REQUIRE(--attempts > 0);
833 }
834
835 // Prepare a pointer to the client, we'll assign it from the request itself.
836 std::shared_ptr<HTTPRemoteClient> client;
837
838 // Wait up to a minute to read the request from the client.
839 // Given that the mock client is itself a mock socket
840 // with hard-coded data it should only take a fraction of that.
841 attempts = 6000;
842 while (true) {
843 {
844 LOCK(requests_mutex);
845 // Connected client should have one request already from the static content.
846 if (requests.size() == 1) {
847 // Check the received request
848 BOOST_CHECK_EQUAL(requests.front()->ReadBody(), R"({"method":"getblockcount","params":[],"id":1})""\n");
849 BOOST_CHECK_EQUAL(requests.front()->GetPeer().ToStringAddrPort(), "5.5.5.5:6789");
850
851 // Inspect the connection pointed to from the request
852 client = requests.front()->GetClient();
853 BOOST_REQUIRE(client);
854 BOOST_CHECK_EQUAL(client->GetOrigin(), "5.5.5.5:6789");
855
856 // Respond to request
857 requests.front()->WriteReply(HTTP_OK, "874140\n");
858
859 break;
860 }
861 }
862 std::this_thread::sleep_for(10ms);
863 BOOST_REQUIRE(--attempts > 0);
864 }
865
866 // Check the sent response from the mock client at the other end of the mock socket
867 constexpr std::string_view expected_response{
868 "HTTP/1.1 200 OK\r\n"
869 "Date: Wed, 11 Dec 2024 00:47:09 GMT\r\n"
870 "Content-Length: 7\r\n"
871 "Content-Type: text/html; charset=ISO-8859-1\r\n"
872 "Connection: close\r\n"
873 "\r\n"
874 "874140\n"};
875 std::string actual;
876 actual.reserve(expected_response.length());
877 // Wait up to one minute for all the bytes to appear in the "send" pipe.
878 char buf[0x10000] = {};
879 attempts = 6000;
880 while (attempts > 0)
881 {
882 ssize_t bytes_read = mock_client_socket_pipes->send.GetBytes(buf, sizeof(buf), 0);
883 if (bytes_read > 0) {
884 actual.append(buf, bytes_read);
885 if (actual.length() >= expected_response.length()) {
886 break;
887 }
888 }
889 std::this_thread::sleep_for(10ms);
890 --attempts;
891 }
892 BOOST_CHECK_EQUAL(actual, expected_response);
893
894 // Wait up to one minute for connection to be automatically closed, because
895 // keep-alive was not set by the client and we are done responding to their request.
896 attempts = 6000;
897 while (server.GetConnectionsCount() != 0) {
898 std::this_thread::sleep_for(10ms);
899 BOOST_REQUIRE(--attempts > 0);
900 }
901
902 // Stop the I/O loop and shutdown
903 server.InterruptNet();
904 // Wait for I/O loop to finish, after all connected sockets are closed
905 server.JoinSocketsThreads();
906 // Close all listening sockets
907 server.StopListening();
908}
909
910BOOST_AUTO_TEST_CASE(http_socket_error_tests)
911{
912 // Create a tiny threadpool for the HTTPRequest handler
913 ThreadPool workers("http");
914 workers.Start(1);
915
916 // Hard-code the server's request handler to respond to each request with
917 // an incremented block count. Handle the replies in the worker thread.
918 std::atomic<int> height{0};
919 HTTPServer server{[&](std::shared_ptr<HTTPRequest> req) {
920 auto item = [req, &height]() {
921 const int h = height.fetch_add(1);
922 req->WriteReply(HTTP_OK, strprintf("height: %d\n", h));
923 };
924 // Can't call BOOST_REQUIRE from worker thread
925 Assert(workers.Submit(std::move(item)));
926 }};
927 server.InitHTTPAllowList();
928
929 // All replies will be the same size
930 static constexpr std::size_t reply_length = std::string_view{
931 "HTTP/1.1 200 OK\r\n"
932 "Date: Thu, 01 Jan 2026 00:00:00 GMT\r\n" // All RFC1123 dates are 29 characters
933 "Content-Length: 10\r\n"
934 "Content-Type: text/html; charset=ISO-8859-1\r\n"
935 "\r\n"
936 "height: 0\n"
937 }.size();
938
948 class ErrorSock : public DynSock
949 {
950 public:
951 explicit ErrorSock(std::shared_ptr<Pipes> pipes) : DynSock{std::move(pipes)} {}
952 DynSock& operator=(Sock&&) override { assert(false); return *this; }
953
954 ssize_t Send(const void* buf, size_t len, int flags) const override
955 {
956 if (len <= reply_length && !m_have_sent) {
957 #ifdef WIN32
958 WSASetLastError(WSAEWOULDBLOCK);
959 #else
960 errno = WSAEAGAIN;
961 #endif
962 return -1;
963 } else {
964 m_have_sent = true;
965 return DynSock::Send(buf, len, flags);
966 }
967 }
968
969 mutable bool m_have_sent{false};
970 };
971
972 // Simpler server startup than the last test
973 CService addr_bind{Lookup("0.0.0.0", /*portDefault=*/0, /*fAllowLookup=*/false).value()};
974 BOOST_REQUIRE(server.BindAndStartListening(addr_bind));
975 server.StartSocketsThreads();
976
977 // Prepare initial requests
978 int num_requests = 2;
979 // Use keep-alive so the server holds the connection open for all requests.
980 std::string keepalive_request{full_request};
981 keepalive_request.replace(keepalive_request.find("Connection: close"), 17, "Connection: keep-alive");
982 // Combine all requests so they are read from the socket on a single iteration of the I/O loop
983 std::string all_requests;
984 for (int i = 0; i < num_requests; i++) {
985 all_requests += keepalive_request;
986 }
987
988 // Watch the log messages to ensure that the first two replies were sent
989 // together. This indicates the non-optimistic send path was used
990 // because a reply was already sitting in the send buffer when a second reply
991 // was added.
992 DebugLogHelper find_two_replies{strprintf("Sent %d bytes to client", reply_length * 2),
993 [&](const std::string* s) {
994 return true;
995 }};
996 // Last reply should be sent on its own by optimistic send path, because
997 // the send buffer was empty when the reply was written.
998 DebugLogHelper find_one_reply{strprintf("Sent %d bytes to client", reply_length),
999 [&](const std::string* s) {
1000 return true;
1001 }};
1002
1003 // Connect the ErrorSock as mock client with the preloaded data and get a handle on the I/O pipes
1004 std::shared_ptr<ErrorSock::Pipes> mock_client_socket_pipes{
1005 ConnectClient<ErrorSock>(std::as_bytes(std::span(all_requests)))
1006 };
1007
1008 // Wait up to one minute for the last reply from the server
1009 std::string actual;
1010 char buf[0x10000] = {};
1011 int attempts = 6000;
1012 while (attempts > 0)
1013 {
1014 ssize_t bytes_read = mock_client_socket_pipes->send.GetBytes(buf, sizeof(buf), 0);
1015 if (bytes_read > 0) {
1016 actual.append(buf, bytes_read);
1017 if (actual.find(strprintf("height: %d", num_requests - 1)) != std::string::npos) {
1018 break;
1019 }
1020 }
1021 std::this_thread::sleep_for(10ms);
1022 --attempts;
1023 }
1024
1025 // Send the third request.
1026 // If there was a race between WriteReply() in the worker thread setting m_send_ready=true
1027 // and SocketHandlerConnected() in the I/O thread flushing the send buffer,
1028 // then the socket would be stuck in write mode with nothing to write,
1029 // the server would never read from the socket, and this request would time out.
1030 // Wait a second to ensure both the worker thread and I/O thread are idle.
1031 // If we send the next request too soon it might get accepted by the server before
1032 // it gets wedged shut.
1033 std::this_thread::sleep_for(1000ms);
1034 mock_client_socket_pipes->recv.PushBytes(keepalive_request.data(), keepalive_request.size());
1035 num_requests++;
1036
1037 // Wait up to one minute for reply
1038 attempts = 6000;
1039 while (attempts > 0)
1040 {
1041 ssize_t bytes_read = mock_client_socket_pipes->send.GetBytes(buf, sizeof(buf), 0);
1042 if (bytes_read > 0) {
1043 actual.append(buf, bytes_read);
1044 if (actual.find(strprintf("height: %d", num_requests - 1)) != std::string::npos) {
1045 break;
1046 }
1047 }
1048 std::this_thread::sleep_for(10ms);
1049 --attempts;
1050 }
1051
1052 // All replies were received
1053 for (int i = 0; i < num_requests; i++) {
1054 BOOST_REQUIRE(actual.find(strprintf("height: %d", i)) != std::string::npos);
1055 }
1056
1057 // Close the keep-alive connection
1058 server.DisconnectAllClients();
1059
1060 workers.Stop();
1061
1062 server.InterruptNet();
1063 server.JoinSocketsThreads();
1064 server.StopListening();
1065}
1066
1067BOOST_AUTO_TEST_CASE(http_server_rejects_disallowed_client_before_read)
1068{
1069 // DynSock reports accepted connections as coming from 5.5.5.5.
1070 gArgs.ForceSetArg("-rpcallowip", "4.4.4.4");
1071
1072 std::atomic_bool request_dispatched{false};
1073 HTTPServer server{[&request_dispatched](std::unique_ptr<HTTPRequest>&&) {
1074 request_dispatched = true;
1075 }};
1076 BOOST_REQUIRE(server.InitHTTPAllowList());
1077
1078 CService addr_bind{Lookup("0.0.0.0", /*portDefault=*/0, /*fAllowLookup=*/false).value()};
1079 BOOST_REQUIRE(server.BindAndStartListening(addr_bind));
1080 server.StartSocketsThreads();
1081
1082 // Queue a complete request; the server should never read from it.
1083 std::shared_ptr<DynSock::Pipes> client_pipes{
1084 ConnectClient(std::as_bytes(std::span(full_request)))};
1085
1086 // Wait for the socket to close with an EOF (bytes_read == 0)
1087 // 'bytes_read > 0' means the server replied to the prohibited client
1088 // 'bytes_read < 0' is an error, expected until the connection is fully processed by the I/O loop
1089 ssize_t bytes_read{};
1090 char buf[0x10000]{};
1091 for (int attempts{0}; attempts != 1'000; ++attempts) {
1092 bytes_read = client_pipes->send.GetBytes(&buf, sizeof(buf), MSG_PEEK);
1093 if (bytes_read >= 0) break;
1094 std::this_thread::sleep_for(10ms);
1095 }
1096
1097 BOOST_CHECK_EQUAL(bytes_read, 0);
1098 BOOST_CHECK(!request_dispatched);
1099 BOOST_CHECK_EQUAL(server.GetConnectionsCount(), 0);
1100
1101 server.InterruptNet();
1102 server.JoinSocketsThreads();
1103 server.StopListening();
1104
1105 // 'recv' buffer still holds the client's request untouched which
1106 // proves the server never called Recv()
1107 const ssize_t recv_bytes{client_pipes->recv.GetBytes(&buf, sizeof(buf))};
1108 BOOST_REQUIRE_EQUAL(recv_bytes, static_cast<ssize_t>(full_request.size()));
1109 BOOST_CHECK_EQUAL(std::string_view(buf, recv_bytes), full_request);
1110}
1111
1112BOOST_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.
void Receive() EXCLUSIVE_LOCKS_REQUIRED(!m_sock_mutex)
Definition: httpserver.cpp:931
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
const HTTPVersion & GetVersion() const LIFETIMEBOUND
Definition: httpserver.h:181
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
Definition: init.h:13
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
FakeNodeClock clock