Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2012-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <httpserver.h>6#include <rpc/protocol.h>7#include <test/util/common.h>8#include <test/util/logging.h>9#include <test/util/setup_common.h>10#include <util/string.h>11#include <util/threadpool.h>13#include <boost/test/unit_test.hpp>15using util::LineReader;16using namespace bitcoin_http;18// HTTP request captured from bitcoin-cli19constexpr 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"26R"({"method":"getblockcount","params":[],"id":1})""\n";28BOOST_FIXTURE_TEST_SUITE(httpserver_tests, SocketTestingSetup)30BOOST_AUTO_TEST_CASE(test_query_parameters)31{32std::string uri {};34// Tolerate a URI with invalid characters (% not followed by hex digits)35uri = "/rest/endpoint/someresource.json?p1=v1&p2=v2%";36BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");37BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p2"), "v2%");39// No parameters40uri = "localhost:8080/rest/headers/someresource.json";41BOOST_CHECK(!GetQueryParameterFromUri(uri, "p1"));43// Single parameter44uri = "localhost:8080/rest/endpoint/someresource.json?p1=v1";45BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");46BOOST_CHECK(!GetQueryParameterFromUri(uri, "p2"));48// Multiple parameters49uri = "/rest/endpoint/someresource.json?p1=v1&p2=v2";50BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");51BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p2"), "v2");53// If the query string contains duplicate keys, the first value is returned54uri = "/rest/endpoint/someresource.json?p1=v1&p1=v2";55BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1");57// Invalid query string syntax is the same as not having parameters58uri = "/rest/endpoint/someresource.json&p1=v1&p2=v2";59BOOST_CHECK(!GetQueryParameterFromUri(uri, "p1"));61// Multiple parameters, some characters encoded62uri = "/rest/endpoint/someresource.json?p1=v1%20&p2=100%25";63BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p1"), "v1 ");64BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p2"), "100%");66// Encoded query delimiters are part of the parameter value, not structure.67uri = "/rest/endpoint/someresource.json?p=a%26b%3Dc%23frag&other=x";68BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "p"), "a&b=c#frag");69BOOST_CHECK_EQUAL(GetQueryParameterFromUri(uri, "other"), "x");71// An encoded question mark in the path does not introduce a query section.72uri = "/rest/endpoint/someresource.json%3Fp1%3Dv1%26p2%3D100%25";73BOOST_CHECK(!GetQueryParameterFromUri(uri, "p1"));74}76BOOST_AUTO_TEST_CASE(http_headers_tests)77{78{79// Writing response headers80HTTPHeaders headers{};81BOOST_CHECK(!headers.FindFirst("Cache-Control"));82headers.Write("Cache-Control", "no-cache");83// Check case-insensitive key matching84BOOST_CHECK_EQUAL(headers.FindFirst("Cache-Control"), "no-cache");85BOOST_CHECK_EQUAL(headers.FindFirst("cache-control"), "no-cache");86// Additional values are appended, compared case-insensitive87headers.Write("cache-control", "max-age=60");88BOOST_CHECK_EQUAL(headers.FindFirst("Cache-Control"), "no-cache");89BOOST_CHECK((headers.FindAll("Cache-Control") == std::vector<std::string_view>{"no-cache", "max-age=60"}));90// Add a few more91headers.Write("Pie", "apple");92headers.Write("Sandwich", "ham");93headers.Write("Coffee", "black");94BOOST_CHECK_EQUAL(headers.FindFirst("Pie"), "apple");95// Remove96headers.RemoveAll("Pie");97BOOST_CHECK(!headers.FindFirst("Pie"));98// Combine for transmission99std::string headers_string{headers.Stringify()};100BOOST_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-cli108constexpr 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";113util::LineReader reader(bitcoin_cli_headers, /*max_line_length=*/MAX_HEADERS_SIZE);114HTTPHeaders headers{};115headers.Read(reader);116BOOST_CHECK_EQUAL(headers.FindFirst("Host"), "127.0.0.1");117BOOST_CHECK_EQUAL(headers.FindFirst("Connection"), "close");118BOOST_CHECK_EQUAL(headers.FindFirst("Content-Type"), "application/json");119BOOST_CHECK_EQUAL(headers.FindFirst("Authorization"), "Basic X19jb29raWVfXzozYzJkNTAxNDFlMGJiYmVhMTI5ODg3NzI5MTM3NTRmNThkNjc2OWMwZTYxZjgzNTgyNzEwYTY1OGRkYjVmZGQ3");120BOOST_CHECK_EQUAL(headers.FindFirst("Content-Length"), "46");121BOOST_CHECK(!headers.FindFirst("Pizza"));122}123// Ensure invalid headers are rejected124{125// missing a colon126util::LineReader reader{"key value\n", /*max_line_length=*/MAX_HEADERS_SIZE};127BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"HTTP header missing colon (:)"});128}129{130// missing a key131util::LineReader reader{":value\n", /*max_line_length=*/MAX_HEADERS_SIZE};132BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Empty HTTP header name"});133}134{135// contains NUL136util::LineReader reader{std::string_view{"X-Custom: foo\0bar\n", 18}, /*max_line_length=*/MAX_HEADERS_SIZE};137BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Header contains invalid character"});138}139{140// contains bare \r (not followed by \n)141util::LineReader reader{std::string_view{"X-Custom: foo\rbar\n"}, /*max_line_length=*/MAX_HEADERS_SIZE};142BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Header contains invalid character"});143}144{145// contains odd \r preceding the expected CRLF146util::LineReader reader{"X-Custom: foo\r\r\n", /*max_line_length=*/MAX_HEADERS_SIZE};147BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"Header contains invalid character"});148}149{150// key contains whitespace151util::LineReader reader{"key : value\n", /*max_line_length=*/MAX_HEADERS_SIZE};152BOOST_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 excessive156std::string lines;157lines.reserve(820 * 10);158for (int i = 0; i < 820; ++i) {159lines.append("key:value\n");160}161std::string_view excessive_headers{lines};162BOOST_CHECK_GT(excessive_headers.size(), MAX_HEADERS_SIZE);163util::LineReader reader{excessive_headers, /*max_line_length=*/MAX_HEADERS_SIZE};164BOOST_CHECK_EXCEPTION(HTTPHeaders{}.Read(reader), std::runtime_error, HasReason{"HTTP headers exceed size limit"});165}166{167// Ok168util::LineReader reader{"key: value\n", /*max_line_length=*/MAX_HEADERS_SIZE};169HTTPHeaders headers{};170headers.Read(reader);171BOOST_CHECK_EQUAL(headers.FindFirst("key"), "value");172}173}175BOOST_AUTO_TEST_CASE(http_response_tests)176{177// Typical HTTP 1.1 response headers178HTTPHeaders headers{};179headers.Write("Content-Length", "41");181// Response points to headers which already exist because some of them182// are set before we even know what the response will be.183HTTPResponse res;184res.version = {.major = 1, .minor = 1};185res.status = HTTP_OK;186res.headers = std::move(headers);187BOOST_CHECK_EQUAL(188res.StringifyHeaders(),189"HTTP/1.1 200 OK\r\n"190"Content-Length: 41\r\n"191"\r\n");192}194BOOST_AUTO_TEST_CASE(http_request_tests)195{196{197HTTPRequest req;198LineReader reader(full_request, MAX_HEADERS_SIZE);199BOOST_CHECK(req.LoadControlData(reader));200BOOST_CHECK(req.LoadHeaders(reader));201BOOST_CHECK(req.LoadBody(reader));202BOOST_CHECK_EQUAL(req.GetRequestMethod(), HTTPRequestMethod::POST);203BOOST_CHECK_EQUAL(req.GetURI(), "/");204BOOST_CHECK_EQUAL(req.GetVersion().major, 1);205BOOST_CHECK_EQUAL(req.GetVersion().minor, 1);206BOOST_CHECK_EQUAL(req.GetHeader("Host"), "127.0.0.1");207BOOST_CHECK_EQUAL(req.GetHeader("Connection"), "close");208BOOST_CHECK_EQUAL(req.GetHeader("Content-Type"), "application/json");209BOOST_CHECK_EQUAL(req.GetHeader("Authorization"), "Basic X19jb29raWVfXzo5OGQ5ODQ3MWNmNjg0NzAzYTkzN2EzNzk0ZDFlODQ1NjZmYTRkZjJiMzFkYjhhODI4ZGY4MjVjOTg5ZGI4OTVl");210BOOST_CHECK_EQUAL(req.GetHeader("Content-Length"), "46");211BOOST_CHECK_EQUAL(req.ReadBody(), R"({"method":"getblockcount","params":[],"id":1})""\n");212}213{214// Malformed: no spaces between data215HTTPRequest req;216LineReader reader("GET/HTTP/1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);217BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line too short"});218}219{220// Malformed: too many spaces221HTTPRequest req;222LineReader reader("GET / HTTP / 1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);223BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line malformed"});224}225{226// Malformed: slash missing before version227HTTPRequest req;228LineReader reader("GET / HTTP1.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);229BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line too short"});230}231{232// Malformed: no decimal in version233HTTPRequest req;234LineReader reader("GET / HTTP/11\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);235BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP request line too short"});236}237{238// Malformed: version is not a number239HTTPRequest req;240LineReader reader("GET / HTTP/1.x\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);241BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});242}243{244// Malformed: version is out of range245HTTPRequest req;246LineReader reader("GET / HTTP/2.0\r\nHost: 127.0.0.1\r\n\r\n", MAX_HEADERS_SIZE);247BOOST_CHECK_EXCEPTION(req.LoadControlData(reader), std::runtime_error, HasReason{"HTTP bad version"});248}249{250// Malformed: version is out of range
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.