refactor: magnet metainfo (#2124)

* refactor: magnet-metainfo
This commit is contained in:
Charles Kerr 2021-11-09 20:42:18 -06:00 committed by GitHub
parent bdf1bb6d17
commit 7693ef69bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 490 additions and 369 deletions

View File

@ -34,8 +34,8 @@
4D4ADFC70DA1631500A68297 /* blocklist.cc in Sources */ = {isa = PBXBuildFile; fileRef = A2D3078E0D9EC45F0051FD27 /* blocklist.cc */; };
4D8017EA10BBC073008A4AF2 /* torrent-magnet.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D8017E810BBC073008A4AF2 /* torrent-magnet.cc */; };
4D8017EB10BBC073008A4AF2 /* torrent-magnet.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D8017E910BBC073008A4AF2 /* torrent-magnet.h */; };
4D80185910BBC0B0008A4AF2 /* magnet.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D80185710BBC0B0008A4AF2 /* magnet.cc */; };
4D80185A10BBC0B0008A4AF2 /* magnet.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D80185810BBC0B0008A4AF2 /* magnet.h */; };
4D80185910BBC0B0008A4AF2 /* magnet-metainfo.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D80185710BBC0B0008A4AF2 /* magnet-metainfo.cc */; };
4D80185A10BBC0B0008A4AF2 /* magnet-metainfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D80185810BBC0B0008A4AF2 /* magnet-metainfo.h */; };
4D9A2BF009E16D21002D0FF9 /* libtransmission.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D18389709DEC0030047D688 /* libtransmission.a */; };
4DB74F080E8CD75100AEB1A8 /* wildmat.c in Sources */ = {isa = PBXBuildFile; fileRef = 4DB74F070E8CD75100AEB1A8 /* wildmat.c */; };
4DCCBB3E09C3D71100D3CABF /* TorrentCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4DCCBB3C09C3D71100D3CABF /* TorrentCell.mm */; };
@ -520,8 +520,8 @@
4D3EA0A908AE13C600EA10C2 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; };
4D8017E810BBC073008A4AF2 /* torrent-magnet.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "torrent-magnet.cc"; sourceTree = "<group>"; };
4D8017E910BBC073008A4AF2 /* torrent-magnet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "torrent-magnet.h"; sourceTree = "<group>"; };
4D80185710BBC0B0008A4AF2 /* magnet.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = magnet.cc; sourceTree = "<group>"; };
4D80185810BBC0B0008A4AF2 /* magnet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = magnet.h; sourceTree = "<group>"; };
4D80185710BBC0B0008A4AF2 /* magnet-metainfo.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = magnet-metainfo.cc; sourceTree = "<group>"; };
4D80185810BBC0B0008A4AF2 /* magnet-metainfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = magnet-metainfo.h; sourceTree = "<group>"; };
4DB74F070E8CD75100AEB1A8 /* wildmat.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = wildmat.c; sourceTree = "<group>"; };
4DCCBB3C09C3D71100D3CABF /* TorrentCell.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TorrentCell.mm; sourceTree = "<group>"; };
4DCCBB3D09C3D71100D3CABF /* TorrentCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TorrentCell.h; sourceTree = "<group>"; };
@ -1376,8 +1376,8 @@
C1077A4D183EB29600634C22 /* file.h */,
C11DEA141FCD31C0009E22B9 /* subprocess-posix.cc */,
C11DEA151FCD31C0009E22B9 /* subprocess.h */,
4D80185710BBC0B0008A4AF2 /* magnet.cc */,
4D80185810BBC0B0008A4AF2 /* magnet.h */,
4D80185710BBC0B0008A4AF2 /* magnet-metainfo.cc */,
4D80185810BBC0B0008A4AF2 /* magnet-metainfo.h */,
4D8017E810BBC073008A4AF2 /* torrent-magnet.cc */,
4D8017E910BBC073008A4AF2 /* torrent-magnet.h */,
0A6169A50FE5C9A200C66CE6 /* bitfield.cc */,
@ -1866,7 +1866,7 @@
0A6169A80FE5C9A200C66CE6 /* bitfield.h in Headers */,
A25964A7106D73A800453B31 /* announcer.h in Headers */,
4D8017EB10BBC073008A4AF2 /* torrent-magnet.h in Headers */,
4D80185A10BBC0B0008A4AF2 /* magnet.h in Headers */,
4D80185A10BBC0B0008A4AF2 /* magnet-metainfo.h in Headers */,
A209EE5D1144B51E002B02D1 /* history.h in Headers */,
A247A443114C701800547DFC /* InfoViewController.h in Headers */,
A220EC5C118C8A060022B4BE /* tr-lpd.h in Headers */,
@ -2459,7 +2459,7 @@
0A6169A70FE5C9A200C66CE6 /* bitfield.cc in Sources */,
A25964A6106D73A800453B31 /* announcer.cc in Sources */,
4D8017EA10BBC073008A4AF2 /* torrent-magnet.cc in Sources */,
4D80185910BBC0B0008A4AF2 /* magnet.cc in Sources */,
4D80185910BBC0B0008A4AF2 /* magnet-metainfo.cc in Sources */,
A220EC5B118C8A060022B4BE /* tr-lpd.cc in Sources */,
C1FEE57A1C3223CC00D62832 /* watchdir.cc in Sources */,
A23547E211CD0B090046EAE6 /* cache.cc in Sources */,

View File

@ -30,7 +30,7 @@ set(PROJECT_FILES
handshake.cc
inout.cc
log.cc
magnet.cc
magnet-metainfo.cc
makemeta.cc
metainfo.cc
natpmp.cc
@ -155,7 +155,7 @@ set(${PROJECT_NAME}_PRIVATE_HEADERS
handshake.h
history.h
inout.h
magnet.h
magnet-metainfo.h
metainfo.h
mime-types.h
natpmp_local.h

View File

@ -1231,11 +1231,12 @@ static void announce_request_delegate(
#endif
auto const announce_sv = tr_quark_get_string_view(request->announce_url);
if (announce_sv.find("http://"sv) == 0 || announce_sv.find("https://"sv) == 0)
if (tr_strvStartsWith(announce_sv, "http://"sv) || tr_strvStartsWith(announce_sv, "https://"sv))
{
tr_tracker_http_announce(session, request, callback, callback_data);
}
else if (announce_sv.find("udp://"sv) == 0)
else if (tr_strvStartsWith(announce_sv, "udp://"sv))
{
tr_tracker_udp_announce(session, request, callback, callback_data);
}
@ -1289,7 +1290,7 @@ static constexpr bool multiscrape_too_big(std::string_view errmsg)
for (auto const& tle : TooLongErrors)
{
if (errmsg.find(tle) != std::string_view::npos)
if (tr_strvContains(errmsg, tle))
{
return true;
}
@ -1483,11 +1484,11 @@ static void scrape_request_delegate(
auto const scrape_sv = tr_quark_get_string_view(request->scrape_url);
if (scrape_sv.find("http://"sv) == 0 || scrape_sv.find("https://"sv) == 0)
if (tr_strvStartsWith(scrape_sv, "http://"sv) || tr_strvStartsWith(scrape_sv, "https://"sv))
{
tr_tracker_http_scrape(session, request, callback, callback_data);
}
else if (scrape_sv.find("udp://"sv) == 0)
else if (tr_strvStartsWith(scrape_sv, "udp://"sv))
{
tr_tracker_udp_scrape(session, request, callback, callback_data);
}

View File

@ -0,0 +1,286 @@
/*
* This file Copyright (C) 2010-2014 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#include <cstring>
#include <string>
#include <string_view>
#include "transmission.h"
#include "crypto-utils.h"
#include "error-types.h"
#include "magnet-metainfo.h"
#include "tr-assert.h"
#include "utils.h"
#include "variant.h"
#include "web-utils.h"
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 bitzi
{
int constexpr base32Lookup[] = {
0xFF, 0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, /* '0', '1', '2', '3', '4', '5', '6', '7' */
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* '8', '9', ':', ';', '<', '=', '>', '?' */
0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G' */
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, /* 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O' */
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, /* 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W' */
0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 'X', 'Y', 'Z', '[', '\', ']', '^', '_' */
0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g' */
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, /* 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o' */
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, /* 'p', 'q', 'r', 's', 't', 'u', 'v', 'w' */
0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF /* 'x', 'y', 'z', '{', '|', '}', '~', 'DEL' */
};
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);
size_t index = 0;
size_t offset = 0;
for (size_t i = 0; i < inlen; ++i)
{
int lookup = in[i] - '0';
/* Skip chars outside the lookup table */
if (lookup < 0)
{
continue;
}
/* If this digit is not in the table, ignore it */
int const digit = base32Lookup[lookup];
if (digit == 0xFF)
{
continue;
}
if (index <= 3)
{
index = (index + 5) % 8;
if (index == 0)
{
out[offset] |= digit;
offset++;
if (offset >= outlen)
{
break;
}
}
else
{
out[offset] |= digit << (8 - index);
}
}
else
{
index = (index + 5) % 8;
out[offset] |= digit >> index;
offset++;
if (offset >= outlen)
{
break;
}
out[offset] |= digit << (8 - index);
}
}
}
} // namespace bitzi
/***
****
***/
std::string tr_magnet_metainfo::magnet() const
{
auto s = std::string{};
s += "magnet:?xt=urn:btih:"sv;
s += infoHashString();
if (!std::empty(name))
{
s += "&dn="sv;
tr_http_escape(s, name, true);
}
for (auto const& it : trackers)
{
s += "&tr="sv;
tr_http_escape(s, tr_quark_get_string_view(it.second.announce_url), true);
}
for (auto const& webseed : webseed_urls)
{
s += "&ws="sv;
tr_http_escape(s, webseed, true);
}
return s;
}
bool tr_magnet_metainfo::addTracker(tr_tracker_tier_t tier, std::string_view announce_sv)
{
announce_sv = tr_strvStrip(announce_sv);
if (!tr_urlIsValidTracker(announce_sv))
{
return false;
}
auto buf = std::string{};
auto const announce_url = tr_quark_new(announce_sv);
auto const scrape_url = convertAnnounceToScrape(buf, announce_sv) ? tr_quark_new(buf) : TR_KEY_NONE;
this->trackers.insert({ tier, { announce_url, scrape_url, tier } });
return true;
}
bool tr_magnet_metainfo::parseMagnet(std::string_view magnet_link, tr_error** error)
{
auto const parsed = tr_urlParse(magnet_link);
if (!parsed || parsed->scheme != "magnet"sv)
{
tr_error_set(error, TR_ERROR_EINVAL, "Error parsing URL");
return false;
}
bool got_checksum = false;
auto tier = tr_tracker_tier_t{ 0 };
for (auto const& [key, value] : tr_url_query_view{ parsed->query })
{
if (key == "dn"sv)
{
this->name = tr_urlPercentDecode(value);
}
else if (key == "tr"sv || tr_strvStartsWith(key, "tr."sv))
{
// "tr." explanation @ https://trac.transmissionbt.com/ticket/3341
addTracker(tier++, tr_urlPercentDecode(value));
}
else if (key == "ws"sv)
{
auto const url = tr_urlPercentDecode(value);
auto const url_sv = tr_strvStrip(url);
if (tr_urlIsValid(url_sv))
{
this->webseed_urls.emplace_back(url_sv);
}
}
else if (key == "xt"sv)
{
auto constexpr ValPrefix = "urn:btih:"sv;
if (tr_strvStartsWith(value, ValPrefix))
{
auto const hash = value.substr(std::size(ValPrefix));
switch (std::size(hash))
{
case 40:
tr_hex_to_sha1(std::data(this->info_hash), std::data(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;
}
}
}
}
return got_checksum;
}
bool tr_magnet_metainfo::convertAnnounceToScrape(std::string& out, std::string_view in)
{
// To derive the scrape URL use the following steps:
// Begin with the announce URL. Find the last '/' in it.
// If the text immediately following that '/' isn't 'announce'
// it will be taken as a sign that that tracker doesn't support
// the scrape convention. If it does, substitute 'scrape' for
// 'announce' to find the scrape page.
auto constexpr oldval = "/announce"sv;
auto pos = in.rfind(oldval.front());
if (pos != in.npos && in.find(oldval, pos) == pos)
{
auto const prefix = in.substr(0, pos);
auto const suffix = in.substr(pos + std::size(oldval));
tr_buildBuf(out, prefix, "/scrape"sv, suffix);
return true;
}
// some torrents with UDP announce URLs don't have /announce
if (tr_strvStartsWith(in, "udp:"sv))
{
out = in;
return true;
}
return false;
}
void tr_magnet_metainfo::toVariant(tr_variant* top) const
{
tr_variantInitDict(top, 4);
// announce list
auto n = std::size(this->trackers);
if (n == 1)
{
tr_variantDictAddStr(top, TR_KEY_announce, tr_quark_get_string_view(std::begin(this->trackers)->second.announce_url));
}
else
{
auto* list = tr_variantDictAddList(top, TR_KEY_announce_list, n);
for (auto const& pair : this->trackers)
{
tr_variantListAddStr(tr_variantListAddList(list, 1), tr_quark_get_string_view(pair.second.announce_url));
}
}
// webseeds
n = std::size(this->webseed_urls);
if (n != 0)
{
tr_variant* list = tr_variantDictAddList(top, TR_KEY_url_list, n);
for (auto& url : this->webseed_urls)
{
tr_variantListAddStr(list, url);
}
}
// nonstandard keys
auto* const d = tr_variantDictAddDict(top, TR_KEY_magnet_info, 2);
tr_variantDictAddRaw(d, TR_KEY_info_hash, std::data(this->info_hash), std::size(this->info_hash));
if (!std::empty(this->name))
{
tr_variantDictAddStr(d, TR_KEY_display_name, this->name);
}
}

View File

@ -0,0 +1,74 @@
/*
* This file Copyright (C) Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#pragma once
#ifndef __TRANSMISSION__
#error only libtransmission should #include this header.
#endif
#include <map>
#include <string>
#include <string_view>
#include <vector>
struct tr_variant;
#include "transmission.h"
#include "error.h"
#include "quark.h"
struct tr_magnet_metainfo
{
bool parseMagnet(std::string_view magnet_link, tr_error** error = nullptr);
std::string magnet() const;
void toVariant(tr_variant*) const;
static bool convertAnnounceToScrape(std::string& setme, std::string_view announce_url);
std::string_view infoHashString() const
{
// trim one byte off the end because of zero termination
return std::string_view{ std::data(info_hash_chars), std::size(info_hash_chars) - 1 };
}
struct tracker_t
{
tr_quark announce_url;
tr_quark scrape_url;
tr_tracker_tier_t tier;
tracker_t(tr_quark announce_in, tr_quark scrape_in, tr_tracker_tier_t tier_in)
: announce_url{ announce_in }
, scrape_url{ scrape_in }
, tier{ tier_in }
{
}
bool operator<(tracker_t const& that) const
{
return announce_url < that.announce_url;
}
};
std::vector<std::string> webseed_urls;
std::string name;
std::multimap<tr_tracker_tier_t, tracker_t> trackers;
tr_sha1_digest_string_t info_hash_chars;
tr_sha1_digest_t info_hash;
protected:
bool addTracker(tr_tracker_tier_t tier, std::string_view announce_url);
};

View File

@ -1,233 +0,0 @@
/*
* This file Copyright (C) 2010-2014 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#include <algorithm>
#include <cstring> /* strchr() */
#include <cstdio> /* sscanf() */
#include "transmission.h"
#include "crypto-utils.h" /* tr_hex_to_sha1() */
#include "magnet.h"
#include "tr-assert.h"
#include "utils.h"
#include "utils.h"
#include "variant.h"
#include "web-utils.h"
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 */
static int constexpr base32Lookup[] = {
0xFF, 0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, /* '0', '1', '2', '3', '4', '5', '6', '7' */
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* '8', '9', ':', ';', '<', '=', '>', '?' */
0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G' */
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, /* 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O' */
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, /* 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W' */
0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* 'X', 'Y', 'Z', '[', '\', ']', '^', '_' */
0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g' */
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, /* 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o' */
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, /* 'p', 'q', 'r', 's', 't', 'u', 'v', 'w' */
0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF /* 'x', 'y', 'z', '{', '|', '}', '~', 'DEL' */
};
static 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);
size_t index = 0;
size_t offset = 0;
for (size_t i = 0; i < inlen; ++i)
{
int lookup = in[i] - '0';
/* Skip chars outside the lookup table */
if (lookup < 0)
{
continue;
}
/* If this digit is not in the table, ignore it */
int const digit = base32Lookup[lookup];
if (digit == 0xFF)
{
continue;
}
if (index <= 3)
{
index = (index + 5) % 8;
if (index == 0)
{
out[offset] |= digit;
offset++;
if (offset >= outlen)
{
break;
}
}
else
{
out[offset] |= digit << (8 - index);
}
}
else
{
index = (index + 5) % 8;
out[offset] |= digit >> index;
offset++;
if (offset >= outlen)
{
break;
}
out[offset] |= digit << (8 - index);
}
}
}
/***
****
***/
tr_magnet_info* tr_magnetParse(std::string_view magnet_link)
{
auto const parsed = tr_urlParse(magnet_link);
if (!parsed || parsed->scheme != "magnet"sv)
{
return nullptr;
}
bool got_checksum = false;
auto tr = std::vector<char*>{};
auto ws = std::vector<char*>{};
char* display_name = nullptr;
uint8_t sha1[SHA_DIGEST_LENGTH];
for (auto const& [key, value] : tr_url_query_view{ parsed->query })
{
if (key == "dn"sv)
{
display_name = tr_strvDup(tr_urlPercentDecode(value));
}
else if (key == "tr"sv || key.find("tr.") == 0)
{
// "tr." explanation @ https://trac.transmissionbt.com/ticket/3341
tr.push_back(tr_strvDup(tr_urlPercentDecode(value)));
}
else if (key == "ws"sv)
{
ws.push_back(tr_strvDup(tr_urlPercentDecode(value)));
}
else if (key == "xt"sv)
{
auto constexpr ValPrefix = "urn:btih:"sv;
if (value.find(ValPrefix) == 0)
{
auto const hash = value.substr(std::size(ValPrefix));
switch (std::size(hash))
{
case 40:
tr_hex_to_sha1(sha1, std::data(hash));
got_checksum = true;
break;
case 32:
base32_to_sha1(sha1, std::data(hash), std::size(hash));
got_checksum = true;
break;
default:
break;
}
}
}
}
if (!got_checksum)
{
std::for_each(std::begin(tr), std::end(tr), tr_free);
std::for_each(std::begin(ws), std::end(ws), tr_free);
return nullptr;
}
auto* const info = tr_new0(tr_magnet_info, 1);
info->displayName = display_name;
info->trackerCount = std::size(tr);
info->trackers = reinterpret_cast<char**>(tr_memdup(std::data(tr), std::size(tr) * sizeof(char*)));
info->webseedCount = std::size(ws);
info->webseeds = reinterpret_cast<char**>(tr_memdup(std::data(ws), std::size(ws) * sizeof(char*)));
std::copy_n(sha1, SHA_DIGEST_LENGTH, info->hash);
return info;
}
void tr_magnetFree(tr_magnet_info* info)
{
if (info != nullptr)
{
std::for_each(info->trackers, info->trackers + info->trackerCount, tr_free);
std::for_each(info->webseeds, info->webseeds + info->webseedCount, tr_free);
tr_free(info->trackers);
tr_free(info->webseeds);
tr_free(info->displayName);
tr_free(info);
}
}
void tr_magnetCreateMetainfo(tr_magnet_info const* info, tr_variant* top)
{
tr_variantInitDict(top, 4);
/* announce list */
if (info->trackerCount == 1)
{
tr_variantDictAddStr(top, TR_KEY_announce, info->trackers[0]);
}
else
{
tr_variant* trackers = tr_variantDictAddList(top, TR_KEY_announce_list, info->trackerCount);
for (int i = 0; i < info->trackerCount; ++i)
{
tr_variantListAddStr(tr_variantListAddList(trackers, 1), info->trackers[i]);
}
}
/* webseeds */
if (info->webseedCount > 0)
{
tr_variant* urls = tr_variantDictAddList(top, TR_KEY_url_list, info->webseedCount);
for (int i = 0; i < info->webseedCount; ++i)
{
tr_variantListAddStr(urls, info->webseeds[i]);
}
}
/* nonstandard keys */
auto* const d = tr_variantDictAddDict(top, TR_KEY_magnet_info, 2);
tr_variantDictAddRaw(d, TR_KEY_info_hash, info->hash, 20);
if (info->displayName != nullptr)
{
tr_variantDictAddStr(d, TR_KEY_display_name, info->displayName);
}
}

View File

@ -1,38 +0,0 @@
/*
* This file Copyright (C) 2010-2014 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#pragma once
#ifndef __TRANSMISSION__
#error only libtransmission should #include this header.
#endif
#include <string_view>
#include "transmission.h"
struct tr_variant;
struct tr_magnet_info
{
uint8_t hash[20];
char* displayName;
int trackerCount;
char** trackers;
int webseedCount;
char** webseeds;
};
tr_magnet_info* tr_magnetParse(std::string_view magnet_link);
void tr_magnetCreateMetainfo(tr_magnet_info const*, tr_variant*);
void tr_magnetFree(tr_magnet_info* info);

View File

@ -13,7 +13,7 @@
#include "transmission.h"
#include "file.h"
#include "magnet.h"
#include "magnet-metainfo.h"
#include "session.h"
#include "torrent.h" /* tr_ctorGetSave() */
#include "tr-assert.h"
@ -93,21 +93,19 @@ char const* tr_ctorGetSourceFile(tr_ctor const* ctor)
int tr_ctorSetMetainfoFromMagnetLink(tr_ctor* ctor, char const* magnet_link)
{
tr_magnet_info* magnet_info = magnet_link ? tr_magnetParse(magnet_link) : nullptr;
if (magnet_info == nullptr)
auto mm = tr_magnet_metainfo{};
if (!mm.parseMagnet(magnet_link ? magnet_link : ""))
{
return -1;
}
auto tmp = tr_variant{};
mm.toVariant(&tmp);
auto len = size_t{};
tr_magnetCreateMetainfo(magnet_info, &tmp);
char* const str = tr_variantToStr(&tmp, TR_VARIANT_FMT_BENC, &len);
auto const err = tr_ctorSetMetainfo(ctor, (uint8_t const*)str, len);
tr_free(str);
tr_variantFree(&tmp);
tr_magnetFree(magnet_info);
return err;
}

View File

@ -16,7 +16,7 @@
#include "crypto-utils.h" /* tr_sha1() */
#include "file.h"
#include "log.h"
#include "magnet.h"
#include "magnet-metainfo.h"
#include "metainfo.h"
#include "resume.h"
#include "torrent-magnet.h"

View File

@ -41,7 +41,7 @@
#include "file.h"
#include "inout.h" /* tr_ioTestPiece() */
#include "log.h"
#include "magnet.h"
#include "magnet-metainfo.h"
#include "metainfo.h"
#include "peer-common.h" /* MAX_BLOCK_SIZE */
#include "peer-mgr.h"
@ -116,16 +116,8 @@ tr_torrent* tr_torrentFindFromHash(tr_session* session, tr_sha1_digest_t const&
tr_torrent* tr_torrentFindFromMagnetLink(tr_session* session, char const* magnet_link)
{
tr_torrent* tor = nullptr;
tr_magnet_info* const info = magnet_link ? tr_magnetParse(magnet_link) : nullptr;
if (info != nullptr)
{
tor = tr_torrentFindFromHash(session, info->hash);
tr_magnetFree(info);
}
return tor;
auto mm = tr_magnet_metainfo{};
return mm.parseMagnet(magnet_link ? magnet_link : "") ? tr_torrentFindFromHash(session, mm.info_hash) : nullptr;
}
tr_torrent* tr_torrentFindFromObfuscatedHash(tr_session* session, uint8_t const* obfuscatedTorrentHash)
@ -2798,7 +2790,7 @@ static constexpr bool isJunkFile(std::string_view base)
#ifdef __APPLE__
// check for resource forks. <http://support.apple.com/kb/TA20578>
if (base.find("._") == 0)
if (tr_strvStartsWith(base, "._"sv))
{
return true;
}

View File

@ -138,3 +138,5 @@ using tr_peer_id_t = std::array<char, PEER_ID_LEN>;
// TODO #2: tr_peer_id_t, tr_sha1_digest_t should be moved into a new 'types.h' header
auto inline constexpr TR_SHA1_DIGEST_LEN = size_t{ 20 };
using tr_sha1_digest_t = std::array<std::byte, TR_SHA1_DIGEST_LEN>;
using tr_sha1_digest_string_t = std::array<char, TR_SHA1_DIGEST_LEN * 2 + 1>;

View File

@ -33,6 +33,7 @@ using tr_piece_index_t = uint32_t;
* if we ever need to grow past that, change this to uint64_t ;) */
using tr_block_index_t = uint32_t;
using tr_port = uint16_t;
using tr_tracker_tier_t = uint32_t;
struct tr_ctor;
struct tr_error;

View File

@ -263,6 +263,11 @@ constexpr bool tr_strvContains(std::string_view sv, T key) // c++23
return sv.find(key) != sv.npos;
}
constexpr bool tr_strvStartsWith(std::string_view sv, char key) // c++20
{
return !std::empty(sv) && sv.front() == key;
}
constexpr bool tr_strvStartsWith(std::string_view sv, std::string_view key) // c++20
{
return std::size(key) <= std::size(sv) && sv.substr(0, std::size(key)) == key;
@ -273,6 +278,11 @@ constexpr bool tr_strvEndsWith(std::string_view sv, std::string_view key) // c++
return std::size(key) <= std::size(sv) && sv.substr(std::size(sv) - std::size(key)) == key;
}
constexpr bool tr_strvEndsWith(std::string_view sv, char key) // c++20
{
return !std::empty(sv) && sv.back() == key;
}
constexpr std::string_view tr_strvSep(std::string_view* sv, char delim)
{
auto pos = sv->find(delim);

View File

@ -186,6 +186,26 @@ void tr_http_escape(struct evbuffer* out, std::string_view str, bool escape_rese
}
}
void tr_http_escape(std::string& appendme, std::string_view str, bool escape_reserved)
{
auto constexpr ReservedChars = std::string_view{ "!*'();:@&=+$,/?%#[]" };
auto constexpr UnescapedChars = std::string_view{ "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.~" };
for (auto& ch : str)
{
if (tr_strvContains(UnescapedChars, ch) || (!escape_reserved && tr_strvContains(ReservedChars, ch)))
{
appendme += ch;
}
else
{
char buf[16];
tr_snprintf(buf, sizeof(buf), "%%%02X", (unsigned)(ch & 0xFF));
appendme += buf;
}
}
}
static bool is_rfc2396_alnum(uint8_t ch)
{
return ('0' <= ch && ch <= '9') || ('A' <= ch && ch <= 'Z') || ('a' <= ch && ch <= 'z') || ch == '.' || ch == '-' ||
@ -331,7 +351,7 @@ std::optional<tr_url_parsed_t> tr_urlParse(std::string_view url)
url = pos == url.npos ? ""sv : url.substr(pos);
// query
if (url.find('?') == 0)
if (tr_strvStartsWith(url, '?'))
{
url.remove_prefix(1);
pos = url.find('#');
@ -340,7 +360,7 @@ std::optional<tr_url_parsed_t> tr_urlParse(std::string_view url)
}
// fragment
if (url.find('#') == 0)
if (tr_strvStartsWith(url, '#'))
{
parsed.fragment = url.substr(1);
}

View File

@ -90,7 +90,9 @@ struct tr_url_query_view
}
};
// TODO: replace evbuffer* with std::string&
void tr_http_escape(std::string& appendme, std::string_view str, bool escape_reserved);
// TODO: remove evbuffer version
void tr_http_escape(struct evbuffer* out, std::string_view str, bool escape_reserved);
void tr_http_escape_sha1(char* out, uint8_t const* sha1_digest);

View File

@ -10,7 +10,7 @@ add_executable(libtransmission-test
getopt-test.cc
history-test.cc
json-test.cc
magnet-test.cc
magnet-metainfo-test.cc
makemeta-test.cc
metainfo-test.cc
move-test.cc

View File

@ -0,0 +1,63 @@
/*
* This file Copyright (C) 2010-2014 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#include "transmission.h"
#include "magnet-metainfo.h"
#include "utils.h"
#include "gtest/gtest.h"
#include <array>
#include <string_view>
using namespace std::literals;
TEST(MagnetMetainfo, magnetParse)
{
auto constexpr ExpectedHash = tr_sha1_digest_t{ std::byte(210), std::byte(53), std::byte(64), std::byte(16),
std::byte(163), std::byte(202), std::byte(74), std::byte(222),
std::byte(91), std::byte(116), std::byte(39), std::byte(187),
std::byte(9), std::byte(58), std::byte(98), std::byte(163),
std::byte(137), std::byte(159), std::byte(243), std::byte(129) };
auto constexpr UriHex =
"magnet:?xt=urn:btih:"
"d2354010a3ca4ade5b7427bb093a62a3899ff381"
"&dn=Display%20Name"
"&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce"
"&tr=http%3A%2F%2Ftracker.opentracker.org%2Fannounce"
"&ws=http%3A%2F%2Fserver.webseed.org%2Fpath%2Fto%2Ffile"sv;
auto constexpr UriBase32 =
"magnet:?xt=urn:btih:"
"2I2UAEFDZJFN4W3UE65QSOTCUOEZ744B"
"&dn=Display%20Name"
"&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce"
"&ws=http%3A%2F%2Fserver.webseed.org%2Fpath%2Fto%2Ffile"
"&tr=http%3A%2F%2Ftracker.opentracker.org%2Fannounce"sv;
for (auto const& uri : { UriHex, UriBase32 })
{
auto mm = tr_magnet_metainfo{};
EXPECT_TRUE(mm.parseMagnet(uri));
EXPECT_EQ(2, std::size(mm.trackers));
auto it = std::begin(mm.trackers);
EXPECT_EQ(0, it->first);
EXPECT_EQ("http://tracker.openbittorrent.com/announce"sv, tr_quark_get_string_view(it->second.announce_url));
EXPECT_EQ("http://tracker.openbittorrent.com/scrape"sv, tr_quark_get_string_view(it->second.scrape_url));
++it;
EXPECT_EQ(1, it->first);
EXPECT_EQ("http://tracker.opentracker.org/announce", tr_quark_get_string_view(it->second.announce_url));
EXPECT_EQ("http://tracker.opentracker.org/scrape", tr_quark_get_string_view(it->second.scrape_url));
EXPECT_EQ(1, std::size(mm.webseed_urls));
EXPECT_EQ("http://server.webseed.org/path/to/file"sv, mm.webseed_urls.front());
EXPECT_EQ("Display Name"sv, mm.name);
EXPECT_EQ(ExpectedHash, mm.info_hash);
}
}

View File

@ -1,57 +0,0 @@
/*
* This file Copyright (C) 2010-2014 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#include "transmission.h"
#include "magnet.h"
#include "utils.h"
#include "gtest/gtest.h"
#include <array>
#include <string_view>
using namespace std::literals;
TEST(Magnet, magnetParse)
{
auto constexpr ExpectedHash = std::array<uint8_t, SHA_DIGEST_LENGTH>{
210, 53, 64, 16, 163, 202, 74, 222, 91, 116, //
39, 187, 9, 58, 98, 163, 137, 159, 243, 129, //
};
auto constexpr UriHex =
"magnet:?xt=urn:btih:"
"d2354010a3ca4ade5b7427bb093a62a3899ff381"
"&dn=Display%20Name"
"&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce"
"&tr=http%3A%2F%2Ftracker.opentracker.org%2Fannounce"
"&ws=http%3A%2F%2Fserver.webseed.org%2Fpath%2Fto%2Ffile"sv;
auto constexpr UriBase32 =
"magnet:?xt=urn:btih:"
"2I2UAEFDZJFN4W3UE65QSOTCUOEZ744B"
"&dn=Display%20Name"
"&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce"
"&ws=http%3A%2F%2Fserver.webseed.org%2Fpath%2Fto%2Ffile"
"&tr=http%3A%2F%2Ftracker.opentracker.org%2Fannounce"sv;
for (auto const& uri : { UriHex, UriBase32 })
{
auto* info = tr_magnetParse(uri);
EXPECT_NE(nullptr, info);
EXPECT_EQ(2, info->trackerCount);
EXPECT_STREQ("http://tracker.openbittorrent.com/announce", info->trackers[0]);
EXPECT_STREQ("http://tracker.opentracker.org/announce", info->trackers[1]);
EXPECT_EQ(1, info->webseedCount);
EXPECT_STREQ("http://server.webseed.org/path/to/file", info->webseeds[0]);
EXPECT_STREQ("Display Name", info->displayName);
EXPECT_EQ(std::size(ExpectedHash), sizeof(info->hash));
EXPECT_EQ(0, memcmp(info->hash, std::data(ExpectedHash), std::size(ExpectedHash)));
tr_magnetFree(info);
}
}

View File

@ -208,9 +208,9 @@ TEST_F(UtilsTest, trStrvUtf8Clean)
EXPECT_EQ(in, out);
// https://trac.transmissionbt.com/ticket/6064
// TODO: It seems like that bug was not fixed so much as we just let
// strlen() solve the problem for us; however, it's probably better to
// wait until https://github.com/transmission/transmission/issues/612
// TODO(anyone): It seems like that bug was not fixed so much as we just
// let strlen() solve the problem for us; however, it's probably better
// to wait until https://github.com/transmission/transmission/issues/612
// is resolved before revisiting this.
in = "\xF4\x00\x81\x82"sv;
out = tr_strvUtf8Clean(in);