mirror of
https://github.com/rakshasa/rtorrent.git
synced 2026-10-04 21:29:21 +00:00
Compare commits
2 Commits
05cdd03cd2
...
439d23ce62
| Author | SHA1 | Date | |
|---|---|---|---|
| 439d23ce62 | |||
| 83b03c2c1e |
+22
-4
@@ -89,6 +89,24 @@ throttle_update(const char* variable, int64_t value) {
|
||||
return torrent::Object();
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
throttle_rate_to_kb(int64_t rate) {
|
||||
if (rate < 0 || rate > std::numeric_limits<unsigned int>::max() - 1)
|
||||
throw torrent::input_error("Throttle rate must be between 0 and 4294967294.");
|
||||
|
||||
return static_cast<unsigned int>(rate >> 10);
|
||||
}
|
||||
|
||||
static void
|
||||
set_up_throttle_i64(ui::Root* root, int64_t rate) {
|
||||
root->set_up_throttle(throttle_rate_to_kb(rate));
|
||||
}
|
||||
|
||||
static void
|
||||
set_down_throttle_i64(ui::Root* root, int64_t rate) {
|
||||
root->set_down_throttle(throttle_rate_to_kb(rate));
|
||||
}
|
||||
|
||||
void
|
||||
initialize_command_throttle() {
|
||||
CMD2_ANY ("throttle.unchoked_uploads", std::bind(&torrent::ResourceManager::currently_upload_unchoked, torrent::resource_manager()));
|
||||
@@ -125,13 +143,13 @@ initialize_command_throttle() {
|
||||
CMD2_ANY ("throttle.global_up.rate", std::bind(&torrent::Rate::rate, torrent::up_rate()));
|
||||
CMD2_ANY ("throttle.global_up.total", std::bind(&torrent::Rate::total, torrent::up_rate()));
|
||||
CMD2_ANY ("throttle.global_up.max_rate", std::bind(&torrent::Throttle::max_rate, torrent::up_throttle_global()));
|
||||
CMD2_ANY_VALUE_V ("throttle.global_up.max_rate.set", std::bind(&ui::Root::set_up_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
CMD2_ANY_VALUE_KB("throttle.global_up.max_rate.set_kb", std::bind(&ui::Root::set_up_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
CMD2_ANY_VALUE_V ("throttle.global_up.max_rate.set", std::bind(&set_up_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
CMD2_ANY_VALUE_KB("throttle.global_up.max_rate.set_kb", std::bind(&set_up_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
CMD2_ANY ("throttle.global_down.rate", std::bind(&torrent::Rate::rate, torrent::down_rate()));
|
||||
CMD2_ANY ("throttle.global_down.total", std::bind(&torrent::Rate::total, torrent::down_rate()));
|
||||
CMD2_ANY ("throttle.global_down.max_rate", std::bind(&torrent::Throttle::max_rate, torrent::down_throttle_global()));
|
||||
CMD2_ANY_VALUE_V ("throttle.global_down.max_rate.set", std::bind(&ui::Root::set_down_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
CMD2_ANY_VALUE_KB("throttle.global_down.max_rate.set_kb", std::bind(&ui::Root::set_down_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
CMD2_ANY_VALUE_V ("throttle.global_down.max_rate.set", std::bind(&set_down_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
CMD2_ANY_VALUE_KB("throttle.global_down.max_rate.set_kb", std::bind(&set_down_throttle_i64, control->ui(), std::placeholders::_2));
|
||||
|
||||
// Temporary names, need to change this to accept real rates rather
|
||||
// than kB.
|
||||
|
||||
+39
-1
@@ -2,8 +2,10 @@
|
||||
|
||||
#include "rpc/jsonrpc.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <torrent/common.h>
|
||||
#include <torrent/torrent.h>
|
||||
#include <torrent/utils/string_manip.h>
|
||||
@@ -223,13 +225,49 @@ handle_notification(const json& request) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
using json_input_adapter = decltype(nlohmann::detail::input_adapter(std::declval<const char*>(), std::declval<const char*>()));
|
||||
using json_dom_parser = nlohmann::detail::json_sax_dom_parser<json, json_input_adapter>;
|
||||
|
||||
class json_depth_limited_parser : public json_dom_parser {
|
||||
public:
|
||||
explicit json_depth_limited_parser(json& root) : json_dom_parser(root) {}
|
||||
|
||||
bool start_object(std::size_t length) { return enter() && json_dom_parser::start_object(length); }
|
||||
bool start_array(std::size_t length) { return enter() && json_dom_parser::start_array(length); }
|
||||
|
||||
bool end_object() { m_depth--; return json_dom_parser::end_object(); }
|
||||
bool end_array() { m_depth--; return json_dom_parser::end_array(); }
|
||||
|
||||
private:
|
||||
bool enter() { return ++m_depth <= max_json_depth; }
|
||||
|
||||
uint32_t m_depth{0};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool
|
||||
JsonRpc::process(const char* in_buffer, uint32_t length, slot_write callback) {
|
||||
json response;
|
||||
json body;
|
||||
|
||||
if (length > m_size_limit) {
|
||||
auto err_str = json_error(JSONRPC_INVALID_REQUEST_ERROR, "content size exceeds maximum RPC limit", nullptr).dump();
|
||||
|
||||
return callback(err_str.c_str(), err_str.size());
|
||||
}
|
||||
|
||||
try {
|
||||
body = json::parse(in_buffer, in_buffer + length);
|
||||
json_depth_limited_parser handler(body);
|
||||
|
||||
if (!json::sax_parse(in_buffer, in_buffer + length, &handler)) {
|
||||
auto err_str = json_error(JSONRPC_INVALID_REQUEST_ERROR, "maximum nesting depth exceeded", nullptr).dump();
|
||||
|
||||
return callback(err_str.c_str(), err_str.size());
|
||||
}
|
||||
|
||||
switch (body.type()) {
|
||||
case json::value_t::object: {
|
||||
if (!body.contains("id")) {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "rpc/scgi_task.h"
|
||||
|
||||
namespace rpc {
|
||||
|
||||
class JsonRpc {
|
||||
@@ -17,6 +19,11 @@ public:
|
||||
bool process(const char* in_buffer, uint32_t length, slot_write callback);
|
||||
|
||||
void insert_command(const char* name, const char* parm, const char* doc) {};
|
||||
|
||||
void set_size_limit(uint64_t size) { m_size_limit = size; }
|
||||
|
||||
private:
|
||||
uint64_t m_size_limit{SCgiTask::max_content_size};
|
||||
};
|
||||
|
||||
} // namespace rpc
|
||||
|
||||
@@ -176,6 +176,7 @@ RpcManager::set_size_limit(uint64_t size) {
|
||||
throw torrent::input_error("XMLRPC size limit is too small to hold a request.");
|
||||
|
||||
m_xmlrpc.set_size_limit(size);
|
||||
m_jsonrpc.set_size_limit(size);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -70,10 +70,6 @@ public:
|
||||
void set_down_throttle(unsigned int throttle);
|
||||
void set_up_throttle(unsigned int throttle);
|
||||
|
||||
// Rename to raw or something, make base function.
|
||||
void set_down_throttle_i64(int64_t throttle) { set_down_throttle(throttle >> 10); }
|
||||
void set_up_throttle_i64(int64_t throttle) { set_up_throttle(throttle >> 10); }
|
||||
|
||||
void adjust_down_throttle(int throttle);
|
||||
void adjust_up_throttle(int throttle);
|
||||
|
||||
|
||||
@@ -67,6 +67,8 @@ rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
|
||||
src/test_command_path.h \
|
||||
src/test_command_string.cc \
|
||||
src/test_command_string.h \
|
||||
src/test_command_throttle.cc \
|
||||
src/test_command_throttle.h \
|
||||
src/test_setup.cc \
|
||||
src/test_setup.h \
|
||||
src/test_ui_download_list.cc \
|
||||
|
||||
@@ -173,3 +173,82 @@ TestJsonrpc::test_response_size_limit() {
|
||||
output.size() <= rpc::SCgiTask::max_response_size);
|
||||
CPPUNIT_ASSERT_EQUAL(expected, output);
|
||||
}
|
||||
|
||||
// The bound is applied while the document is parsed: json_to_object only ever
|
||||
// walks "params", and by the time it runs the whole tree already exists.
|
||||
void
|
||||
TestJsonrpc::test_depth_limit() {
|
||||
// A JSON string holding a quote and brackets that must not be counted.
|
||||
const std::string tricky = R"("\"[[[")";
|
||||
|
||||
std::vector<std::tuple<std::string, std::string, std::string>> requests = {
|
||||
std::make_tuple("Nesting under the limit is accepted",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" +
|
||||
std::string(1000, '[') + std::string(1000, ']') + R"(], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[)" +
|
||||
std::string(1000, '[') + std::string(1000, ']') + R"(]})"),
|
||||
|
||||
// Nesting outside "params" is never converted, so json_to_object's own
|
||||
// bound never sees it.
|
||||
std::make_tuple("Nesting outside params is rejected",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": [""], "id": 1, "x": )" +
|
||||
std::string(2000, '[') + std::string(2000, ']') + R"(})",
|
||||
R"({"error":{"code":-32600,"message":"maximum nesting depth exceeded"},"id":null,"jsonrpc":"2.0"})"),
|
||||
|
||||
std::make_tuple("Nesting over the limit is rejected",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" +
|
||||
std::string(2000, '[') + std::string(2000, ']') + R"(], "id": 1})",
|
||||
R"({"error":{"code":-32600,"message":"maximum nesting depth exceeded"},"id":null,"jsonrpc":"2.0"})"),
|
||||
|
||||
std::make_tuple("Brackets inside a string are not nesting",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", ")" +
|
||||
std::string(2000, '[') + R"("], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[")" +
|
||||
std::string(2000, '[') + R"("]})"),
|
||||
|
||||
std::make_tuple("An escaped quote does not end a string",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" + tricky + R"(, ")" +
|
||||
std::string(2000, '[') + R"("], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[)" + tricky + R"(,")" +
|
||||
std::string(2000, '[') + R"("]})"),
|
||||
|
||||
// The bound is on the whole document, so the outer object and the params
|
||||
// array are two of the 1024 containers and 1022 are left for the payload.
|
||||
std::make_tuple("Nesting one below the limit is accepted",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" +
|
||||
std::string(1021, '[') + std::string(1021, ']') + R"(], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[)" +
|
||||
std::string(1021, '[') + std::string(1021, ']') + R"(]})"),
|
||||
|
||||
std::make_tuple("Nesting at the limit is accepted",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" +
|
||||
std::string(1022, '[') + std::string(1022, ']') + R"(], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[)" +
|
||||
std::string(1022, '[') + std::string(1022, ']') + R"(]})"),
|
||||
|
||||
std::make_tuple("Nesting one over the limit is rejected",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", )" +
|
||||
std::string(1023, '[') + std::string(1023, ']') + R"(], "id": 1})",
|
||||
R"({"error":{"code":-32600,"message":"maximum nesting depth exceeded"},"id":null,"jsonrpc":"2.0"})"),
|
||||
};
|
||||
|
||||
for (auto& test : requests) {
|
||||
std::string output;
|
||||
m_jsonrpc.process(std::get<1>(test).c_str(), std::get<1>(test).size(), [&output](const char* c, uint32_t l) { output.append(c, l); return true; });
|
||||
CPPUNIT_ASSERT_EQUAL_MESSAGE(std::get<0>(test), std::get<2>(test), output);
|
||||
}
|
||||
}
|
||||
|
||||
// network.xmlrpc.size_limit is the only knob bounding how much input a single
|
||||
// request may spend memory on, and it has to bound the JSON path too.
|
||||
void
|
||||
TestJsonrpc::test_size_limit() {
|
||||
const std::string request = R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": [""], "id": 1})";
|
||||
const std::string expected = R"({"error":{"code":-32600,"message":"content size exceeds maximum RPC limit"},"id":null,"jsonrpc":"2.0"})";
|
||||
|
||||
std::string output;
|
||||
m_jsonrpc.set_size_limit(1);
|
||||
m_jsonrpc.process(request.c_str(), request.size(), [&output](const char* c, uint32_t l) { output.append(c, l); return true; });
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(expected, output);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ class TestJsonrpc : public test_fixture {
|
||||
|
||||
CPPUNIT_TEST(test_basics);
|
||||
CPPUNIT_TEST(test_response_size_limit);
|
||||
CPPUNIT_TEST(test_depth_limit);
|
||||
CPPUNIT_TEST(test_size_limit);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
@@ -18,6 +20,8 @@ public:
|
||||
|
||||
void test_basics();
|
||||
void test_response_size_limit();
|
||||
void test_depth_limit();
|
||||
void test_size_limit();
|
||||
|
||||
private:
|
||||
std::unique_ptr<TestMainThread> m_test_main_thread;
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "test/src/test_command_throttle.h"
|
||||
|
||||
#include <torrent/throttle.h>
|
||||
#include <torrent/torrent.h>
|
||||
|
||||
#include "core/manager.h"
|
||||
#include "control.h"
|
||||
#include "globals.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandThrottle);
|
||||
|
||||
void initialize_command_throttle();
|
||||
|
||||
static void
|
||||
call_set(const char* key, const char* value) {
|
||||
rpc::commands.call_command(key, torrent::Object(std::string(value)));
|
||||
}
|
||||
|
||||
static void
|
||||
call_named(const char* key, const char* name, const char* value) {
|
||||
torrent::Object::list_type args;
|
||||
args.push_back(torrent::Object(std::string(name)));
|
||||
args.push_back(torrent::Object(std::string(value)));
|
||||
|
||||
rpc::commands.call_command(key, torrent::Object::create_list_range(args.begin(), args.end()));
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
down_rate() {
|
||||
return torrent::down_throttle_global()->max_rate();
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::setUp() {
|
||||
torrent::initialize_main_thread();
|
||||
torrent::initialize();
|
||||
|
||||
if (control == nullptr)
|
||||
control = new Control;
|
||||
|
||||
if (!rpc::commands.has("throttle.global_down.max_rate.set_kb"))
|
||||
initialize_command_throttle();
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::tearDown() {
|
||||
torrent::cleanup();
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::test_global_rate_in_range() {
|
||||
call_set("throttle.global_down.max_rate.set_kb", "1024");
|
||||
CPPUNIT_ASSERT_EQUAL(uint64_t{1048576}, down_rate());
|
||||
|
||||
call_set("throttle.global_down.max_rate.set_kb", "4194303");
|
||||
CPPUNIT_ASSERT_EQUAL(uint64_t{4294966272}, down_rate());
|
||||
|
||||
call_set("throttle.global_down.max_rate.set", "4294966272");
|
||||
CPPUNIT_ASSERT_EQUAL(uint64_t{4294966272}, down_rate());
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::test_global_rate_kb_out_of_range() {
|
||||
call_set("throttle.global_down.max_rate.set_kb", "1024");
|
||||
|
||||
CPPUNIT_ASSERT_THROW(call_set("throttle.global_down.max_rate.set_kb", "4194304"), torrent::input_error);
|
||||
CPPUNIT_ASSERT_EQUAL(uint64_t{1048576}, down_rate());
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::test_global_rate_bytes_out_of_range() {
|
||||
call_set("throttle.global_down.max_rate.set", "1048576");
|
||||
|
||||
CPPUNIT_ASSERT_THROW(call_set("throttle.global_down.max_rate.set", "4294967296"), torrent::input_error);
|
||||
CPPUNIT_ASSERT_EQUAL(uint64_t{1048576}, down_rate());
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::test_global_rate_negative() {
|
||||
call_set("throttle.global_down.max_rate.set", "1048576");
|
||||
|
||||
CPPUNIT_ASSERT_THROW(call_set("throttle.global_down.max_rate.set", "-1"), torrent::input_error);
|
||||
CPPUNIT_ASSERT_EQUAL(uint64_t{1048576}, down_rate());
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::test_named_rate_in_range() {
|
||||
call_named("throttle.down", "test_named_in_range", "1024");
|
||||
|
||||
auto itr = control->core()->throttles().find("test_named_in_range");
|
||||
|
||||
CPPUNIT_ASSERT(itr != control->core()->throttles().end());
|
||||
CPPUNIT_ASSERT_EQUAL(uint64_t{1048576}, itr->second.second->max_rate());
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandThrottle::test_named_rate_out_of_range() {
|
||||
CPPUNIT_ASSERT_THROW(call_named("throttle.down", "test_named_out_of_range", "18014398509481984"), torrent::input_error);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "test/helpers/test_fixture.h"
|
||||
|
||||
class TestCommandThrottle : public test_fixture {
|
||||
CPPUNIT_TEST_SUITE(TestCommandThrottle);
|
||||
|
||||
CPPUNIT_TEST(test_global_rate_in_range);
|
||||
CPPUNIT_TEST(test_global_rate_kb_out_of_range);
|
||||
CPPUNIT_TEST(test_global_rate_bytes_out_of_range);
|
||||
CPPUNIT_TEST(test_global_rate_negative);
|
||||
CPPUNIT_TEST(test_named_rate_in_range);
|
||||
CPPUNIT_TEST(test_named_rate_out_of_range);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
||||
void test_global_rate_in_range();
|
||||
void test_global_rate_kb_out_of_range();
|
||||
void test_global_rate_bytes_out_of_range();
|
||||
void test_global_rate_negative();
|
||||
void test_named_rate_in_range();
|
||||
void test_named_rate_out_of_range();
|
||||
};
|
||||
Reference in New Issue
Block a user