feat: add support for adding torrents by raw hash values (#2608)
* Add support for adding torrents by raw hash values Co-authored-by: vjunk <vjunk@mail.ru>
This commit is contained in:
parent
dfe79af34c
commit
96178b1a9f
20
cli/cli.cc
20
cli/cli.cc
|
@ -18,6 +18,7 @@
|
|||
#include <libtransmission/utils.h> /* tr_wait_msec */
|
||||
#include <libtransmission/variant.h>
|
||||
#include <libtransmission/version.h>
|
||||
#include <libtransmission/web-utils.h>
|
||||
#include <libtransmission/web.h> /* tr_webRun */
|
||||
|
||||
/***
|
||||
|
@ -216,8 +217,6 @@ static char const* getConfigDir(int argc, char const** argv)
|
|||
|
||||
int tr_main(int argc, char* argv[])
|
||||
{
|
||||
tr_session* h;
|
||||
tr_ctor* ctor;
|
||||
tr_variant settings;
|
||||
char const* configDir;
|
||||
|
||||
|
@ -275,25 +274,20 @@ int tr_main(int argc, char* argv[])
|
|||
}
|
||||
}
|
||||
|
||||
h = tr_sessionInit(configDir, false, &settings);
|
||||
|
||||
ctor = tr_ctorNew(h);
|
||||
auto* const h = tr_sessionInit(configDir, false, &settings);
|
||||
auto* const ctor = tr_ctorNew(h);
|
||||
|
||||
tr_ctorSetPaused(ctor, TR_FORCE, false);
|
||||
|
||||
if (tr_sys_path_exists(torrentPath, nullptr))
|
||||
if (tr_ctorSetMetainfoFromFile(ctor, torrentPath, nullptr) || tr_ctorSetMetainfoFromMagnetLink(ctor, torrentPath, nullptr))
|
||||
{
|
||||
tr_ctorSetMetainfoFromFile(ctor, torrentPath, nullptr);
|
||||
// all good
|
||||
}
|
||||
else if (memcmp(torrentPath, "magnet:?", 8) == 0)
|
||||
{
|
||||
tr_ctorSetMetainfoFromMagnetLink(ctor, torrentPath, nullptr);
|
||||
}
|
||||
else if (memcmp(torrentPath, "http", 4) == 0)
|
||||
else if (tr_urlIsValid(torrentPath))
|
||||
{
|
||||
// fetch it
|
||||
tr_webRun(h, torrentPath, onTorrentFileDownloaded, ctor);
|
||||
waitingOnWeb = true;
|
||||
|
||||
while (waitingOnWeb)
|
||||
{
|
||||
tr_wait_msec(1000);
|
||||
|
|
|
@ -461,32 +461,30 @@ TorrentFileChooserDialog::TorrentFileChooserDialog(Gtk::Window& parent, Glib::Re
|
|||
|
||||
void TorrentUrlChooserDialog::onOpenURLResponse(int response, Glib::RefPtr<Session> const& core)
|
||||
{
|
||||
bool handled = false;
|
||||
|
||||
if (response == Gtk::RESPONSE_ACCEPT)
|
||||
{
|
||||
auto* e = static_cast<Gtk::Entry*>(get_data("url-entry"));
|
||||
auto const url = gtr_str_strip(e->get_text());
|
||||
|
||||
if (!url.empty())
|
||||
{
|
||||
handled = core->add_from_url(url);
|
||||
|
||||
if (!handled)
|
||||
{
|
||||
gtr_unrecognized_url_dialog(*this, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (response == Gtk::RESPONSE_CANCEL)
|
||||
{
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if (handled)
|
||||
if (response == Gtk::RESPONSE_CANCEL)
|
||||
{
|
||||
hide();
|
||||
}
|
||||
else if (response == Gtk::RESPONSE_ACCEPT)
|
||||
{
|
||||
auto* const e = static_cast<Gtk::Entry*>(get_data("url-entry"));
|
||||
auto const url = gtr_str_strip(e->get_text());
|
||||
|
||||
if (url.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (core->add_from_url(url))
|
||||
{
|
||||
hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
gtr_unrecognized_url_dialog(*this, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<TorrentUrlChooserDialog> TorrentUrlChooserDialog::create(Gtk::Window& parent, Glib::RefPtr<Session> const& core)
|
||||
|
|
106
gtk/Session.cc
106
gtk/Session.cc
|
@ -1128,69 +1128,47 @@ void Session::Impl::add_file_async_callback(
|
|||
|
||||
bool Session::Impl::add_file(Glib::RefPtr<Gio::File> const& file, bool do_start, bool do_prompt, bool do_notify)
|
||||
{
|
||||
bool handled = false;
|
||||
|
||||
if (auto const* const session = get_session(); session != nullptr)
|
||||
auto const* const session = get_session();
|
||||
if (session == nullptr)
|
||||
{
|
||||
tr_ctor* ctor;
|
||||
bool tried = false;
|
||||
bool loaded = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
ctor = tr_ctorNew(session);
|
||||
core_apply_defaults(ctor);
|
||||
tr_ctorSetPaused(ctor, TR_FORCE, !do_start);
|
||||
bool handled = false;
|
||||
auto* ctor = tr_ctorNew(session);
|
||||
core_apply_defaults(ctor);
|
||||
tr_ctorSetPaused(ctor, TR_FORCE, !do_start);
|
||||
|
||||
/* local files... */
|
||||
if (!tried)
|
||||
{
|
||||
auto const str = file->get_path();
|
||||
bool loaded = false;
|
||||
if (auto const path = file->get_path(); !std::empty(path))
|
||||
{
|
||||
// try to treat it as a file...
|
||||
loaded = tr_ctorSetMetainfoFromFile(ctor, path.c_str(), nullptr);
|
||||
}
|
||||
|
||||
if ((tried = !str.empty() && Glib::file_test(str, Glib::FILE_TEST_EXISTS)))
|
||||
{
|
||||
loaded = tr_ctorSetMetainfoFromFile(ctor, str.c_str(), nullptr);
|
||||
}
|
||||
}
|
||||
if (!loaded)
|
||||
{
|
||||
// try to treat it as a magnet link...
|
||||
loaded = tr_ctorSetMetainfoFromMagnetLink(ctor, file->get_uri().c_str(), nullptr);
|
||||
}
|
||||
|
||||
/* magnet links... */
|
||||
if (!tried && file->has_uri_scheme("magnet"))
|
||||
{
|
||||
/* GFile mangles the original string with /// so we have to un-mangle */
|
||||
auto const str = file->get_parse_name();
|
||||
auto const magnet = gtr_sprintf("magnet:%s", str.substr(str.find('?')));
|
||||
tried = true;
|
||||
loaded = tr_ctorSetMetainfoFromMagnetLink(ctor, magnet.c_str(), nullptr);
|
||||
}
|
||||
|
||||
/* hashcodes that we can turn into magnet links... */
|
||||
if (!tried)
|
||||
{
|
||||
auto const str = file->get_basename();
|
||||
|
||||
if (gtr_is_hex_hashcode(str))
|
||||
{
|
||||
auto const magnet = gtr_sprintf("magnet:?xt=urn:btih:%s", str);
|
||||
loaded = tr_ctorSetMetainfoFromMagnetLink(ctor, magnet.c_str(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
/* if we were able to load the metainfo, add the torrent */
|
||||
if (loaded)
|
||||
{
|
||||
handled = true;
|
||||
add_ctor(ctor, do_prompt, do_notify);
|
||||
}
|
||||
else if (file->has_uri_scheme("http") || file->has_uri_scheme("https") || file->has_uri_scheme("ftp"))
|
||||
{
|
||||
handled = true;
|
||||
inc_busy();
|
||||
file->load_contents_async([this, file, ctor, do_prompt, do_notify](auto& result)
|
||||
{ add_file_async_callback(file, result, ctor, do_prompt, do_notify); });
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_ctorFree(ctor);
|
||||
g_message(_("Skipping unknown torrent \"%s\""), file->get_parse_name().c_str());
|
||||
}
|
||||
// if we could make sense of it, add it
|
||||
if (loaded)
|
||||
{
|
||||
handled = true;
|
||||
add_ctor(ctor, do_prompt, do_notify);
|
||||
}
|
||||
else if (tr_urlIsValid(file->get_uri()))
|
||||
{
|
||||
handled = true;
|
||||
inc_busy();
|
||||
file->load_contents_async([this, file, ctor, do_prompt, do_notify](auto& result)
|
||||
{ add_file_async_callback(file, result, ctor, do_prompt, do_notify); });
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_ctorFree(ctor);
|
||||
g_message(_("Skipping unknown torrent \"%s\""), file->get_parse_name().c_str());
|
||||
}
|
||||
|
||||
return handled;
|
||||
|
@ -1203,15 +1181,13 @@ bool Session::add_from_url(Glib::ustring const& uri)
|
|||
|
||||
bool Session::Impl::add_from_url(Glib::ustring const& uri)
|
||||
{
|
||||
bool handled;
|
||||
bool const do_start = gtr_pref_flag_get(TR_KEY_start_added_torrents);
|
||||
bool const do_prompt = gtr_pref_flag_get(TR_KEY_show_options_window);
|
||||
bool const do_notify = false;
|
||||
|
||||
auto const file = Gio::File::create_for_uri(uri);
|
||||
handled = add_file(file, do_start, do_prompt, do_notify);
|
||||
torrents_added();
|
||||
auto const do_start = gtr_pref_flag_get(TR_KEY_start_added_torrents);
|
||||
auto const do_prompt = gtr_pref_flag_get(TR_KEY_show_options_window);
|
||||
auto const do_notify = false;
|
||||
|
||||
auto const handled = add_file(file, do_start, do_prompt, do_notify);
|
||||
torrents_added();
|
||||
return handled;
|
||||
}
|
||||
|
||||
|
|
64
gtk/Utils.cc
64
gtk/Utils.cc
|
@ -13,10 +13,12 @@
|
|||
#include <glibmm/i18n.h>
|
||||
|
||||
#include <libtransmission/transmission.h> /* TR_RATIO_NA, TR_RATIO_INF */
|
||||
|
||||
#include <libtransmission/error.h>
|
||||
#include <libtransmission/torrent-metainfo.h>
|
||||
#include <libtransmission/utils.h> /* tr_strratio() */
|
||||
#include <libtransmission/web-utils.h>
|
||||
#include <libtransmission/version.h> /* SHORT_VERSION_STRING */
|
||||
#include <libtransmission/web-utils.h>
|
||||
|
||||
#include "HigWorkarea.h"
|
||||
#include "Prefs.h"
|
||||
|
@ -126,40 +128,6 @@ Glib::ustring tr_strltime(time_t seconds)
|
|||
namespace
|
||||
{
|
||||
|
||||
bool gtr_is_supported_url(Glib::ustring const& str)
|
||||
{
|
||||
return !str.empty() &&
|
||||
(Glib::str_has_prefix(str, "ftp://") || Glib::str_has_prefix(str, "http://") || Glib::str_has_prefix(str, "https://"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool gtr_is_magnet_link(Glib::ustring const& str)
|
||||
{
|
||||
return !str.empty() && Glib::str_has_prefix(str, "magnet:?");
|
||||
}
|
||||
|
||||
bool gtr_is_hex_hashcode(std::string const& str)
|
||||
{
|
||||
if (str.size() != 40)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 40; ++i)
|
||||
{
|
||||
if (!isxdigit(str[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
Gtk::Window* getWindow(Gtk::Widget* w)
|
||||
{
|
||||
if (w == nullptr)
|
||||
|
@ -476,8 +444,6 @@ void gtr_dialog_set_content(Gtk::Dialog& dialog, Gtk::Widget& content)
|
|||
|
||||
void gtr_unrecognized_url_dialog(Gtk::Widget& parent, Glib::ustring const& url)
|
||||
{
|
||||
char const* xt = "xt=urn:btih";
|
||||
|
||||
auto* window = getWindow(&parent);
|
||||
|
||||
Glib::ustring gstr;
|
||||
|
@ -492,13 +458,10 @@ void gtr_unrecognized_url_dialog(Gtk::Widget& parent, Glib::ustring const& url)
|
|||
|
||||
gstr += gtr_sprintf(_("Transmission doesn't know how to use \"%s\""), url);
|
||||
|
||||
if (gtr_is_magnet_link(url) && url.find(xt) == Glib::ustring::npos)
|
||||
if (tr_magnet_metainfo{}.parseMagnet(url.raw()))
|
||||
{
|
||||
gstr += "\n \n";
|
||||
gstr += gtr_sprintf(
|
||||
_("This magnet link appears to be intended for something other than BitTorrent. "
|
||||
"BitTorrent magnet links have a section containing \"%s\"."),
|
||||
xt);
|
||||
gstr += _("This magnet link appears to be intended for something other than BitTorrent.");
|
||||
}
|
||||
|
||||
w->set_secondary_text(gstr);
|
||||
|
@ -510,19 +473,16 @@ void gtr_unrecognized_url_dialog(Gtk::Widget& parent, Glib::ustring const& url)
|
|||
****
|
||||
***/
|
||||
|
||||
void gtr_paste_clipboard_url_into_entry(Gtk::Entry& e)
|
||||
void gtr_paste_clipboard_url_into_entry(Gtk::Entry& entry)
|
||||
{
|
||||
Glib::ustring const text[] = {
|
||||
gtr_str_strip(Gtk::Clipboard::get(GDK_SELECTION_PRIMARY)->wait_for_text()),
|
||||
gtr_str_strip(Gtk::Clipboard::get(GDK_SELECTION_CLIPBOARD)->wait_for_text()),
|
||||
};
|
||||
|
||||
for (auto const& s : text)
|
||||
for (auto const& str : { Gtk::Clipboard::get(GDK_SELECTION_PRIMARY)->wait_for_text(),
|
||||
Gtk::Clipboard::get(GDK_SELECTION_CLIPBOARD)->wait_for_text() })
|
||||
{
|
||||
if (!s.empty() && (gtr_is_supported_url(s) || gtr_is_magnet_link(s) || gtr_is_hex_hashcode(s)))
|
||||
auto const sv = tr_strvStrip(str.raw());
|
||||
if (!sv.empty() && (tr_urlIsValid(sv) || tr_magnet_metainfo{}.parseMagnet(sv)))
|
||||
{
|
||||
e.set_text(s);
|
||||
break;
|
||||
entry.set_text(str);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,14 +61,6 @@ Glib::ustring tr_strltime(time_t secs);
|
|||
****
|
||||
***/
|
||||
|
||||
bool gtr_is_magnet_link(Glib::ustring const& str);
|
||||
|
||||
bool gtr_is_hex_hashcode(std::string const& str);
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
void gtr_open_uri(Glib::ustring const& uri);
|
||||
|
||||
void gtr_open_file(std::string const& path);
|
||||
|
|
|
@ -223,9 +223,17 @@ static void tr_hex_to_binary(char const* input, void* voutput, size_t byte_lengt
|
|||
}
|
||||
}
|
||||
|
||||
tr_sha1_digest_t tr_sha1_from_string(std::string_view hex)
|
||||
std::optional<tr_sha1_digest_t> tr_sha1_from_string(std::string_view hex)
|
||||
{
|
||||
TR_ASSERT(std::size(hex) == TR_SHA1_DIGEST_STRLEN);
|
||||
if (std::size(hex) != TR_SHA1_DIGEST_STRLEN)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!std::all_of(std::begin(hex), std::end(hex), [](unsigned char ch) { return isxdigit(ch); }))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto digest = tr_sha1_digest_t{};
|
||||
tr_hex_to_binary(std::data(hex), std::data(digest), std::size(digest));
|
||||
|
|
|
@ -194,7 +194,7 @@ std::string tr_sha1_to_string(tr_sha1_digest_t const&);
|
|||
/**
|
||||
* @brief Generate a sha1 digest from a hex string.
|
||||
*/
|
||||
tr_sha1_digest_t tr_sha1_from_string(std::string_view hex);
|
||||
std::optional<tr_sha1_digest_t> tr_sha1_from_string(std::string_view hex);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <cctype> // isxdigit()
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
|
@ -21,10 +22,13 @@
|
|||
|
||||
using namespace std::literals;
|
||||
|
||||
/* this base32 code converted from code by Robert Kaye and Gordon Mohr
|
||||
* and is public domain. see http://bitzi.com/publicdomain for more info */
|
||||
namespace
|
||||
{
|
||||
|
||||
auto constexpr Base32HashStrLen = size_t{ 32 };
|
||||
|
||||
/* this base32 code converted from code by Robert Kaye and Gordon Mohr
|
||||
* and is public domain. see http://bitzi.com/publicdomain for more info */
|
||||
namespace bitzi
|
||||
{
|
||||
|
||||
|
@ -43,8 +47,6 @@ auto constexpr Base32Lookup = std::array<int, 80>{
|
|||
|
||||
void base32_to_sha1(uint8_t* out, char const* in, size_t const inlen)
|
||||
{
|
||||
TR_ASSERT(inlen == 32);
|
||||
|
||||
size_t const outlen = 20;
|
||||
|
||||
memset(out, 0, 20);
|
||||
|
@ -105,6 +107,43 @@ void base32_to_sha1(uint8_t* out, char const* in, size_t const inlen)
|
|||
}
|
||||
|
||||
} // namespace bitzi
|
||||
|
||||
std::optional<tr_sha1_digest_t> parseBase32Hash(std::string_view sv)
|
||||
{
|
||||
if (std::size(sv) != Base32HashStrLen)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!std::all_of(std::begin(sv), std::end(sv), [](unsigned char ch) { return bitzi::Base32Lookup[ch] - '0' != 0xFF; }))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto digest = tr_sha1_digest_t{};
|
||||
bitzi::base32_to_sha1(reinterpret_cast<uint8_t*>(std::data(digest)), std::data(sv), std::size(sv));
|
||||
return digest;
|
||||
}
|
||||
|
||||
std::optional<tr_sha1_digest_t> parseHash(std::string_view sv)
|
||||
{
|
||||
// http://bittorrent.org/beps/bep_0009.html
|
||||
// Is the info-hash hex encoded, for a total of 40 characters.
|
||||
// For compatability with existing links in the wild, clients
|
||||
// should also support the 32 character base32 encoded info-hash.
|
||||
|
||||
if (auto const hash = tr_sha1_from_string(sv); hash)
|
||||
{
|
||||
return hash;
|
||||
}
|
||||
if (auto const hash = parseBase32Hash(sv); hash)
|
||||
{
|
||||
return hash;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/***
|
||||
|
@ -141,6 +180,13 @@ std::string tr_magnet_metainfo::magnet() const
|
|||
|
||||
bool tr_magnet_metainfo::parseMagnet(std::string_view magnet_link, tr_error** error)
|
||||
{
|
||||
magnet_link = tr_strvStrip(magnet_link);
|
||||
|
||||
if (auto const hash = parseHash(magnet_link); hash)
|
||||
{
|
||||
return parseMagnet(tr_strvJoin("magnet:?xt=urn:btih:", tr_sha1_to_string(*hash)));
|
||||
}
|
||||
|
||||
auto const parsed = tr_urlParse(magnet_link);
|
||||
if (!parsed || parsed->scheme != "magnet"sv)
|
||||
{
|
||||
|
@ -148,7 +194,7 @@ bool tr_magnet_metainfo::parseMagnet(std::string_view magnet_link, tr_error** er
|
|||
return false;
|
||||
}
|
||||
|
||||
bool got_checksum = false;
|
||||
bool got_hash = false;
|
||||
for (auto const& [key, value] : tr_url_query_view{ parsed->query })
|
||||
{
|
||||
if (key == "dn"sv)
|
||||
|
@ -174,24 +220,10 @@ bool tr_magnet_metainfo::parseMagnet(std::string_view magnet_link, tr_error** er
|
|||
auto constexpr ValPrefix = "urn:btih:"sv;
|
||||
if (tr_strvStartsWith(value, ValPrefix))
|
||||
{
|
||||
auto const hash = value.substr(std::size(ValPrefix));
|
||||
switch (std::size(hash))
|
||||
if (auto const hash = parseHash(value.substr(std::size(ValPrefix))); hash)
|
||||
{
|
||||
case TR_SHA1_DIGEST_STRLEN:
|
||||
this->info_hash_ = tr_sha1_from_string(hash);
|
||||
got_checksum = true;
|
||||
break;
|
||||
|
||||
case 32:
|
||||
bitzi::base32_to_sha1(
|
||||
reinterpret_cast<uint8_t*>(std::data(this->info_hash_)),
|
||||
std::data(hash),
|
||||
std::size(hash));
|
||||
got_checksum = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
this->info_hash_ = *hash;
|
||||
got_hash = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -199,5 +231,10 @@ bool tr_magnet_metainfo::parseMagnet(std::string_view magnet_link, tr_error** er
|
|||
|
||||
info_hash_str_ = tr_sha1_to_string(this->infoHash());
|
||||
|
||||
return got_checksum;
|
||||
if (std::empty(name()))
|
||||
{
|
||||
this->setName(info_hash_str_);
|
||||
}
|
||||
|
||||
return got_hash;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ class tr_magnet_metainfo
|
|||
{
|
||||
public:
|
||||
bool parseMagnet(std::string_view magnet_link, tr_error** error = nullptr);
|
||||
|
||||
std::string magnet() const;
|
||||
|
||||
auto const& infoHash() const
|
||||
|
|
|
@ -1695,14 +1695,10 @@ static char const* torrentAdd(tr_session* session, tr_variant* args_in, tr_varia
|
|||
// these two tr_ctorSet*() functions require zero-terminated strings
|
||||
auto const filename_sz = std::string{ filename };
|
||||
|
||||
if (tr_strvStartsWith(filename, "magnet:?"sv))
|
||||
if (!tr_ctorSetMetainfoFromFile(ctor, filename_sz.c_str(), nullptr))
|
||||
{
|
||||
tr_ctorSetMetainfoFromMagnetLink(ctor, filename_sz.c_str(), nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_ctorSetMetainfoFromFile(ctor, filename_sz.c_str(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
addTorrentImpl(idle_data, ctor);
|
||||
|
|
|
@ -2783,7 +2783,6 @@ void tr_sessionRemoveTorrent(tr_session* session, tr_torrent* tor)
|
|||
|
||||
tr_torrent* tr_session::getTorrent(std::string_view info_dict_hash_string)
|
||||
{
|
||||
return std::size(info_dict_hash_string) == TR_SHA1_DIGEST_STRLEN ?
|
||||
this->getTorrent(tr_sha1_from_string(info_dict_hash_string)) :
|
||||
nullptr;
|
||||
auto const info_hash = tr_sha1_from_string(info_dict_hash_string);
|
||||
return info_hash ? this->getTorrent(*info_hash) : nullptr;
|
||||
}
|
||||
|
|
|
@ -33,43 +33,34 @@ QString getNameFromMetainfo(QByteArray const& benc)
|
|||
|
||||
int AddData::set(QString const& key)
|
||||
{
|
||||
if (Utils::isMagnetLink(key))
|
||||
if (auto const key_std = key.toStdString(); tr_urlIsValid(key_std))
|
||||
{
|
||||
magnet = key;
|
||||
type = MAGNET;
|
||||
}
|
||||
else if (Utils::isUriWithSupportedScheme(key))
|
||||
{
|
||||
url = key;
|
||||
this->url = key;
|
||||
type = URL;
|
||||
}
|
||||
else if (QFile(key).exists())
|
||||
{
|
||||
filename = QDir::fromNativeSeparators(key);
|
||||
this->filename = QDir::fromNativeSeparators(key);
|
||||
type = FILENAME;
|
||||
|
||||
QFile file(key);
|
||||
auto file = QFile{ key };
|
||||
file.open(QIODevice::ReadOnly);
|
||||
metainfo = file.readAll();
|
||||
this->metainfo = file.readAll();
|
||||
file.close();
|
||||
}
|
||||
else if (Utils::isHexHashcode(key))
|
||||
else if (tr_magnet_metainfo{}.parseMagnet(key_std))
|
||||
{
|
||||
magnet = QStringLiteral("magnet:?xt=urn:btih:") + key;
|
||||
type = MAGNET;
|
||||
this->magnet = key;
|
||||
this->type = MAGNET;
|
||||
}
|
||||
else if (auto const raw = QByteArray::fromBase64(key.toUtf8()); !raw.isEmpty())
|
||||
{
|
||||
this->metainfo.append(raw);
|
||||
this->type = METAINFO;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto raw = QByteArray::fromBase64(key.toUtf8());
|
||||
if (!raw.isEmpty())
|
||||
{
|
||||
metainfo.append(raw);
|
||||
type = METAINFO;
|
||||
}
|
||||
else
|
||||
{
|
||||
type = NONE;
|
||||
}
|
||||
this->type = NONE;
|
||||
}
|
||||
|
||||
return type;
|
||||
|
|
|
@ -1545,7 +1545,7 @@ void MainWindow::dragEnterEvent(QDragEnterEvent* event)
|
|||
|
||||
if (mime->hasFormat(QStringLiteral("application/x-bittorrent")) || mime->hasUrls() ||
|
||||
mime->text().trimmed().endsWith(QStringLiteral(".torrent"), Qt::CaseInsensitive) ||
|
||||
mime->text().startsWith(QStringLiteral("magnet:"), Qt::CaseInsensitive))
|
||||
tr_magnet_metainfo{}.parseMagnet(mime->text().toStdString()))
|
||||
{
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
|
@ -1591,19 +1591,17 @@ bool MainWindow::event(QEvent* e)
|
|||
}
|
||||
|
||||
if (auto const text = QGuiApplication::clipboard()->text().trimmed();
|
||||
text.endsWith(QStringLiteral(".torrent"), Qt::CaseInsensitive) ||
|
||||
text.startsWith(QStringLiteral("magnet:"), Qt::CaseInsensitive))
|
||||
text.endsWith(QStringLiteral(".torrent"), Qt::CaseInsensitive) || tr_magnet_metainfo{}.parseMagnet(text.toStdString()))
|
||||
{
|
||||
for (QString const& entry : text.split(QLatin1Char('\n')))
|
||||
for (auto const& entry : text.split(QLatin1Char('\n')))
|
||||
{
|
||||
QString key = entry.trimmed();
|
||||
|
||||
auto key = entry.trimmed();
|
||||
if (key.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (QUrl const url(key); url.isLocalFile())
|
||||
if (auto const url = QUrl{ key }; url.isLocalFile())
|
||||
{
|
||||
key = url.toLocalFile();
|
||||
}
|
||||
|
|
10
qt/Torrent.h
10
qt/Torrent.h
|
@ -121,12 +121,18 @@ public:
|
|||
|
||||
explicit TorrentHash(char const* str)
|
||||
{
|
||||
data_ = tr_sha1_from_string(str != nullptr ? str : "");
|
||||
if (auto const hash = tr_sha1_from_string(str != nullptr ? str : ""); hash)
|
||||
{
|
||||
data_ = *hash;
|
||||
}
|
||||
}
|
||||
|
||||
explicit TorrentHash(QString const& str)
|
||||
{
|
||||
data_ = tr_sha1_from_string(str.toStdString());
|
||||
if (auto const hash = tr_sha1_from_string(str.toStdString()); hash)
|
||||
{
|
||||
data_ = *hash;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(TorrentHash const& that) const
|
||||
|
|
33
qt/Utils.h
33
qt/Utils.h
|
@ -74,37 +74,4 @@ public:
|
|||
dialog->activateWindow();
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// URLs
|
||||
///
|
||||
|
||||
static bool isMagnetLink(QString const& s)
|
||||
{
|
||||
return s.startsWith(QStringLiteral("magnet:?"));
|
||||
}
|
||||
|
||||
static bool isHexHashcode(QString const& s)
|
||||
{
|
||||
if (s.length() != 40)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto const& ch : s)
|
||||
{
|
||||
if (!isxdigit(ch.unicode()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isUriWithSupportedScheme(QString const& s)
|
||||
{
|
||||
return s.startsWith(QStringLiteral("ftp://")) || s.startsWith(QStringLiteral("http://")) ||
|
||||
s.startsWith(QStringLiteral("https://"));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -174,19 +174,26 @@ TEST(Crypto, ssha1)
|
|||
EXPECT_TRUE(tr_ssha1_matches("{d209a21d3bc4f8fc4f8faf347e69f3def597eb170pySy4ai1ZPMjeU1", "test"));
|
||||
}
|
||||
|
||||
TEST(Crypto, hex)
|
||||
TEST(Crypto, sha1FromString)
|
||||
{
|
||||
auto constexpr Hex = std::array<std::string_view, 2>{
|
||||
"a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"sv,
|
||||
"d209a21d3bc4f8fc4f8faf347e69f3def597eb17"sv,
|
||||
};
|
||||
// bad lengths
|
||||
EXPECT_FALSE(tr_sha1_from_string(""));
|
||||
EXPECT_FALSE(tr_sha1_from_string("a94a8fe5ccb19ba61c4c0873d391e987982fbbd"sv));
|
||||
EXPECT_FALSE(tr_sha1_from_string("a94a8fe5ccb19ba61c4c0873d391e987982fbbd33"sv));
|
||||
// nonhex
|
||||
EXPECT_FALSE(tr_sha1_from_string("a94a8fe5ccb19ba61c4cz873d391e987982fbbd3"sv));
|
||||
EXPECT_FALSE(tr_sha1_from_string("a94a8fe5ccb19 61c4c0873d391e987982fbbd3"sv));
|
||||
|
||||
for (auto const& hex : Hex)
|
||||
{
|
||||
auto const digest = tr_sha1_from_string(hex);
|
||||
auto const str = tr_sha1_to_string(digest);
|
||||
EXPECT_EQ(hex, str);
|
||||
}
|
||||
// lowecase hex
|
||||
auto const baseline = "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"sv;
|
||||
auto const lc = tr_sha1_from_string(baseline);
|
||||
EXPECT_TRUE(lc);
|
||||
EXPECT_EQ(baseline, tr_sha1_to_string(*lc));
|
||||
|
||||
// uppercase hex should yield the same result
|
||||
auto const uc = tr_sha1_from_string(tr_strupper(baseline));
|
||||
EXPECT_TRUE(uc);
|
||||
EXPECT_EQ(*lc, *uc);
|
||||
}
|
||||
|
||||
TEST(Crypto, random)
|
||||
|
|
|
@ -78,4 +78,14 @@ TEST(MagnetMetainfo, magnetParse)
|
|||
EXPECT_EQ("Display Name"sv, mm.name());
|
||||
EXPECT_EQ(ExpectedHash, mm.infoHash());
|
||||
}
|
||||
|
||||
for (auto const& uri : { "2I2UAEFDZJFN4W3UE65QSOTCUOEZ744B"sv, "d2354010a3ca4ade5b7427bb093a62a3899ff381"sv })
|
||||
{
|
||||
auto mm = tr_magnet_metainfo{};
|
||||
|
||||
EXPECT_TRUE(mm.parseMagnet(uri));
|
||||
EXPECT_EQ(0, std::size(mm.announceList()));
|
||||
EXPECT_EQ(0, mm.webseedCount());
|
||||
EXPECT_EQ(ExpectedHash, mm.infoHash());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue