Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/bitcoin/network/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ enum error_t : uint8_t
jsonrpc_v1_requires_params,
jsonrpc_v1_requires_array_params,
jsonrpc_v1_requires_id,
jsonrpc_params_not_collection,
jsonrpc_reader_bad_buffer,
jsonrpc_reader_stall,
jsonrpc_reader_exception,
Expand Down
3 changes: 3 additions & 0 deletions include/bitcoin/network/impl/channels/channel_rpc.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ inline void CLASS::receive() NOEXCEPT
const auto in = system::to_shared<rpc::request>();
using namespace std::placeholders;

// Electrum, allow params singleton to be accepted as array.
in->strict = false;

// Post handle_read to strand upon stop, error, or buffer full.
read(request_buffer(), *in,
std::bind(&channel_rpc::handle_receive,
Expand Down
2 changes: 1 addition & 1 deletion include/bitcoin/network/messages/http_body.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ struct BCT_API body
}, value.value());
}

private:
protected:
header& header_;
value_type& value_;
body_reader reader_;
Expand Down
1 change: 1 addition & 0 deletions include/bitcoin/network/messages/rpc/body.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ struct message_type
: public json::json_value
{
Type message{};
bool strict{ true };
};

/// Derived boost::beast::http body for JSON-RPC messages.
Expand Down
2 changes: 2 additions & 0 deletions include/bitcoin/network/messages/rpc/model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ using value_option = std::optional<value_t>;

using params_t = std::variant
<
// non-standard rpc, required for Electrum compat.
value_t,
array_t,
object_t
>;
Expand Down
1 change: 1 addition & 0 deletions src/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error)
{ jsonrpc_v1_requires_params, "jsonrpc v1 requires params" },
{ jsonrpc_v1_requires_array_params, "jsonrpc v1 requires array params" },
{ jsonrpc_v1_requires_id, "jsonrpc v1 requires id" },
{ jsonrpc_params_not_collection, "jsonrpc params not collection" },
{ jsonrpc_reader_bad_buffer, "jsonrpc reader bad buffer" },
{ jsonrpc_reader_stall, "jsonrpc reader stall" },
{ jsonrpc_reader_exception, "jsonrpc reader exception" },
Expand Down
16 changes: 16 additions & 0 deletions src/messages/rpc/body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,22 @@ finish(boost_code& ec) NOEXCEPT
value_.message.params.value()))
ec = code{ error::jsonrpc_v1_requires_array_params };
}
else if (value_.message.params.has_value() &&
std::holds_alternative<value_t>(value_.message.params.value()))
{
if (!value_.strict)
{
// Convert non-standard rpc, required for Electrum compat.
value_.message.params.emplace(array_t
{
std::get<value_t>(std::move(value_.message.params.value()))
});
}
else
{
ec = code{ error::jsonrpc_params_not_collection };
}
}
}

// rpc::body<response_t>::reader (unused)
Expand Down
18 changes: 9 additions & 9 deletions src/messages/rpc/model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,15 +233,7 @@ DEFINE_JSON_FROM_TAG(request_t)

if (instance.params.has_value())
{
if (const auto& params = instance.params.value();
std::holds_alternative<array_t>(params))
{
object["params"] = value_from(std::get<array_t>(params));
}
else
{
object["params"] = value_from(std::get<object_t>(params));
}
object["params"] = value_from(instance.params.value());
}

value = object;
Expand Down Expand Up @@ -290,6 +282,14 @@ DEFINE_JSON_TO_TAG(request_t)
std::in_place_type<object_t>, value_to<object_t>(params)
};
}
else
{
// Allow non-standard rpc, required for Electrum compat.
request.params = params_t
{
std::in_place_type<value_t>, value_to<value_t>(params)
};
}
}

return request;
Expand Down
9 changes: 9 additions & 0 deletions test/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2140,6 +2140,15 @@ BOOST_AUTO_TEST_CASE(error_t__code__jsonrpc_v1_requires_id__true_expected_messag
BOOST_REQUIRE_EQUAL(ec.message(), "jsonrpc v1 requires id");
}

BOOST_AUTO_TEST_CASE(error_t__code__jsonrpc_params_not_collection__true_expected_message)
{
constexpr auto value = error::jsonrpc_params_not_collection;
const auto ec = code(value);
BOOST_REQUIRE(ec);
BOOST_REQUIRE(ec == value);
BOOST_REQUIRE_EQUAL(ec.message(), "jsonrpc params not collection");
}

BOOST_AUTO_TEST_CASE(error_t__code__jsonrpc_reader_bad_buffer__true_expected_message)
{
constexpr auto value = error::jsonrpc_reader_bad_buffer;
Expand Down
96 changes: 68 additions & 28 deletions test/messages/http_body_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,84 +18,124 @@
*/
#include "../test.hpp"

#if defined(HAVE_SLOW_TESTS)
////#if defined(HAVE_SLOW_TESTS)

using namespace http;
using namespace network::http;

struct accessor
: public body::reader
{
// Forwarding constructor required because base is explicit and templated.
template <bool IsRequest, class Fields>
accessor(http::message_header<IsRequest, Fields>& header,
body::value_type& value) NOEXCEPT
: base(header, value)
{
}

using base = body::reader;
using base::reader;
using base::to_reader;
using base::reader_;
};

BOOST_AUTO_TEST_SUITE(http_body_reader_tests)

BOOST_AUTO_TEST_CASE(http_body_reader__to_reader__bogus__constructs_empty_reader)
BOOST_AUTO_TEST_CASE(http_body_reader__init__bogus__constructs_empty_reader)
{
message_header<false, fields> header{};
message_header<true, fields> header{};
header.set(http::field::content_type, "bogus");
body::value_type value{};
value = empty_body::value_type{};
const auto variant = accessor::to_reader(header, value);
BOOST_REQUIRE(std::holds_alternative<empty_reader>(variant));
accessor reader(header, value);
length_type length{ max_size_t };
boost_code ec{};
reader.init(length, ec);
BOOST_REQUIRE(std::holds_alternative<empty_reader>(reader.reader_));
}

BOOST_AUTO_TEST_CASE(http_body_reader__init__plain_json__constructs_json_reader)
{
message_header<true, fields> header{};
header.set(http::field::content_type, "application/json");
body::value_type value{};
value = json_body::value_type{};
value.plain_json = true;
accessor reader(header, value);
length_type length{ max_size_t };
boost_code ec{};
reader.init(length, ec);
BOOST_REQUIRE(std::holds_alternative<json_reader>(reader.reader_));
}

BOOST_AUTO_TEST_CASE(http_body_reader__to_reader__json__constructs_json_reader)
BOOST_AUTO_TEST_CASE(http_body_reader__init__rpc_json__constructs_rpc_reader)
{
message_header<false, fields> header{};
message_header<true, fields> header{};
header.set(http::field::content_type, "application/json");
body::value_type value{};
value = json_body::value_type{};
const auto variant = accessor::to_reader(header, value);
BOOST_REQUIRE(std::holds_alternative<json_reader>(variant));
value.plain_json = false;
accessor reader(header, value);
length_type length{ max_size_t };
boost_code ec{};
reader.init(length, ec);
BOOST_REQUIRE(std::holds_alternative<rpc::reader>(reader.reader_));
}

BOOST_AUTO_TEST_CASE(http_body_reader__to_reader__application_octet_stream__constructs_data_reader)
BOOST_AUTO_TEST_CASE(http_body_reader__init__application_octet_stream__constructs_data_reader)
{
message_header<false, fields> header{};
message_header<true, fields> header{};
header.set(http::field::content_type, "application/octet-stream");
header.set(http::field::content_disposition, "bogus");
body::value_type value{};
value = chunk_body::value_type{};
const auto variant = accessor::to_reader(header, value);
BOOST_REQUIRE(std::holds_alternative<data_reader>(variant));
accessor reader(header, value);
length_type length{ max_size_t };
boost_code ec{};
reader.init(length, ec);
BOOST_REQUIRE(std::holds_alternative<data_reader>(reader.reader_));
}

BOOST_AUTO_TEST_CASE(http_body_reader__to_reader__application_octet_stream_with_attachment__constructs_file_reader)
BOOST_AUTO_TEST_CASE(http_body_reader__init__application_octet_stream_with_attachment__constructs_file_reader)
{
message_header<false, fields> header{};
message_header<true, fields> header{};
header.set(http::field::content_type, "application/octet-stream");
header.set(http::field::content_disposition, "filename=somenonsense.jpg");
body::value_type value{};
value = file_body::value_type{};
const auto variant = accessor::to_reader(header, value);
BOOST_REQUIRE(std::holds_alternative<file_reader>(variant));
accessor reader(header, value);
length_type length{ max_size_t };
boost_code ec{};
reader.init(length, ec);
BOOST_REQUIRE(std::holds_alternative<file_reader>(reader.reader_));
}

BOOST_AUTO_TEST_CASE(http_body_reader__to_reader__application_octet_stream_with_dirty_attachment__constructs_file_reader)
BOOST_AUTO_TEST_CASE(http_body_reader__init__application_octet_stream_with_dirty_attachment__constructs_file_reader)
{
message_header<false, fields> header{};
message_header<true, fields> header{};
header.set(http::field::content_type, "application/octet-stream");
header.set(http::field::content_disposition, "dirty 42; filename* = somenonsense.jpg; some other nonsense");
body::value_type value{};
value = file_body::value_type{};
const auto variant = accessor::to_reader(header, value);
BOOST_REQUIRE(std::holds_alternative<file_reader>(variant));
accessor reader(header, value);
length_type length{ max_size_t };
boost_code ec{};
reader.init(length, ec);
BOOST_REQUIRE(std::holds_alternative<file_reader>(reader.reader_));
}

BOOST_AUTO_TEST_CASE(http_body_reader__to_reader__text_plain__constructs_string_reader)
BOOST_AUTO_TEST_CASE(http_body_reader__init__text_plain__constructs_string_reader)
{
message_header<false, fields> header{};
message_header<true, fields> header{};
header.set(http::field::content_type, "text/plain");
body::value_type value{};
value = string_body::value_type{};
const auto variant = accessor::to_reader(header, value);
BOOST_REQUIRE(std::holds_alternative<string_reader>(variant));
accessor reader(header, value);
length_type length{ max_size_t };
boost_code ec{};
reader.init(length, ec);
BOOST_REQUIRE(std::holds_alternative<string_reader>(reader.reader_));
}

BOOST_AUTO_TEST_SUITE_END()

#endif // HAVE_SLOW_TESTS
///#endif // HAVE_SLOW_TESTS
4 changes: 2 additions & 2 deletions test/messages/http_body_writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/
#include "../test.hpp"

#if defined(HAVE_SLOW_TESTS)
////#if defined(HAVE_SLOW_TESTS)

using namespace http;
using namespace network::http;
Expand Down Expand Up @@ -118,5 +118,5 @@ BOOST_AUTO_TEST_CASE(http_body_writer__to_writer__file__constructs_file_writer)

BOOST_AUTO_TEST_SUITE_END()

#endif // HAVE_SLOW_TESTS
////#endif // HAVE_SLOW_TESTS

28 changes: 14 additions & 14 deletions test/messages/json_body_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/
#include "../test.hpp"

#if defined(HAVE_SLOW_TESTS)
////#if defined(HAVE_SLOW_TESTS)

BOOST_AUTO_TEST_SUITE(json_body_reader_tests)

Expand All @@ -28,25 +28,25 @@ using value = boost::json::value;

BOOST_AUTO_TEST_CASE(json_body_reader__construct1__default__null_model)
{
json::body::value_type body{};
json::body::reader reader(body);
json::body<>::value_type body{};
json::body<>::reader reader(body);
BOOST_REQUIRE(boost::get<value>(body.model).is_null());
}

BOOST_AUTO_TEST_CASE(json_body_reader__construct2__default__null_model)
{
request_header header{};
json::body::value_type body{};
json::body::reader reader(header, body);
json::body<>::value_type body{};
json::body<>::reader reader(header, body);
BOOST_REQUIRE(boost::get<value>(body.model).is_null());
}

BOOST_AUTO_TEST_CASE(json_body_reader__init__simple_object__success)
{
const std::string_view text{ R"({"key":"value"})" };
const asio::const_buffer buffer{ text.data(), text.size() };
json::body::value_type body{};
json::body::reader reader(body);
json::body<>::value_type body{};
json::body<>::reader reader(body);
boost_code ec{};
reader.init(text.size(), ec);
BOOST_REQUIRE(!ec);
Expand All @@ -56,8 +56,8 @@ BOOST_AUTO_TEST_CASE(json_body_reader__put__simple_object__success_expected_cons
{
const std::string_view text{ R"({"key":"value"})" };
const asio::const_buffer buffer{ text.data(), text.size() };
json::body::value_type body{};
json::body::reader reader(body);
json::body<>::value_type body{};
json::body<>::reader reader(body);
boost_code ec{};
reader.init(text.size(), ec);
BOOST_REQUIRE(!ec);
Expand All @@ -71,8 +71,8 @@ BOOST_AUTO_TEST_CASE(json_body_reader__finish__simple_object__success_expected_m
const std::string_view text{ R"({"key":"value"})" };
const asio::const_buffer buffer{ text.data(), text.size() };

json::body::value_type body{};
json::body::reader reader(body);
json::body<>::value_type body{};
json::body<>::reader reader(body);
boost_code ec{};
reader.init(text.size(), ec);
BOOST_REQUIRE(!ec);
Expand All @@ -91,8 +91,8 @@ BOOST_AUTO_TEST_CASE(json_body_reader__put__over_length__protocol_error)
{
const std::string_view text{ R"({"key":"value"})" };
const asio::const_buffer buffer{ text.data(), text.size() };
json::body::value_type body{};
json::body::reader reader(body);
json::body<>::value_type body{};
json::body<>::reader reader(body);
boost_code ec{};
reader.init(10, ec);
BOOST_REQUIRE(!ec);
Expand All @@ -102,4 +102,4 @@ BOOST_AUTO_TEST_CASE(json_body_reader__put__over_length__protocol_error)

BOOST_AUTO_TEST_SUITE_END()

#endif // HAVE_SLOW_TESTS
////#endif // HAVE_SLOW_TESTS
Loading
Loading