Allow using vendored tinyxml2 for XMLRPC

By default, builds will still not have XMLRPC enabled at all and the
configure flag `--with-xmlrpc-tinyxml2` must be specified. If both
xmlrpc-c and tinyxml2 are specified, xmlrpc-c takes precedence.

Basic benchmarks indicate tinyxml2 is 2x faster for small
requests/responses, and that only increases as response sizes get
larger.
This commit is contained in:
kannibalox
2024-10-19 20:23:18 -04:00
committed by Jari Sundell
parent 6ab265ecd4
commit 87c6422052
17 changed files with 6764 additions and 576 deletions
+2
View File
@@ -56,6 +56,8 @@ AC_LANG_PUSH(C++)
TORRENT_WITH_XMLRPC_C
AC_LANG_POP(C++)
TORRENT_WITH_TINYXML2
AC_DEFINE(HAVE_CONFIG_H, 1, true if config.h was included)
AC_DEFINE(USER_AGENT, [std::string(PACKAGE "/" VERSION "/") + torrent::version()], Http user agent)
+12
View File
@@ -426,6 +426,18 @@ AC_DEFUN([TORRENT_WITH_XMLRPC_C], [
])
])
AC_DEFUN([TORRENT_WITH_TINYXML2], [
AC_MSG_CHECKING(for tinyxml2)
AC_ARG_WITH(xmlrpc-tinyxml2,
AS_HELP_STRING([--with-xmlrpc-tinyxml2],[enable XMLRPC support via tinyxml2]),
[
AC_MSG_RESULT(yes)
AC_DEFINE(HAVE_XMLRPC_TINYXML2, 1, Support for XMLRPC via tinyxml2.)
],[
AC_MSG_RESULT(ignored)
])
])
AC_DEFUN([TORRENT_WITH_INOTIFY], [
AC_LANG_PUSH(C++)
+7 -1
View File
@@ -121,6 +121,8 @@ libsub_root_a_SOURCES = \
rpc/scgi_task.h \
rpc/xmlrpc.h \
rpc/xmlrpc.cc \
rpc/xmlrpc_c.cc \
rpc/xmlrpc_tinyxml2.cc \
\
ui/download.cc \
ui/download.h \
@@ -188,6 +190,10 @@ libsub_root_a_SOURCES = \
thread_base.cc \
thread_base.h \
thread_worker.cc \
thread_worker.h
thread_worker.h \
\
vendor/tinyxml2/tinyxml2.h \
vendor/tinyxml2/tinyxml2.cpp
AM_CPPFLAGS = -I$(srcdir) -I$(top_srcdir)
+16
View File
@@ -136,6 +136,18 @@ system_method_generate_command2(torrent::Object* object, torrent::Object::list_c
// }
// This is only used by tinyxml2, xmlrpc-c intercepts the call internally
torrent::Object
system_listMethods() {
torrent::Object resultRaw = torrent::Object::create_list();
torrent::Object::list_type& result = resultRaw.as_list();
result.push_back("system.multicall"); // Handled directly by the XMLRPC code
for (auto itr : rpc::commands) {
result.push_back(itr.first);
}
return resultRaw;
}
torrent::Object
system_method_insert_object(const torrent::Object::list_type& args, int flags) {
if (args.empty())
@@ -431,6 +443,10 @@ cmd_catch(rpc::target_type target, const torrent::Object& args) {
void
initialize_command_dynamic() {
// clang-format off
#ifndef HAVE_XMLRPC_C
CMD2_ANY ("system.listMethods", std::bind(&system_listMethods)); // only used by tinyxml2
#endif
CMD2_VAR_BOOL ("method.use_deprecated", true);
CMD2_VAR_VALUE ("method.use_intermediate", 1);
+2 -2
View File
@@ -299,8 +299,8 @@ initialize_command_network() {
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));
CMD2_ANY_VALUE_V ("network.xmlrpc.size_limit.set", std::bind(&rpc::XmlRpc::set_size_limit, 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 ("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));
+6 -569
View File
@@ -36,579 +36,13 @@
#include "config.h"
#ifdef HAVE_XMLRPC_C
#include <stdlib.h>
#include <xmlrpc-c/server.h>
#endif
#include <cctype>
#include <rak/string_manip.h>
#include <torrent/object.h>
#include <torrent/exceptions.h>
#ifndef HAVE_XMLRPC_C
#ifndef HAVE_XMLRPC_TINYXML2
#include "xmlrpc.h"
#include "parse_commands.h"
namespace rpc {
#ifdef HAVE_XMLRPC_C
class xmlrpc_error : public torrent::base_error {
public:
xmlrpc_error(xmlrpc_env* env) : m_type(env->fault_code), m_msg(env->fault_string) {}
xmlrpc_error(int type, const char* 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; }
private:
int m_type;
const char* m_msg;
};
torrent::Object xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType = 0, rpc::target_type* target = NULL);
inline torrent::Object
xmlrpc_list_entry_to_object(xmlrpc_env* env, xmlrpc_value* src, int index) {
xmlrpc_value* tmp;
xmlrpc_array_read_item(env, src, index, &tmp);
if (env->fault_occurred)
throw xmlrpc_error(env);
torrent::Object obj = xmlrpc_to_object(env, tmp);
xmlrpc_DECREF(tmp);
return obj;
}
int64_t
xmlrpc_list_entry_to_value(xmlrpc_env* env, xmlrpc_value* src, int index) {
xmlrpc_value* tmp;
xmlrpc_array_read_item(env, src, index, &tmp);
if (env->fault_occurred)
throw xmlrpc_error(env);
switch (xmlrpc_value_type(tmp)) {
case XMLRPC_TYPE_INT:
int v;
xmlrpc_read_int(env, tmp, &v);
xmlrpc_DECREF(tmp);
return v;
#ifdef XMLRPC_HAVE_I8
case XMLRPC_TYPE_I8:
xmlrpc_int64 v2;
xmlrpc_read_i8(env, tmp, &v2);
xmlrpc_DECREF(tmp);
return v2;
#endif
case XMLRPC_TYPE_STRING:
{
const char* str;
xmlrpc_read_string(env, tmp, &str);
if (env->fault_occurred)
throw xmlrpc_error(env);
const char* end = str;
int64_t v3 = ::strtoll(str, (char**)&end, 0);
::free((void*)str);
if (*str == '\0' || *end != '\0')
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Invalid index.");
return v3;
}
default:
xmlrpc_DECREF(tmp);
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Invalid type found.");
}
}
// 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) {
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: "<hash>:f<index>"
// Trackers: "<hash>:t<index>"
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();
}
}
rpc::target_type
xmlrpc_to_index_type(int index, int callType, core::Download* download) {
void* result;
switch (callType) {
case XmlRpc::call_file: result = xmlrpc.slot_find_file()(download, index); break;
case XmlRpc::call_tracker: result = xmlrpc.slot_find_tracker()(download, index); break;
default: result = NULL; break;
}
if (result == NULL)
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Invalid index.");
return rpc::make_target(callType, result);
}
torrent::Object
xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType, rpc::target_type* target) {
switch (xmlrpc_value_type(value)) {
case XMLRPC_TYPE_INT:
int v;
xmlrpc_read_int(env, value, &v);
return torrent::Object((int64_t)v);
#ifdef XMLRPC_HAVE_I8
case XMLRPC_TYPE_I8:
xmlrpc_int64 v2;
xmlrpc_read_i8(env, value, &v2);
return torrent::Object((int64_t)v2);
#endif
// case XMLRPC_TYPE_BOOL:
// case XMLRPC_TYPE_DOUBLE:
// case XMLRPC_TYPE_DATETIME:
case XMLRPC_TYPE_STRING:
if (callType != XmlRpc::call_generic) {
// 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);
return torrent::Object();
} else {
const char* valueString;
xmlrpc_read_string(env, value, &valueString);
if (env->fault_occurred)
throw xmlrpc_error(env);
torrent::Object result = torrent::Object(std::string(valueString));
// Urgh, seriously?
::free((void*)valueString);
return result;
}
case XMLRPC_TYPE_BASE64:
{
size_t valueSize;
const char* valueString;
xmlrpc_read_base64(env, value, &valueSize, (const unsigned char**)&valueString);
if (env->fault_occurred)
throw xmlrpc_error(env);
torrent::Object result = torrent::Object(std::string(valueString, valueSize));
// Urgh, seriously?
::free((void*)valueString);
return result;
}
case XMLRPC_TYPE_ARRAY:
{
unsigned int current = 0;
unsigned int last = xmlrpc_array_size(env, value);
if (env->fault_occurred)
throw xmlrpc_error(env);
if (callType != XmlRpc::call_generic && last != 0) {
if (last < 1)
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Too few arguments.");
xmlrpc_value* tmp;
xmlrpc_array_read_item(env, value, current++, &tmp);
if (env->fault_occurred)
throw xmlrpc_error(env);
*target = xmlrpc_to_target(env, tmp);
xmlrpc_DECREF(tmp);
if (env->fault_occurred)
throw xmlrpc_error(env);
if (target->first == XmlRpc::call_download &&
(callType == XmlRpc::call_file || callType == XmlRpc::call_tracker)) {
// If we have a download target and the call type requires
// another contained type, then we try to use the next
// parameter as the index to support old-style calls.
if (current == last)
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Too few arguments, missing index.");
*target = xmlrpc_to_index_type(xmlrpc_list_entry_to_value(env, value, current++), callType, (core::Download*)target->second);
}
}
if (current + 1 < last) {
torrent::Object result = torrent::Object::create_list();
torrent::Object::list_type& listRef = result.as_list();
while (current != last)
listRef.push_back(xmlrpc_list_entry_to_object(env, value, current++));
return result;
} else if (current + 1 == last) {
return xmlrpc_list_entry_to_object(env, value, current);
} else {
return torrent::Object();
}
}
// case XMLRPC_TYPE_STRUCT:
// case XMLRPC_TYPE_C_PTR:
// case XMLRPC_TYPE_NIL:
// case XMLRPC_TYPE_DEAD:
default:
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Unsupported type found.");
}
}
xmlrpc_value*
object_to_xmlrpc(xmlrpc_env* env, const torrent::Object& object) {
switch (object.type()) {
case torrent::Object::TYPE_VALUE:
#ifdef XMLRPC_HAVE_I8
if (xmlrpc.dialect() != XmlRpc::dialect_generic)
return xmlrpc_i8_new(env, object.as_value());
#else
return xmlrpc_int_new(env, object.as_value());
#endif
case torrent::Object::TYPE_STRING:
{
#ifdef XMLRPC_HAVE_I8
// The versions that support I8 do implicit utf-8 validation.
xmlrpc_value* result = xmlrpc_string_new(env, object.as_string().c_str());
#else
// In older versions, xmlrpc-c doesn't validate the utf-8 encoding itself.
xmlrpc_validate_utf8(env, object.as_string().c_str(), object.as_string().length());
xmlrpc_value* result = env->fault_occurred ? NULL : xmlrpc_string_new(env, object.as_string().c_str());
#endif
if (env->fault_occurred) {
xmlrpc_env_clean(env);
xmlrpc_env_init(env);
const std::string& str = object.as_string();
char buffer[str.size() + 1];
char* dst = buffer;
for (std::string::const_iterator itr = str.begin(); itr != str.end(); ++itr)
*dst++ = ((*itr < 0x20 && *itr != '\r' && *itr != '\n' && *itr != '\t') || (*itr & 0x80)) ? '?' : *itr;
*dst = 0;
result = xmlrpc_string_new(env, buffer);
}
return result;
}
case torrent::Object::TYPE_LIST:
{
xmlrpc_value* result = xmlrpc_array_new(env);
for (torrent::Object::list_const_iterator itr = object.as_list().begin(), last = object.as_list().end(); itr != last; itr++) {
xmlrpc_value* item = object_to_xmlrpc(env, *itr);
xmlrpc_array_append_item(env, result, item);
xmlrpc_DECREF(item);
}
return result;
}
case torrent::Object::TYPE_MAP:
{
xmlrpc_value* result = xmlrpc_struct_new(env);
for (torrent::Object::map_const_iterator itr = object.as_map().begin(), last = object.as_map().end(); itr != last; itr++) {
xmlrpc_value* item = object_to_xmlrpc(env, itr->second);
xmlrpc_struct_set_value(env, result, itr->first.c_str(), item);
xmlrpc_DECREF(item);
}
return result;
}
case torrent::Object::TYPE_DICT_KEY:
{
xmlrpc_value* result = xmlrpc_array_new(env);
xmlrpc_value* key_item = object_to_xmlrpc(env, object.as_dict_key());
xmlrpc_array_append_item(env, result, key_item);
xmlrpc_DECREF(key_item);
if (object.as_dict_obj().is_list()) {
for (torrent::Object::list_const_iterator
itr = object.as_dict_obj().as_list().begin(),
last = object.as_dict_obj().as_list().end();
itr != last; itr++) {
xmlrpc_value* item = object_to_xmlrpc(env, *itr);
xmlrpc_array_append_item(env, result, item);
xmlrpc_DECREF(item);
}
} else {
xmlrpc_value* arg_item = object_to_xmlrpc(env, object.as_dict_obj());
xmlrpc_array_append_item(env, result, arg_item);
xmlrpc_DECREF(arg_item);
}
return result;
}
default:
return xmlrpc_int_new(env, 0);
}
}
xmlrpc_value*
xmlrpc_call_command(xmlrpc_env* env, xmlrpc_value* args, void* voidServerInfo) {
CommandMap::iterator itr = commands.find((const char*)voidServerInfo);
if (itr == commands.end()) {
xmlrpc_env_set_fault(env, XMLRPC_PARSE_ERROR, ("Command \"" + std::string((const char*)voidServerInfo) + "\" does not exist.").c_str());
return NULL;
}
try {
torrent::Object object;
rpc::target_type target = rpc::make_target();
if (itr->second.m_flags & CommandMap::flag_no_target)
xmlrpc_to_object(env, args, XmlRpc::call_generic, &target).swap(object);
else if (itr->second.m_flags & CommandMap::flag_file_target)
xmlrpc_to_object(env, args, XmlRpc::call_file, &target).swap(object);
else if (itr->second.m_flags & CommandMap::flag_tracker_target)
xmlrpc_to_object(env, args, XmlRpc::call_tracker, &target).swap(object);
else
xmlrpc_to_object(env, args, XmlRpc::call_any, &target).swap(object);
if (env->fault_occurred)
return NULL;
return object_to_xmlrpc(env, rpc::commands.call_command(itr, object, target));
} catch (xmlrpc_error& e) {
xmlrpc_env_set_fault(env, e.type(), e.what());
return NULL;
} catch (torrent::local_error& e) {
xmlrpc_env_set_fault(env, XMLRPC_PARSE_ERROR, e.what());
return NULL;
}
}
void
XmlRpc::initialize() {
#ifndef XMLRPC_HAVE_I8
m_dialect = dialect_generic;
#endif
m_env = new xmlrpc_env;
xmlrpc_env_init((xmlrpc_env*)m_env);
m_registry = xmlrpc_registry_new((xmlrpc_env*)m_env);
}
void
XmlRpc::cleanup() {
if (!is_valid())
return;
xmlrpc_registry_free((xmlrpc_registry*)m_registry);
xmlrpc_env_clean((xmlrpc_env*)m_env);
delete (xmlrpc_env*)m_env;
}
bool
XmlRpc::process(const char* inBuffer, uint32_t length, slot_write slotWrite) {
xmlrpc_env localEnv;
xmlrpc_env_init(&localEnv);
xmlrpc_mem_block* memblock = xmlrpc_registry_process_call(&localEnv, (xmlrpc_registry*)m_registry, NULL, inBuffer, length);
if (localEnv.fault_occurred && localEnv.fault_code == XMLRPC_INTERNAL_ERROR)
throw torrent::internal_error("Internal error in XMLRPC.");
bool result = slotWrite((const char*)xmlrpc_mem_block_contents(memblock),
xmlrpc_mem_block_size(memblock));
xmlrpc_mem_block_free(memblock);
xmlrpc_env_clean(&localEnv);
return result;
}
void
XmlRpc::insert_command(const char* name, const char* parm, const char* doc) {
xmlrpc_env localEnv;
xmlrpc_env_init(&localEnv);
xmlrpc_registry_add_method_w_doc(&localEnv, (xmlrpc_registry*)m_registry, NULL, name,
&xmlrpc_call_command, const_cast<char*>(name), parm, doc);
if (localEnv.fault_occurred)
throw torrent::internal_error("Fault occured while inserting xmlrpc call.");
xmlrpc_env_clean(&localEnv);
}
void
XmlRpc::set_dialect(int dialect) {
if (!is_valid())
throw torrent::input_error("Cannot select XMLRPC dialect before it is initialized.");
xmlrpc_env localEnv;
xmlrpc_env_init(&localEnv);
switch (dialect) {
case dialect_generic:
break;
#ifdef XMLRPC_HAVE_I8
case dialect_i8:
xmlrpc_registry_set_dialect(&localEnv, (xmlrpc_registry*)m_registry, xmlrpc_dialect_i8);
break;
case dialect_apache:
xmlrpc_registry_set_dialect(&localEnv, (xmlrpc_registry*)m_registry, xmlrpc_dialect_apache);
break;
#endif
default:
xmlrpc_env_clean(&localEnv);
throw torrent::input_error("Unsupported XMLRPC dialect selected.");
}
if (localEnv.fault_occurred) {
xmlrpc_env_clean(&localEnv);
throw torrent::input_error("Unsupported XMLRPC dialect selected.");
}
xmlrpc_env_clean(&localEnv);
m_dialect = dialect;
}
int64_t
XmlRpc::size_limit() {
return xmlrpc_limit_get(XMLRPC_XML_SIZE_LIMIT_ID);
}
void
XmlRpc::set_size_limit(uint64_t size) {
if (size >= (64 << 20))
throw torrent::input_error("Invalid XMLRPC limit size.");
xmlrpc_limit_set(XMLRPC_XML_SIZE_LIMIT_ID, size);
}
#else
void XmlRpc::initialize() { throw torrent::resource_error("XMLRPC not supported."); }
void XmlRpc::cleanup() {}
@@ -620,6 +54,9 @@ bool XmlRpc::process(__UNUSED const char* inBuffer, __UNUSED uint32_t length, __
int64_t XmlRpc::size_limit() { return 0; }
void XmlRpc::set_size_limit(uint64_t size) {}
#endif
bool XmlRpc::is_valid() const { return false; }
}
#endif
#endif
+5 -3
View File
@@ -76,7 +76,7 @@ public:
XmlRpc() : m_env(NULL), m_registry(NULL), m_dialect(dialect_i8) {}
bool is_valid() const { return m_env != NULL; }
bool is_valid() const;
void initialize();
void cleanup();
@@ -93,8 +93,8 @@ public:
slot_tracker& slot_find_tracker() { return m_slotFindTracker; }
slot_peer& slot_find_peer() { return m_slotFindPeer; }
static int64_t size_limit();
static void set_size_limit(uint64_t size);
int64_t size_limit();
void set_size_limit(uint64_t size);
private:
void* m_env;
@@ -102,6 +102,8 @@ private:
int m_dialect;
bool m_isValid;
slot_download m_slotFindDownload;
slot_file m_slotFindFile;
slot_tracker m_slotFindTracker;
+614
View File
@@ -0,0 +1,614 @@
// 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"
#ifdef HAVE_XMLRPC_C
#include <cctype>
#include <string>
#include <stdlib.h>
#include <xmlrpc-c/server.h>
#include <rak/string_manip.h>
#include <torrent/object.h>
#include <torrent/exceptions.h>
#include "xmlrpc.h"
#include "parse_commands.h"
namespace rpc {
class xmlrpc_error : public torrent::base_error {
public:
xmlrpc_error(xmlrpc_env* env) : m_type(env->fault_code), m_msg(env->fault_string) {}
xmlrpc_error(int type, const char* 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; }
private:
int m_type;
const char* m_msg;
};
torrent::Object xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType = 0, rpc::target_type* target = NULL);
inline torrent::Object
xmlrpc_list_entry_to_object(xmlrpc_env* env, xmlrpc_value* src, int index) {
xmlrpc_value* tmp;
xmlrpc_array_read_item(env, src, index, &tmp);
if (env->fault_occurred)
throw xmlrpc_error(env);
torrent::Object obj = xmlrpc_to_object(env, tmp);
xmlrpc_DECREF(tmp);
return obj;
}
int64_t
xmlrpc_list_entry_to_value(xmlrpc_env* env, xmlrpc_value* src, int index) {
xmlrpc_value* tmp;
xmlrpc_array_read_item(env, src, index, &tmp);
if (env->fault_occurred)
throw xmlrpc_error(env);
switch (xmlrpc_value_type(tmp)) {
case XMLRPC_TYPE_INT:
int v;
xmlrpc_read_int(env, tmp, &v);
xmlrpc_DECREF(tmp);
return v;
#ifdef XMLRPC_HAVE_I8
case XMLRPC_TYPE_I8:
xmlrpc_int64 v2;
xmlrpc_read_i8(env, tmp, &v2);
xmlrpc_DECREF(tmp);
return v2;
#endif
case XMLRPC_TYPE_STRING:
{
const char* str;
xmlrpc_read_string(env, tmp, &str);
if (env->fault_occurred)
throw xmlrpc_error(env);
const char* end = str;
int64_t v3 = ::strtoll(str, (char**)&end, 0);
::free((void*)str);
if (*str == '\0' || *end != '\0')
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Invalid index.");
return v3;
}
default:
xmlrpc_DECREF(tmp);
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Invalid type found.");
}
}
// 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) {
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: "<hash>:f<index>"
// Trackers: "<hash>:t<index>"
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();
}
}
rpc::target_type
xmlrpc_to_index_type(int index, int callType, core::Download* download) {
void* result;
switch (callType) {
case XmlRpc::call_file: result = xmlrpc.slot_find_file()(download, index); break;
case XmlRpc::call_tracker: result = xmlrpc.slot_find_tracker()(download, index); break;
default: result = NULL; break;
}
if (result == NULL)
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Invalid index.");
return rpc::make_target(callType, result);
}
torrent::Object
xmlrpc_to_object(xmlrpc_env* env, xmlrpc_value* value, int callType, rpc::target_type* target) {
switch (xmlrpc_value_type(value)) {
case XMLRPC_TYPE_INT:
int v;
xmlrpc_read_int(env, value, &v);
return torrent::Object((int64_t)v);
#ifdef XMLRPC_HAVE_I8
case XMLRPC_TYPE_I8:
xmlrpc_int64 v2;
xmlrpc_read_i8(env, value, &v2);
return torrent::Object((int64_t)v2);
#endif
// case XMLRPC_TYPE_BOOL:
// case XMLRPC_TYPE_DOUBLE:
// case XMLRPC_TYPE_DATETIME:
case XMLRPC_TYPE_STRING:
if (callType != XmlRpc::call_generic) {
// 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);
return torrent::Object();
} else {
const char* valueString;
xmlrpc_read_string(env, value, &valueString);
if (env->fault_occurred)
throw xmlrpc_error(env);
torrent::Object result = torrent::Object(std::string(valueString));
// Urgh, seriously?
::free((void*)valueString);
return result;
}
case XMLRPC_TYPE_BASE64:
{
size_t valueSize;
const char* valueString;
xmlrpc_read_base64(env, value, &valueSize, (const unsigned char**)&valueString);
if (env->fault_occurred)
throw xmlrpc_error(env);
torrent::Object result = torrent::Object(std::string(valueString, valueSize));
// Urgh, seriously?
::free((void*)valueString);
return result;
}
case XMLRPC_TYPE_ARRAY:
{
unsigned int current = 0;
unsigned int last = xmlrpc_array_size(env, value);
if (env->fault_occurred)
throw xmlrpc_error(env);
if (callType != XmlRpc::call_generic && last != 0) {
if (last < 1)
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Too few arguments.");
xmlrpc_value* tmp;
xmlrpc_array_read_item(env, value, current++, &tmp);
if (env->fault_occurred)
throw xmlrpc_error(env);
*target = xmlrpc_to_target(env, tmp);
xmlrpc_DECREF(tmp);
if (env->fault_occurred)
throw xmlrpc_error(env);
if (target->first == XmlRpc::call_download &&
(callType == XmlRpc::call_file || callType == XmlRpc::call_tracker)) {
// If we have a download target and the call type requires
// another contained type, then we try to use the next
// parameter as the index to support old-style calls.
if (current == last)
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Too few arguments, missing index.");
*target = xmlrpc_to_index_type(xmlrpc_list_entry_to_value(env, value, current++), callType, (core::Download*)target->second);
}
}
if (current + 1 < last) {
torrent::Object result = torrent::Object::create_list();
torrent::Object::list_type& listRef = result.as_list();
while (current != last)
listRef.push_back(xmlrpc_list_entry_to_object(env, value, current++));
return result;
} else if (current + 1 == last) {
return xmlrpc_list_entry_to_object(env, value, current);
} else {
return torrent::Object();
}
}
// case XMLRPC_TYPE_STRUCT:
// case XMLRPC_TYPE_C_PTR:
// case XMLRPC_TYPE_NIL:
// case XMLRPC_TYPE_DEAD:
default:
throw xmlrpc_error(XMLRPC_TYPE_ERROR, "Unsupported type found.");
}
}
xmlrpc_value*
object_to_xmlrpc(xmlrpc_env* env, const torrent::Object& object) {
switch (object.type()) {
case torrent::Object::TYPE_VALUE:
#ifdef XMLRPC_HAVE_I8
if (xmlrpc.dialect() != XmlRpc::dialect_generic)
return xmlrpc_i8_new(env, object.as_value());
#else
return xmlrpc_int_new(env, object.as_value());
#endif
case torrent::Object::TYPE_STRING:
{
#ifdef XMLRPC_HAVE_I8
// The versions that support I8 do implicit utf-8 validation.
xmlrpc_value* result = xmlrpc_string_new(env, object.as_string().c_str());
#else
// In older versions, xmlrpc-c doesn't validate the utf-8 encoding itself.
xmlrpc_validate_utf8(env, object.as_string().c_str(), object.as_string().length());
xmlrpc_value* result = env->fault_occurred ? NULL : xmlrpc_string_new(env, object.as_string().c_str());
#endif
if (env->fault_occurred) {
xmlrpc_env_clean(env);
xmlrpc_env_init(env);
const std::string& str = object.as_string();
char buffer[str.size() + 1];
char* dst = buffer;
for (std::string::const_iterator itr = str.begin(); itr != str.end(); ++itr)
*dst++ = ((*itr < 0x20 && *itr != '\r' && *itr != '\n' && *itr != '\t') || (*itr & 0x80)) ? '?' : *itr;
*dst = 0;
result = xmlrpc_string_new(env, buffer);
}
return result;
}
case torrent::Object::TYPE_LIST:
{
xmlrpc_value* result = xmlrpc_array_new(env);
for (torrent::Object::list_const_iterator itr = object.as_list().begin(), last = object.as_list().end(); itr != last; itr++) {
xmlrpc_value* item = object_to_xmlrpc(env, *itr);
xmlrpc_array_append_item(env, result, item);
xmlrpc_DECREF(item);
}
return result;
}
case torrent::Object::TYPE_MAP:
{
xmlrpc_value* result = xmlrpc_struct_new(env);
for (torrent::Object::map_const_iterator itr = object.as_map().begin(), last = object.as_map().end(); itr != last; itr++) {
xmlrpc_value* item = object_to_xmlrpc(env, itr->second);
xmlrpc_struct_set_value(env, result, itr->first.c_str(), item);
xmlrpc_DECREF(item);
}
return result;
}
case torrent::Object::TYPE_DICT_KEY:
{
xmlrpc_value* result = xmlrpc_array_new(env);
xmlrpc_value* key_item = object_to_xmlrpc(env, object.as_dict_key());
xmlrpc_array_append_item(env, result, key_item);
xmlrpc_DECREF(key_item);
if (object.as_dict_obj().is_list()) {
for (torrent::Object::list_const_iterator
itr = object.as_dict_obj().as_list().begin(),
last = object.as_dict_obj().as_list().end();
itr != last; itr++) {
xmlrpc_value* item = object_to_xmlrpc(env, *itr);
xmlrpc_array_append_item(env, result, item);
xmlrpc_DECREF(item);
}
} else {
xmlrpc_value* arg_item = object_to_xmlrpc(env, object.as_dict_obj());
xmlrpc_array_append_item(env, result, arg_item);
xmlrpc_DECREF(arg_item);
}
return result;
}
default:
return xmlrpc_int_new(env, 0);
}
}
xmlrpc_value*
xmlrpc_call_command(xmlrpc_env* env, xmlrpc_value* args, void* voidServerInfo) {
CommandMap::iterator itr = commands.find((const char*)voidServerInfo);
if (itr == commands.end()) {
xmlrpc_env_set_fault(env, XMLRPC_PARSE_ERROR, ("Command \"" + std::string((const char*)voidServerInfo) + "\" does not exist.").c_str());
return NULL;
}
try {
torrent::Object object;
rpc::target_type target = rpc::make_target();
if (itr->second.m_flags & CommandMap::flag_no_target)
xmlrpc_to_object(env, args, XmlRpc::call_generic, &target).swap(object);
else if (itr->second.m_flags & CommandMap::flag_file_target)
xmlrpc_to_object(env, args, XmlRpc::call_file, &target).swap(object);
else if (itr->second.m_flags & CommandMap::flag_tracker_target)
xmlrpc_to_object(env, args, XmlRpc::call_tracker, &target).swap(object);
else
xmlrpc_to_object(env, args, XmlRpc::call_any, &target).swap(object);
if (env->fault_occurred)
return NULL;
return object_to_xmlrpc(env, rpc::commands.call_command(itr, object, target));
} catch (xmlrpc_error& e) {
xmlrpc_env_set_fault(env, e.type(), e.what());
return NULL;
} catch (torrent::local_error& e) {
xmlrpc_env_set_fault(env, XMLRPC_PARSE_ERROR, e.what());
return NULL;
}
}
void
XmlRpc::initialize() {
#ifndef XMLRPC_HAVE_I8
m_dialect = dialect_generic;
#endif
m_env = new xmlrpc_env;
xmlrpc_env_init((xmlrpc_env*)m_env);
m_registry = xmlrpc_registry_new((xmlrpc_env*)m_env);
}
void
XmlRpc::cleanup() {
if (!is_valid())
return;
xmlrpc_registry_free((xmlrpc_registry*)m_registry);
xmlrpc_env_clean((xmlrpc_env*)m_env);
delete (xmlrpc_env*)m_env;
}
bool
XmlRpc::process(const char* inBuffer, uint32_t length, slot_write slotWrite) {
xmlrpc_env localEnv;
xmlrpc_env_init(&localEnv);
xmlrpc_mem_block* memblock = xmlrpc_registry_process_call(&localEnv, (xmlrpc_registry*)m_registry, NULL, inBuffer, length);
if (localEnv.fault_occurred && localEnv.fault_code == XMLRPC_INTERNAL_ERROR)
throw torrent::internal_error("Internal error in XMLRPC.");
bool result = slotWrite((const char*)xmlrpc_mem_block_contents(memblock),
xmlrpc_mem_block_size(memblock));
xmlrpc_mem_block_free(memblock);
xmlrpc_env_clean(&localEnv);
return result;
}
void
XmlRpc::insert_command(const char* name, const char* parm, const char* doc) {
xmlrpc_env localEnv;
xmlrpc_env_init(&localEnv);
xmlrpc_registry_add_method_w_doc(&localEnv, (xmlrpc_registry*)m_registry, NULL, name,
&xmlrpc_call_command, const_cast<char*>(name), parm, doc);
if (localEnv.fault_occurred)
throw torrent::internal_error("Fault occured while inserting xmlrpc call.");
xmlrpc_env_clean(&localEnv);
}
void
XmlRpc::set_dialect(int dialect) {
if (!is_valid())
throw torrent::input_error("Cannot select XMLRPC dialect before it is initialized.");
xmlrpc_env localEnv;
xmlrpc_env_init(&localEnv);
switch (dialect) {
case dialect_generic:
break;
#ifdef XMLRPC_HAVE_I8
case dialect_i8:
xmlrpc_registry_set_dialect(&localEnv, (xmlrpc_registry*)m_registry, xmlrpc_dialect_i8);
break;
case dialect_apache:
xmlrpc_registry_set_dialect(&localEnv, (xmlrpc_registry*)m_registry, xmlrpc_dialect_apache);
break;
#endif
default:
xmlrpc_env_clean(&localEnv);
throw torrent::input_error("Unsupported XMLRPC dialect selected.");
}
if (localEnv.fault_occurred) {
xmlrpc_env_clean(&localEnv);
throw torrent::input_error("Unsupported XMLRPC dialect selected.");
}
xmlrpc_env_clean(&localEnv);
m_dialect = dialect;
}
int64_t
XmlRpc::size_limit() {
return xmlrpc_limit_get(XMLRPC_XML_SIZE_LIMIT_ID);
}
void
XmlRpc::set_size_limit(uint64_t size) {
if (size >= (64 << 20))
throw torrent::input_error("Invalid XMLRPC limit size.");
xmlrpc_limit_set(XMLRPC_XML_SIZE_LIMIT_ID, size);
}
bool
XmlRpc::is_valid() const { return m_env != NULL; }
} // namespace rpc
#endif
+454
View File
@@ -0,0 +1,454 @@
// 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"
#ifdef HAVE_XMLRPC_TINYXML2
#ifndef HAVE_XMLRPC_C
#include <cctype>
#include <string>
#include <stdlib.h>
#include <rak/string_manip.h>
#include <torrent/object.h>
#include <torrent/exceptions.h>
#include "vendor/tinyxml2/tinyxml2.h"
#include "utils/base64.h"
#include "xmlrpc.h"
#include "parse_commands.h"
namespace rpc {
// Taken from xmlrpc-c
const int XMLRPC_INTERNAL_ERROR = -500;
const int XMLRPC_TYPE_ERROR = -501;
const int XMLRPC_INDEX_ERROR = -502;
const int XMLRPC_PARSE_ERROR = -503;
const int XMLRPC_NETWORK_ERROR = -504;
const int XMLRPC_TIMEOUT_ERROR = -505;
const int XMLRPC_NO_SUCH_METHOD_ERROR = -506;
const int XMLRPC_REQUEST_REFUSED_ERROR = -507;
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::string elemNames) {
// Helper function to check each step of a element access, in lieu of XPath
char token = ',';
std::string stack("");
const tinyxml2::XMLElement* result = elem;
size_t start = 0;
size_t end = 0;
do {
end = elemNames.find(token, start);
auto item = elemNames.substr(start, end - start);
stack.append(item);
result = result->FirstChildElement(item.c_str());
if (result == nullptr) {
throw xmlrpc_error(XMLRPC_PARSE_ERROR, "could not find expected element " + stack);
}
stack.append("->");
start = end + 1;
} while (end != std::string::npos);
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");
}
if (std::string("value") != elem->Value()) {
throw xmlrpc_error(XMLRPC_INTERNAL_ERROR, "received non-value element to convert");
}
auto valueElem = elem->FirstChild();
auto elemType = std::string(valueElem->Value());
if (elemType == "string") {
auto elemChild = valueElem->FirstChild();
if (elemChild == nullptr) {
return torrent::Object("");
}
return torrent::Object(elemChild->ToText()->Value());
} else if (elemType == "i4" or elemType == "i8" or elemType == "int") {
auto elemValue = std::string(valueElem->FirstChild()->ToText()->Value());
return torrent::Object(std::stol(elemValue));
} else if (elemType == "boolean") {
auto boolText = std::string(valueElem->FirstChild()->ToText()->Value());
if (boolText == "1") {
return torrent::Object((int64_t)1);
} else if (boolText == "0") {
return torrent::Object((int64_t)0);
}
} else if (elemType == "array") {
auto array = torrent::Object::create_list();
auto dataElem = element_access(valueElem->ToElement(), "data");
for (auto arrayValueElem = dataElem->FirstChildElement("value"); arrayValueElem; arrayValueElem = arrayValueElem->NextSiblingElement("value")) {
array.as_list().push_back(xml_value_to_object(arrayValueElem));
}
return array;
} else if (elemType == "struct") {
auto map = torrent::Object::create_map();
for (auto memberElem = valueElem->FirstChildElement("member"); memberElem; memberElem = memberElem->NextSiblingElement("member")) {
auto key = memberElem->FirstChildElement("name")->GetText();
auto valElem = xml_value_to_object(element_access(memberElem, "value"));
map.as_map()[key] = valElem;
}
return map;
} else if (elemType == "base64") {
auto elemChild = valueElem->FirstChild();
if (elemChild == nullptr) {
return torrent::Object("");
}
auto base64string = std::string(elemChild->ToText()->Value());
base64string.erase(std::remove_if(base64string.begin(), base64string.end(),
[](char c) { return c == '\n' || c == '\r'; }),
base64string.end());
return torrent::Object(utils::base64decode(base64string));
return torrent::Object("");
} else {
throw xmlrpc_error(XMLRPC_INTERNAL_ERROR, "received unsupported value type: " + elemType);
}
return torrent::Object();
}
void
print_object_xml(const torrent::Object& obj, tinyxml2::XMLPrinter* printer) {
switch (obj.type()) {
case torrent::Object::TYPE_STRING:
printer->OpenElement("string", true);
printer->PushText(obj.as_string().c_str());
printer->CloseElement(true);
break;
case torrent::Object::TYPE_VALUE:
if (obj.as_value() > ((torrent::Object::value_type)2 << 30) || obj.as_value() < -((torrent::Object::value_type)2 << 30)) {
printer->OpenElement("i8", true);
} else {
printer->OpenElement("i4", true);
}
printer->PushText(std::to_string(obj.as_value()).c_str());
printer->CloseElement(true);
break;
case torrent::Object::TYPE_LIST:
printer->OpenElement("array", true);
for (const auto& itr : obj.as_list()) {
printer->OpenElement("value", true);
print_object_xml(itr, printer);
printer->CloseElement(true);
}
printer->CloseElement(true);
break;
case torrent::Object::TYPE_MAP:
printer->OpenElement("struct", true);
for (const auto& itr : obj.as_map()) {
printer->OpenElement("member", true);
printer->OpenElement("name", true);
printer->PushText(itr.first.c_str());
printer->CloseElement(true);
printer->OpenElement("value", true);
print_object_xml(itr.second, printer);
printer->CloseElement(true);
printer->CloseElement(true);
}
printer->CloseElement(true);
break;
case torrent::Object::TYPE_DICT_KEY:
printer->OpenElement("array", true);
printer->OpenElement("value", true);
print_object_xml(obj.as_dict_key(), printer);
printer->CloseElement(true);
if (obj.as_dict_obj().is_list()) {
for (auto itr : obj.as_dict_obj().as_list()) {
printer->OpenElement("value", true);
print_object_xml(obj.as_dict_key(), printer);
printer->CloseElement(true);
}
} else {
printer->OpenElement("value", true);
print_object_xml(obj.as_dict_obj(), printer);
printer->CloseElement(true);
}
printer->CloseElement(true);
break;
default:
printer->OpenElement("i4", true);
printer->PushText(0);
printer->CloseElement(true);
}
}
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 targetString = obj.as_string();
bool requireIndex = (callFlags & (CommandMap::flag_tracker_target | CommandMap::flag_file_target));
if (targetString.size() == 0 && !requireIndex) {
return;
}
// Length of SHA1 hash is 40
if (targetString.size() < 40) {
throw torrent::input_error("invalid parameters: invalid target");
}
char type = 'd';
std::string hash;
std::string index;
const auto& delimPos = targetString.find_first_of(':', 40);
if (delimPos == targetString.npos ||
delimPos + 2 >= targetString.size()) {
if (requireIndex) {
throw torrent::input_error("invalid parameters: no index");
}
hash = targetString;
} else {
hash = targetString.substr(0, delimPos);
type = targetString[delimPos + 1];
index = targetString.substr(delimPos + 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 methodName, const tinyxml2::XMLElement* paramsElem) {
if (paramsElem == nullptr) {
throw xmlrpc_error(XMLRPC_INTERNAL_ERROR, "invalid parameters: null");
}
CommandMap::iterator cmdItr = commands.find(methodName.c_str());
if (cmdItr == commands.end() || !(cmdItr->second.m_flags & CommandMap::flag_public_xmlrpc)) {
throw xmlrpc_error(XMLRPC_NO_SUCH_METHOD_ERROR, "Method '" + std::string(methodName) + "' not defined");
}
torrent::Object paramsRaw = torrent::Object::create_list();
torrent::Object::list_type& params = paramsRaw.as_list();
if (paramsElem != nullptr) {
for (auto paramElem = paramsElem->FirstChildElement("param"); paramElem; paramElem = paramElem->NextSiblingElement("param")) {
params.push_back(xml_value_to_object(paramElem->FirstChildElement("value")));
}
}
rpc::target_type target = rpc::make_target();
if (params.size() == 0 && (cmdItr->second.m_flags & (CommandMap::flag_file_target | CommandMap::flag_tracker_target))) {
throw xmlrpc_error(XMLRPC_INTERNAL_ERROR, "invalid parameters: too few");
}
if (params.size() > 0) {
object_to_target(params.front(), cmdItr->second.m_flags, &target);
params.erase(params.begin());
}
return rpc::commands.call_command(cmdItr, paramsRaw, target);
}
void
process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer) {
if (doc->Error()) {
throw xmlrpc_error(XMLRPC_PARSE_ERROR, doc->ErrorStr());
}
if (doc->FirstChildElement("methodCall") == nullptr) {
throw xmlrpc_error(XMLRPC_PARSE_ERROR, "methodCall element not found");
}
auto methodName = element_access(doc->FirstChildElement("methodCall"), "methodName")->GetText();
torrent::Object result;
// Add a shim here for system.multicall to allow better code reuse, and
// because system.multicall is one of the few methods that doesn't take a target
if (methodName == std::string("system.multicall")) {
result = torrent::Object::create_list();
torrent::Object::list_type& resultList = result.as_list();
auto valueElems = element_access(doc->RootElement(), "params,param,value,array,data");
for (auto structElem = valueElems->FirstChildElement("value"); structElem; structElem = structElem->NextSiblingElement("value")) {
auto subMethodName = element_access(structElem, "struct,member,value,string")->GetText();
auto subParams = element_access(structElem, "struct,member")->NextSiblingElement("member")->FirstChildElement("value");
try {
auto subResult = torrent::Object::create_list();
subResult.as_list().push_back(execute_command(subMethodName, subParams));
resultList.push_back(subResult);
} catch (xmlrpc_error& e) {
auto fault = torrent::Object::create_map();
fault.as_map()["faultString"] = e.what();
fault.as_map()["faultCode"] = e.type();
resultList.push_back(fault);
} catch (torrent::local_error& e) {
auto fault = torrent::Object::create_map();
fault.as_map()["faultString"] = e.what();
fault.as_map()["faultCode"] = XMLRPC_INTERNAL_ERROR;
resultList.push_back(fault);
}
}
} else {
result = execute_command(methodName, doc->FirstChildElement("methodCall")->FirstChildElement("params"));
}
printer->PushHeader(false, true);
printer->OpenElement("methodReponse", true);
printer->OpenElement("params", true);
printer->OpenElement("param", true);
printer->OpenElement("value", true);
print_object_xml(result, printer);
printer->CloseElement(true);
printer->CloseElement(true);
printer->CloseElement(true);
printer->CloseElement(true);
tinyxml2::XMLDocument resultDoc;
}
void
print_xmlrpc_fault(int faultCode, std::string faultString, tinyxml2::XMLPrinter* printer) {
printer->PushHeader(false, true);
printer->OpenElement("methodReponse", true);
printer->OpenElement("fault", true);
printer->OpenElement("struct", true);
printer->OpenElement("member", true);
printer->OpenElement("name", true);
printer->PushText("faultCode");
printer->CloseElement(true);
printer->OpenElement("value", true);
printer->OpenElement("int", true);
printer->PushText(faultCode);
printer->CloseElement(true);
printer->CloseElement(true);
printer->CloseElement(true);
printer->OpenElement("member", true);
printer->OpenElement("name", true);
printer->PushText("faultString");
printer->CloseElement(true);
printer->OpenElement("value", true);
printer->OpenElement("string", true);
printer->PushText(faultString.c_str());
printer->CloseElement(true);
printer->CloseElement(true);
printer->CloseElement(true);
printer->CloseElement(true);
printer->CloseElement(true);
printer->CloseElement(true);
}
bool
XmlRpc::process(const char* inBuffer, uint32_t length, slot_write slotWrite) {
tinyxml2::XMLDocument doc;
doc.Parse(inBuffer, length);
try {
// This printer can't be reused in the 'catch' because while the
// buffer can be cleared, the internal stack of opened tags
// remains.
tinyxml2::XMLPrinter printer(nullptr, true, 0);
process_document(&doc, &printer);
return slotWrite(printer.CStr(), printer.CStrSize()-1);
} catch (xmlrpc_error& e) {
tinyxml2::XMLPrinter printer(nullptr, true, 0);
print_xmlrpc_fault(e.type(), e.what(), &printer);
return slotWrite(printer.CStr(), printer.CStrSize()-1);
} catch (torrent::local_error& e) {
tinyxml2::XMLPrinter printer(nullptr, true, 0);
print_xmlrpc_fault(XMLRPC_INTERNAL_ERROR, e.what(), &printer);
return slotWrite(printer.CStr(), printer.CStrSize()-1);
}
}
void XmlRpc::initialize() { m_isValid = true; }
void XmlRpc::cleanup() {}
void XmlRpc::insert_command(const char*, const char*, const char*) {}
void XmlRpc::set_dialect(int) {}
int64_t XmlRpc::size_limit() { return std::numeric_limits<int64_t>::max(); }
void XmlRpc::set_size_limit(uint64_t) {}
bool XmlRpc::is_valid() const { return m_isValid; }
}
#endif // ifndef HAVE_XMLRPC_C
#endif // ifdef HAVE_XMLRPC_TINYXML2
+59
View File
@@ -0,0 +1,59 @@
#include <string>
#include <torrent/exceptions.h>
namespace utils {
constexpr static char padCharacter = '=';
static std::string
base64decode(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] == padCharacter)
padding++;
if (input[input.length() - 2] == padCharacter)
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 == padCharacter) // 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;
}
}
+3031
View File
File diff suppressed because it is too large Load Diff
+2387
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -9,6 +9,8 @@ rtorrentTest_SOURCES = \
rpc/command_test.h \
rpc/command_map_test.cc \
rpc/command_map_test.h \
rpc/xmlrpc_test.cc \
rpc/xmlrpc_test.h \
rpc/command_slot_test.cc \
rpc/command_slot_test.h \
rpc/object_storage_test.cc \
+83
View File
@@ -0,0 +1,83 @@
#include "config.h"
#include <string>
#include <iostream>
#include "command_helpers.h"
#include "rpc/command_map.h"
#include "xmlrpc_test.h"
#include "control.h"
#include "globals.h"
CPPUNIT_TEST_SUITE_REGISTRATION(XmlrpcTest);
torrent::Object xmlprpc_cmd_test_reflect(rpc::target_type t, const torrent::Object& obj) { return obj; }
void initialize_command_dynamic();
#if defined(HAVE_XMLRPC_TINYXML2) && !defined(HAVE_XMLRPC_C)
void
XmlrpcTest::setUp() {
m_commandItr = m_commands;
m_xmlrpc = rpc::XmlRpc();
m_xmlrpc.initialize();
setlocale(LC_ALL, "");
cachedTime = rak::timer::current();
control = new Control;
if (rpc::commands.find("xmlrpc_reflect") == rpc::commands.end()) {
CMD2_ANY("xmlrpc_reflect", &xmlprpc_cmd_test_reflect);
}
}
void
XmlrpcTest::test_basics() {
std::ifstream file; file.open("rpc/xmlrpc_test_data.txt");
CPPUNIT_ASSERT(file.good());
std::vector<std::string> titles;
std::vector<std::string> inputs;
std::vector<std::string> outputs;
std::string line;
int index = 0;
// Read file into inputs/outputs
while (std::getline(file, line)) {
if (line.size() == 0) {
continue;
}
if (line[0] == '#') {
titles.push_back(line);
continue;
}
if (index % 2) {
outputs.push_back(line);
} else {
inputs.push_back(line);
}
index++;
}
// Sanity check the above parser
CPPUNIT_ASSERT_MESSAGE("Could not parse test data", inputs.size() > 0 && inputs.size() == outputs.size() && inputs.size() == titles.size());
for (int i = 0; i < inputs.size(); i++) {
std::cout << titles[i] << "\n";
auto output = std::string("");
m_xmlrpc.process(inputs[i].c_str(), inputs[i].size(), [&output](const char* c, uint32_t l){ output.append(c, l); return true;});
CPPUNIT_ASSERT_EQUAL_MESSAGE(titles[i], std::string(outputs[i]), output);
}
}
void
XmlrpcTest::test_invalid_utf8() {
// Surprisingly, this call doesn't fail. TinyXML-2 technically expects
// valid UTF-8, but doesn't check strings, and Object strings are
// just a series of bytes so it reflects just fine.
std::string input = "<?xml version=\"1.0\"?><methodCall><methodName>xmlrpc_reflect</methodName><params><param><value><string></string></value></param><param><value><string>\xc3\x28</string></value></param></params></methodCall>";
std::string expected = "<?xml version=\"1.0\"?><methodReponse><params><param><value><array><value><string>\xc3\x28</string></value></array></value></param></params></methodReponse>";
auto output = std::string("");
m_xmlrpc.process(input.c_str(), input.size(), [&output](const char* c, uint32_t l){ output.append(c, l); return true;});
CPPUNIT_ASSERT_EQUAL(expected, output);
}
#else
void XmlrpcTest::test_invalid_utf8() {}
void XmlrpcTest::test_basics() {}
void XmlrpcTest::setUp() {}
#endif
+28
View File
@@ -0,0 +1,28 @@
#include <cppunit/extensions/HelperMacros.h>
#include "rpc/command_map.h"
#include "rpc/xmlrpc.h"
class XmlrpcTest : public CppUnit::TestFixture {
CPPUNIT_TEST_SUITE(XmlrpcTest);
CPPUNIT_TEST(test_basics);
CPPUNIT_TEST(test_invalid_utf8);
CPPUNIT_TEST_SUITE_END();
public:
static const int cmd_size = 256;
void setUp();
void tearDown() {}
void test_basics();
void test_invalid_utf8();
private:
rpc::XmlRpc m_xmlrpc;
rpc::CommandMap m_map;
rpc::command_base m_commands[cmd_size];
rpc::command_base* m_commandItr;
};
+55
View File
@@ -0,0 +1,55 @@
# Basic call
<?xml version="1.0"?><methodCall><methodName>xmlrpc_reflect</methodName><params></params></methodCall>
<?xml version="1.0"?><methodReponse><params><param><value><array/></value></param></params></methodReponse>
# UTF-8 string
<?xml version="1.0"?><methodCall><methodName>xmlrpc_reflect</methodName><params><param><value><string></string></value></param><param><value><string>чао</string></value></param></params></methodCall>
<?xml version="1.0"?><methodReponse><params><param><value><array><value><string>чао</string></value></array></value></param></params></methodReponse>
# emoji string
<?xml version="1.0"?><methodCall><methodName>xmlrpc_reflect</methodName><params><param><value><string></string></value></param><param><value><string>😊</string></value></param></params></methodCall>
<?xml version="1.0"?><methodReponse><params><param><value><array><value><string>😊</string></value></array></value></param></params></methodReponse>
# base64 data (which gets returned as a string)
<?xml version="1.0"?><methodCall><methodName>xmlrpc_reflect</methodName><params><param><value><string></string></value></param><param><value><base64>Zm9vYmFy</base64></value></param></params></methodCall>
<?xml version="1.0"?><methodReponse><params><param><value><array><value><string>foobar</string></value></array></value></param></params></methodReponse>
# i4 ints
<?xml version="1.0"?><methodCall><methodName>xmlrpc_reflect</methodName><params><param><value><string></string></value></param><param><value><i4>41</i4></value></param></params></methodCall>
<?xml version="1.0"?><methodReponse><params><param><value><array><value><i4>41</i4></value></array></value></param></params></methodReponse>
# i8 ints
<?xml version="1.0"?><methodCall><methodName>xmlrpc_reflect</methodName><params><param><value><string></string></value></param><param><value><i8>2247483647</i8></value></param></params></methodCall>
<?xml version="1.0"?><methodReponse><params><param><value><array><value><i8>2247483647</i8></value></array></value></param></params></methodReponse>
# negative i8 ints
<?xml version="1.0"?><methodCall><methodName>xmlrpc_reflect</methodName><params><param><value><string></string></value></param><param><value><i8>-2347483647</i8></value></param></params></methodCall>
<?xml version="1.0"?><methodReponse><params><param><value><array><value><i8>-2347483647</i8></value></array></value></param></params></methodReponse>
# Empty array
<?xml version="1.0"?><methodCall><methodName>xmlrpc_reflect</methodName><params><param><value><string></string></value></param><param><value><array><data><value><i8>2247483647</i8></value></data></array></value></param></params></methodCall>
<?xml version="1.0"?><methodReponse><params><param><value><array><value><array><value><i8>2247483647</i8></value></array></value></array></value></param></params></methodReponse>
# Simple array
<?xml version="1.0"?><methodCall><methodName>xmlrpc_reflect</methodName><params><param><value><string></string></value></param><param><value><array><data></data></array></value></param></params></methodCall>
<?xml version="1.0"?><methodReponse><params><param><value><array><value><array/></value></array></value></param></params></methodReponse>
# Empty struct
<?xml version="1.0"?><methodCall><methodName>xmlrpc_reflect</methodName><params><param><value><string></string></value></param><param><value><struct></struct></value></param></params></methodCall>
<?xml version="1.0"?><methodReponse><params><param><value><array><value><struct/></value></array></value></param></params></methodReponse>
# Simple struct
<?xml version="1.0"?><methodCall><methodName>xmlrpc_reflect</methodName><params><param><value><string></string></value></param><param><value><struct><member><name>lowerBound</name><value><i4>18</i4></value></member><member><name>upperBound</name><value><i4>139</i4></value></member></struct></value></param></params></methodCall>
<?xml version="1.0"?><methodReponse><params><param><value><array><value><struct><member><name>lowerBound</name><value><i4>18</i4></value></member><member><name>upperBound</name><value><i4>139</i4></value></member></struct></value></array></value></param></params></methodReponse>
# Invalid - missing method
<?xml version="1.0"?><methodCall><methodName>no_such_method</methodName><params><param><value><i4>41</i4></value></param></params></methodCall>
<?xml version="1.0"?><methodReponse><fault><struct><member><name>faultCode</name><value><int>-506</int></value></member><member><name>faultString</name><value><string>Method 'no_such_method' not defined</string></value></member></struct></fault></methodReponse>
# Invalid - i4 target
<?xml version="1.0"?><methodCall><methodName>xmlrpc_reflect</methodName><params><param><value><i4>41</i4></value></param></params></methodCall>
<?xml version="1.0"?><methodReponse><fault><struct><member><name>faultCode</name><value><int>-500</int></value></member><member><name>faultString</name><value><string>invalid parameters: target must be a string</string></value></member></struct></fault></methodReponse>
# Invalid - broken XML
thodCall><methodName>test_a</methodName><params><param><value><i4>41</i4></value></param></params></method
<?xml version="1.0"?><methodReponse><fault><struct><member><name>faultCode</name><value><int>-503</int></value></member><member><name>faultString</name><value><string>Error=XML_ERROR_PARSING_ELEMENT ErrorID=6 (0x6) Line number=1: XMLElement name=method</string></value></member></struct></fault></methodReponse>
+1 -1
View File
@@ -17,7 +17,7 @@ void initialize_command_ui();
void
CommandDynamicTest::setUp() {
if (rpc::commands.empty()) {
if (rpc::commands.find("method.insert") == rpc::commands.end()) {
setlocale(LC_ALL, "");
cachedTime = rak::timer::current();
control = new Control;