mirror of
https://github.com/rakshasa/rtorrent.git
synced 2026-08-05 17:52:29 +00:00
Add JSON-RPC capability
Inline nlohmann/json for the JSON parsing itself, and handle requests with the same SCGI interface as XML-RPC. Based off the work in https://github.com/jesec/rtorrent
This commit is contained in:
@@ -109,6 +109,10 @@ libsub_root_a_SOURCES = \
|
||||
rpc/ip_table_list.h \
|
||||
rpc/lua.h \
|
||||
rpc/lua.cc \
|
||||
rpc/jsonrpc.cc \
|
||||
rpc/jsonrpc.h \
|
||||
rpc/rpc_manager.cc \
|
||||
rpc/rpc_manager.h \
|
||||
rpc/object_storage.cc \
|
||||
rpc/object_storage.h \
|
||||
rpc/parse.cc \
|
||||
@@ -127,6 +131,7 @@ libsub_root_a_SOURCES = \
|
||||
rpc/xmlrpc_tinyxml2.cc \
|
||||
rpc/tinyxml2/tinyxml2.h \
|
||||
rpc/tinyxml2/tinyxml2.cc \
|
||||
rpc/nlohmann/json.h \
|
||||
\
|
||||
ui/download.cc \
|
||||
ui/download.h \
|
||||
@@ -157,6 +162,7 @@ libsub_root_a_SOURCES = \
|
||||
ui/root.cc \
|
||||
ui/root.h \
|
||||
\
|
||||
utils/base64.cc \
|
||||
utils/base64.h \
|
||||
utils/directory.cc \
|
||||
utils/directory.h \
|
||||
|
||||
@@ -187,7 +187,7 @@ system_method_insert_object(const torrent::Object::list_type& args, int flags) {
|
||||
if (!(flags & rpc::object_storage::flag_static))
|
||||
cmd_flags |= rpc::CommandMap::flag_modifiable;
|
||||
if (!(flags & rpc::object_storage::flag_private))
|
||||
cmd_flags |= rpc::CommandMap::flag_public_xmlrpc;
|
||||
cmd_flags |= rpc::CommandMap::flag_public_rpc;
|
||||
|
||||
if ((flags & rpc::object_storage::mask_type) == rpc::object_storage::flag_list_type) {
|
||||
torrent::Object valueList = torrent::Object::create_list();
|
||||
@@ -360,7 +360,7 @@ system_method_redirect(const torrent::Object::list_type& args) {
|
||||
std::string new_key = torrent::object_create_string(args.front());
|
||||
std::string dest_key = torrent::object_create_string(args.back());
|
||||
|
||||
rpc::commands.create_redirect(new_key, dest_key, rpc::CommandMap::flag_public_xmlrpc | rpc::CommandMap::flag_modifiable);
|
||||
rpc::commands.create_redirect(new_key, dest_key, rpc::CommandMap::flag_public_rpc | rpc::CommandMap::flag_modifiable);
|
||||
|
||||
return torrent::Object();
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ void initialize_commands();
|
||||
|
||||
#define CMD2_A_FUNCTION(key, function, slot, parm, doc) \
|
||||
rpc::commands.insert_slot<rpc::command_base_is_type<rpc::function>::type>(key, slot, &rpc::function, \
|
||||
rpc::CommandMap::flag_dont_delete | rpc::CommandMap::flag_public_xmlrpc, NULL, NULL);
|
||||
rpc::CommandMap::flag_dont_delete | rpc::CommandMap::flag_public_rpc, NULL, NULL);
|
||||
|
||||
#define CMD2_A_FUNCTION_PRIVATE(key, function, slot, parm, doc) \
|
||||
rpc::commands.insert_slot<rpc::command_base_is_type<rpc::function>::type>(key, slot, &rpc::function, \
|
||||
@@ -137,19 +137,19 @@ void initialize_commands();
|
||||
std::placeholders::_1, std::placeholders::_2));
|
||||
|
||||
#define CMD2_REDIRECT(from_key, to_key) \
|
||||
rpc::commands.create_redirect(from_key, to_key, rpc::CommandMap::flag_public_xmlrpc | rpc::CommandMap::flag_dont_delete);
|
||||
rpc::commands.create_redirect(from_key, to_key, rpc::CommandMap::flag_public_rpc | rpc::CommandMap::flag_dont_delete);
|
||||
#define CMD2_REDIRECT_GENERIC(from_key, to_key) \
|
||||
rpc::commands.create_redirect(from_key, to_key, rpc::CommandMap::flag_public_xmlrpc | rpc::CommandMap::flag_no_target | rpc::CommandMap::flag_dont_delete);
|
||||
rpc::commands.create_redirect(from_key, to_key, rpc::CommandMap::flag_public_rpc | rpc::CommandMap::flag_no_target | rpc::CommandMap::flag_dont_delete);
|
||||
#define CMD2_REDIRECT_GENERIC_NO_EXPORT(from_key, to_key) \
|
||||
rpc::commands.create_redirect(from_key, to_key, rpc::CommandMap::flag_no_target | rpc::CommandMap::flag_dont_delete);
|
||||
#define CMD2_REDIRECT_FILE(from_key, to_key) \
|
||||
rpc::commands.create_redirect(from_key, to_key, rpc::CommandMap::flag_public_xmlrpc | rpc::CommandMap::flag_file_target | rpc::CommandMap::flag_dont_delete);
|
||||
rpc::commands.create_redirect(from_key, to_key, rpc::CommandMap::flag_public_rpc | rpc::CommandMap::flag_file_target | rpc::CommandMap::flag_dont_delete);
|
||||
#define CMD2_REDIRECT_TRACKER(from_key, to_key) \
|
||||
rpc::commands.create_redirect(from_key, to_key, rpc::CommandMap::flag_public_xmlrpc | rpc::CommandMap::flag_tracker_target | rpc::CommandMap::flag_dont_delete);
|
||||
rpc::commands.create_redirect(from_key, to_key, rpc::CommandMap::flag_public_rpc | rpc::CommandMap::flag_tracker_target | rpc::CommandMap::flag_dont_delete);
|
||||
|
||||
#define CMD2_REDIRECT_GENERIC_STR(from_key, to_key) \
|
||||
rpc::commands.create_redirect(from_key, to_key, \
|
||||
rpc::CommandMap::flag_public_xmlrpc | rpc::CommandMap::flag_no_target);
|
||||
rpc::CommandMap::flag_public_rpc | rpc::CommandMap::flag_no_target);
|
||||
|
||||
#define CMD2_REDIRECT_GENERIC_STR_NO_EXPORT(from_key, to_key) \
|
||||
rpc::commands.create_redirect(from_key, to_key, \
|
||||
|
||||
@@ -142,5 +142,6 @@ initialize_command_logging() {
|
||||
|
||||
CMD2_ANY_STRING ("log.execute", std::bind(&apply_log, std::placeholders::_2, 0));
|
||||
CMD2_ANY_STRING ("log.vmmap.dump", std::bind(&log_vmmap_dump, std::placeholders::_2));
|
||||
CMD2_ANY_STRING_V("log.xmlrpc", std::bind(&ThreadWorker::set_xmlrpc_log, worker_thread, std::placeholders::_2));
|
||||
CMD2_ANY_STRING_V("log.rpc", std::bind(&ThreadWorker::set_rpc_log, worker_thread, std::placeholders::_2));
|
||||
CMD2_REDIRECT ("log.xmlrpc", "log.rpc"); // For backwards compatibility
|
||||
}
|
||||
|
||||
+20
-18
@@ -95,7 +95,7 @@ apply_tos(const torrent::Object::string_type& arg) {
|
||||
torrent::Object apply_encoding_list(const std::string& arg) { torrent::encoding_list()->push_back(arg); return torrent::Object(); }
|
||||
|
||||
torrent::File*
|
||||
xmlrpc_find_file(core::Download* download, uint32_t index) {
|
||||
rpc_find_file(core::Download* download, uint32_t index) {
|
||||
if (index >= download->file_list()->size_files())
|
||||
return NULL;
|
||||
|
||||
@@ -104,7 +104,7 @@ xmlrpc_find_file(core::Download* download, uint32_t index) {
|
||||
|
||||
// Ergh... time to update the Tracker API to allow proper ptrs.
|
||||
torrent::Tracker*
|
||||
xmlrpc_find_tracker(core::Download* download, uint32_t index) {
|
||||
rpc_find_tracker(core::Download* download, uint32_t index) {
|
||||
if (index >= download->tracker_list()->size())
|
||||
return NULL;
|
||||
|
||||
@@ -112,7 +112,7 @@ xmlrpc_find_tracker(core::Download* download, uint32_t index) {
|
||||
}
|
||||
|
||||
torrent::Peer*
|
||||
xmlrpc_find_peer(core::Download* download, const torrent::HashString& hash) {
|
||||
rpc_find_peer(core::Download* download, const torrent::HashString& hash) {
|
||||
torrent::ConnectionList::iterator itr = download->connection_list()->find(hash.c_str());
|
||||
|
||||
if (itr == download->connection_list()->end())
|
||||
@@ -122,23 +122,23 @@ xmlrpc_find_peer(core::Download* download, const torrent::HashString& hash) {
|
||||
}
|
||||
|
||||
void
|
||||
initialize_xmlrpc() {
|
||||
rpc::xmlrpc.initialize();
|
||||
rpc::xmlrpc.slot_find_download() = std::bind(&core::DownloadList::find_hex_ptr, control->core()->download_list(), std::placeholders::_1);
|
||||
rpc::xmlrpc.slot_find_file() = std::bind(&xmlrpc_find_file, std::placeholders::_1, std::placeholders::_2);
|
||||
rpc::xmlrpc.slot_find_tracker() = std::bind(&xmlrpc_find_tracker, std::placeholders::_1, std::placeholders::_2);
|
||||
rpc::xmlrpc.slot_find_peer() = std::bind(&xmlrpc_find_peer, std::placeholders::_1, std::placeholders::_2);
|
||||
initialize_rpc() {
|
||||
rpc::rpc.initialize();
|
||||
rpc::rpc.slot_find_download() = [](const char* hash) { return control->core()->download_list()->find_hex_ptr(hash); };
|
||||
rpc::rpc.slot_find_file() = [](core::Download* d, uint32_t index) { return rpc_find_file(d, index); };
|
||||
rpc::rpc.slot_find_tracker() = [](core::Download* d, uint32_t index) { return rpc_find_tracker(d, index); };
|
||||
rpc::rpc.slot_find_peer() = [](core::Download* d, const torrent::HashString& hash) { return rpc_find_peer(d, hash); };
|
||||
|
||||
unsigned int count = 0;
|
||||
|
||||
for (rpc::CommandMap::const_iterator itr = rpc::commands.begin(), last = rpc::commands.end(); itr != last; itr++, count++) {
|
||||
if (!(itr->second.m_flags & rpc::CommandMap::flag_public_xmlrpc))
|
||||
if (!(itr->second.m_flags & rpc::CommandMap::flag_public_rpc))
|
||||
continue;
|
||||
|
||||
rpc::xmlrpc.insert_command(itr->first.c_str(), itr->second.m_parm, itr->second.m_doc);
|
||||
rpc::rpc.insert_command(itr->first.c_str(), itr->second.m_parm, itr->second.m_doc);
|
||||
}
|
||||
|
||||
lt_log_print(torrent::LOG_RPC_EVENTS, "XMLRPC initialized with %u functions.", count);
|
||||
lt_log_print(torrent::LOG_RPC_EVENTS, "RPC initialized with %u functions.", count);
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
@@ -146,8 +146,7 @@ apply_scgi(const std::string& arg, int type) {
|
||||
if (worker_thread->scgi() != NULL)
|
||||
throw torrent::input_error("SCGI already enabled.");
|
||||
|
||||
if (!rpc::xmlrpc.is_valid())
|
||||
initialize_xmlrpc();
|
||||
initialize_rpc();
|
||||
|
||||
rpc::SCgi* scgi = new rpc::SCgi;
|
||||
|
||||
@@ -225,7 +224,7 @@ apply_xmlrpc_dialect(const std::string& arg) {
|
||||
else
|
||||
value = -1;
|
||||
|
||||
rpc::xmlrpc.set_dialect(value);
|
||||
rpc::rpc.set_dialect(value);
|
||||
return torrent::Object();
|
||||
}
|
||||
|
||||
@@ -298,9 +297,12 @@ initialize_command_network() {
|
||||
CMD2_ANY_STRING ("network.scgi.open_local", std::bind(&apply_scgi, std::placeholders::_2, 2));
|
||||
CMD2_VAR_BOOL ("network.scgi.dont_route", false);
|
||||
|
||||
CMD2_ANY_STRING ("network.xmlrpc.dialect.set", std::bind(&apply_xmlrpc_dialect, std::placeholders::_2));
|
||||
CMD2_ANY ("network.xmlrpc.size_limit", std::bind(&rpc::XmlRpc::size_limit, rpc::xmlrpc));
|
||||
CMD2_ANY_VALUE_V ("network.xmlrpc.size_limit.set", std::bind(&rpc::XmlRpc::set_size_limit, rpc::xmlrpc, std::placeholders::_2));
|
||||
CMD2_ANY_STRING ("network.xmlrpc.dialect.set", [](const auto&, const auto& arg) { return apply_xmlrpc_dialect(arg); })
|
||||
CMD2_ANY ("network.xmlrpc.size_limit", [](const auto&, const auto&){ return rpc::rpc.size_limit(); });
|
||||
CMD2_ANY_VALUE_V ("network.xmlrpc.size_limit.set", [](const auto&, const auto& arg){ return rpc::rpc.set_size_limit(arg); });
|
||||
|
||||
CMD2_VAR_BOOL ("network.rpc.use_xmlrpc", true);
|
||||
CMD2_VAR_BOOL ("network.rpc.use_jsonrpc", true);
|
||||
|
||||
CMD2_ANY ("network.block.ipv4", std::bind(&torrent::ConnectionManager::is_block_ipv4, cm));
|
||||
CMD2_ANY_VALUE_V ("network.block.ipv4.set", std::bind(&torrent::ConnectionManager::set_block_ipv4, cm, std::placeholders::_2));
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@ Control::initialize() {
|
||||
void
|
||||
Control::cleanup() {
|
||||
// delete m_scgi; m_scgi = NULL;
|
||||
rpc::xmlrpc.cleanup();
|
||||
rpc::rpc.cleanup();
|
||||
|
||||
priority_queue_erase(&taskScheduler, &m_taskShutdown);
|
||||
|
||||
|
||||
+25
-2
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "rpc/parse_commands.h"
|
||||
#include "utils/directory.h"
|
||||
#include "utils/base64.h"
|
||||
#include "utils/file_status_cache.h"
|
||||
|
||||
#include "globals.h"
|
||||
@@ -313,6 +314,21 @@ Manager::receive_http_failed(std::string msg) {
|
||||
push_log_std("Http download error: \"" + msg + "\"");
|
||||
}
|
||||
|
||||
bool
|
||||
is_data_uri(const std::string& uri) {
|
||||
return std::strncmp(uri.c_str(), "data:", 5) == 0;
|
||||
}
|
||||
|
||||
std::string
|
||||
decode_data_uri(const std::string& uri) {
|
||||
const auto start = uri.find("base64,", 5) + 7;
|
||||
if (start == std::string::npos)
|
||||
throw torrent::input_error("Invalid data uri: not base64 encoded.");
|
||||
if (start >= uri.size())
|
||||
throw torrent::input_error("Empty base64.");
|
||||
return utils::decode_base64(uri.substr(start));
|
||||
}
|
||||
|
||||
void
|
||||
Manager::try_create_download(const std::string& uri, int flags, const command_list_type& commands) {
|
||||
// If the path was attempted loaded before, skip it.
|
||||
@@ -320,6 +336,7 @@ Manager::try_create_download(const std::string& uri, int flags, const command_li
|
||||
!(flags & create_raw_data) &&
|
||||
!is_network_uri(uri) &&
|
||||
!is_magnet_uri(uri) &&
|
||||
!is_data_uri(uri) &&
|
||||
!file_status_cache()->insert(uri, 0))
|
||||
return;
|
||||
|
||||
@@ -333,10 +350,16 @@ Manager::try_create_download(const std::string& uri, int flags, const command_li
|
||||
f->set_print_log(!(flags & create_quiet));
|
||||
f->slot_finished([f]() { delete f; });
|
||||
|
||||
if (flags & create_raw_data)
|
||||
if (is_data_uri(uri)) {
|
||||
// Allow the use of data URIs, primarily for JSON-RPC which
|
||||
// doesn't have a defined mechanism for binary data
|
||||
f->load_raw_data(decode_data_uri(uri));
|
||||
f->variables()["tied_to_file"] = (int64_t)false;
|
||||
} else if (flags & create_raw_data) {
|
||||
f->load_raw_data(uri);
|
||||
else
|
||||
} else {
|
||||
f->load(uri);
|
||||
}
|
||||
|
||||
f->commit();
|
||||
}
|
||||
|
||||
@@ -48,8 +48,7 @@
|
||||
#include "command.h"
|
||||
#include "command_map.h"
|
||||
|
||||
// For XMLRPC stuff, clean up.
|
||||
#include "xmlrpc.h"
|
||||
#include "rpc_manager.h"
|
||||
#include "parse_commands.h"
|
||||
|
||||
namespace rpc {
|
||||
@@ -64,8 +63,8 @@ CommandMap::insert(const key_type& key, int flags, const char* parm, const char*
|
||||
throw torrent::internal_error("CommandMap::insert(...) tried to insert an already existing key.");
|
||||
|
||||
// TODO: This is not honoring the public_xmlrpc flags!!!
|
||||
if (rpc::xmlrpc.is_valid() && (flags & flag_public_xmlrpc))
|
||||
rpc::xmlrpc.insert_command(key.c_str(), parm, doc);
|
||||
if (rpc::rpc.is_initialized() && (flags & flag_public_rpc))
|
||||
rpc::rpc.insert_command(key.c_str(), parm, doc);
|
||||
|
||||
return base_type::insert(itr, value_type(key, command_map_data_type(flags, parm, doc)));
|
||||
}
|
||||
@@ -102,11 +101,11 @@ CommandMap::create_redirect(const key_type& key_new, const key_type& key_dest, i
|
||||
|
||||
dest_itr->second.m_flags |= flag_has_redirects;
|
||||
|
||||
flags |= dest_itr->second.m_flags & ~(flag_has_redirects | flag_public_xmlrpc);
|
||||
flags |= dest_itr->second.m_flags & ~(flag_has_redirects | flag_public_rpc);
|
||||
|
||||
// TODO: This is not honoring the public_xmlrpc flags!!!
|
||||
if (rpc::xmlrpc.is_valid() && (flags & flag_public_xmlrpc))
|
||||
rpc::xmlrpc.insert_command(key_new.c_str(), dest_itr->second.m_parm, dest_itr->second.m_doc);
|
||||
if (rpc::rpc.is_initialized() && (flags & flag_public_rpc))
|
||||
rpc::rpc.insert_command(key_new.c_str(), dest_itr->second.m_parm, dest_itr->second.m_doc);
|
||||
|
||||
iterator itr = base_type::insert(base_type::end(),
|
||||
value_type(key_new, command_map_data_type(flags,
|
||||
|
||||
@@ -86,7 +86,7 @@ public:
|
||||
using base_type::find;
|
||||
|
||||
static const int flag_dont_delete = 0x1;
|
||||
static const int flag_public_xmlrpc = 0x4;
|
||||
static const int flag_public_rpc = 0x4;
|
||||
static const int flag_modifiable = 0x10;
|
||||
static const int flag_is_redirect = 0x20;
|
||||
static const int flag_has_redirects = 0x40;
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "rpc/jsonrpc.h"
|
||||
|
||||
#include "rpc/rpc_manager.h"
|
||||
#include "torrent/exceptions.h"
|
||||
#include "torrent/object.h"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include <torrent/common.h>
|
||||
#include <torrent/torrent.h>
|
||||
|
||||
#include "rpc/command.h"
|
||||
#include "rpc/command_map.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
#include "thread_base.h"
|
||||
|
||||
#include "nlohmann/json.h"
|
||||
|
||||
namespace rpc {
|
||||
|
||||
// Taken from https://www.jsonrpc.org/specification
|
||||
constexpr int JSONRPC_PARSE_ERROR = -32700;
|
||||
constexpr int JSONRPC_INVALID_REQUEST_ERROR = -32600;
|
||||
constexpr int JSONRPC_METHOD_NOT_FOUND_ERROR = -32601;
|
||||
constexpr int JSONRPC_INVALID_PARAMS_ERROR = -32602;
|
||||
constexpr int JSONRPC_INTERNAL_ERROR = -32000;
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
torrent::Object
|
||||
json_to_object(const json& value) {
|
||||
switch (value.type()) {
|
||||
case json::value_t::number_unsigned:
|
||||
case json::value_t::number_integer:
|
||||
return torrent::Object(value.get<int64_t>());
|
||||
case json::value_t::boolean:
|
||||
return value.get<bool>() ? torrent::Object(int64_t(1)) : torrent::Object(int64_t(0));
|
||||
case json::value_t::string:
|
||||
return torrent::Object(value.get<std::string>());
|
||||
case json::value_t::array: {
|
||||
auto array_raw = torrent::Object::create_list();
|
||||
auto& array = array_raw.as_list();
|
||||
for (const auto& entry : value) {
|
||||
array.push_back(json_to_object(entry));
|
||||
}
|
||||
return array_raw;
|
||||
}
|
||||
case json::value_t::object: {
|
||||
auto map_raw = torrent::Object::create_map();
|
||||
auto& map = map_raw.as_map();
|
||||
for (const auto& entry : value.items()) {
|
||||
map[entry.key()] = json_to_object(entry.value());
|
||||
}
|
||||
return map_raw;
|
||||
}
|
||||
case json::value_t::number_float:
|
||||
// type_name() for floats returns 'number', which is accurate for JSON but misleading
|
||||
throw torrent::input_error("invalid parameters: unexpected data type float");
|
||||
default:
|
||||
throw torrent::input_error("invalid parameters: unexpected data type " + std::string(value.type_name()));
|
||||
}
|
||||
}
|
||||
|
||||
json
|
||||
object_to_json(const torrent::Object& object) noexcept {
|
||||
switch (object.type()) {
|
||||
case torrent::Object::TYPE_VALUE:
|
||||
return object.as_value();
|
||||
case torrent::Object::TYPE_STRING:
|
||||
return object.as_string();
|
||||
case torrent::Object::TYPE_LIST: {
|
||||
json result = json::array();
|
||||
std::transform(object.as_list().cbegin(), object.as_list().cend(), std::back_inserter(result), [](const torrent::Object& element) {
|
||||
return object_to_json(element);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
case torrent::Object::TYPE_MAP: {
|
||||
json result = json::object();
|
||||
for (const auto& entry : object.as_map()) {
|
||||
result.emplace(entry.first, object_to_json(entry.second));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case torrent::Object::TYPE_DICT_KEY: {
|
||||
json result = json::array();
|
||||
result.push_back(object_to_json(object.as_dict_key()));
|
||||
|
||||
const auto& dict_obj = object.as_dict_obj();
|
||||
if (dict_obj.is_list()) {
|
||||
std::transform(dict_obj.as_list().cbegin(), dict_obj.as_list().cend(), std::back_inserter(result), [](const torrent::Object& element) {
|
||||
return object_to_json(element);
|
||||
});
|
||||
} else {
|
||||
result.push_back(object_to_json(dict_obj));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
json
|
||||
jsonrpc_call_command(const std::string& method, const json& params) {
|
||||
if (params.type() == json::value_t::object) {
|
||||
// Named parameters is valid JSON-RPC, rtorrent just doesn't support it
|
||||
throw rpc_error(JSONRPC_INVALID_PARAMS_ERROR, "invalid parameter: procedure named parameter not supported");
|
||||
} else if (params.type() != json::value_t::array) {
|
||||
throw rpc_error(JSONRPC_INVALID_REQUEST_ERROR, "invalid request: params field must be an array");
|
||||
}
|
||||
|
||||
CommandMap::iterator itr = commands.find(method.c_str());
|
||||
|
||||
if (itr == commands.end()) {
|
||||
throw rpc_error(JSONRPC_METHOD_NOT_FOUND_ERROR, "method not found: " + method);
|
||||
}
|
||||
|
||||
torrent::Object params_object = json_to_object(params);
|
||||
auto& params_object_list = params_object.as_list();
|
||||
rpc::target_type target = rpc::make_target();
|
||||
|
||||
if (!(itr->second.m_flags & CommandMap::flag_no_target)) {
|
||||
// Provide a blank target if none was provided
|
||||
if (params_object_list.empty())
|
||||
params_object_list.push_back("");
|
||||
|
||||
if (!params_object_list.begin()->is_string())
|
||||
throw torrent::input_error("invalid parameters: target must be a string");
|
||||
|
||||
RpcManager::object_to_target(params_object_list.begin()->as_string(), itr->second.m_flags, &target);
|
||||
params_object_list.erase(params_object_list.begin());
|
||||
}
|
||||
|
||||
const auto& result = rpc::commands.call_command(itr, params_object, target);
|
||||
|
||||
return object_to_json(result);
|
||||
}
|
||||
|
||||
json
|
||||
json_error(int code, const std::string& msg, json id) {
|
||||
return json{{"jsonrpc", "2.0"}, {"id", id}, {"error", {{"code", code}, {"message", msg}}}};
|
||||
}
|
||||
|
||||
json
|
||||
handle_request(const json& request) {
|
||||
json response = {{"jsonrpc", "2.0"}, {"id", nullptr}};
|
||||
|
||||
try {
|
||||
if (!request["id"].is_number() && !request["id"].is_string() && !request["id"].is_null())
|
||||
return json_error(JSONRPC_INVALID_REQUEST_ERROR, "request id is invalid type " + std::string(request["id"].type_name()), response["id"]);
|
||||
response["id"] = request["id"];
|
||||
if (!request.contains("method") || !request["method"].is_string())
|
||||
return json_error(JSONRPC_INVALID_REQUEST_ERROR, "method string not present", response["id"]);
|
||||
if (request.contains("params"))
|
||||
response["result"] = jsonrpc_call_command(request["method"], request["params"]);
|
||||
else
|
||||
response["result"] = jsonrpc_call_command(request["method"], json::array({""}));
|
||||
return response;
|
||||
} catch (rpc_error& e) {
|
||||
return json_error(e.type(), e.what(), response["id"]);
|
||||
} catch (torrent::input_error& e) {
|
||||
return json_error(JSONRPC_INVALID_PARAMS_ERROR, e.what(), response["id"]);
|
||||
} catch (torrent::local_error& e) {
|
||||
return json_error(JSONRPC_INTERNAL_ERROR, e.what(), response["id"]);
|
||||
}
|
||||
}
|
||||
|
||||
// Notifications are basically the same as requests, except we can
|
||||
// just drop the message on the floor if there are any errors
|
||||
void
|
||||
handle_notification(const json& request) noexcept {
|
||||
if (!request.contains("method") || !request["method"].is_string())
|
||||
return;
|
||||
try {
|
||||
if (request.contains("params"))
|
||||
jsonrpc_call_command(request["method"], request["params"]);
|
||||
else
|
||||
jsonrpc_call_command(request["method"], json::array({""}));
|
||||
} catch (std::exception& e) {
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
JsonRpc::process(const char* in_buffer, uint32_t length, slot_write callback) {
|
||||
json response;
|
||||
json body;
|
||||
|
||||
try {
|
||||
body = json::parse(in_buffer, in_buffer + length);
|
||||
switch (body.type()) {
|
||||
case json::value_t::object: {
|
||||
if (!body.contains("id")) {
|
||||
handle_notification(body);
|
||||
return callback("", 0);
|
||||
} else {
|
||||
response = handle_request(body);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case json::value_t::array: {
|
||||
// Empty batch requests are invalid as per the spec
|
||||
if (body.empty()) {
|
||||
response = json_error(JSONRPC_INVALID_REQUEST_ERROR, "invalid request: empty batch", nullptr);
|
||||
break;
|
||||
}
|
||||
response = json::array();
|
||||
for (const auto& sub_body : body) {
|
||||
if (!sub_body.contains("id"))
|
||||
handle_notification(sub_body);
|
||||
else
|
||||
response.push_back(handle_request(sub_body));
|
||||
}
|
||||
// This indicates the batch was composed entirely of
|
||||
// notifications, in which case nothing is returned
|
||||
if (response.empty())
|
||||
return callback("", 0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
response = json_error(JSONRPC_PARSE_ERROR, "message type " + std::string(body.type_name()) + " unsupported", nullptr);
|
||||
}
|
||||
|
||||
std::string response_str = response.dump();
|
||||
|
||||
return callback(response_str.c_str(), response_str.size());
|
||||
|
||||
} catch (json::parse_error& e) {
|
||||
auto err_str = json_error(JSONRPC_PARSE_ERROR, e.what(), nullptr).dump(-1, ' ', false, json::error_handler_t::replace);
|
||||
return callback(err_str.c_str(), err_str.size());
|
||||
} catch (json::type_error& e) {
|
||||
// Type errors may be caused by invalid UTF-8 strings in exception strings, hence the ::replace
|
||||
auto err_str = json_error(JSONRPC_PARSE_ERROR, e.what(), nullptr).dump(-1, ' ', false, json::error_handler_t::replace);
|
||||
return callback(err_str.c_str(), err_str.size());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rpc
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef RTORRENT_RPC_JSONRPC_H
|
||||
#define RTORRENT_RPC_JSONRPC_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace rpc {
|
||||
|
||||
class JsonRpc {
|
||||
public:
|
||||
using slot_write = std::function<bool(const char*, uint32_t)>;
|
||||
|
||||
void initialize() {};
|
||||
void cleanup() {};
|
||||
|
||||
bool process(const char* in_buffer, uint32_t length, slot_write callback);
|
||||
|
||||
void insert_command(const char* name, const char* parm, const char* doc) {};
|
||||
};
|
||||
|
||||
} // namespace rpc
|
||||
|
||||
#endif
|
||||
+25427
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "rpc/rpc_manager.h"
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
@@ -49,7 +50,7 @@
|
||||
namespace rpc {
|
||||
|
||||
CommandMap commands;
|
||||
XmlRpc xmlrpc;
|
||||
RpcManager rpc;
|
||||
ExecFile execFile;
|
||||
|
||||
inline bool command_map_is_space(char c) {
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include "command_map.h"
|
||||
#include "exec_file.h"
|
||||
#include "xmlrpc.h"
|
||||
#include "rpc_manager.h"
|
||||
|
||||
namespace core {
|
||||
class Download;
|
||||
@@ -52,7 +53,7 @@ namespace rpc {
|
||||
|
||||
// Move to another file?
|
||||
extern CommandMap commands;
|
||||
extern XmlRpc xmlrpc;
|
||||
extern RpcManager rpc;
|
||||
extern ExecFile execFile;
|
||||
|
||||
typedef std::pair<torrent::Object, const char*> parse_command_type;
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <torrent/exceptions.h>
|
||||
|
||||
#include "parse_commands.h"
|
||||
#include "rpc/rpc_manager.h"
|
||||
|
||||
namespace rpc {
|
||||
|
||||
void
|
||||
RpcManager::object_to_target(const torrent::Object& obj, int call_flags, rpc::target_type* target) {
|
||||
if (call_flags & CommandMap::flag_no_target)
|
||||
return;
|
||||
|
||||
if (!obj.is_string()) {
|
||||
throw torrent::input_error("invalid parameters: target must be a string");
|
||||
}
|
||||
std::string target_string = obj.as_string();
|
||||
bool require_index = (call_flags & (CommandMap::flag_tracker_target | CommandMap::flag_file_target));
|
||||
if (target_string.size() == 0 && !require_index) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Length of SHA1 hash is 40
|
||||
if (target_string.size() < 40) {
|
||||
throw torrent::input_error("invalid parameters: invalid target");
|
||||
}
|
||||
|
||||
char type = 'd';
|
||||
std::string hash;
|
||||
std::string index;
|
||||
const auto& delim_pos = target_string.find_first_of(':', 40);
|
||||
if (delim_pos == target_string.npos ||
|
||||
delim_pos + 2 >= target_string.size()) {
|
||||
if (require_index) {
|
||||
throw torrent::input_error("invalid parameters: no index");
|
||||
}
|
||||
hash = target_string;
|
||||
} else {
|
||||
hash = target_string.substr(0, delim_pos);
|
||||
type = target_string[delim_pos + 1];
|
||||
index = target_string.substr(delim_pos + 2);
|
||||
}
|
||||
core::Download* download = rpc.slot_find_download()(hash.c_str());
|
||||
|
||||
if (download == nullptr)
|
||||
throw torrent::input_error("invalid parameters: info-hash not found");
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case 'd':
|
||||
*target = rpc::make_target(download);
|
||||
break;
|
||||
case 'f':
|
||||
*target = rpc::make_target(
|
||||
command_base::target_file,
|
||||
rpc.slot_find_file()(download, std::stoi(std::string(index))));
|
||||
break;
|
||||
case 't':
|
||||
*target = rpc::make_target(
|
||||
command_base::target_tracker,
|
||||
rpc.slot_find_tracker()(download, std::stoi(std::string(index))));
|
||||
break;
|
||||
case 'p': {
|
||||
if (index.size() < 40) {
|
||||
throw torrent::input_error("invalid parameters: not a hash string.");
|
||||
}
|
||||
torrent::HashString hash;
|
||||
torrent::hash_string_from_hex_c_str(index.c_str(), hash);
|
||||
*target = rpc::make_target(
|
||||
command_base::target_peer,
|
||||
rpc.slot_find_peer()(download, hash));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw torrent::input_error("invalid parameters: unexpected target type");
|
||||
}
|
||||
} catch (const std::logic_error&) {
|
||||
throw torrent::input_error("invalid parameters: invalid index");
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
RpcManager::process(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback) {
|
||||
switch (type) {
|
||||
case RPCType::XML: {
|
||||
if (m_xmlrpc.is_valid() && rpc::call_command_value("network.rpc.use_xmlrpc")) {
|
||||
return m_xmlrpc.process(in_buffer, length, callback);
|
||||
} else {
|
||||
const std::string response = "<?xml version=\"1.0\"?><methodResponse><fault><struct><member><name>faultCode</name><value><i8>-501</i8></value></member><member><name>faultString</name><value><string>XML-RPC not supported</string></value></member></struct></fault></methodResponse>";
|
||||
return callback(response.c_str(), response.size());
|
||||
}
|
||||
}
|
||||
case RPCType::JSON: {
|
||||
if (rpc::call_command_value("network.rpc.use_jsonrpc")) {
|
||||
return m_jsonrpc.process(in_buffer, length, callback);
|
||||
} else {
|
||||
const std::string response = "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"JSON-RPC not supported\"},\"id\":null}";
|
||||
return callback(response.c_str(), response.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RpcManager::initialize() {
|
||||
m_xmlrpc.initialize();
|
||||
m_jsonrpc.initialize();
|
||||
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
void
|
||||
RpcManager::cleanup() {
|
||||
m_xmlrpc.cleanup();
|
||||
m_jsonrpc.cleanup();
|
||||
}
|
||||
|
||||
bool
|
||||
RpcManager::is_type_enabled(RPCType type) const {
|
||||
switch (type) {
|
||||
case RPCType::XML:
|
||||
return m_is_xmlrpc_enabled;
|
||||
case RPCType::JSON:
|
||||
return m_is_jsonrpc_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
RpcManager::set_type_enabled(RPCType type, bool enabled) {
|
||||
switch (type) {
|
||||
case RPCType::XML:
|
||||
m_is_xmlrpc_enabled = enabled;
|
||||
case RPCType::JSON:
|
||||
m_is_jsonrpc_enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
RpcManager::is_initialized() const {
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
void
|
||||
RpcManager::insert_command(const char* name, const char* parm, const char* doc) {
|
||||
m_xmlrpc.insert_command(name, parm, doc);
|
||||
m_jsonrpc.insert_command(name, parm, doc);
|
||||
}
|
||||
|
||||
} // namespace rpc
|
||||
@@ -0,0 +1,88 @@
|
||||
#ifndef RTORRENT_RPC_MANAGER_H
|
||||
#define RTORRENT_RPC_MANAGER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
#include "rpc/command.h"
|
||||
#include "rpc/jsonrpc.h"
|
||||
#include "rpc/xmlrpc.h"
|
||||
|
||||
namespace core {
|
||||
class Download;
|
||||
}
|
||||
|
||||
namespace torrent {
|
||||
class File;
|
||||
class Object;
|
||||
class Tracker;
|
||||
} // namespace torrent
|
||||
|
||||
namespace rpc {
|
||||
|
||||
class rpc_error : public torrent::base_error {
|
||||
public:
|
||||
rpc_error(int type, std::string msg) :
|
||||
m_type(type), m_msg(msg) {}
|
||||
virtual ~rpc_error() throw() {}
|
||||
|
||||
virtual int type() const throw() { return m_type; }
|
||||
virtual const char* what() const throw() { return m_msg.c_str(); }
|
||||
|
||||
private:
|
||||
int m_type;
|
||||
std::string m_msg;
|
||||
};
|
||||
|
||||
class RpcManager {
|
||||
public:
|
||||
using slot_download = std::function<core::Download*(const char*)>;
|
||||
using slot_file = std::function<torrent::File*(core::Download*, uint32_t)>;
|
||||
using slot_tracker = std::function<torrent::Tracker*(core::Download*, uint32_t)>;
|
||||
using slot_peer = std::function<torrent::Peer*(core::Download*, const torrent::HashString&)>;
|
||||
using slot_response_callback = std::function<bool(const char*, uint32_t)>;
|
||||
|
||||
enum RPCType { XML,
|
||||
JSON };
|
||||
|
||||
RpcManager() = default;
|
||||
~RpcManager() = default;
|
||||
|
||||
void initialize();
|
||||
void cleanup();
|
||||
bool is_initialized() const;
|
||||
|
||||
int64_t size_limit() { return m_xmlrpc.size_limit(); };
|
||||
void set_size_limit(uint64_t size) { m_xmlrpc.set_size_limit(size); };
|
||||
int dialect() { return m_xmlrpc.dialect(); }
|
||||
void set_dialect(int dialect) { m_xmlrpc.set_dialect(dialect); }
|
||||
|
||||
bool is_type_enabled(RPCType type) const;
|
||||
void set_type_enabled(RPCType type, bool enabled);
|
||||
|
||||
bool process(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback);
|
||||
|
||||
void insert_command(const char* name, const char* parm, const char* doc);
|
||||
static void object_to_target(const torrent::Object& obj, int callFlags, rpc::target_type* target);
|
||||
|
||||
slot_download& slot_find_download() { return m_slot_find_download; }
|
||||
slot_file& slot_find_file() { return m_slot_find_file; }
|
||||
slot_tracker& slot_find_tracker() { return m_slot_find_tracker; }
|
||||
slot_peer& slot_find_peer() { return m_slot_find_peer; }
|
||||
|
||||
private:
|
||||
XmlRpc m_xmlrpc;
|
||||
JsonRpc m_jsonrpc;
|
||||
|
||||
bool m_initialized = false;
|
||||
bool m_is_jsonrpc_enabled = true;
|
||||
bool m_is_xmlrpc_enabled = true;
|
||||
|
||||
slot_download m_slot_find_download;
|
||||
slot_file m_slot_find_file;
|
||||
slot_tracker m_slot_find_tracker;
|
||||
slot_peer m_slot_find_peer;
|
||||
};
|
||||
} // namespace rpc
|
||||
|
||||
#endif
|
||||
+10
-2
@@ -36,6 +36,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "rpc/scgi_task.h"
|
||||
#include <rak/error_number.h>
|
||||
#include <rak/socket_address.h>
|
||||
#include <sys/un.h>
|
||||
@@ -166,11 +167,18 @@ SCgi::event_error() {
|
||||
|
||||
bool
|
||||
SCgi::receive_call(SCgiTask* task, const char* buffer, uint32_t length) {
|
||||
bool result = false;
|
||||
torrent::thread_base::acquire_global_lock();
|
||||
torrent::main_thread()->interrupt();
|
||||
|
||||
bool result = xmlrpc.process(buffer, length, [task](const char* b, uint32_t l) { return task->receive_write(b, l); });
|
||||
|
||||
switch (task->content_type()) {
|
||||
case rpc::SCgiTask::ContentType::JSON:
|
||||
result = rpc.process(RpcManager::RPCType::JSON, buffer, length, [task](const char* b, uint32_t l) { return task->receive_write(b, l); });
|
||||
break;
|
||||
case rpc::SCgiTask::ContentType::XML:
|
||||
result = rpc.process(RpcManager::RPCType::XML, buffer, length, [task](const char* b, uint32_t l) { return task->receive_write(b, l); });
|
||||
break;
|
||||
}
|
||||
torrent::thread_base::release_global_lock();
|
||||
|
||||
return result;
|
||||
|
||||
+76
-60
@@ -1,44 +1,9 @@
|
||||
// rTorrent - BitTorrent client
|
||||
// Copyright (C) 2005-2011, Jari Sundell
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation; either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//
|
||||
// In addition, as a special exception, the copyright holders give
|
||||
// permission to link the code of portions of this program with the
|
||||
// OpenSSL library under certain conditions as described in each
|
||||
// individual source file, and distribute linked combinations
|
||||
// including the two.
|
||||
//
|
||||
// You must obey the GNU General Public License in all respects for
|
||||
// all of the code used other than OpenSSL. If you modify file(s)
|
||||
// with this exception, you may extend this exception to your version
|
||||
// of the file(s), but you are not obligated to do so. If you do not
|
||||
// wish to do so, delete this exception statement from your version.
|
||||
// If you delete this exception statement from all source files in the
|
||||
// program, then also delete it here.
|
||||
//
|
||||
// Contact: Jari Sundell <jaris@ifi.uio.no>
|
||||
//
|
||||
// Skomakerveien 33
|
||||
// 3185 Skoppum, NORWAY
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <rak/allocators.h>
|
||||
#include <rak/error_number.h>
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <torrent/exceptions.h>
|
||||
@@ -126,47 +91,94 @@ SCgiTask::event_read() {
|
||||
// receive all the data we need the first time.
|
||||
char* current;
|
||||
|
||||
int contentSize;
|
||||
int headerSize = strtol(m_buffer, ¤t, 0);
|
||||
int header_size = strtol(m_buffer, ¤t, 0);
|
||||
|
||||
if (current == m_position)
|
||||
return;
|
||||
|
||||
// If the request doesn't start with an integer or if it didn't
|
||||
// end in ':', then close the connection.
|
||||
if (current == m_buffer || *current != ':' || headerSize < 17 || headerSize > max_header_size)
|
||||
if (current == m_buffer || *current != ':' || header_size < 17 || header_size > max_header_size)
|
||||
goto event_read_failed;
|
||||
|
||||
if (std::distance(++current, m_position) < headerSize + 1)
|
||||
if (std::distance(++current, m_position) < header_size + 1)
|
||||
return;
|
||||
|
||||
// We'll parse this fully below, but the SCGI spec requires it to
|
||||
// be the first header.
|
||||
if (std::memcmp(current, "CONTENT_LENGTH", 15) != 0)
|
||||
goto event_read_failed;
|
||||
|
||||
char* contentPos;
|
||||
contentSize = strtol(current + 15, &contentPos, 0);
|
||||
std::string content_type = "";
|
||||
size_t content_length = 0;
|
||||
const char* header_end = current + header_size;
|
||||
|
||||
if (*contentPos != '\0' || contentSize <= 0 || contentSize > max_content_size)
|
||||
// Parse out the null-terminated header keys and values, with
|
||||
// checks to ensure it doesn't scan beyond the limits of the
|
||||
// header
|
||||
while (current < header_end) {
|
||||
char* key = current;
|
||||
char* key_end = static_cast<char*>(std::memchr(current, '\0', header_end - current));
|
||||
if (!key_end)
|
||||
goto event_read_failed;
|
||||
|
||||
current = key_end + 1;
|
||||
if (current >= header_end)
|
||||
goto event_read_failed;
|
||||
|
||||
char* value = current;
|
||||
char* value_end = static_cast<char*>(std::memchr(current, '\0', header_end - current));
|
||||
if (!value_end)
|
||||
goto event_read_failed;
|
||||
current = value_end + 1;
|
||||
|
||||
if (strcmp(key, "CONTENT_LENGTH") == 0) {
|
||||
char* content_pos;
|
||||
content_length = strtol(value, &content_pos, 10);
|
||||
if (*content_pos != '\0' || content_length <= 0 || content_length > max_content_size)
|
||||
goto event_read_failed;
|
||||
} else if (strcmp(key, "CONTENT_TYPE") == 0) {
|
||||
content_type = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (current != header_end)
|
||||
goto event_read_failed;
|
||||
|
||||
m_body = current + headerSize + 1;
|
||||
headerSize = std::distance(m_buffer, m_body);
|
||||
if (content_length <= 0)
|
||||
goto event_read_failed;
|
||||
|
||||
if ((unsigned int)(contentSize + headerSize) < m_bufferSize) {
|
||||
m_bufferSize = contentSize + headerSize;
|
||||
m_body = current + 1;
|
||||
header_size = std::distance(m_buffer, m_body);
|
||||
|
||||
} else if ((unsigned int)contentSize <= default_buffer_size) {
|
||||
m_bufferSize = contentSize;
|
||||
if (content_type == "") {
|
||||
// If no CONTENT_TYPE was supplied, peek at the body to check if it's JSON
|
||||
// { is a single request object, while [ is a batch array
|
||||
if (*m_body == '{' || *m_body == '[')
|
||||
m_content_type = ContentType::JSON;
|
||||
} else if (content_type == "application/json") {
|
||||
m_content_type = ContentType::JSON;
|
||||
} else if (content_type == "text/xml") {
|
||||
m_content_type = ContentType::XML;
|
||||
} else {
|
||||
goto event_read_failed;
|
||||
}
|
||||
|
||||
if ((unsigned int)(content_length + header_size) < m_bufferSize) {
|
||||
m_bufferSize = content_length + header_size;
|
||||
|
||||
} else if ((unsigned int)content_length <= default_buffer_size) {
|
||||
m_bufferSize = content_length;
|
||||
|
||||
std::memmove(m_buffer, m_body, std::distance(m_body, m_position));
|
||||
m_position = m_buffer + std::distance(m_body, m_position);
|
||||
m_body = m_buffer;
|
||||
m_body = m_buffer;
|
||||
|
||||
} else {
|
||||
realloc_buffer((m_bufferSize = contentSize) + 1, m_body, std::distance(m_body, m_position));
|
||||
realloc_buffer((m_bufferSize = content_length) + 1, m_body, std::distance(m_body, m_position));
|
||||
|
||||
m_position = m_buffer + std::distance(m_body, m_position);
|
||||
m_body = m_buffer;
|
||||
m_body = m_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,14 +205,14 @@ SCgiTask::event_read() {
|
||||
|
||||
return;
|
||||
|
||||
event_read_failed:
|
||||
// throw torrent::internal_error("SCgiTask::event_read() fault not handled.");
|
||||
event_read_failed:
|
||||
// throw torrent::internal_error("SCgiTask::event_read() fault not handled.");
|
||||
close();
|
||||
}
|
||||
|
||||
void
|
||||
SCgiTask::event_write() {
|
||||
// Apple and Solaris do not support MSG_NOSIGNAL,
|
||||
// Apple and Solaris do not support MSG_NOSIGNAL,
|
||||
// so disable this fix until we find a better solution
|
||||
#if defined(__APPLE__) || defined(__sun__)
|
||||
int bytes = ::send(m_fileDesc, m_position, m_bufferSize, 0);
|
||||
@@ -236,12 +248,16 @@ SCgiTask::receive_write(const char* buffer, uint32_t length) {
|
||||
if (length + 256 > std::max(m_bufferSize, (unsigned int)default_buffer_size))
|
||||
realloc_buffer(length + 256, NULL, 0);
|
||||
|
||||
// Who ever bothers to check the return value?
|
||||
int headerSize = sprintf(m_buffer, "Status: 200 OK\r\nContent-Type: text/xml\r\nContent-Length: %i\r\n\r\n", length);
|
||||
const auto header = m_content_type == ContentType::JSON
|
||||
? "Status: 200 OK\r\nContent-Type: application/json\r\nContent-Length: %i\r\n\r\n"
|
||||
: "Status: 200 OK\r\nContent-Type: text/xml\r\nContent-Length: %i\r\n\r\n";
|
||||
|
||||
m_position = m_buffer;
|
||||
// Who ever bothers to check the return value?
|
||||
int headerSize = sprintf(m_buffer, header, length);
|
||||
|
||||
m_position = m_buffer;
|
||||
m_bufferSize = length + headerSize;
|
||||
|
||||
|
||||
std::memcpy(m_buffer + headerSize, buffer, length);
|
||||
|
||||
if (m_parent->log_fd() >= 0) {
|
||||
@@ -258,4 +274,4 @@ SCgiTask::receive_write(const char* buffer, uint32_t length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace rpc
|
||||
|
||||
@@ -53,6 +53,8 @@ public:
|
||||
static const int max_header_size = 2000;
|
||||
static const int max_content_size = (2 << 23);
|
||||
|
||||
enum ContentType { XML, JSON };
|
||||
|
||||
SCgiTask() { m_fileDesc = -1; }
|
||||
|
||||
bool is_open() const { return m_fileDesc != -1; }
|
||||
@@ -61,6 +63,8 @@ public:
|
||||
void open(SCgi* parent, int fd);
|
||||
void close();
|
||||
|
||||
ContentType content_type() const { return m_content_type; }
|
||||
|
||||
virtual void event_read();
|
||||
virtual void event_write();
|
||||
virtual void event_error();
|
||||
@@ -79,6 +83,8 @@ private:
|
||||
char* m_body;
|
||||
|
||||
unsigned int m_bufferSize;
|
||||
|
||||
ContentType m_content_type{ XML };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+1
-89
@@ -44,98 +44,10 @@
|
||||
|
||||
namespace rpc {
|
||||
|
||||
class xmlrpc_error : public torrent::base_error {
|
||||
public:
|
||||
xmlrpc_error(int type, std::string msg) : m_type(type), m_msg(msg) {}
|
||||
virtual ~xmlrpc_error() throw() {}
|
||||
|
||||
virtual int type() const throw() { return m_type; }
|
||||
virtual const char* what() const throw() { return m_msg.c_str(); }
|
||||
|
||||
private:
|
||||
int m_type;
|
||||
std::string m_msg;
|
||||
};
|
||||
|
||||
void
|
||||
XmlRpc::object_to_target(const torrent::Object& obj, int callFlags, rpc::target_type* target) {
|
||||
if (callFlags & CommandMap::flag_no_target)
|
||||
return;
|
||||
|
||||
if (!obj.is_string()) {
|
||||
throw torrent::input_error("invalid parameters: target must be a string");
|
||||
}
|
||||
std::string target_string = obj.as_string();
|
||||
bool require_index = (callFlags & (CommandMap::flag_tracker_target | CommandMap::flag_file_target));
|
||||
if (target_string.size() == 0 && !require_index) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Length of SHA1 hash is 40
|
||||
if (target_string.size() < 40) {
|
||||
throw torrent::input_error("invalid parameters: invalid target");
|
||||
}
|
||||
|
||||
char type = 'd';
|
||||
std::string hash;
|
||||
std::string index;
|
||||
const auto& delim_pos = target_string.find_first_of(':', 40);
|
||||
if (delim_pos == target_string.npos ||
|
||||
delim_pos + 2 >= target_string.size()) {
|
||||
if (require_index) {
|
||||
throw torrent::input_error("invalid parameters: no index");
|
||||
}
|
||||
hash = target_string;
|
||||
} else {
|
||||
hash = target_string.substr(0, delim_pos);
|
||||
type = target_string[delim_pos + 1];
|
||||
index = target_string.substr(delim_pos + 2);
|
||||
}
|
||||
core::Download* download = xmlrpc.slot_find_download()(hash.c_str());
|
||||
|
||||
if (download == nullptr)
|
||||
throw torrent::input_error("invalid parameters: info-hash not found");
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case 'd':
|
||||
*target = rpc::make_target(download);
|
||||
break;
|
||||
case 'f':
|
||||
*target = rpc::make_target(
|
||||
command_base::target_file,
|
||||
xmlrpc.slot_find_file()(download, std::stoi(std::string(index))));
|
||||
break;
|
||||
case 't':
|
||||
*target = rpc::make_target(
|
||||
command_base::target_tracker,
|
||||
xmlrpc.slot_find_tracker()(download, std::stoi(std::string(index))));
|
||||
break;
|
||||
case 'p': {
|
||||
if (index.size() < 40) {
|
||||
// -501 == XMLRPC_TYPE_ERROR, used here directly to avoid
|
||||
// conflicts between tinyxml2 and xmlrpc-c
|
||||
throw xmlrpc_error(-501, "Not a hash string.");
|
||||
}
|
||||
torrent::HashString hash;
|
||||
torrent::hash_string_from_hex_c_str(index.c_str(), hash);
|
||||
*target = rpc::make_target(
|
||||
command_base::target_peer,
|
||||
xmlrpc.slot_find_peer()(download, hash));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw torrent::input_error("invalid parameters: unexpected target type");
|
||||
}
|
||||
} catch (const std::logic_error&) {
|
||||
throw torrent::input_error("invalid parameters: invalid index");
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef HAVE_XMLRPC_C
|
||||
#ifndef HAVE_XMLRPC_TINYXML2
|
||||
|
||||
void XmlRpc::initialize() { throw torrent::resource_error("XMLRPC not supported."); }
|
||||
void XmlRpc::initialize() {}
|
||||
void XmlRpc::cleanup() {}
|
||||
|
||||
void XmlRpc::insert_command(__UNUSED const char* name, __UNUSED const char* parm, __UNUSED const char* doc) {}
|
||||
|
||||
+2
-1
@@ -51,6 +51,7 @@
|
||||
#include <torrent/object.h>
|
||||
#include <torrent/exceptions.h>
|
||||
|
||||
#include "rpc_manager.h"
|
||||
#include "xmlrpc.h"
|
||||
#include "parse_commands.h"
|
||||
|
||||
@@ -138,7 +139,7 @@ rpc::target_type
|
||||
xmlrpc_to_target(xmlrpc_env* env, xmlrpc_value* value, int callType) {
|
||||
rpc::target_type target;
|
||||
|
||||
XmlRpc::object_to_target(xmlrpc_to_object(env, value, -1, nullptr), callType, &target);
|
||||
Rpc::object_to_target(xmlrpc_to_object(env, value, -1, nullptr), callType, &target);
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
+19
-32
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "parse_commands.h"
|
||||
#include "rpc/tinyxml2/tinyxml2.h"
|
||||
#include "rpc/rpc_manager.h"
|
||||
#include "utils/base64.h"
|
||||
#include "xmlrpc.h"
|
||||
|
||||
@@ -35,20 +36,6 @@ const int XMLRPC_INTROSPECTION_DISABLED_ERROR = -508;
|
||||
const int XMLRPC_LIMIT_EXCEEDED_ERROR = -509;
|
||||
const int XMLRPC_INVALID_UTF8_ERROR = -510;
|
||||
|
||||
class xmlrpc_error : public torrent::base_error {
|
||||
public:
|
||||
xmlrpc_error(int type, std::string msg) :
|
||||
m_type(type), m_msg(msg) {}
|
||||
virtual ~xmlrpc_error() throw() {}
|
||||
|
||||
virtual int type() const throw() { return m_type; }
|
||||
virtual const char* what() const throw() { return m_msg.c_str(); }
|
||||
|
||||
private:
|
||||
int m_type;
|
||||
std::string m_msg;
|
||||
};
|
||||
|
||||
const tinyxml2::XMLElement*
|
||||
element_access(const tinyxml2::XMLElement* elem, std::initializer_list<std::string> names) {
|
||||
// Helper function to check each step of a element access, in lieu of XPath
|
||||
@@ -56,7 +43,7 @@ element_access(const tinyxml2::XMLElement* elem, std::initializer_list<std::stri
|
||||
for (auto itr : names) {
|
||||
result = result->FirstChildElement(itr.c_str());
|
||||
if (result == nullptr) {
|
||||
throw xmlrpc_error(XMLRPC_PARSE_ERROR, "could not find expected element " + itr);
|
||||
throw rpc_error(XMLRPC_PARSE_ERROR, "could not find expected element " + itr);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -66,22 +53,22 @@ long long
|
||||
element_to_int(const tinyxml2::XMLNode* elem) {
|
||||
char* pos;
|
||||
if (elem->FirstChild() == nullptr) {
|
||||
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "unable to parse empty integer");
|
||||
throw rpc_error(XMLRPC_TYPE_ERROR, "unable to parse empty integer");
|
||||
}
|
||||
auto str = elem->FirstChild()->ToText()->Value();
|
||||
auto result = std::strtoll(str, &pos, 10);
|
||||
if (pos == str || *pos != '\0')
|
||||
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "unable to parse integer value");
|
||||
throw rpc_error(XMLRPC_TYPE_ERROR, "unable to parse integer value");
|
||||
return result;
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
xml_value_to_object(const tinyxml2::XMLNode* elem) {
|
||||
if (elem == nullptr) {
|
||||
throw xmlrpc_error(XMLRPC_INTERNAL_ERROR, "received null element to convert");
|
||||
throw rpc_error(XMLRPC_INTERNAL_ERROR, "received null element to convert");
|
||||
}
|
||||
if (std::strncmp(elem->Value(), "value", sizeof("value")) != 0) {
|
||||
throw xmlrpc_error(XMLRPC_INTERNAL_ERROR, "received non-value element to convert");
|
||||
throw rpc_error(XMLRPC_INTERNAL_ERROR, "received non-value element to convert");
|
||||
}
|
||||
auto root_element = elem->FirstChild();
|
||||
auto root_type = root_element->Value();
|
||||
@@ -102,13 +89,13 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
|
||||
} else if (boolean_text == "0") {
|
||||
return torrent::Object((int64_t)0);
|
||||
}
|
||||
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "unknown boolean value: " + boolean_text);
|
||||
throw rpc_error(XMLRPC_TYPE_ERROR, "unknown boolean value: " + boolean_text);
|
||||
} else if (std::strncmp(root_type, "array", sizeof("array")) == 0) {
|
||||
auto array_raw = torrent::Object::create_list();
|
||||
auto& array = array_raw.as_list();
|
||||
auto data_element = root_element->ToElement()->FirstChildElement("data");
|
||||
if (data_element == nullptr)
|
||||
throw xmlrpc_error(XMLRPC_PARSE_ERROR, "could not find expected data element in array");
|
||||
throw rpc_error(XMLRPC_PARSE_ERROR, "could not find expected data element in array");
|
||||
for (auto child = data_element->FirstChildElement("value"); child; child = child->NextSiblingElement("value")) {
|
||||
array.push_back(xml_value_to_object(child));
|
||||
}
|
||||
@@ -128,7 +115,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
|
||||
}
|
||||
return torrent::Object(utils::decode_base64(utils::remove_newlines(child_element->ToText()->Value())));
|
||||
} else {
|
||||
throw xmlrpc_error(XMLRPC_INTERNAL_ERROR, "received unsupported value type: " + std::string(root_type));
|
||||
throw rpc_error(XMLRPC_INTERNAL_ERROR, "received unsupported value type: " + std::string(root_type));
|
||||
}
|
||||
return torrent::Object();
|
||||
}
|
||||
@@ -202,8 +189,8 @@ print_object_xml(const torrent::Object& obj, tinyxml2::XMLPrinter* printer) {
|
||||
torrent::Object
|
||||
execute_command(std::string method_name, const tinyxml2::XMLElement* params_element) {
|
||||
CommandMap::iterator cmd_itr = commands.find(method_name.c_str());
|
||||
if (cmd_itr == commands.end() || !(cmd_itr->second.m_flags & CommandMap::flag_public_xmlrpc)) {
|
||||
throw xmlrpc_error(XMLRPC_NO_SUCH_METHOD_ERROR, "method '" + method_name + "' not defined");
|
||||
if (cmd_itr == commands.end() || !(cmd_itr->second.m_flags & CommandMap::flag_public_rpc)) {
|
||||
throw rpc_error(XMLRPC_NO_SUCH_METHOD_ERROR, "method '" + method_name + "' not defined");
|
||||
}
|
||||
torrent::Object params_raw = torrent::Object::create_list();
|
||||
torrent::Object::list_type& params = params_raw.as_list();
|
||||
@@ -214,7 +201,7 @@ execute_command(std::string method_name, const tinyxml2::XMLElement* params_elem
|
||||
const auto* child = params_element->FirstChildElement("param");
|
||||
if (child != nullptr) {
|
||||
if (!(cmd_itr->second.m_flags & CommandMap::flag_no_target)) {
|
||||
XmlRpc::object_to_target(xml_value_to_object(child->FirstChildElement("value")), cmd_itr->second.m_flags, &target);
|
||||
RpcManager::object_to_target(xml_value_to_object(child->FirstChildElement("value")), cmd_itr->second.m_flags, &target);
|
||||
child = child->NextSiblingElement("param");
|
||||
}
|
||||
// Parse out any other params
|
||||
@@ -228,7 +215,7 @@ execute_command(std::string method_name, const tinyxml2::XMLElement* params_elem
|
||||
const auto* child = params_element->FirstChildElement("data")->FirstChildElement("value");
|
||||
if (child != nullptr) {
|
||||
if (!(cmd_itr->second.m_flags & CommandMap::flag_no_target)) {
|
||||
XmlRpc::object_to_target(xml_value_to_object(child), cmd_itr->second.m_flags, &target);
|
||||
RpcManager::object_to_target(xml_value_to_object(child), cmd_itr->second.m_flags, &target);
|
||||
child = child->NextSiblingElement("value");
|
||||
}
|
||||
while (child != nullptr) {
|
||||
@@ -239,7 +226,7 @@ execute_command(std::string method_name, const tinyxml2::XMLElement* params_elem
|
||||
}
|
||||
}
|
||||
if (params.empty() && (cmd_itr->second.m_flags & (CommandMap::flag_file_target | CommandMap::flag_tracker_target))) {
|
||||
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "invalid parameters: too few");
|
||||
throw rpc_error(XMLRPC_TYPE_ERROR, "invalid parameters: too few");
|
||||
}
|
||||
return rpc::commands.call_command(cmd_itr, params_raw, target);
|
||||
}
|
||||
@@ -247,11 +234,11 @@ execute_command(std::string method_name, const tinyxml2::XMLElement* params_elem
|
||||
void
|
||||
process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer) {
|
||||
if (doc->Error())
|
||||
throw xmlrpc_error(XMLRPC_PARSE_ERROR, doc->ErrorStr());
|
||||
throw rpc_error(XMLRPC_PARSE_ERROR, doc->ErrorStr());
|
||||
if (doc->FirstChildElement("methodCall") == nullptr)
|
||||
throw xmlrpc_error(XMLRPC_PARSE_ERROR, "methodCall element not found");
|
||||
throw rpc_error(XMLRPC_PARSE_ERROR, "methodCall element not found");
|
||||
if (doc->FirstChildElement("methodCall")->FirstChildElement("methodName") == nullptr)
|
||||
throw xmlrpc_error(XMLRPC_PARSE_ERROR, "methodName element not found");
|
||||
throw rpc_error(XMLRPC_PARSE_ERROR, "methodName element not found");
|
||||
auto method_name = doc->FirstChildElement("methodCall")->FirstChildElement("methodName")->GetText();
|
||||
torrent::Object result;
|
||||
|
||||
@@ -276,7 +263,7 @@ process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer
|
||||
auto sub_result = torrent::Object::create_list();
|
||||
sub_result.as_list().push_back(execute_command(sub_method_name, sub_params));
|
||||
result_list.push_back(sub_result);
|
||||
} catch (xmlrpc_error& e) {
|
||||
} catch (rpc_error& e) {
|
||||
auto fault = torrent::Object::create_map();
|
||||
fault.as_map()["faultString"] = e.what();
|
||||
fault.as_map()["faultCode"] = e.type();
|
||||
@@ -357,7 +344,7 @@ XmlRpc::process(const char* inBuffer, uint32_t length, slot_write slotWrite) {
|
||||
tinyxml2::XMLPrinter printer(nullptr, true, 0);
|
||||
process_document(&doc, &printer);
|
||||
return slotWrite(printer.CStr(), printer.CStrSize() - 1);
|
||||
} catch (xmlrpc_error& e) {
|
||||
} catch (rpc_error& e) {
|
||||
tinyxml2::XMLPrinter printer(nullptr, true, 0);
|
||||
print_xmlrpc_fault(e.type(), e.what(), &printer);
|
||||
return slotWrite(printer.CStr(), printer.CStrSize() - 1);
|
||||
|
||||
+12
-13
@@ -48,7 +48,6 @@
|
||||
|
||||
#include "core/manager.h"
|
||||
#include "rpc/scgi.h"
|
||||
#include "rpc/xmlrpc.h"
|
||||
#include "rpc/parse_commands.h"
|
||||
|
||||
ThreadWorker::ThreadWorker() {
|
||||
@@ -71,17 +70,17 @@ ThreadWorker::set_scgi(rpc::SCgi* scgi) {
|
||||
if (!m_scgi.compare_exchange_strong(expected, scgi))
|
||||
return false;
|
||||
|
||||
change_xmlrpc_log();
|
||||
change_rpc_log();
|
||||
|
||||
queue_item((thread_base_func)&start_scgi);
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
ThreadWorker::set_xmlrpc_log(const std::string& filename) {
|
||||
m_xmlrpcLog = filename;
|
||||
ThreadWorker::set_rpc_log(const std::string& filename) {
|
||||
m_rpcLog = filename;
|
||||
|
||||
queue_item((thread_base_func)&msg_change_xmlrpc_log);
|
||||
queue_item((thread_base_func)&msg_change_rpc_log);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -95,34 +94,34 @@ ThreadWorker::start_scgi(ThreadBase* baseThread) {
|
||||
}
|
||||
|
||||
void
|
||||
ThreadWorker::msg_change_xmlrpc_log(ThreadBase* baseThread) {
|
||||
ThreadWorker::msg_change_rpc_log(ThreadBase* baseThread) {
|
||||
ThreadWorker* thread = (ThreadWorker*)baseThread;
|
||||
|
||||
acquire_global_lock();
|
||||
thread->change_xmlrpc_log();
|
||||
thread->change_rpc_log();
|
||||
release_global_lock();
|
||||
}
|
||||
|
||||
void
|
||||
ThreadWorker::change_xmlrpc_log() {
|
||||
ThreadWorker::change_rpc_log() {
|
||||
if (scgi() == NULL)
|
||||
return;
|
||||
|
||||
if (scgi()->log_fd() != -1) {
|
||||
::close(scgi()->log_fd());
|
||||
scgi()->set_log_fd(-1);
|
||||
control->core()->push_log("Closed XMLRPC log.");
|
||||
control->core()->push_log("Closed RPC log.");
|
||||
}
|
||||
|
||||
if (m_xmlrpcLog.empty())
|
||||
if (m_rpcLog.empty())
|
||||
return;
|
||||
|
||||
scgi()->set_log_fd(open(rak::path_expand(m_xmlrpcLog).c_str(), O_WRONLY | O_APPEND | O_CREAT, 0644));
|
||||
scgi()->set_log_fd(open(rak::path_expand(m_rpcLog).c_str(), O_WRONLY | O_APPEND | O_CREAT, 0644));
|
||||
|
||||
if (scgi()->log_fd() == -1) {
|
||||
control->core()->push_log_std("Could not open XMLRPC log file '" + m_xmlrpcLog + "'.");
|
||||
control->core()->push_log_std("Could not open RPC log file '" + m_rpcLog + "'.");
|
||||
return;
|
||||
}
|
||||
|
||||
control->core()->push_log_std("Logging XMLRPC events to '" + m_xmlrpcLog + "'.");
|
||||
control->core()->push_log_std("Logging RPC events to '" + m_rpcLog + "'.");
|
||||
}
|
||||
|
||||
+4
-4
@@ -61,21 +61,21 @@ public:
|
||||
rpc::SCgi* scgi() { return m_scgi; }
|
||||
bool set_scgi(rpc::SCgi* scgi);
|
||||
|
||||
void set_xmlrpc_log(const std::string& filename);
|
||||
void set_rpc_log(const std::string& filename);
|
||||
|
||||
static void start_scgi(ThreadBase* thread);
|
||||
static void msg_change_xmlrpc_log(ThreadBase* thread);
|
||||
static void msg_change_rpc_log(ThreadBase* thread);
|
||||
|
||||
private:
|
||||
void task_touch_log();
|
||||
|
||||
void change_xmlrpc_log();
|
||||
void change_rpc_log();
|
||||
|
||||
std::atomic<rpc::SCgi*> m_scgi{ nullptr };
|
||||
|
||||
// The following types shall only be modified while holding the
|
||||
// global lock.
|
||||
std::string m_xmlrpcLog;
|
||||
std::string m_rpcLog;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#include "base64.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace utils {
|
||||
|
||||
std::string
|
||||
remove_newlines(const std::string& str) {
|
||||
std::string result;
|
||||
for (auto& itr : str) {
|
||||
if (itr != '\n' || itr != '\n')
|
||||
result.push_back(itr);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Modified from the public domain code in
|
||||
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/Base64#C++_2
|
||||
constexpr static char base64_pad_character = '=';
|
||||
|
||||
std::string
|
||||
decode_base64(const std::string& input) {
|
||||
if (input.length() % 4) // Sanity check
|
||||
throw torrent::input_error("Invalid base64.");
|
||||
size_t padding = 0;
|
||||
if (input.length()) {
|
||||
if (input[input.length() - 1] == base64_pad_character)
|
||||
padding++;
|
||||
if (input[input.length() - 2] == base64_pad_character)
|
||||
padding++;
|
||||
}
|
||||
// Setup a vector to hold the result
|
||||
std::string decodedBytes;
|
||||
decodedBytes.reserve(((input.length() / 4) * 3) - padding);
|
||||
long temp = 0; // Holds decoded quanta
|
||||
std::string::const_iterator cursor = input.begin();
|
||||
while (cursor < input.end()) {
|
||||
for (size_t quantumPosition = 0; quantumPosition < 4; quantumPosition++) {
|
||||
temp <<= 6;
|
||||
if (*cursor >= 0x41 && *cursor <= 0x5A) // This area will need tweaking if
|
||||
temp |= *cursor - 0x41; // you are using an alternate alphabet
|
||||
else if (*cursor >= 0x61 && *cursor <= 0x7A)
|
||||
temp |= *cursor - 0x47;
|
||||
else if (*cursor >= 0x30 && *cursor <= 0x39)
|
||||
temp |= *cursor + 0x04;
|
||||
else if (*cursor == 0x2B)
|
||||
temp |= 0x3E; // change to 0x2D for URL alphabet
|
||||
else if (*cursor == 0x2F)
|
||||
temp |= 0x3F; // change to 0x5F for URL alphabet
|
||||
else if (*cursor == base64_pad_character) // pad
|
||||
{
|
||||
switch (input.end() - cursor) {
|
||||
case 1: // One pad character
|
||||
decodedBytes.push_back((temp >> 16) & 0x000000FF);
|
||||
decodedBytes.push_back((temp >> 8) & 0x000000FF);
|
||||
return decodedBytes;
|
||||
case 2: // Two pad characters
|
||||
decodedBytes.push_back((temp >> 10) & 0x000000FF);
|
||||
return decodedBytes;
|
||||
default:
|
||||
throw torrent::input_error("Invalid padding in base64.");
|
||||
}
|
||||
} else
|
||||
throw torrent::input_error("Invalid character in base64.");
|
||||
cursor++;
|
||||
}
|
||||
decodedBytes.push_back((temp >> 16) & 0x000000FF);
|
||||
decodedBytes.push_back((temp >> 8) & 0x000000FF);
|
||||
decodedBytes.push_back((temp) & 0x000000FF);
|
||||
}
|
||||
return decodedBytes;
|
||||
}
|
||||
} // namespace utils
|
||||
+7
-65
@@ -1,73 +1,15 @@
|
||||
#ifndef RTORRENT_UTILS_BASE64_H
|
||||
#define RTORRENT_UTILS_BASE64_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <torrent/exceptions.h>
|
||||
|
||||
namespace utils {
|
||||
|
||||
std::string
|
||||
remove_newlines(const std::string& str) {
|
||||
std::string result;
|
||||
for (auto &itr : str) {
|
||||
if (itr != '\n' || itr != '\n')
|
||||
result.push_back(itr);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
std::string remove_newlines(const std::string& str);
|
||||
std::string decode_base64(const std::string& input);
|
||||
|
||||
// Modified from the public domain code in
|
||||
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/Base64#C++_2
|
||||
constexpr static char base64_pad_character = '=';
|
||||
} // namespace utils
|
||||
|
||||
std::string
|
||||
decode_base64(const std::string& input) {
|
||||
if (input.length() % 4) // Sanity check
|
||||
throw torrent::input_error("Invalid base64.");
|
||||
size_t padding = 0;
|
||||
if (input.length()) {
|
||||
if (input[input.length() - 1] == base64_pad_character)
|
||||
padding++;
|
||||
if (input[input.length() - 2] == base64_pad_character)
|
||||
padding++;
|
||||
}
|
||||
// Setup a vector to hold the result
|
||||
std::string decodedBytes;
|
||||
decodedBytes.reserve(((input.length() / 4) * 3) - padding);
|
||||
long temp = 0; // Holds decoded quanta
|
||||
std::string::const_iterator cursor = input.begin();
|
||||
while (cursor < input.end()) {
|
||||
for (size_t quantumPosition = 0; quantumPosition < 4; quantumPosition++) {
|
||||
temp <<= 6;
|
||||
if (*cursor >= 0x41 && *cursor <= 0x5A) // This area will need tweaking if
|
||||
temp |= *cursor - 0x41; // you are using an alternate alphabet
|
||||
else if (*cursor >= 0x61 && *cursor <= 0x7A)
|
||||
temp |= *cursor - 0x47;
|
||||
else if (*cursor >= 0x30 && *cursor <= 0x39)
|
||||
temp |= *cursor + 0x04;
|
||||
else if (*cursor == 0x2B)
|
||||
temp |= 0x3E; // change to 0x2D for URL alphabet
|
||||
else if (*cursor == 0x2F)
|
||||
temp |= 0x3F; // change to 0x5F for URL alphabet
|
||||
else if (*cursor == base64_pad_character) // pad
|
||||
{
|
||||
switch (input.end() - cursor) {
|
||||
case 1: // One pad character
|
||||
decodedBytes.push_back((temp >> 16) & 0x000000FF);
|
||||
decodedBytes.push_back((temp >> 8) & 0x000000FF);
|
||||
return decodedBytes;
|
||||
case 2: // Two pad characters
|
||||
decodedBytes.push_back((temp >> 10) & 0x000000FF);
|
||||
return decodedBytes;
|
||||
default:
|
||||
throw torrent::input_error("Invalid padding in base64.");
|
||||
}
|
||||
} else
|
||||
throw torrent::input_error("Invalid character in base64.");
|
||||
cursor++;
|
||||
}
|
||||
decodedBytes.push_back((temp >> 16) & 0x000000FF);
|
||||
decodedBytes.push_back((temp >> 8) & 0x000000FF);
|
||||
decodedBytes.push_back((temp)&0x000000FF);
|
||||
}
|
||||
return decodedBytes;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -9,6 +9,8 @@ rtorrentTest_SOURCES = \
|
||||
rpc/command_test.h \
|
||||
rpc/command_map_test.cc \
|
||||
rpc/command_map_test.h \
|
||||
rpc/jsonrpc_test.cc \
|
||||
rpc/jsonrpc_test.h \
|
||||
rpc/xmlrpc_test.cc \
|
||||
rpc/xmlrpc_test.h \
|
||||
rpc/command_slot_test.cc \
|
||||
|
||||
@@ -11,7 +11,7 @@ CPPUNIT_TEST_SUITE_REGISTRATION(CommandMapTest);
|
||||
|
||||
#define CMD2_A_FUNCTION(key, function, slot, parm, doc) \
|
||||
m_map.insert_slot<rpc::command_base_is_type<rpc::function>::type>(key, slot, &rpc::function, \
|
||||
rpc::CommandMap::flag_dont_delete | rpc::CommandMap::flag_public_xmlrpc, NULL, NULL);
|
||||
rpc::CommandMap::flag_dont_delete | rpc::CommandMap::flag_public_rpc, NULL, NULL);
|
||||
|
||||
torrent::Object cmd_test_map_a(rpc::target_type t, const torrent::Object& obj) { return obj; }
|
||||
torrent::Object cmd_test_map_b(rpc::target_type t, const torrent::Object& obj, uint64_t c) { return torrent::Object(c); }
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
#include "config.h"
|
||||
#include <string>
|
||||
|
||||
#include "command_helpers.h"
|
||||
#include "rpc/command_map.h"
|
||||
|
||||
#include "control.h"
|
||||
#include "globals.h"
|
||||
#include "jsonrpc_test.h"
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(JsonrpcTest);
|
||||
|
||||
torrent::Object
|
||||
jsonrpc_cmd_test_reflect(rpc::target_type t, const torrent::Object& obj) { return obj; }
|
||||
|
||||
void initialize_command_dynamic();
|
||||
|
||||
// Name, Request, Expected response
|
||||
std::vector<std::tuple<std::string, std::string, std::string>> basic_jsonrpc_requests = {
|
||||
std::make_tuple("Basic call",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": [""], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[]})"),
|
||||
|
||||
std::make_tuple("Basic call with empty params",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": [], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[]})"),
|
||||
|
||||
std::make_tuple("Basic call without params",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[]})"),
|
||||
|
||||
std::make_tuple("Basic call with null id",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "id": null})",
|
||||
R"({"id":null,"jsonrpc":"2.0","result":[]})"),
|
||||
|
||||
std::make_tuple("Basic call with string id",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "id": "1"})",
|
||||
R"({"id":"1","jsonrpc":"2.0","result":[]})"),
|
||||
|
||||
std::make_tuple("UTF-8 string",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", "чао"], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":["чао"]})"),
|
||||
|
||||
std::make_tuple("Emoji string",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", "😊"], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":["😊"]})"),
|
||||
|
||||
std::make_tuple("Small int",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", 41], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[41]})"),
|
||||
|
||||
std::make_tuple("Large int",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", 2247483647], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[2247483647]})"),
|
||||
|
||||
std::make_tuple("Boolean",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", true], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[1]})"),
|
||||
|
||||
std::make_tuple("Negative large ints",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", -2247483647], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[-2247483647]})"),
|
||||
|
||||
std::make_tuple("Simple array",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", [2247483647]], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[[2247483647]]})"),
|
||||
|
||||
std::make_tuple("Empty array",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", []], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[[]]})"),
|
||||
|
||||
std::make_tuple("Empty struct",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", {}], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[{}]})"),
|
||||
|
||||
std::make_tuple("Simple struct",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", {"lowerBound": 18}], "id": 1})",
|
||||
R"({"id":1,"jsonrpc":"2.0","result":[{"lowerBound":18}]})"),
|
||||
|
||||
std::make_tuple("Notification",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": [""]})",
|
||||
""),
|
||||
|
||||
std::make_tuple("Batch",
|
||||
R"([{"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": [""], "id": 1},{"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": [""], "id": 2}])",
|
||||
R"([{"id":1,"jsonrpc":"2.0","result":[]},{"id":2,"jsonrpc":"2.0","result":[]}])"),
|
||||
|
||||
std::make_tuple("Batch with notification",
|
||||
R"([{"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": [""], "id": 1},{"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": [""]}])",
|
||||
R"([{"id":1,"jsonrpc":"2.0","result":[]}])"),
|
||||
|
||||
std::make_tuple("Invalid - empty batch",
|
||||
"[]",
|
||||
R"({"error":{"code":-32600,"message":"invalid request: empty batch"},"id":null,"jsonrpc":"2.0"})"),
|
||||
|
||||
std::make_tuple("Invalid - missing method",
|
||||
R"({"jsonrpc": "2.0", "method": "no_such_method", "id": 1})",
|
||||
R"({"error":{"code":-32601,"message":"method not found: no_such_method"},"id":1,"jsonrpc":"2.0"})"),
|
||||
|
||||
std::make_tuple("Invalid - i8 target",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": [41], "id": 1})",
|
||||
R"({"error":{"code":-32602,"message":"invalid parameters: target must be a string"},"id":1,"jsonrpc":"2.0"})"),
|
||||
|
||||
std::make_tuple("Invalid - broken JSON",
|
||||
R"(nrpc": "2.0", "method": "jsonrpc_reflect", "params": [41], ")",
|
||||
R"({"error":{"code":-32700,"message":"[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid literal; last read: 'nr'"},"id":null,"jsonrpc":"2.0"})"),
|
||||
|
||||
std::make_tuple("Invalid - float",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", 3.14], "id": 1})",
|
||||
R"({"error":{"code":-32602,"message":"invalid parameters: unexpected data type float"},"id":1,"jsonrpc":"2.0"})"),
|
||||
|
||||
std::make_tuple("Invalid - invalid UTF-8",
|
||||
R"({"jsonrpc": "2.0", "method": "jsonrpc_reflect", "params": ["", ")"
|
||||
"\xc3\x28"
|
||||
R"("], "id": 1})",
|
||||
R"({"error":{"code":-32700,"message":"[json.exception.parse_error.101] parse error at line 1, column 66: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"�('"},"id":null,"jsonrpc":"2.0"})"),
|
||||
};
|
||||
|
||||
void
|
||||
JsonrpcTest::setUp() {
|
||||
m_jsonrpc = rpc::JsonRpc();
|
||||
m_jsonrpc.initialize();
|
||||
setlocale(LC_ALL, "");
|
||||
cachedTime = rak::timer::current();
|
||||
control = new Control;
|
||||
if (rpc::commands.find("jsonrpc_reflect") == rpc::commands.end()) {
|
||||
CMD2_ANY("jsonrpc_reflect", &jsonrpc_cmd_test_reflect);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
JsonrpcTest::test_basics() {
|
||||
for (auto& test : basic_jsonrpc_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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
#include "rpc/command_map.h"
|
||||
#include "rpc/jsonrpc.h"
|
||||
|
||||
class JsonrpcTest : public CppUnit::TestFixture {
|
||||
CPPUNIT_TEST_SUITE(JsonrpcTest);
|
||||
CPPUNIT_TEST(test_basics);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void setUp();
|
||||
void tearDown() {}
|
||||
|
||||
void test_basics();
|
||||
|
||||
private:
|
||||
rpc::JsonRpc m_jsonrpc;
|
||||
};
|
||||
Reference in New Issue
Block a user