fix: magnet link regression (#2390)

This commit is contained in:
Charles Kerr 2022-01-11 08:28:14 -06:00 committed by GitHub
parent 43b9d5c147
commit a9284c0a6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 151 additions and 51 deletions

View File

@ -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)

View File

@ -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<tr_tracker_tier_t>{};
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<char const*>(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);
}
}

View File

@ -16,6 +16,8 @@
#include <cstddef> // size_t
#include <ctime>
#include "transmission.h"
struct tr_torrent;
// defined by BEP #9

View File

@ -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<tr_announce_list>(list);
}

View File

@ -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<tr_announce_list> announce_list;
std::vector<tr_file> files;