parent
bdf1bb6d17
commit
7693ef69bf
|
@ -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 */,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue