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