From a9284c0a6b07e6f674d1f4ce072eb42aeafef1ec Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 11 Jan 2022 08:28:14 -0600 Subject: [PATCH] fix: magnet link regression (#2390) --- libtransmission/torrent-ctor.cc | 2 +- libtransmission/torrent-magnet.cc | 154 ++++++++++++++++++++++-------- libtransmission/torrent-magnet.h | 2 + libtransmission/torrent.cc | 37 +++++-- libtransmission/transmission.h | 7 ++ 5 files changed, 151 insertions(+), 51 deletions(-) diff --git a/libtransmission/torrent-ctor.cc b/libtransmission/torrent-ctor.cc index 39f2ee5bc..b8f25f978 100644 --- a/libtransmission/torrent-ctor.cc +++ b/libtransmission/torrent-ctor.cc @@ -298,7 +298,7 @@ bool tr_ctorGetIncompleteDir(tr_ctor const* ctor, char const** setme) tr_torrent_metainfo const* tr_ctorGetMetainfo(tr_ctor const* ctor) { - return !std::empty(ctor->metainfo) ? &ctor->metainfo : nullptr; + return !std::empty(ctor->metainfo.infoHashString()) ? &ctor->metainfo : nullptr; } tr_session* tr_ctorGetSession(tr_ctor const* ctor) diff --git a/libtransmission/torrent-magnet.cc b/libtransmission/torrent-magnet.cc index 029f307ba..bc9b27a73 100644 --- a/libtransmission/torrent-magnet.cc +++ b/libtransmission/torrent-magnet.cc @@ -17,12 +17,14 @@ #include "transmission.h" #include "crypto-utils.h" /* tr_sha1() */ +#include "error.h" #include "file.h" #include "log.h" #include "magnet-metainfo.h" #include "metainfo.h" #include "resume.h" #include "torrent-magnet.h" +#include "torrent-metainfo.h" #include "torrent.h" #include "tr-assert.h" #include "utils.h" @@ -227,7 +229,80 @@ static int getPieceLength(struct tr_incomplete_metadata const* m, int piece) METADATA_PIECE_SIZE; } -static bool useNewMetainfo(tr_torrent* tor, tr_incomplete_metadata* m) +static void tr_buildMetainfoExceptInfoDict(tr_info const& tm, tr_variant* top) +{ + tr_variantInitDict(top, 6); + + if (auto const& val = tm.comment(); !std::empty(val)) + { + tr_variantDictAddStr(top, TR_KEY_comment, val); + } + + if (auto const& val = tm.source(); !std::empty(val)) + { + tr_variantDictAddStr(top, TR_KEY_source, val); + } + + if (auto const& val = tm.creator(); !std::empty(val)) + { + tr_variantDictAddStr(top, TR_KEY_created_by, val); + } + + if (auto const val = tm.dateCreated(); val != 0) + { + tr_variantDictAddInt(top, TR_KEY_creation_date, val); + } + + if (auto const& announce_list = tm.announceList(); !std::empty(announce_list)) + { + auto const n = std::size(announce_list); + if (n == 1) + { + tr_variantDictAddStr(top, TR_KEY_announce, announce_list.at(0).announce_str.sv()); + } + else + { + auto* const announce_list_variant = tr_variantDictAddList(top, TR_KEY_announce_list, n); + tr_variant* tier_variant = nullptr; + auto current_tier = std::optional{}; + for (auto const& tracker : announce_list) + { + if (!current_tier || *current_tier != tracker.tier) + { + tier_variant = tr_variantListAddList(announce_list_variant, n); + } + + tr_variantListAddStr(tier_variant, tracker.announce_str.sv()); + } + } + } + + if (auto const n_webseeds = tm.webseedCount(); n_webseeds > 0) + { + auto* const webseeds_variant = tr_variantDictAddList(top, TR_KEY_url_list, n_webseeds); + for (size_t i = 0; i < n_webseeds; ++i) + { + tr_variantListAddStr(webseeds_variant, tm.webseed(i)); + } + } + + if (tm.fileCount() == 0) + { + // local transmission extensions. + // these temporary placeholders are used for magnets until we have the info dict. + auto* const magnet_info = tr_variantDictAddDict(top, TR_KEY_magnet_info, 2); + tr_variantDictAddStr( + magnet_info, + TR_KEY_info_hash, + std::string_view{ reinterpret_cast(std::data(tm.infoHash())), std::size(tm.infoHash()) }); + if (auto const& val = tm.name(); !std::empty(val)) + { + tr_variantDictAddStr(magnet_info, TR_KEY_display_name, val); + } + } +} + +static bool useNewMetainfo(tr_torrent* tor, tr_incomplete_metadata* m, tr_error** error) { auto const sha1 = tr_sha1(std::string_view{ m->metadata, m->metadata_size }); bool const checksum_passed = sha1 && *sha1 == tor->infoHash(); @@ -237,58 +312,53 @@ static bool useNewMetainfo(tr_torrent* tor, tr_incomplete_metadata* m) } // checksum passed; now try to parse it as benc - auto infoDict = tr_variant{}; - auto const metadata_sv = std::string_view{ m->metadata, m->metadata_size }; - auto const metainfoParsed = tr_variantFromBuf(&infoDict, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, metadata_sv); - if (!metainfoParsed) + auto info_dict_v = tr_variant{}; + auto const info_dict_sv = std::string_view{ m->metadata, m->metadata_size }; + if (!tr_variantFromBuf(&info_dict_v, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, info_dict_sv, nullptr, error)) { return false; } - // yay we have bencoded metainfo... merge it into our .torrent file - auto success = bool{ false }; - tr_variant newMetainfo; - auto const path = tor->torrentFile(); + // yay we have an info dict. Let's make a .torrent file + auto top_v = tr_variant{}; + tr_buildMetainfoExceptInfoDict(tor->info, &top_v); + tr_variantMergeDicts(tr_variantDictAddDict(&top_v, TR_KEY_info, 0), &info_dict_v); + auto const benc = tr_variantToStr(&top_v, TR_VARIANT_FMT_BENC); - if (tr_variantFromFile(&newMetainfo, TR_VARIANT_PARSE_BENC, path, nullptr)) + // does this synthetic .torrent file parse? + auto parsed = tr_metainfoParse(tor->session, &top_v, error); + tr_variantFree(&top_v); + tr_variantFree(&info_dict_v); + if (!parsed) { - // remove any old .torrent and .resume files - tr_sys_path_remove(path.c_str(), nullptr); - tr_torrentRemoveResume(tor); - - dbgmsg(tor, "Saving completed metadata to \"%s\"", path.c_str()); - tr_variantMergeDicts(tr_variantDictAddDict(&newMetainfo, TR_KEY_info, 0), &infoDict); - - auto info = tr_metainfoParse(tor->session, &newMetainfo, nullptr); - success = !!info; - if (info && tr_block_info::bestBlockSize(info->info.pieceSize()) == 0) - { - tor->setLocalError(_("Magnet torrent's metadata is not usable")); - success = false; - } - - if (success) - { - // tor should keep this metainfo - tor->swapMetainfo(*info); - - // save the new .torrent file - tr_variantToFile(&newMetainfo, TR_VARIANT_FMT_BENC, tor->torrentFile()); - tr_torrentGotNewInfoDict(tor); - tor->setDirty(); - } - - tr_variantFree(&newMetainfo); + return false; } - tr_variantFree(&infoDict); + // save it + auto const& torrent_dir = tor->session->torrent_dir; + auto const filename = tr_magnet_metainfo::makeFilename( + torrent_dir, + tor->name(), + tor->infoHashString(), + tr_magnet_metainfo::BasenameFormat::Hash, + ".torrent"sv); + if (!tr_saveFile(filename, benc, error)) + { + return false; + } - return success; + // tor should keep this metainfo + tor->swapMetainfo(*parsed); + tr_torrentGotNewInfoDict(tor); + tor->setDirty(); + + return true; } static void onHaveAllMetainfo(tr_torrent* tor, tr_incomplete_metadata* m) { - if (useNewMetainfo(tor, m)) + tr_error* error = nullptr; + if (useNewMetainfo(tor, m, &error)) { incompleteMetadataFree(tor->incompleteMetadata); tor->incompleteMetadata = nullptr; @@ -308,7 +378,9 @@ static void onHaveAllMetainfo(tr_torrent* tor, tr_incomplete_metadata* m) } m->piecesNeededCount = n; - dbgmsg(tor, "metadata error; trying again. %d pieces left", n); + char const* const msg = error != nullptr && error->message != nullptr ? error->message : "unknown error"; + dbgmsg(tor, "metadata error: %s. (trying again; %d pieces left)", msg, n); + tr_error_clear(&error); } } diff --git a/libtransmission/torrent-magnet.h b/libtransmission/torrent-magnet.h index aef3dcc66..805692308 100644 --- a/libtransmission/torrent-magnet.h +++ b/libtransmission/torrent-magnet.h @@ -16,6 +16,8 @@ #include // size_t #include +#include "transmission.h" + struct tr_torrent; // defined by BEP #9 diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index 6ce072d86..9265055d2 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -797,20 +797,14 @@ tr_torrent* tr_torrentNew(tr_ctor const* ctor, tr_torrent** setme_duplicate_of) TR_ASSERT(tr_isSession(session)); // is the metainfo valid? - auto top = tr_variant{}; - if (!tr_variantFromBuf(&top, TR_VARIANT_PARSE_BENC, tr_ctorGetContents(ctor), nullptr, nullptr)) - { - return nullptr; - } - auto parsed = tr_metainfoParse(session, &top, nullptr); - tr_variantFree(&top); - if (!parsed) + auto const* metainfo = tr_ctorGetMetainfo(ctor); + if (metainfo == nullptr) { return nullptr; } // is it a duplicate? - if (auto* const duplicate_of = session->getTorrent(parsed->info.infoHash()); duplicate_of != nullptr) + if (auto* const duplicate_of = session->getTorrent(metainfo->infoHash()); duplicate_of != nullptr) { if (setme_duplicate_of != nullptr) { @@ -820,6 +814,26 @@ tr_torrent* tr_torrentNew(tr_ctor const* ctor, tr_torrent** setme_duplicate_of) return nullptr; } + // build a variant to parse + auto top_variant = tr_variant{}; + if (std::empty(*metainfo)) + { + metainfo->toVariant(&top_variant); + } + else + { + tr_variantFromBuf(&top_variant, TR_VARIANT_PARSE_BENC, tr_ctorGetContents(ctor), nullptr, nullptr); + } + + // parse the metainfo + tr_error* error = nullptr; + auto parsed = tr_metainfoParse(session, &top_variant, &error); + tr_variantFree(&top_variant); + if (!parsed) + { + return nullptr; + } + // add it auto* const tor = new tr_torrent{ parsed->info }; tor->swapMetainfo(*parsed); @@ -3144,3 +3158,8 @@ void tr_torrent::setFileSubpath(tr_file_index_t i, std::string_view subpath) { this->info.setFileSubpath(i, subpath); } + +void tr_info::setAnnounceList(tr_announce_list const& list) +{ + this->announce_list = std::make_shared(list); +} diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h index aa80a58fe..8e9e3b9f6 100644 --- a/libtransmission/transmission.h +++ b/libtransmission/transmission.h @@ -1608,6 +1608,13 @@ struct tr_info return is_private_; } + void setAnnounceList(tr_announce_list const& list); + + tr_announce_list const& announceList() const + { + return *announce_list; + } + tr_sha1_digest_t hash_; std::shared_ptr announce_list; std::vector files;