refactor: strbuf for metainfo files (#2833)

* refactor: tr_ctorSaveContents takes a string_view filename

* refactor: remove tr_ctorSaveMagnetContents

* refactor: announce_list::save takes a std::string_view

* refactor: magnet() takes an OutputIt arg

Generate the magnet link URL into an output iterator

* refactor: remove deprecated calls to tr_http_escape

* refactor: tr_torrent.torrentFile takes an OutputIterator

* refactor: tr_torrent.torrentFile returns a tr_pathbuf

* refactor: tr_torrent_metainfo.makeFilename returns a tr_pathbuf

* refactor: use tr_urlbuf in announcer-http
This commit is contained in:
Charles Kerr 2022-03-28 17:13:32 -05:00 committed by GitHub
parent 3ddf76b560
commit 2996e223dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 157 additions and 188 deletions

View File

@ -223,7 +223,7 @@ bool tr_announce_list::canAdd(tr_url_parsed_t const& announce)
return std::none_of(std::begin(trackers_), std::end(trackers_), is_same);
}
bool tr_announce_list::save(std::string const& torrent_file, tr_error** error) const
bool tr_announce_list::save(std::string_view torrent_file, tr_error** error) const
{
// load the torrent file
auto metainfo = tr_variant{};

View File

@ -126,7 +126,7 @@ public:
bool parse(std::string_view text);
[[nodiscard]] std::string toString() const;
bool save(std::string const& torrent_file, tr_error** error = nullptr) const;
bool save(std::string_view torrent_file, tr_error** error = nullptr) const;
static std::optional<std::string> announceToScrape(std::string_view announce);
static tr_quark announceToScrape(tr_quark announce);

View File

@ -6,12 +6,12 @@
#include <climits> /* USHRT_MAX */
#include <cstdio> /* fprintf() */
#include <cstring> /* strchr(), memcmp(), memcpy() */
#include <iostream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <string>
#include <string_view>
#include <event2/buffer.h>
#include <event2/http.h> /* for HTTP_OK */
#include <fmt/core.h>
@ -46,58 +46,56 @@ static char const* get_event_string(tr_announce_request const* req)
return req->partial_seed && (req->event != TR_ANNOUNCE_EVENT_STOPPED) ? "paused" : tr_announce_event_get_string(req->event);
}
static std::string announce_url_new(tr_session const* session, tr_announce_request const* req)
static tr_urlbuf announce_url_new(tr_session const* session, tr_announce_request const* req)
{
auto const announce_sv = req->announce_url.sv();
auto url = tr_urlbuf{};
auto out = std::back_inserter(url);
auto escaped_info_hash = std::array<char, SHA_DIGEST_LENGTH * 3 + 1>{};
tr_http_escape_sha1(std::data(escaped_info_hash), req->info_hash);
auto* const buf = evbuffer_new();
evbuffer_expand(buf, 1024);
evbuffer_add_printf(
buf,
"%" TR_PRIsv
"%c"
"info_hash=%s"
"&peer_id=%" TR_PRIsv
"&port=%d"
"&uploaded=%" PRIu64 //
"&downloaded=%" PRIu64 //
"&left=%" PRIu64
"&numwant=%d"
"&key=%x"
fmt::format_to(
out,
"{url}"
"{sep}info_hash={info_hash}"
"&peer_id={peer_id}"
"&port={port}"
"&uploaded={uploaded}"
"&downloaded={downloaded}"
"&left={left}"
"&numwant={numwant}"
"&key={key}"
"&compact=1"
"&supportcrypto=1",
TR_PRIsv_ARG(announce_sv),
announce_sv.find('?') == std::string_view::npos ? '?' : '&',
std::data(escaped_info_hash),
TR_PRIsv_ARG(req->peer_id),
req->port,
req->up,
req->down,
req->leftUntilComplete,
req->numwant,
req->key);
fmt::arg("url", req->announce_url),
fmt::arg("sep", tr_strvContains(req->announce_url.sv(), '?') ? '&' : '?'),
fmt::arg("info_hash", std::data(escaped_info_hash)),
fmt::arg("peer_id", std::string_view{ std::data(req->peer_id), std::size(req->peer_id) }),
fmt::arg("port", req->port),
fmt::arg("uploaded", req->up),
fmt::arg("downloaded", req->down),
fmt::arg("left", req->leftUntilComplete),
fmt::arg("numwant", req->numwant),
fmt::arg("key", req->key));
if (session->encryptionMode == TR_ENCRYPTION_REQUIRED)
{
evbuffer_add_printf(buf, "&requirecrypto=1");
fmt::format_to(out, "&requirecrypto=1");
}
if (req->corrupt != 0)
{
evbuffer_add_printf(buf, "&corrupt=%" PRIu64, req->corrupt);
fmt::format_to(out, "&corrupt={}", req->corrupt);
}
if (char const* str = get_event_string(req); !tr_str_is_empty(str))
{
evbuffer_add_printf(buf, "&event=%s", str);
fmt::format_to(out, "&event={}", str);
}
if (!std::empty(req->tracker_id))
{
evbuffer_add_printf(buf, "&trackerid=%" TR_PRIsv, TR_PRIsv_ARG(req->tracker_id));
fmt::format_to(out, "&trackerid={}", req->tracker_id);
}
/* There are two incompatible techniques for announcing an IPv6 address.
@ -113,11 +111,11 @@ static std::string announce_url_new(tr_session const* session, tr_announce_reque
{
auto ipv6_readable = std::array<char, INET6_ADDRSTRLEN>{};
evutil_inet_ntop(AF_INET6, ipv6, std::data(ipv6_readable), std::size(ipv6_readable));
evbuffer_add_printf(buf, "&ipv6=");
tr_http_escape(buf, std::data(ipv6_readable), true);
fmt::format_to(out, "&ipv6=");
tr_http_escape(out, std::data(ipv6_readable), true);
}
return evbuffer_free_to_str(buf);
return url;
}
static void verboseLog(std::string_view description, tr_direction direction, std::string_view message)
@ -343,7 +341,7 @@ void tr_tracker_http_announce(
auto const url = announce_url_new(session, request);
tr_logAddTrace(fmt::format("Sending announce to libcurl: '{}'", url), request->log_name);
auto options = tr_web::FetchOptions{ url, onAnnounceDone, d };
auto options = tr_web::FetchOptions{ url.sv(), onAnnounceDone, d };
options.timeout_secs = 90L;
options.sndbuf = 1024;
options.rcvbuf = 3072;
@ -498,23 +496,22 @@ static void onScrapeDone(tr_web::FetchResponse const& web_response)
delete data;
}
static std::string scrape_url_new(tr_scrape_request const* req)
static auto scrape_url_new(tr_scrape_request const* req)
{
auto const sv = req->scrape_url.sv();
char delimiter = tr_strvContains(sv, '?') ? '&' : '?';
auto* const buf = evbuffer_new();
evbuffer_add(buf, std::data(sv), std::size(sv));
auto scrape_url = tr_pathbuf{ sv };
char delimiter = sv.find('?') == std::string_view::npos ? '?' : '&';
for (int i = 0; i < req->info_hash_count; ++i)
{
char str[SHA_DIGEST_LENGTH * 3 + 1];
tr_http_escape_sha1(str, req->info_hash[i]);
evbuffer_add_printf(buf, "%cinfo_hash=%s", delimiter, str);
scrape_url.append(delimiter, "info_hash=", str);
delimiter = '&';
}
return evbuffer_free_to_str(buf);
return scrape_url;
}
void tr_tracker_http_scrape(

View File

@ -10,7 +10,6 @@
#include <cstring>
#include <ctime>
#include <deque>
#include <iterator>
#include <map>
#include <set>
#include <string>
@ -1297,7 +1296,7 @@ static void checkMultiscrapeMax(tr_announcer* announcer, tr_scrape_response cons
{
// don't log the full URL, since that might have a personal announce id
// (note: we know 'parsed' will be successful since this url has a scrape_info)
auto const parsed = *tr_urlParse(url.sv());
auto const parsed = *tr_urlParse(url);
auto clean_url = std::string{};
tr_buildBuf(clean_url, parsed.scheme, "://"sv, parsed.host, ":"sv, parsed.portstr);
tr_logAddDebug(fmt::format("Reducing multiscrape max to {}", n), clean_url);
@ -1325,8 +1324,6 @@ static void on_scrape_done(tr_scrape_response const* response, void* vsession)
continue;
}
auto const scrape_url_sv = response->scrape_url.sv();
tr_logAddTraceTier(
tier,
fmt::format(
@ -1340,7 +1337,7 @@ static void on_scrape_done(tr_scrape_response const* response, void* vsession)
"downloaders:{} "
"min_request_interval:{} "
"err:{} ",
scrape_url_sv,
response->scrape_url.sv(),
response->did_connect,
response->did_timeout,
row.seeders,

View File

@ -5,7 +5,7 @@
#include <array>
#include <cstring>
#include <cctype> // isxdigit()
#include <iterator> // back_inserter
#include <string>
#include <string_view>
@ -150,29 +150,26 @@ std::optional<tr_sha1_digest_t> parseHash(std::string_view sv)
****
***/
std::string tr_magnet_metainfo::magnet() const
tr_urlbuf tr_magnet_metainfo::magnet() const
{
auto s = std::string{};
s += "magnet:?xt=urn:btih:"sv;
s += infoHashString();
auto s = tr_urlbuf{ "magnet:?xt=urn:btih:"sv, infoHashString() };
if (!std::empty(name_))
{
s += "&dn="sv;
tr_http_escape(s, name_, true);
tr_http_escape(std::back_inserter(s), name_, true);
}
for (auto const& tracker : this->announceList())
{
s += "&tr="sv;
tr_http_escape(s, tracker.announce.full, true);
tr_http_escape(std::back_inserter(s), tracker.announce.full, true);
}
for (auto const& webseed : webseed_urls_)
{
s += "&ws="sv;
tr_http_escape(s, webseed, true);
tr_http_escape(std::back_inserter(s), webseed, true);
}
return s;

View File

@ -12,6 +12,7 @@
#include "transmission.h"
#include "announce-list.h"
#include "tr-strbuf.h" // tr_urlbuf
struct tr_error;
struct tr_variant;
@ -21,7 +22,7 @@ class tr_magnet_metainfo
public:
bool parseMagnet(std::string_view magnet_link, tr_error** error = nullptr);
std::string magnet() const;
[[nodiscard]] tr_urlbuf magnet() const;
auto const& infoHash() const
{

View File

@ -958,7 +958,8 @@ void save(tr_torrent* tor)
saveLabels(&top, tor);
saveGroup(&top, tor);
if (auto const err = tr_variantToFile(&top, TR_VARIANT_FMT_BENC, tor->resumeFile()); err != 0)
auto const resume_file = tor->resumeFile();
if (auto const err = tr_variantToFile(&top, TR_VARIANT_FMT_BENC, resume_file); err != 0)
{
tor->setLocalError(tr_strvJoin("Unable to save resume file: ", tr_strerror(err)));
}

View File

@ -794,7 +794,7 @@ static void initField(tr_torrent const* const tor, tr_stat const* const st, tr_v
}
case TR_KEY_torrentFile:
tr_variantInitStrView(initme, tor->torrentFile());
tr_variantInitStr(initme, tor->torrentFile());
break;
case TR_KEY_totalSize:
@ -1084,6 +1084,7 @@ static char const* addTrackerUrls(tr_torrent* tor, tr_variant* urls)
}
tor->announceList().save(tor->torrentFile());
return nullptr;
}
@ -1109,6 +1110,7 @@ static char const* replaceTrackers(tr_torrent* tor, tr_variant* urls)
}
tor->announceList().save(tor->torrentFile());
return nullptr;
}
@ -1134,6 +1136,7 @@ static char const* removeTrackers(tr_torrent* tor, tr_variant* ids)
}
tor->announceList().save(tor->torrentFile());
return nullptr;
}

View File

@ -124,8 +124,14 @@ tr_peer_id_t tr_peerIdInit()
std::optional<std::string> tr_session::WebMediator::cookieFile() const
{
auto const str = tr_strvPath(session_->config_dir, "cookies.txt");
return tr_sys_path_exists(str.c_str()) ? std::optional<std::string>{ str } : std::nullopt;
auto const path = tr_pathbuf{ session_->config_dir, "/cookies.txt" };
if (!tr_sys_path_exists(path))
{
return {};
}
return std::string{ path };
}
std::optional<std::string> tr_session::WebMediator::userAgent() const
@ -489,7 +495,7 @@ bool tr_sessionLoadSettings(tr_variant* dict, char const* config_dir, char const
auto fileSettings = tr_variant{};
auto const filename = tr_pathbuf{ config_dir, "/settings.json"sv };
auto success = bool{};
if (tr_error* error = nullptr; tr_variantFromFile(&fileSettings, TR_VARIANT_PARSE_JSON, filename.sv(), &error))
if (tr_error* error = nullptr; tr_variantFromFile(&fileSettings, TR_VARIANT_PARSE_JSON, filename, &error))
{
tr_variantMergeDicts(dict, &fileSettings);
tr_variantFree(&fileSettings);
@ -518,7 +524,7 @@ void tr_sessionSaveSettings(tr_session* session, char const* config_dir, tr_vari
{
tr_variant fileSettings;
if (tr_variantFromFile(&fileSettings, TR_VARIANT_PARSE_JSON, filename.sv(), nullptr))
if (tr_variantFromFile(&fileSettings, TR_VARIANT_PARSE_JSON, filename, nullptr))
{
tr_variantMergeDicts(&settings, &fileSettings);
tr_variantFree(&fileSettings);
@ -538,7 +544,7 @@ void tr_sessionSaveSettings(tr_session* session, char const* config_dir, tr_vari
}
/* save the result */
tr_variantToFile(&settings, TR_VARIANT_FMT_JSON, filename.sv());
tr_variantToFile(&settings, TR_VARIANT_FMT_JSON, filename);
/* cleanup */
tr_variantFree(&settings);
@ -2033,9 +2039,9 @@ static void sessionLoadTorrents(struct sessionLoadTorrentsData* const data)
auto const path = tr_pathbuf{ dirname_sv, "/"sv, name };
// is a magnet link?
if (!tr_ctorSetMetainfoFromFile(data->ctor, std::string{ path.sv() }, nullptr))
if (!tr_ctorSetMetainfoFromFile(data->ctor, std::string{ path }, nullptr))
{
if (auto buf = std::vector<char>{}; tr_loadFile(path.sv(), buf))
if (auto buf = std::vector<char>{}; tr_loadFile(path, buf))
{
tr_ctorSetMetainfoFromMagnetLink(
data->ctor,
@ -2876,7 +2882,7 @@ static void bandwidthGroupRead(tr_session* session, std::string_view config_dir)
{
auto const filename = tr_pathbuf{ config_dir, "/"sv, BandwidthGroupsFilename };
auto groups_dict = tr_variant{};
if (!tr_variantFromFile(&groups_dict, TR_VARIANT_PARSE_JSON, filename.sv(), nullptr) || !tr_variantIsList(&groups_dict))
if (!tr_variantFromFile(&groups_dict, TR_VARIANT_PARSE_JSON, filename, nullptr) || !tr_variantIsList(&groups_dict))
{
return;
}
@ -2887,7 +2893,7 @@ static void bandwidthGroupRead(tr_session* session, std::string_view config_dir)
while (tr_variantDictChild(&groups_dict, idx++, &key, &dict))
{
auto name = tr_interned_string(key);
auto& group = session->getBandwidthGroup(name.sv());
auto& group = session->getBandwidthGroup(name);
auto limits = tr_bandwidth_limits{};
tr_variantDictFindBool(dict, TR_KEY_uploadLimited, &limits.up_limited);
@ -2935,7 +2941,7 @@ static int bandwidthGroupWrite(tr_session const* session, std::string_view confi
}
auto const filename = tr_pathbuf{ config_dir, "/"sv, BandwidthGroupsFilename };
auto const ret = tr_variantToFile(&groups_dict, TR_VARIANT_FMT_JSON, filename.sv());
auto const ret = tr_variantToFile(&groups_dict, TR_VARIANT_FMT_JSON, filename);
tr_variantFree(&groups_dict);
return ret;
}

View File

@ -113,7 +113,7 @@ char const* tr_ctorGetSourceFile(tr_ctor const* ctor)
return ctor->torrent_filename.c_str();
}
bool tr_ctorSaveContents(tr_ctor const* ctor, std::string const& filename, tr_error** error)
bool tr_ctorSaveContents(tr_ctor const* ctor, std::string_view filename, tr_error** error)
{
TR_ASSERT(ctor != nullptr);
TR_ASSERT(!std::empty(filename));
@ -127,20 +127,6 @@ bool tr_ctorSaveContents(tr_ctor const* ctor, std::string const& filename, tr_er
return tr_saveFile(filename, ctor->contents, error);
}
bool tr_ctorSaveMagnetContents(tr_torrent* tor, std::string const& filename, tr_error** error)
{
TR_ASSERT(tor != nullptr);
TR_ASSERT(!std::empty(filename));
auto const magnet = tor->magnet();
if (std::empty(magnet))
{
tr_error_set(error, EINVAL, "torrent has no magnetlink to save"sv);
return false;
}
return tr_saveFile(filename, magnet, error);
}
/***
****
***/

View File

@ -123,7 +123,7 @@ void* tr_torrentGetMetadataPiece(tr_torrent const* tor, int piece, size_t* len)
return nullptr;
}
auto const fd = tr_sys_file_open(tor->torrentFile().c_str(), TR_SYS_FILE_READ, 0);
auto const fd = tr_sys_file_open(tor->torrentFile(), TR_SYS_FILE_READ, 0);
if (fd == TR_BAD_SYS_FILE)
{
return nullptr;
@ -273,13 +273,13 @@ static bool useNewMetainfo(tr_torrent* tor, tr_incomplete_metadata const* m, tr_
}
// save it
if (auto const filename = tor->torrentFile(); !tr_saveFile(filename, benc, error))
if (!tr_saveFile(tor->torrentFile(), benc, error))
{
return false;
}
// remove .magnet file
tr_sys_path_remove(tor->magnetFile().c_str());
tr_sys_path_remove(tor->magnetFile());
// tor should keep this metainfo
tor->setMetainfo(metainfo);

View File

@ -6,7 +6,6 @@
#include <algorithm>
#include <array>
#include <cctype>
#include <iterator>
#include <numeric>
#include <string>
#include <string_view>
@ -501,7 +500,7 @@ tr_sha1_digest_t const& tr_torrent_metainfo::pieceHash(tr_piece_index_t piece) c
return this->pieces_[piece];
}
std::string tr_torrent_metainfo::makeFilename(
tr_pathbuf tr_torrent_metainfo::makeFilename(
std::string_view dirname,
std::string_view name,
std::string_view info_hash_string,
@ -510,8 +509,8 @@ std::string tr_torrent_metainfo::makeFilename(
{
// `${dirname}/${name}.${info_hash}${suffix}`
// `${dirname}/${info_hash}${suffix}`
return format == BasenameFormat::Hash ? tr_strvJoin(dirname, "/"sv, info_hash_string, suffix) :
tr_strvJoin(dirname, "/"sv, name, "."sv, info_hash_string.substr(0, 16), suffix);
return format == BasenameFormat::Hash ? tr_pathbuf{ dirname, "/"sv, info_hash_string, suffix } :
tr_pathbuf{ dirname, "/"sv, name, "."sv, info_hash_string.substr(0, 16), suffix };
}
bool tr_torrent_metainfo::migrateFile(
@ -556,11 +555,8 @@ void tr_torrent_metainfo::removeFile(
std::string_view info_hash_string,
std::string_view suffix)
{
auto filename = makeFilename(dirname, name, info_hash_string, BasenameFormat::NameAndPartialHash, suffix);
tr_sys_path_remove(filename.c_str());
filename = makeFilename(dirname, name, info_hash_string, BasenameFormat::Hash, suffix);
tr_sys_path_remove(filename.c_str());
tr_sys_path_remove(makeFilename(dirname, name, info_hash_string, BasenameFormat::NameAndPartialHash, suffix));
tr_sys_path_remove(makeFilename(dirname, name, info_hash_string, BasenameFormat::Hash, suffix));
}
std::string const& tr_torrent_metainfo::fileSubpath(tr_file_index_t i) const

View File

@ -14,6 +14,7 @@
#include "block-info.h"
#include "magnet-metainfo.h"
#include "tr-strbuf.h"
struct tr_error;
@ -134,17 +135,17 @@ public:
return pieces_offset_;
}
[[nodiscard]] std::string torrentFile(std::string_view torrent_dir) const
[[nodiscard]] auto torrentFile(std::string_view torrent_dir) const
{
return makeFilename(torrent_dir, name(), infoHashString(), BasenameFormat::Hash, ".torrent");
}
[[nodiscard]] std::string magnetFile(std::string_view torrent_dir) const
[[nodiscard]] auto magnetFile(std::string_view torrent_dir) const
{
return makeFilename(torrent_dir, name(), infoHashString(), BasenameFormat::Hash, ".magnet");
}
[[nodiscard]] std::string resumeFile(std::string_view resume_dir) const
[[nodiscard]] auto resumeFile(std::string_view resume_dir) const
{
return makeFilename(resume_dir, name(), infoHashString(), BasenameFormat::Hash, ".resume");
}
@ -175,14 +176,14 @@ private:
NameAndPartialHash
};
static std::string makeFilename(
[[nodiscard]] static tr_pathbuf makeFilename(
std::string_view dirname,
std::string_view name,
std::string_view info_hash_string,
BasenameFormat format,
std::string_view suffix);
[[nodiscard]] std::string makeFilename(std::string_view dirname, BasenameFormat format, std::string_view suffix) const
auto makeFilename(std::string_view dirname, BasenameFormat format, std::string_view suffix) const
{
return makeFilename(dirname, name(), infoHashString(), format, suffix);
}

View File

@ -3,13 +3,12 @@
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#include <algorithm> /* EINVAL */
#include <algorithm>
#include <array>
#include <cerrno> /* EINVAL */
#include <cerrno> // EINVAL
#include <climits> /* INT_MAX */
#include <cmath>
#include <csignal> /* signal() */
#include <cstring> /* memcmp */
#include <ctime>
#include <map>
#include <set>
@ -779,29 +778,34 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor)
[](auto mtime) { return mtime > 0; });
}
// if we don't have a local .torrent or .magnet file already, assume the torrent is new
auto const filename = tor->hasMetadata() ? tor->torrentFile() : tor->magnetFile();
// if we don't have a local .torrent or .magnet file already,
// assume the torrent is new
bool const is_new_torrent = !tr_sys_path_exists(filename.c_str());
if (is_new_torrent)
{
tr_error* error = nullptr;
if (tor->hasMetadata())
if (tor->hasMetadata()) // torrent file
{
if (!tr_ctorSaveContents(ctor, filename, &error))
{
tor->setLocalError(
tr_strvJoin("Unable to save torrent file: ", error->message, " ("sv, std::to_string(error->code), ")"sv));
}
tr_ctorSaveContents(ctor, filename, &error);
}
else
else // magnet link
{
// magnet link
if (!tr_ctorSaveMagnetContents(tor, filename, &error))
{
tor->setLocalError(
tr_strvJoin("Unable to save magnet file: ", error->message, " ("sv, std::to_string(error->code), ")"sv));
}
auto const magnet_link = tor->magnet();
tr_saveFile(filename, magnet_link, &error);
}
if (error != nullptr)
{
tor->setLocalError(fmt::format(
_("Couldn't save '{path}': {error} ({error_code})"),
fmt::arg("path", filename),
fmt::arg("error", error->message),
fmt::arg("error_code", error->code)));
tr_error_clear(&error);
}
tr_error_clear(&error);
@ -1782,7 +1786,7 @@ static void torrentCallScript(tr_torrent const* tor, char const* script)
tr_localtime_r(&now, &tm);
strftime(ctime_str, sizeof(ctime_str), "%a %b %d %T %Y%n", &tm); /* ctime equiv */
auto torrent_dir = std::string{ tor->currentDir().sv() };
auto torrent_dir = std::string{ tor->currentDir() };
tr_sys_path_native_separators(std::data(torrent_dir));
auto const cmd = std::array<char const*, 2>{ script, nullptr };
@ -2085,7 +2089,7 @@ bool tr_torrent::setTrackerList(std::string_view text)
}
auto const has_metadata = this->hasMetadata();
if (has_metadata && !announce_list.save(this->torrentFile()))
if (has_metadata && !announce_list.save(torrentFile()))
{
return false;
}
@ -2096,12 +2100,14 @@ bool tr_torrent::setTrackerList(std::string_view text)
// magnet links
if (!has_metadata)
{
auto const magnet_file = magnetFile();
auto const magnet_link = this->magnet();
tr_error* save_error = nullptr;
if (!tr_ctorSaveMagnetContents(this, this->magnetFile(), &save_error))
if (!tr_saveFile(magnet_file, magnet_link, &save_error))
{
this->setLocalError(fmt::format(
_("Couldn't save '{path}': {error} ({error_code})"),
fmt::arg("path", this->magnetFile()),
fmt::arg("path", magnet_file),
fmt::arg("error", save_error->message),
fmt::arg("error_code", save_error->code)));
tr_error_clear(&save_error);
@ -2242,7 +2248,7 @@ static void deleteLocalData(tr_torrent const* tor, tr_fileFunc func)
{
auto files = std::vector<std::string>{};
auto folders = std::set<std::string>{};
auto const top = std::string{ tor->currentDir().sv() };
auto const top = std::string{ tor->currentDir() };
/* don't try to delete local data if the directory's gone missing */
if (!tr_sys_path_exists(top.c_str()))
@ -2674,7 +2680,7 @@ std::optional<tr_torrent::tr_found_file_t> tr_torrent::findFile(std::string& fil
if (!std::empty(this->downloadDir()))
{
auto const base = this->downloadDir().sv();
auto const base = this->downloadDir();
tr_buildBuf(filename, base, "/"sv, subpath);
if (tr_sys_path_get_info(filename.c_str(), 0, &file_info))
@ -2691,7 +2697,7 @@ std::optional<tr_torrent::tr_found_file_t> tr_torrent::findFile(std::string& fil
if (!std::empty(this->incompleteDir()))
{
auto const base = this->incompleteDir().sv();
auto const base = this->incompleteDir();
tr_buildBuf(filename, base, "/"sv, subpath);
if (tr_sys_path_get_info(filename.c_str(), 0, &file_info))
@ -2960,7 +2966,7 @@ static int renamePath(tr_torrent* tor, char const* oldpath, char const* newname)
{
int err = 0;
auto const base = tor->isDone() || std::empty(tor->incompleteDir()) ? tor->downloadDir().sv() : tor->incompleteDir().sv();
auto const base = tor->isDone() || std::empty(tor->incompleteDir()) ? tor->downloadDir() : tor->incompleteDir();
auto src = tr_strvPath(base, oldpath);

View File

@ -31,6 +31,7 @@
#include "session.h"
#include "torrent-metainfo.h"
#include "tr-macros.h"
#include "tr-strbuf.h"
class tr_swarm;
struct tr_error;
@ -52,9 +53,7 @@ void tr_ctorInitTorrentPriorities(tr_ctor const* ctor, tr_torrent* tor);
void tr_ctorInitTorrentWanted(tr_ctor const* ctor, tr_torrent* tor);
bool tr_ctorSaveContents(tr_ctor const* ctor, std::string const& filename, tr_error** error);
bool tr_ctorSaveMagnetContents(tr_torrent* tor, std::string const& filename, tr_error** error);
bool tr_ctorSaveContents(tr_ctor const* ctor, std::string_view filename, tr_error** error);
std::string_view tr_ctorGetContents(tr_ctor const* ctor);

View File

@ -14,7 +14,6 @@
#include <cstdlib> // getenv()
#include <cstring> /* strerror() */
#include <ctime> // nanosleep()
#include <iterator> // std::back_inserter
#include <set>
#include <string>
#include <string_view>

View File

@ -13,8 +13,6 @@
#include <string>
#include <string_view>
#include <event2/buffer.h>
#define PSL_STATIC
#include <libpsl.h>
@ -171,44 +169,6 @@ char const* tr_webGetResponseStr(long code)
}
}
void tr_http_escape(struct evbuffer* out, std::string_view str, bool escape_reserved)
{
auto constexpr ReservedChars = std::string_view{ "!*'();:@&=+$,/?%#[]" };
auto constexpr UnescapedChars = std::string_view{ "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.~" };
for (auto const& ch : str)
{
if (tr_strvContains(UnescapedChars, ch) || (tr_strvContains(ReservedChars, ch) && !escape_reserved))
{
evbuffer_add_printf(out, "%c", ch);
}
else
{
evbuffer_add_printf(out, "%%%02X", (unsigned)(ch & 0xFF));
}
}
}
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 const& 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 == '-' ||

View File

@ -11,6 +11,8 @@
#include <utility>
#include "tr-macros.h" // tr_sha1_digest_t
#include "tr-strbuf.h" // tr_urlbuf
#include "utils.h"
struct evbuffer;
@ -91,10 +93,24 @@ struct tr_url_query_view
}
};
void tr_http_escape(std::string& appendme, std::string_view str, bool escape_reserved);
template<typename OutputIt>
void tr_http_escape(OutputIt out, std::string_view str, bool escape_reserved)
{
auto constexpr ReservedChars = std::string_view{ "!*'();:@&=+$,/?%#[]" };
auto constexpr UnescapedChars = std::string_view{ "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.~" };
// TODO: remove evbuffer version
void tr_http_escape(struct evbuffer* out, std::string_view str, bool escape_reserved);
for (auto const& ch : str)
{
if (tr_strvContains(UnescapedChars, ch) || (tr_strvContains(ReservedChars, ch) && !escape_reserved))
{
out = ch;
}
else
{
fmt::format_to(out, "%{:02X}", unsigned(ch & 0xFF));
}
}
}
void tr_http_escape_sha1(char* out, uint8_t const* sha1_digest);

View File

@ -4,6 +4,7 @@
// License text can be found in the licenses/ folder.
#include <algorithm>
#include <iterator>
#include <set>
#include <string>
#include <string_view>
@ -456,16 +457,17 @@ void onPartialDataFetched(tr_web::FetchResponse const& web_response)
on_idle(webseed);
}
std::string make_url(tr_webseed* w, std::string_view name)
template<typename OutputIt>
void makeUrl(tr_webseed* w, std::string_view name, OutputIt out)
{
auto url = w->base_url;
auto const url = w->base_url;
out = std::copy(std::begin(url), std::end(url), out);
if (tr_strvEndsWith(url, "/"sv) && !std::empty(name))
{
tr_http_escape(url, name, false);
tr_http_escape(out, name, false);
}
return url;
}
void task_request_next_chunk(tr_webseed_task* task)
@ -485,8 +487,9 @@ void task_request_next_chunk(tr_webseed_task* task)
webseed->connection_limiter.taskStarted();
auto const url = make_url(webseed, tor->fileSubpath(file_index));
auto options = tr_web::FetchOptions{ url, onPartialDataFetched, task };
auto url = tr_urlbuf{};
makeUrl(webseed, tor->fileSubpath(file_index), std::back_inserter(url));
auto options = tr_web::FetchOptions{ url.sv(), onPartialDataFetched, task };
options.range = tr_strvJoin(std::to_string(file_offset), "-"sv, std::to_string(file_offset + this_chunk - 1));
options.speed_limit_tag = tor->uniqueId;
options.buffer = task->content();

View File

@ -42,7 +42,7 @@ TEST_F(TorrentMetainfoTest, magnetLink)
EXPECT_TRUE(metainfo.parseMagnet(MagnetLink));
EXPECT_EQ(0, metainfo.fileCount()); // because it's a magnet link
EXPECT_EQ(2, std::size(metainfo.announceList()));
EXPECT_EQ(MagnetLink, metainfo.magnet());
EXPECT_EQ(MagnetLink, metainfo.magnet().sv());
}
#define BEFORE_PATH \
@ -166,7 +166,7 @@ TEST_F(TorrentMetainfoTest, ctorSaveContents)
// try saving without passing any metainfo.
auto* ctor = tr_ctorNew(session_);
tr_error* error = nullptr;
EXPECT_FALSE(tr_ctorSaveContents(ctor, tgt_filename.c_str(), &error));
EXPECT_FALSE(tr_ctorSaveContents(ctor, tgt_filename.sv(), &error));
EXPECT_NE(nullptr, error);
if (error != nullptr)
{
@ -177,7 +177,7 @@ TEST_F(TorrentMetainfoTest, ctorSaveContents)
// now try saving _with_ metainfo
EXPECT_TRUE(tr_ctorSetMetainfoFromFile(ctor, src_filename.c_str(), &error));
EXPECT_EQ(nullptr, error) << *error;
EXPECT_TRUE(tr_ctorSaveContents(ctor, tgt_filename.c_str(), &error));
EXPECT_TRUE(tr_ctorSaveContents(ctor, tgt_filename.sv(), &error));
EXPECT_EQ(nullptr, error) << *error;
// the saved contents should match the source file's contents

View File

@ -3,6 +3,9 @@
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#include <cstring>
#include <set>
#include "transmission.h"
#include "torrent.h"
@ -11,9 +14,6 @@
#include "gtest/gtest.h"
#include <cstring>
#include <set>
using namespace std::literals;
using TorrentsTest = ::testing::Test;

View File

@ -7,6 +7,7 @@
#include <array>
#include <cstdio>
#include <ctime>
#include <iterator>
#include <string>
#include <string_view>