diff --git a/src/command_download.cc b/src/command_download.cc index a90a6873..0c2c67eb 100644 --- a/src/command_download.cc +++ b/src/command_download.cc @@ -644,33 +644,45 @@ void cg_d_group_set(core::Download* download, const torrent::Objec void initialize_command_download() { - CMD2_DL("d.hash", [](auto* download, auto) { return torrent::utils::transform_to_hex_str(download->info()->hash()); }); - CMD2_DL("d.local_id", [](auto* download, auto) { return torrent::utils::transform_to_hex_str(download->info()->local_id()); }); - CMD2_DL("d.local_id_html", [](auto* download, auto) { return torrent::utils::copy_escape_html_str(download->info()->local_id()); }); - CMD2_DL("d.bitfield", [](auto* download, auto) { return torrent::utils::transform_to_hex_str(*download->download()->file_list()->bitfield()); }); - CMD2_DL("d.base_path", [](auto* download, auto) { return retrieve_d_base_path(download).str(); }); - CMD2_DL("d.base_path.base64", [](auto* download, auto) { return retrieve_d_base_path(download).object_base64(); }); - CMD2_DL("d.base_path.or_base64", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_base64(); }); - CMD2_DL("d.base_filename", [](auto* download, auto) { return retrieve_d_base_filename(download).str(); }); - CMD2_DL("d.base_filename.base64", [](auto* download, auto) { return retrieve_d_base_filename(download).object_base64(); }); - CMD2_DL("d.base_filename.or_base64", [](auto* download, auto) { return retrieve_d_base_filename(download).object_utf8_or_base64(); }); + CMD2_DL("d.hash", [](auto* download, auto) { return torrent::utils::transform_to_hex_str(download->info()->hash()); }); + CMD2_DL("d.local_id", [](auto* download, auto) { return torrent::utils::transform_to_hex_str(download->info()->local_id()); }); + CMD2_DL("d.local_id_html", [](auto* download, auto) { return torrent::utils::copy_escape_html_str(download->info()->local_id()); }); + CMD2_DL("d.bitfield", [](auto* download, auto) { return torrent::utils::transform_to_hex_str(*download->download()->file_list()->bitfield()); }); + CMD2_DL("d.base_path", [](auto* download, auto) { return retrieve_d_base_path(download).str(); }); + CMD2_DL("d.base_path.hex", [](auto* download, auto) { return retrieve_d_base_path(download).object_hex(); }); + CMD2_DL("d.base_path.base64", [](auto* download, auto) { return retrieve_d_base_path(download).object_base64(); }); + CMD2_DL("d.base_path.base64_as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_base64_as_binary(); }); + CMD2_DL("d.base_path.as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_as_binary(); }); + CMD2_DL("d.base_path.or_base64", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_base64(); }); + CMD2_DL("d.base_path.or_as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_as_binary(); }); + CMD2_DL("d.base_filename", [](auto* download, auto) { return retrieve_d_base_filename(download).str(); }); + CMD2_DL("d.base_filename.hex", [](auto* download, auto) { return retrieve_d_base_filename(download).object_hex(); }); + CMD2_DL("d.base_filename.base64", [](auto* download, auto) { return retrieve_d_base_filename(download).object_base64(); }); + CMD2_DL("d.base_filename.base64_as_binary", [](auto* download, auto) { return retrieve_d_base_filename(download).object_base64_as_binary(); }); + CMD2_DL("d.base_filename.as_binary", [](auto* download, auto) { return retrieve_d_base_filename(download).object_as_binary(); }); + CMD2_DL("d.base_filename.or_base64", [](auto* download, auto) { return retrieve_d_base_filename(download).object_utf8_or_base64(); }); + CMD2_DL("d.base_filename.or_as_binary", [](auto* download, auto) { return retrieve_d_base_filename(download).object_utf8_or_as_binary(); }); - CMD2_DL("d.name", [](auto* download, auto) { return download->info()->name().str(); }); - CMD2_DL("d.name.base64", [](auto* download, auto) { return download->info()->name().object_base64(); }); - CMD2_DL("d.name.or_base64", [](auto* download, auto) { return download->info()->name().object_utf8_or_base64(); }); - CMD2_DL("d.creation_date", [](auto* download, auto) { return download->info()->creation_date(); }); - CMD2_DL("d.load_date", [](auto* download, auto) { return download->info()->load_date(); }); + CMD2_DL("d.name", [](auto* download, auto) { return download->info()->name().str(); }); + CMD2_DL("d.name.hex", [](auto* download, auto) { return download->info()->name().object_hex(); }); + CMD2_DL("d.name.base64", [](auto* download, auto) { return download->info()->name().object_base64(); }); + CMD2_DL("d.name.base64_as_binary", [](auto* download, auto) { return download->info()->name().object_base64_as_binary(); }); + CMD2_DL("d.name.as_binary", [](auto* download, auto) { return download->info()->name().object_as_binary(); }); + CMD2_DL("d.name.or_base64", [](auto* download, auto) { return download->info()->name().object_utf8_or_base64(); }); + CMD2_DL("d.name.or_as_binary", [](auto* download, auto) { return download->info()->name().object_utf8_or_as_binary(); }); + CMD2_DL("d.creation_date", [](auto* download, auto) { return download->info()->creation_date(); }); + CMD2_DL("d.load_date", [](auto* download, auto) { return download->info()->load_date(); }); // // Network related: // - CMD2_DL ("d.up.rate", [](auto* download, auto) { return download->info()->up_rate()->rate(); }); - CMD2_DL ("d.up.total", [](auto* download, auto) { return download->info()->up_rate()->total(); }); - CMD2_DL ("d.down.rate", [](auto* download, auto) { return download->info()->down_rate()->rate(); }); - CMD2_DL ("d.down.total", [](auto* download, auto) { return download->info()->down_rate()->total(); }); - CMD2_DL ("d.skip.rate", [](auto* download, auto) { return download->info()->skip_rate()->rate(); }); - CMD2_DL ("d.skip.total", [](auto* download, auto) { return download->info()->skip_rate()->total(); }); + CMD2_DL ("d.up.rate", [](auto* download, auto) { return download->info()->up_rate()->rate(); }); + CMD2_DL ("d.up.total", [](auto* download, auto) { return download->info()->up_rate()->total(); }); + CMD2_DL ("d.down.rate", [](auto* download, auto) { return download->info()->down_rate()->rate(); }); + CMD2_DL ("d.down.total", [](auto* download, auto) { return download->info()->down_rate()->total(); }); + CMD2_DL ("d.skip.rate", [](auto* download, auto) { return download->info()->skip_rate()->rate(); }); + CMD2_DL ("d.skip.total", [](auto* download, auto) { return download->info()->skip_rate()->total(); }); CMD2_DL ("d.peer_exchange", [](auto* download, auto) { return download->info()->is_pex_enabled(); }); CMD2_DL_VALUE_V ("d.peer_exchange.set", [](auto* download, auto value) { download->download()->set_pex_enabled(value); }); @@ -900,14 +912,26 @@ initialize_command_download() { rpc::rpc.mark_safe("d.local_id_html"); rpc::rpc.mark_safe("d.bitfield"); rpc::rpc.mark_safe("d.base_path"); + rpc::rpc.mark_safe("d.base_path.hex"); rpc::rpc.mark_safe("d.base_path.base64"); + rpc::rpc.mark_safe("d.base_path.base64_as_binary"); + rpc::rpc.mark_safe("d.base_path.as_binary"); rpc::rpc.mark_safe("d.base_path.or_base64"); + rpc::rpc.mark_safe("d.base_path.or_as_binary"); rpc::rpc.mark_safe("d.base_filename"); + rpc::rpc.mark_safe("d.base_filename.hex"); rpc::rpc.mark_safe("d.base_filename.base64"); + rpc::rpc.mark_safe("d.base_filename.base64_as_binary"); + rpc::rpc.mark_safe("d.base_filename.as_binary"); rpc::rpc.mark_safe("d.base_filename.or_base64"); + rpc::rpc.mark_safe("d.base_filename.or_as_binary"); rpc::rpc.mark_safe("d.name"); + rpc::rpc.mark_safe("d.name.hex"); rpc::rpc.mark_safe("d.name.base64"); + rpc::rpc.mark_safe("d.name.base64_as_binary"); + rpc::rpc.mark_safe("d.name.as_binary"); rpc::rpc.mark_safe("d.name.or_base64"); + rpc::rpc.mark_safe("d.name.or_as_binary"); rpc::rpc.mark_safe("d.directory"); rpc::rpc.mark_safe("d.directory_base"); rpc::rpc.mark_safe("d.creation_date"); diff --git a/src/command_file.cc b/src/command_file.cc index 878c97e6..a72c1e6f 100644 --- a/src/command_file.cc +++ b/src/command_file.cc @@ -34,34 +34,22 @@ apply_f_path(torrent::File* file) { } torrent::Object -apply_f_path_components(torrent::File* file) { +apply_f_path_components(torrent::File* file, int output_type) { auto result_raw = torrent::Object::create_list(); auto& result = result_raw.as_list(); - for (const auto& itr : *file->path()) - result.push_back(itr.str()); - - return result_raw; -} - -torrent::Object -apply_f_path_components_base64(torrent::File* file) { - auto result_raw = torrent::Object::create_list(); - auto& result = result_raw.as_list(); - - for (const auto& itr : *file->path()) - result.push_back(itr.object_base64()); - - return result_raw; -} - -torrent::Object -apply_f_path_components_or_base64(torrent::File* file) { - auto result_raw = torrent::Object::create_list(); - auto& result = result_raw.as_list(); - - for (const auto& itr : *file->path()) - result.push_back(itr.object_utf8_or_base64()); + for (const auto& itr : *file->path()) { + switch (output_type) { + case 0: result.push_back(itr.str()); break; + case 1: result.push_back(itr.object_hex()); break; + case 2: result.push_back(itr.object_base64()); break; + case 3: result.push_back(itr.object_base64_as_binary()); break; + case 4: result.push_back(itr.object_as_binary()); break; + case 5: result.push_back(itr.object_utf8_or_base64()); break; + case 6: result.push_back(itr.object_utf8_or_as_binary()); break; + default: throw torrent::input_error("apply_f_path_components(): invalid output type"); + }; + } return result_raw; } @@ -102,25 +90,32 @@ initialize_command_file() { CMD2_FILE_V("f.prioritize_last.enable", std::bind(&torrent::File::set_flags, std::placeholders::_1, torrent::File::flag_prioritize_last)); CMD2_FILE_V("f.prioritize_last.disable", std::bind(&torrent::File::unset_flags, std::placeholders::_1, torrent::File::flag_prioritize_last)); - CMD2_FILE("f.size_bytes", std::bind(&torrent::File::size_bytes, std::placeholders::_1)); - CMD2_FILE("f.size_chunks", std::bind(&torrent::File::size_chunks, std::placeholders::_1)); - CMD2_FILE("f.completed_chunks", std::bind(&torrent::File::completed_chunks, std::placeholders::_1)); + CMD2_FILE("f.size_bytes", [](auto* file, auto) { return file->size_bytes(); }); + CMD2_FILE("f.size_chunks", [](auto* file, auto) { return file->size_chunks(); }); + CMD2_FILE("f.completed_chunks", [](auto* file, auto) { return file->completed_chunks(); }); - CMD2_FILE("f.offset", std::bind(&torrent::File::offset, std::placeholders::_1)); - CMD2_FILE("f.range_first", std::bind(&torrent::File::range_first, std::placeholders::_1)); - CMD2_FILE("f.range_second", std::bind(&torrent::File::range_second, std::placeholders::_1)); + CMD2_FILE("f.offset", [](auto* file, auto) { return file->offset(); }); + CMD2_FILE("f.range_first", [](auto* file, auto) { return file->range_first(); }); + CMD2_FILE("f.range_second", [](auto* file, auto) { return file->range_second(); }); - CMD2_FILE("f.priority", std::bind(&torrent::File::priority, std::placeholders::_1)); - CMD2_FILE_VALUE_V("f.priority.set", std::bind(&apply_f_set_priority, std::placeholders::_1, std::placeholders::_2)); + CMD2_FILE("f.priority", [](auto* file, auto) { return file->priority(); }); + CMD2_FILE_VALUE_V("f.priority.set", [](auto* file, auto v) { apply_f_set_priority(file, v); }); - CMD2_FILE("f.path", [](auto* file, auto) { return apply_f_path(file); }); - CMD2_FILE("f.path_components", [](auto* file, auto) { return apply_f_path_components(file); }); - CMD2_FILE("f.path_components.base64", [](auto* file, auto) { return apply_f_path_components_base64(file); }); - CMD2_FILE("f.path_components.or_base64", [](auto* file, auto) { return apply_f_path_components_or_base64(file); }); - CMD2_FILE("f.path_depth", [](auto* file, auto) { return apply_f_path_depth(file); }); - CMD2_FILE("f.frozen_path", [](auto* file, auto) { return file->frozen_path().str(); }); - CMD2_FILE("f.frozen_path.base64", [](auto* file, auto) { return file->frozen_path().object_base64(); }); - CMD2_FILE("f.frozen_path.or_base64", [](auto* file, auto) { return file->frozen_path().object_utf8_or_base64(); }); + CMD2_FILE("f.path", [](auto* file, auto) { return apply_f_path(file); }); + CMD2_FILE("f.path_components", [](auto* file, auto) { return apply_f_path_components(file, 0); }); + CMD2_FILE("f.path_components.hex", [](auto* file, auto) { return apply_f_path_components(file, 1); }); + CMD2_FILE("f.path_components.base64", [](auto* file, auto) { return apply_f_path_components(file, 2); }); + CMD2_FILE("f.path_components.base64_as_binary", [](auto* file, auto) { return apply_f_path_components(file, 3); }); + CMD2_FILE("f.path_components.as_binary", [](auto* file, auto) { return apply_f_path_components(file, 4); }); + CMD2_FILE("f.path_components.or_base64", [](auto* file, auto) { return apply_f_path_components(file, 5); }); + CMD2_FILE("f.path_components.or_as_binary", [](auto* file, auto) { return apply_f_path_components(file, 6); }); + CMD2_FILE("f.path_depth", [](auto* file, auto) { return apply_f_path_depth(file); }); + CMD2_FILE("f.frozen_path", [](auto* file, auto) { return file->frozen_path().str(); }); + CMD2_FILE("f.frozen_path.hex", [](auto* file, auto) { return file->frozen_path().object_hex(); }); + CMD2_FILE("f.frozen_path.base64", [](auto* file, auto) { return file->frozen_path().object_base64(); }); + CMD2_FILE("f.frozen_path.as_binary", [](auto* file, auto) { return file->frozen_path().object_as_binary(); }); + CMD2_FILE("f.frozen_path.or_base64", [](auto* file, auto) { return file->frozen_path().object_utf8_or_base64(); }); + CMD2_FILE("f.frozen_path.or_as_binary", [](auto* file, auto) { return file->frozen_path().object_utf8_or_as_binary(); }); CMD2_FILE("f.match_depth_prev", std::bind(&torrent::File::match_depth_prev, std::placeholders::_1)); CMD2_FILE("f.match_depth_next", std::bind(&torrent::File::match_depth_next, std::placeholders::_1)); @@ -134,8 +129,12 @@ initialize_command_file() { rpc::rpc.mark_safe("f.path_components"); rpc::rpc.mark_safe("f.path_depth"); rpc::rpc.mark_safe("f.frozen_path"); + rpc::rpc.mark_safe("f.frozen_path.hex"); rpc::rpc.mark_safe("f.frozen_path.base64"); + rpc::rpc.mark_safe("f.frozen_path.base64_as_binary"); + rpc::rpc.mark_safe("f.frozen_path.as_binary"); rpc::rpc.mark_safe("f.frozen_path.or_base64"); + rpc::rpc.mark_safe("f.frozen_path.or_as_binary"); rpc::rpc.mark_safe("f.offset"); rpc::rpc.mark_safe("f.size_bytes"); rpc::rpc.mark_safe("f.size_chunks"); diff --git a/src/rpc/jsonrpc.cc b/src/rpc/jsonrpc.cc index a18ffaf7..c7274de9 100644 --- a/src/rpc/jsonrpc.cc +++ b/src/rpc/jsonrpc.cc @@ -16,6 +16,7 @@ #include "torrent/exceptions.h" #include "torrent/object.h" #include "utils/functional.h" +#include "utils/base64.h" namespace rpc { @@ -62,18 +63,28 @@ json_to_object(const json& value) { } } +// Note: Only throw rpc_errors here. + json -object_to_json(const torrent::Object& object) noexcept { +object_to_json(const torrent::Object& object) { switch (object.type()) { case torrent::Object::TYPE_VALUE: return object.as_value(); case torrent::Object::TYPE_STRING: - if (object.flags() & torrent::Object::flag_base64) { - std::vector binary_data; - torrent::utils::transform_from_hex(object.as_string(), binary_data); + if (object.flags() & torrent::Object::flag_as_binary) { - return json::binary(binary_data); + // We should optimize our imported json library to support copying base64 strings directly. + if (object.flags() & torrent::Object::flag_base64) { + auto binary_data = utils::base64_to_vector_unsafe(object.as_string()); + + if (!binary_data.has_value()) + throw rpc_error(JSONRPC_INTERNAL_ERROR, "invalid base64 string in base64-as-binary object"); + + return json::binary(*binary_data); + } + + return json::binary({object.as_string().begin(), object.as_string().end()}); } return object.as_string(); diff --git a/src/rpc/xmlrpc_c.cc b/src/rpc/xmlrpc_c.cc index 8253347c..df197634 100644 --- a/src/rpc/xmlrpc_c.cc +++ b/src/rpc/xmlrpc_c.cc @@ -278,8 +278,20 @@ object_to_xmlrpc(xmlrpc_env* env, const torrent::Object& object) { case torrent::Object::TYPE_STRING: { - if (object.flags() & torrent::Object::flag_base64) + if (object.flags() & torrent::Object::flag_binary) { + + // This causes decode-and-reencode for base64, as XMLRPC-C doesn't allow us to pass base64 strings. + if (object.flags() & torrent::Object::flag_base64) { + auto binary_data = utils::base64_to_vector_unsafe(object.as_string()); + + if (!binary_data.has_value()) + throw torrent::input_error("invalid base64 string in base64-as-binary object"); + + return xmlrpc_base64_new(env, (const char*)binary_data->data(), binary_data->size()); + } + return xmlrpc_base64_new(env, object.as_string().c_str(), object.as_string().size()); + } #ifdef XMLRPC_HAVE_I8 // The versions that support I8 do implicit utf-8 validation. diff --git a/src/rpc/xmlrpc_tinyxml2.cc b/src/rpc/xmlrpc_tinyxml2.cc index 52f575a5..f8d450a3 100644 --- a/src/rpc/xmlrpc_tinyxml2.cc +++ b/src/rpc/xmlrpc_tinyxml2.cc @@ -152,9 +152,17 @@ void print_object_xml(const torrent::Object& obj, tinyxml2::XMLPrinter* printer) { switch (obj.type()) { case torrent::Object::TYPE_STRING: - if (obj.flags() & torrent::Object::flag_base64) { + if (obj.flags() & torrent::Object::flag_as_binary) { + // It is optimal for tinyxml2 to pass base64 strings directly from torrent::string_utf8 sources. + if (obj.flags() & torrent::Object::flag_base64) { + printer->OpenElement("base64", true); + printer->PushText(obj.as_string().c_str()); + printer->CloseElement(true); + break; + } + printer->OpenElement("base64", true); - printer->PushText(obj.as_string().c_str()); + printer->PushText(utils::openssl_base64_encode(obj.as_string()).c_str()); printer->CloseElement(true); break; } @@ -169,6 +177,7 @@ print_object_xml(const torrent::Object& obj, tinyxml2::XMLPrinter* printer) { printer->PushText(std::to_string(obj.as_value()).c_str()); printer->CloseElement(true); break; + case torrent::Object::TYPE_LIST: printer->OpenElement("array", true); printer->OpenElement("data", true); diff --git a/src/utils/base64.cc b/src/utils/base64.cc index 54fc91b0..45a36233 100644 --- a/src/utils/base64.cc +++ b/src/utils/base64.cc @@ -1,6 +1,7 @@ #include "base64.h" #include +#include namespace utils { @@ -70,4 +71,51 @@ decode_base64(const std::string& input) { } return decodedBytes; } + +// TODO: Move to torrent::utils. + +std::optional> +base64_to_vector_unsafe(const std::string& src) { + if (src.empty()) + return std::vector{}; + + if (src.length() % 4) + return std::nullopt; + + std::vector bytes((src.length() * 3) / 4); + + int decoded_len = EVP_DecodeBlock(bytes.data(), reinterpret_cast(src.data()), src.length()); + + if (decoded_len <= 0) + return std::nullopt; + + if (src.back() == '=') + decoded_len--; + + if (src.length() > 1 && src[src.length() - 2] == '=') + decoded_len--; + + // If the input contains extra padding characters, this could cause negative decoded_len. + if (decoded_len < 0) + return std::nullopt; + + bytes.resize(decoded_len); + + return bytes; +} + +std::string +openssl_base64_encode(const std::string& src) { + if (src.empty()) return {}; + + std::string result((4 * ((src.size() + 2) / 3)), '\0'); + + int actual_length = EVP_EncodeBlock(reinterpret_cast(result.data()), + reinterpret_cast(src.data()), + src.size()); + + result.resize(actual_length); + return result; +} + } // namespace utils diff --git a/src/utils/base64.h b/src/utils/base64.h index 321d8611..c880e8d1 100644 --- a/src/utils/base64.h +++ b/src/utils/base64.h @@ -1,12 +1,18 @@ #ifndef RTORRENT_UTILS_BASE64_H #define RTORRENT_UTILS_BASE64_H +#include #include - +#include #include namespace utils { +// TODO: Refactor and move to torrent/string_manip.h. +std::optional> base64_to_vector_unsafe(const std::string& src); +std::string openssl_base64_encode(const std::string& src); + +// TODO: Deprecate. std::string remove_newlines(const std::string& str); std::string decode_base64(const std::string& input);