diff --git a/src/rpc/xmlrpc.cc b/src/rpc/xmlrpc.cc index 65fa60ab..f82b8bad 100644 --- a/src/rpc/xmlrpc.cc +++ b/src/rpc/xmlrpc.cc @@ -36,15 +36,102 @@ #include "config.h" -#ifndef HAVE_XMLRPC_C -#ifndef HAVE_XMLRPC_TINYXML2 - #include "xmlrpc.h" +#include "parse_commands.h" + #include 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 (!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::cleanup() {} @@ -58,7 +145,7 @@ void XmlRpc::set_size_limit(uint64_t size) {} bool XmlRpc::is_valid() const { return false; } -} +#endif +#endif -#endif -#endif +} diff --git a/src/rpc/xmlrpc.h b/src/rpc/xmlrpc.h index 47b1a283..5c4e70d9 100644 --- a/src/rpc/xmlrpc.h +++ b/src/rpc/xmlrpc.h @@ -37,10 +37,11 @@ #ifndef RTORRENT_RPC_XMLRPC_H #define RTORRENT_RPC_XMLRPC_H -#include "scgi_task.h" - #include +#include "command.h" +#include "scgi_task.h" + #include namespace core { @@ -76,6 +77,8 @@ public: static const int call_file = 5; static const int call_file_itr = 6; + static void object_to_target(const torrent::Object& obj, int callFlags, rpc::target_type* target); + XmlRpc() : m_env(NULL), m_registry(NULL), m_dialect(dialect_i8), m_sizeLimit(SCgiTask::max_content_size) {} bool is_valid() const; diff --git a/src/rpc/xmlrpc_c.cc b/src/rpc/xmlrpc_c.cc index 8592f8c9..af8bc189 100644 --- a/src/rpc/xmlrpc_c.cc +++ b/src/rpc/xmlrpc_c.cc @@ -134,101 +134,12 @@ xmlrpc_list_entry_to_value(xmlrpc_env* env, xmlrpc_value* src, int index) { } } -// Consider making a helper function that creates a target_type from a -// torrent::Object, then we can just use xmlrpc_to_object. rpc::target_type -xmlrpc_to_target(xmlrpc_env* env, xmlrpc_value* value) { +xmlrpc_to_target(xmlrpc_env* env, xmlrpc_value* value, int callType) { rpc::target_type target; - switch (xmlrpc_value_type(value)) { - case XMLRPC_TYPE_STRING: - { - const char* str; - xmlrpc_read_string(env, value, &str); - - if (env->fault_occurred) - throw xmlrpc_error(env); - - if (std::strlen(str) == 0) { - // When specifying void, we require a zero-length string. - ::free((void*)str); - return rpc::make_target(); - - } else if (std::strlen(str) < 40) { - ::free((void*)str); - throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Unsupported target type found."); - } - - core::Download* download = xmlrpc.slot_find_download()(str); - - if (download == NULL) { - ::free((void*)str); - throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Could not find info-hash."); - } - - if (std::strlen(str) == 40) { - ::free((void*)str); - return rpc::make_target(download); - } - - if (std::strlen(str) < 42 || str[40] != ':') { - ::free((void*)str); - throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Unsupported target type found."); - } - - // Files: ":f" - // Trackers: ":t" - - int index; - const char* end_ptr = str + 42; - - switch (str[41]) { - case 'f': - index = ::strtol(str + 42, (char**)&end_ptr, 0); - - if (*str == '\0' || *end_ptr != '\0') - throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Invalid index."); - - target = rpc::make_target(XmlRpc::call_file, xmlrpc.slot_find_file()(download, index)); - break; - - case 't': - index = ::strtol(str + 42, (char**)&end_ptr, 0); - - if (*str == '\0' || *end_ptr != '\0') - throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Invalid index."); - - target = rpc::make_target(XmlRpc::call_tracker, xmlrpc.slot_find_tracker()(download, index)); - break; - - case 'p': - { - torrent::HashString hash; - const char* hash_end = torrent::hash_string_from_hex_c_str(str + 42, hash); - - if (hash_end == end_ptr || *hash_end != '\0') - throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Not a hash string."); - - target = rpc::make_target(XmlRpc::call_peer, xmlrpc.slot_find_peer()(download, hash)); - break; - } - default: - ::free((void*)str); - throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Unsupported target type found."); - } - - ::free((void*)str); - - // Check if the target pointer is NULL. - if (target.second == NULL) - throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Invalid index."); - - return target; - } - - default: - return rpc::make_target(); - } + XmlRpc::object_to_target(xmlrpc_to_object(env, value, -1, nullptr), callType, &target); + return target; } rpc::target_type @@ -270,12 +181,12 @@ xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType, rpc::target case XMLRPC_TYPE_STRING: - if (callType != XmlRpc::call_generic) { + if (callType != XmlRpc::call_generic && target != nullptr) { // When the call type is not supposed to be void, we'll try to // convert it to a command target. It's not that important that // it is converted to the right type here, as an mismatch will // be caught when executing the command. - *target = xmlrpc_to_target(env, value); + *target = xmlrpc_to_target(env, value, callType); return torrent::Object(); } else { @@ -327,7 +238,8 @@ xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType, rpc::target if (env->fault_occurred) throw xmlrpc_error(env); - *target = xmlrpc_to_target(env, tmp); + if (target != nullptr) + *target = xmlrpc_to_target(env, tmp, callType); xmlrpc_DECREF(tmp); if (env->fault_occurred) diff --git a/src/rpc/xmlrpc_tinyxml2.cc b/src/rpc/xmlrpc_tinyxml2.cc index d047af58..462454af 100644 --- a/src/rpc/xmlrpc_tinyxml2.cc +++ b/src/rpc/xmlrpc_tinyxml2.cc @@ -235,75 +235,6 @@ print_object_xml(const torrent::Object& obj, tinyxml2::XMLPrinter* printer) { } } -void -object_to_target(const torrent::Object& obj, int callFlags, rpc::target_type* target) { - 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) { - throw xmlrpc_error(XMLRPC_TYPE_ERROR, "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"); - } -} torrent::Object execute_command(std::string method_name, const tinyxml2::XMLElement* params_element) { if (params_element == nullptr) @@ -319,7 +250,7 @@ torrent::Object execute_command(std::string method_name, const tinyxml2::XMLElem // Parse out the target if available auto child = params_element->FirstChildElement("param"); if (child != nullptr) { - object_to_target(xml_value_to_object(child->FirstChildElement("value")), cmd_itr->second.m_flags, &target); + XmlRpc::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 while (child != nullptr) {