mirror of
https://github.com/transmission/transmission.git
synced 2026-05-08 09:39:08 +01:00
@@ -251,12 +251,12 @@ static void on_announce_done(
|
||||
|
||||
if (tr_variantDictFindStrView(&benc, TR_KEY_failure_reason, &sv))
|
||||
{
|
||||
response->errmsg = tr_strvdup(sv);
|
||||
response->errmsg = tr_strvDup(sv);
|
||||
}
|
||||
|
||||
if (tr_variantDictFindStrView(&benc, TR_KEY_warning_message, &sv))
|
||||
{
|
||||
response->warning = tr_strvdup(sv);
|
||||
response->warning = tr_strvDup(sv);
|
||||
}
|
||||
|
||||
if (tr_variantDictFindInt(&benc, TR_KEY_interval, &i))
|
||||
@@ -271,7 +271,7 @@ static void on_announce_done(
|
||||
|
||||
if (tr_variantDictFindStrView(&benc, TR_KEY_tracker_id, &sv))
|
||||
{
|
||||
response->tracker_id_str = tr_strvdup(sv);
|
||||
response->tracker_id_str = tr_strvDup(sv);
|
||||
}
|
||||
|
||||
if (tr_variantDictFindInt(&benc, TR_KEY_complete, &i))
|
||||
|
||||
@@ -253,7 +253,7 @@ static void trackerConstruct(tr_announcer* announcer, tr_tracker* tracker, tr_tr
|
||||
{
|
||||
memset(tracker, 0, sizeof(tr_tracker));
|
||||
tracker->key = tr_announcerGetKey(inf->announce);
|
||||
tracker->announce_url = tr_quark_new(tr_strvstrip(inf->announce));
|
||||
tracker->announce_url = tr_quark_new(tr_strvStrip(inf->announce));
|
||||
tracker->scrape_info = inf->scrape == nullptr ? nullptr : tr_announcerGetScrapeInfo(announcer, tr_quark_new(inf->scrape));
|
||||
tracker->id = inf->id;
|
||||
tracker->seederCount = -1;
|
||||
|
||||
@@ -275,3 +275,53 @@ void* tr_base64_decode_str(char const* input, size_t* output_length)
|
||||
{
|
||||
return tr_base64_decode(input, input == nullptr ? 0 : strlen(input), output_length);
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
static void tr_binary_to_hex(void const* vinput, void* voutput, size_t byte_length)
|
||||
{
|
||||
static char const hex[] = "0123456789abcdef";
|
||||
|
||||
auto const* input = static_cast<uint8_t const*>(vinput);
|
||||
auto* output = static_cast<char*>(voutput);
|
||||
|
||||
/* go from back to front to allow for in-place conversion */
|
||||
input += byte_length;
|
||||
output += byte_length * 2;
|
||||
|
||||
*output = '\0';
|
||||
|
||||
while (byte_length-- > 0)
|
||||
{
|
||||
unsigned int const val = *(--input);
|
||||
*(--output) = hex[val & 0xf];
|
||||
*(--output) = hex[val >> 4];
|
||||
}
|
||||
}
|
||||
|
||||
void tr_sha1_to_hex(void* hex, void const* sha1)
|
||||
{
|
||||
tr_binary_to_hex(sha1, hex, SHA_DIGEST_LENGTH);
|
||||
}
|
||||
|
||||
static void tr_hex_to_binary(void const* vinput, void* voutput, size_t byte_length)
|
||||
{
|
||||
static char const hex[] = "0123456789abcdef";
|
||||
|
||||
auto const* input = static_cast<uint8_t const*>(vinput);
|
||||
auto* output = static_cast<uint8_t*>(voutput);
|
||||
|
||||
for (size_t i = 0; i < byte_length; ++i)
|
||||
{
|
||||
int const hi = strchr(hex, tolower(*input++)) - hex;
|
||||
int const lo = strchr(hex, tolower(*input++)) - hex;
|
||||
*output++ = (uint8_t)((hi << 4) | lo);
|
||||
}
|
||||
}
|
||||
|
||||
void tr_hex_to_sha1(void* sha1, void const* hex)
|
||||
{
|
||||
tr_hex_to_binary(hex, sha1, SHA_DIGEST_LENGTH);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
#include "transmission.h" /* SHA_DIGEST_LENGTH */
|
||||
#include "tr-macros.h"
|
||||
#include "utils.h" // tr_binary_to_hex(), tr_hex_to_binary()
|
||||
|
||||
/**
|
||||
*** @addtogroup utils Utilities
|
||||
@@ -178,18 +177,12 @@ void* tr_base64_decode_str(char const* input, size_t* output_length) TR_GNUC_MAL
|
||||
/**
|
||||
* @brief Wrapper around tr_binary_to_hex() for SHA_DIGEST_LENGTH.
|
||||
*/
|
||||
static inline void tr_sha1_to_hex(void* hex, void const* sha1)
|
||||
{
|
||||
tr_binary_to_hex(sha1, hex, SHA_DIGEST_LENGTH);
|
||||
}
|
||||
void tr_sha1_to_hex(void* hex, void const* sha1);
|
||||
|
||||
/**
|
||||
* @brief Wrapper around tr_hex_to_binary() for SHA_DIGEST_LENGTH.
|
||||
*/
|
||||
static inline void tr_hex_to_sha1(void* sha1, void const* hex)
|
||||
{
|
||||
tr_hex_to_binary(hex, sha1, SHA_DIGEST_LENGTH);
|
||||
}
|
||||
void tr_hex_to_sha1(void* sha1, void const* hex);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
||||
@@ -126,16 +126,16 @@ tr_magnet_info* tr_magnetParse(std::string_view magnet_link)
|
||||
{
|
||||
if (key == "dn"sv)
|
||||
{
|
||||
display_name = tr_strvdup(tr_urlPercentDecode(value));
|
||||
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)));
|
||||
tr.push_back(tr_strvDup(tr_urlPercentDecode(value)));
|
||||
}
|
||||
else if (key == "ws"sv)
|
||||
{
|
||||
ws.push_back(tr_strvdup(tr_urlPercentDecode(value)));
|
||||
ws.push_back(tr_strvDup(tr_urlPercentDecode(value)));
|
||||
}
|
||||
else if (key == "xt"sv)
|
||||
{
|
||||
|
||||
@@ -368,21 +368,15 @@ static void getFileInfo(
|
||||
/* build the path list */
|
||||
tr_variantInitList(uninitialized_path, 0);
|
||||
|
||||
if (strlen(file->filename) > offset)
|
||||
auto filename = std::string_view{ file->filename };
|
||||
if (std::size(filename) > offset)
|
||||
{
|
||||
char* filename = tr_strdup(file->filename + offset);
|
||||
char* walk = filename;
|
||||
char const* token = nullptr;
|
||||
|
||||
while ((token = tr_strsep(&walk, TR_PATH_DELIMITER_STR)) != nullptr)
|
||||
filename.remove_prefix(offset);
|
||||
auto token = std::string_view{};
|
||||
while (tr_strvSep(&filename, &token, TR_PATH_DELIMITER))
|
||||
{
|
||||
if (!tr_str_is_empty(token))
|
||||
{
|
||||
tr_variantListAddStr(uninitialized_path, token);
|
||||
}
|
||||
tr_variantListAddStr(uninitialized_path, token);
|
||||
}
|
||||
|
||||
tr_free(filename);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -127,7 +127,7 @@ bool tr_metainfoAppendSanitizedPathComponent(std::string& out, std::string_view
|
||||
auto constexpr ensure_legal_char = [](auto ch)
|
||||
{
|
||||
auto constexpr Banned = std::string_view{ "<>:\"/\\|?*" };
|
||||
auto const banned = Banned.find(ch) != Banned.npos || (unsigned char)ch < 0x20;
|
||||
auto const banned = tr_strvContains(Banned, ch) || (unsigned char)ch < 0x20;
|
||||
return banned ? '_' : ch;
|
||||
};
|
||||
auto const old_out_len = std::size(out);
|
||||
@@ -311,7 +311,7 @@ static char* tr_convertAnnounceToScrape(std::string_view url)
|
||||
// some torrents with UDP announce URLs don't have /announce
|
||||
else if (url.find("udp:"sv) == 0)
|
||||
{
|
||||
scrape = tr_strvdup(url);
|
||||
scrape = tr_strvDup(url);
|
||||
}
|
||||
|
||||
return scrape;
|
||||
@@ -348,13 +348,13 @@ static char const* getannounce(tr_info* inf, tr_variant* meta)
|
||||
{
|
||||
if (tr_variantGetStrView(tr_variantListChild(tier, j), &url))
|
||||
{
|
||||
url = tr_strvstrip(url);
|
||||
url = tr_strvStrip(url);
|
||||
|
||||
if (tr_urlIsValidTracker(url))
|
||||
{
|
||||
tr_tracker_info* t = trackers + trackerCount;
|
||||
t->tier = validTiers;
|
||||
t->announce = tr_strvdup(url);
|
||||
t->announce = tr_strvDup(url);
|
||||
t->scrape = tr_convertAnnounceToScrape(url);
|
||||
t->id = trackerCount;
|
||||
|
||||
@@ -381,13 +381,13 @@ static char const* getannounce(tr_info* inf, tr_variant* meta)
|
||||
/* Regular announce value */
|
||||
if (trackerCount == 0 && tr_variantDictFindStrView(meta, TR_KEY_announce, &url))
|
||||
{
|
||||
url = tr_strvstrip(url);
|
||||
url = tr_strvStrip(url);
|
||||
|
||||
if (tr_urlIsValidTracker(url))
|
||||
{
|
||||
trackers = tr_new0(tr_tracker_info, 1);
|
||||
trackers[trackerCount].tier = 0;
|
||||
trackers[trackerCount].announce = tr_strvdup(url);
|
||||
trackers[trackerCount].announce = tr_strvDup(url);
|
||||
trackers[trackerCount].scrape = tr_convertAnnounceToScrape(url);
|
||||
trackers[trackerCount].id = 0;
|
||||
trackerCount++;
|
||||
@@ -413,7 +413,7 @@ static char const* getannounce(tr_info* inf, tr_variant* meta)
|
||||
*/
|
||||
static char* fix_webseed_url(tr_info const* inf, std::string_view url)
|
||||
{
|
||||
url = tr_strvstrip(url);
|
||||
url = tr_strvStrip(url);
|
||||
|
||||
if (!tr_urlIsValid(url))
|
||||
{
|
||||
@@ -425,7 +425,7 @@ static char* fix_webseed_url(tr_info const* inf, std::string_view url)
|
||||
return tr_strdup_printf("%" TR_PRIsv "/", TR_PRIsv_ARG(url));
|
||||
}
|
||||
|
||||
return tr_strvdup(url);
|
||||
return tr_strvDup(url);
|
||||
}
|
||||
|
||||
static void geturllist(tr_info* inf, tr_variant* meta)
|
||||
@@ -514,8 +514,8 @@ static char const* tr_metainfoParseImpl(
|
||||
{
|
||||
tr_free(inf->name);
|
||||
tr_free(inf->originalName);
|
||||
inf->name = tr_strvdup(sv);
|
||||
inf->originalName = tr_strvdup(sv);
|
||||
inf->name = tr_strvDup(sv);
|
||||
inf->originalName = tr_strvDup(sv);
|
||||
}
|
||||
|
||||
if (inf->name == nullptr)
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "crypto.h"
|
||||
#include "net.h" /* tr_address */
|
||||
#include "peer-socket.h"
|
||||
#include "utils.h" // tr_time()
|
||||
|
||||
class tr_peerIo;
|
||||
struct Bandwidth;
|
||||
|
||||
@@ -396,7 +396,7 @@ static uint64_t loadName(tr_variant* dict, tr_torrent* tor)
|
||||
if (name != tr_torrentName(tor))
|
||||
{
|
||||
tr_free(tor->info.name);
|
||||
tor->info.name = tr_strvdup(name);
|
||||
tor->info.name = tr_strvDup(name);
|
||||
}
|
||||
|
||||
return TR_FR_NAME;
|
||||
@@ -445,7 +445,7 @@ static uint64_t loadFilenames(tr_variant* dict, tr_torrent* tor)
|
||||
if (tr_variantGetStrView(tr_variantListChild(list, i), &sv) && !std::empty(sv))
|
||||
{
|
||||
tr_free(files[i].name);
|
||||
files[i].name = tr_strvdup(sv);
|
||||
files[i].name = tr_strvDup(sv);
|
||||
files[i].is_renamed = true;
|
||||
}
|
||||
}
|
||||
@@ -792,7 +792,7 @@ static uint64_t loadFromFile(tr_torrent* tor, uint64_t fieldsToLoad, bool* didRe
|
||||
{
|
||||
bool const is_current_dir = tor->currentDir == tor->downloadDir;
|
||||
tr_free(tor->downloadDir);
|
||||
tor->downloadDir = tr_strvdup(sv);
|
||||
tor->downloadDir = tr_strvDup(sv);
|
||||
|
||||
if (is_current_dir)
|
||||
{
|
||||
@@ -807,7 +807,7 @@ static uint64_t loadFromFile(tr_torrent* tor, uint64_t fieldsToLoad, bool* didRe
|
||||
{
|
||||
bool const is_current_dir = tor->currentDir == tor->incompleteDir;
|
||||
tr_free(tor->incompleteDir);
|
||||
tor->incompleteDir = tr_strvdup(sv);
|
||||
tor->incompleteDir = tr_strvDup(sv);
|
||||
|
||||
if (is_current_dir)
|
||||
{
|
||||
|
||||
@@ -1149,7 +1149,7 @@ tr_rpc_server* tr_rpcInit(tr_session* session, tr_variant* settings)
|
||||
}
|
||||
else
|
||||
{
|
||||
s->url = tr_strvdup(url);
|
||||
s->url = tr_strvDup(url);
|
||||
}
|
||||
|
||||
key = TR_KEY_rpc_whitelist_enabled;
|
||||
|
||||
@@ -962,13 +962,13 @@ static char const* setLabels(tr_torrent* tor, tr_variant* list)
|
||||
continue;
|
||||
}
|
||||
|
||||
label = tr_strvstrip(label);
|
||||
label = tr_strvStrip(label);
|
||||
if (std::empty(label))
|
||||
{
|
||||
return "labels cannot be empty";
|
||||
}
|
||||
|
||||
if (label.find(',') != label.npos)
|
||||
if (tr_strvContains(label, ','))
|
||||
{
|
||||
return "labels cannot contain comma (,) character";
|
||||
}
|
||||
@@ -1115,7 +1115,7 @@ static char const* addTrackerUrls(tr_torrent* tor, tr_variant* urls)
|
||||
if (tr_variantGetStrView(val, &announce) && tr_urlIsValidTracker(announce) && !hasAnnounceUrl(trackers, n, announce))
|
||||
{
|
||||
trackers[n].tier = ++tier; /* add a new tier */
|
||||
trackers[n].announce = tr_strvdup(announce);
|
||||
trackers[n].announce = tr_strvDup(announce);
|
||||
++n;
|
||||
changed = true;
|
||||
}
|
||||
@@ -1158,7 +1158,7 @@ static char const* replaceTrackers(tr_torrent* tor, tr_variant* urls)
|
||||
pos >= 0)
|
||||
{
|
||||
tr_free(trackers[pos].announce);
|
||||
trackers[pos].announce = tr_strvdup(newval);
|
||||
trackers[pos].announce = tr_strvDup(newval);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -1820,7 +1820,7 @@ static char const* torrentAdd(tr_session* session, tr_variant* args_in, tr_varia
|
||||
}
|
||||
else
|
||||
{
|
||||
char* fname = tr_strstrip(tr_strdup(filename));
|
||||
char* fname = tr_strdup(filename);
|
||||
|
||||
if (fname == nullptr)
|
||||
{
|
||||
|
||||
@@ -2936,7 +2936,7 @@ static void deleteLocalData(tr_torrent* tor, tr_fileFunc func)
|
||||
/* go from the bottom up */
|
||||
for (auto const& file : files)
|
||||
{
|
||||
char* walk = tr_strvdup(file);
|
||||
char* walk = tr_strvDup(file);
|
||||
|
||||
while (tr_sys_path_exists(walk, nullptr) && !tr_sys_path_is_same(tmpdir, walk, nullptr))
|
||||
{
|
||||
|
||||
+39
-138
@@ -415,7 +415,7 @@ char* evbuffer_free_to_str(struct evbuffer* buf, size_t* result_len)
|
||||
return ret;
|
||||
}
|
||||
|
||||
char* tr_strvdup(std::string_view in)
|
||||
char* tr_strvDup(std::string_view in)
|
||||
{
|
||||
auto const n = std::size(in);
|
||||
auto* const ret = tr_new(char, n + 1);
|
||||
@@ -427,7 +427,7 @@ char* tr_strvdup(std::string_view in)
|
||||
char* tr_strndup(void const* vin, size_t len)
|
||||
{
|
||||
auto const* const in = static_cast<char const*>(vin);
|
||||
return in == nullptr ? nullptr : tr_strvdup({ in, len == TR_BAD_SIZE ? strlen(in) : len });
|
||||
return in == nullptr ? nullptr : tr_strvDup({ in, len == TR_BAD_SIZE ? strlen(in) : len });
|
||||
}
|
||||
|
||||
char* tr_strdup(void const* in)
|
||||
@@ -546,45 +546,7 @@ int tr_strcmp0(char const* str1, char const* str2)
|
||||
*****
|
||||
****/
|
||||
|
||||
/* https://bugs.launchpad.net/percona-patches/+bug/526863/+attachment/1160199/+files/solaris_10_fix.patch */
|
||||
char* tr_strsep(char** str, char const* delims)
|
||||
{
|
||||
#ifdef HAVE_STRSEP
|
||||
|
||||
return strsep(str, delims);
|
||||
|
||||
#else
|
||||
|
||||
char* token;
|
||||
|
||||
if (*str == nullptr) /* no more tokens */
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
token = *str;
|
||||
|
||||
while (**str != '\0')
|
||||
{
|
||||
if (strchr(delims, **str) != nullptr)
|
||||
{
|
||||
**str = '\0';
|
||||
(*str)++;
|
||||
return token;
|
||||
}
|
||||
|
||||
(*str)++;
|
||||
}
|
||||
|
||||
/* there is not another token */
|
||||
*str = nullptr;
|
||||
|
||||
return token;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string_view tr_strvstrip(std::string_view str)
|
||||
std::string_view tr_strvStrip(std::string_view str)
|
||||
{
|
||||
auto constexpr test = [](auto ch)
|
||||
{
|
||||
@@ -600,32 +562,6 @@ std::string_view tr_strvstrip(std::string_view str)
|
||||
return str;
|
||||
}
|
||||
|
||||
char* tr_strstrip(char* str)
|
||||
{
|
||||
if (str != nullptr)
|
||||
{
|
||||
size_t len = strlen(str);
|
||||
|
||||
while (len != 0 && isspace(str[len - 1]))
|
||||
{
|
||||
--len;
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
|
||||
while (pos < len && isspace(str[pos]))
|
||||
{
|
||||
++pos;
|
||||
}
|
||||
|
||||
len -= pos;
|
||||
memmove(str, str + pos, len);
|
||||
str[len] = '\0';
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
bool tr_str_has_suffix(char const* str, char const* suffix)
|
||||
{
|
||||
if (str == nullptr)
|
||||
@@ -763,42 +699,6 @@ double tr_getRatio(uint64_t numerator, uint64_t denominator)
|
||||
return TR_RATIO_NA;
|
||||
}
|
||||
|
||||
void tr_binary_to_hex(void const* vinput, void* voutput, size_t byte_length)
|
||||
{
|
||||
static char const hex[] = "0123456789abcdef";
|
||||
|
||||
auto const* input = static_cast<uint8_t const*>(vinput);
|
||||
auto* output = static_cast<char*>(voutput);
|
||||
|
||||
/* go from back to front to allow for in-place conversion */
|
||||
input += byte_length;
|
||||
output += byte_length * 2;
|
||||
|
||||
*output = '\0';
|
||||
|
||||
while (byte_length-- > 0)
|
||||
{
|
||||
unsigned int const val = *(--input);
|
||||
*(--output) = hex[val & 0xf];
|
||||
*(--output) = hex[val >> 4];
|
||||
}
|
||||
}
|
||||
|
||||
void tr_hex_to_binary(void const* vinput, void* voutput, size_t byte_length)
|
||||
{
|
||||
static char const hex[] = "0123456789abcdef";
|
||||
|
||||
auto const* input = static_cast<uint8_t const*>(vinput);
|
||||
auto* output = static_cast<uint8_t*>(voutput);
|
||||
|
||||
for (size_t i = 0; i < byte_length; ++i)
|
||||
{
|
||||
int const hi = strchr(hex, tolower(*input++)) - hex;
|
||||
int const lo = strchr(hex, tolower(*input++)) - hex;
|
||||
*output++ = (uint8_t)((hi << 4) | lo);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
@@ -838,16 +738,19 @@ static char* strip_non_utf8(char const* in, size_t inlen)
|
||||
|
||||
static char* to_utf8(char const* in, size_t inlen)
|
||||
{
|
||||
char* ret = nullptr;
|
||||
|
||||
#ifdef HAVE_ICONV
|
||||
|
||||
char const* encodings[] = { "CURRENT", "ISO-8859-15" };
|
||||
size_t const buflen = inlen * 4 + 10;
|
||||
char* out = tr_new(char, buflen);
|
||||
|
||||
for (size_t i = 0; ret == nullptr && i < TR_N_ELEMENTS(encodings); ++i)
|
||||
auto constexpr Encodings = std::array<char const*, 2>{ "CURRENT", "ISO-8859-15" };
|
||||
for (auto const* test_encoding : Encodings)
|
||||
{
|
||||
iconv_t cd = iconv_open("UTF-8", test_encoding);
|
||||
if (cd == (iconv_t)-1) // NOLINT(performance-no-int-to-ptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef ICONV_SECOND_ARGUMENT_IS_CONST
|
||||
auto const* inbuf = in;
|
||||
#else
|
||||
@@ -856,18 +759,13 @@ static char* to_utf8(char const* in, size_t inlen)
|
||||
char* outbuf = out;
|
||||
size_t inbytesleft = inlen;
|
||||
size_t outbytesleft = buflen;
|
||||
char const* test_encoding = encodings[i];
|
||||
|
||||
iconv_t cd = iconv_open("UTF-8", test_encoding);
|
||||
|
||||
if (cd != (iconv_t)-1) // NOLINT(performance-no-int-to-ptr)
|
||||
auto const rv = iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
|
||||
iconv_close(cd);
|
||||
if (rv != size_t(-1))
|
||||
{
|
||||
if (iconv(cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft) != (size_t)-1)
|
||||
{
|
||||
ret = tr_strndup(out, buflen - outbytesleft);
|
||||
}
|
||||
|
||||
iconv_close(cd);
|
||||
char* const ret = tr_strndup(out, buflen - outbytesleft);
|
||||
tr_free(out);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -875,12 +773,7 @@ static char* to_utf8(char const* in, size_t inlen)
|
||||
|
||||
#endif
|
||||
|
||||
if (ret == nullptr)
|
||||
{
|
||||
ret = strip_non_utf8(in, inlen);
|
||||
}
|
||||
|
||||
return ret;
|
||||
return strip_non_utf8(in, inlen);
|
||||
}
|
||||
|
||||
char* tr_utf8clean(std::string_view str)
|
||||
@@ -891,6 +784,24 @@ char* tr_utf8clean(std::string_view str)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool tr_strvUtf8Validate(std::string_view sv)
|
||||
{
|
||||
return tr_utf8_validate(std::data(sv), std::size(sv), nullptr);
|
||||
}
|
||||
|
||||
std::string tr_strvUtf8Clean(std::string_view sv)
|
||||
{
|
||||
if (tr_strvUtf8Validate(sv))
|
||||
{
|
||||
return std::string{ sv };
|
||||
}
|
||||
|
||||
auto* const tmp = to_utf8(std::data(sv), std::size(sv));
|
||||
auto ret = std::string{ tmp ? tmp : "" };
|
||||
tr_free(tmp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
char* tr_win32_native_to_utf8(wchar_t const* text, int text_size)
|
||||
@@ -1161,24 +1072,14 @@ static bool parseNumberSection(std::string_view str, number_range& range)
|
||||
std::vector<int> tr_parseNumberRange(std::string_view str)
|
||||
{
|
||||
auto values = std::set<int>{};
|
||||
|
||||
for (;;)
|
||||
auto token = std::string_view{};
|
||||
auto range = number_range{};
|
||||
while (tr_strvSep(&str, &token, ',') && parseNumberSection(token, range))
|
||||
{
|
||||
auto const delim = str.find(',');
|
||||
auto range = number_range{};
|
||||
if (!parseNumberSection(str.substr(0, delim), range))
|
||||
{
|
||||
break;
|
||||
}
|
||||
for (auto i = range.low; i <= range.high; ++i)
|
||||
{
|
||||
values.insert(i);
|
||||
}
|
||||
if (delim == std::string_view::npos)
|
||||
{
|
||||
break;
|
||||
}
|
||||
str.remove_prefix(delim + 1);
|
||||
}
|
||||
|
||||
return { std::begin(values), std::end(values) };
|
||||
|
||||
+38
-13
@@ -208,8 +208,6 @@ void* tr_memdup(void const* src, size_t byteCount);
|
||||
*/
|
||||
char* tr_strndup(void const* in, size_t len) TR_GNUC_MALLOC;
|
||||
|
||||
char* tr_strvdup(std::string_view) TR_GNUC_MALLOC;
|
||||
|
||||
/**
|
||||
* @brief make a newly-allocated copy of a string
|
||||
* @param in is a void* so that callers can pass in both signed & unsigned without a cast
|
||||
@@ -246,12 +244,6 @@ int tr_snprintf(void* buf, size_t buflen, char const* fmt, ...) TR_GNUC_PRINTF(3
|
||||
@param errnum the error number to describe */
|
||||
char const* tr_strerror(int errnum);
|
||||
|
||||
/** @brief strips leading and trailing whitspace from a string
|
||||
@return the stripped string */
|
||||
char* tr_strstrip(char* str);
|
||||
|
||||
std::string_view tr_strvstrip(std::string_view str);
|
||||
|
||||
/** @brief Returns true if the string ends with the specified case-insensitive suffix */
|
||||
bool tr_str_has_suffix(char const* str, char const* suffix);
|
||||
|
||||
@@ -261,16 +253,49 @@ char const* tr_memmem(char const* haystack, size_t haystack_len, char const* nee
|
||||
/** @brief Portability wrapper for strcasestr() that uses the system implementation if available */
|
||||
char const* tr_strcasestr(char const* haystack, char const* needle);
|
||||
|
||||
/** @brief Portability wrapper for strsep() that uses the system implementation if available */
|
||||
char* tr_strsep(char** str, char const* delim);
|
||||
/***
|
||||
**** std::string_view utils
|
||||
***/
|
||||
|
||||
template<typename T>
|
||||
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, std::string_view key) // c++20
|
||||
{
|
||||
return std::size(key) <= std::size(sv) && sv.substr(0, std::size(key)) == key;
|
||||
}
|
||||
|
||||
constexpr bool tr_strvEndsWith(std::string_view sv, std::string_view key) // c++20
|
||||
{
|
||||
return std::size(key) <= std::size(sv) && sv.substr(std::size(sv) - std::size(key)) == key;
|
||||
}
|
||||
|
||||
constexpr std::string_view tr_strvSep(std::string_view* sv, char delim)
|
||||
{
|
||||
auto pos = sv->find(delim);
|
||||
auto const ret = sv->substr(0, pos);
|
||||
sv->remove_prefix(pos != sv->npos ? pos + 1 : std::size(*sv));
|
||||
return ret;
|
||||
}
|
||||
|
||||
constexpr bool tr_strvSep(std::string_view* sv, std::string_view* token, char delim)
|
||||
{
|
||||
return !std::empty((*token = tr_strvSep(sv, delim)));
|
||||
}
|
||||
|
||||
std::string_view tr_strvStrip(std::string_view sv);
|
||||
|
||||
char* tr_strvDup(std::string_view) TR_GNUC_MALLOC;
|
||||
|
||||
std::string tr_strvUtf8Clean(std::string_view sv);
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
void tr_binary_to_hex(void const* input, void* output, size_t byte_length) TR_GNUC_NONNULL(1, 2);
|
||||
void tr_hex_to_binary(void const* input, void* output, size_t byte_length) TR_GNUC_NONNULL(1, 2);
|
||||
|
||||
/** @brief return TR_RATIO_NA, TR_RATIO_INF, or a number in [0..1]
|
||||
@return TR_RATIO_NA, TR_RATIO_INF, or a number in [0..1] */
|
||||
double tr_getRatio(uint64_t numerator, uint64_t denominator);
|
||||
|
||||
@@ -175,7 +175,7 @@ void tr_http_escape(struct evbuffer* out, std::string_view str, bool escape_rese
|
||||
|
||||
for (auto& ch : str)
|
||||
{
|
||||
if ((UnescapedChars.find(ch) != std::string_view::npos) || (ReservedChars.find(ch) && !escape_reserved))
|
||||
if (tr_strvContains(UnescapedChars, ch) || (tr_strvContains(ReservedChars, ch) && !escape_reserved))
|
||||
{
|
||||
evbuffer_add_printf(out, "%c", ch);
|
||||
}
|
||||
@@ -276,7 +276,7 @@ bool urlCharsAreValid(std::string_view url)
|
||||
};
|
||||
|
||||
return !std::empty(url) &&
|
||||
std::all_of(std::begin(url), std::end(url), [&ValidChars](auto ch) { return ValidChars.find(ch) != ValidChars.npos; });
|
||||
std::all_of(std::begin(url), std::end(url), [&ValidChars](auto ch) { return tr_strvContains(ValidChars, ch); });
|
||||
}
|
||||
|
||||
bool tr_isValidTrackerScheme(std::string_view scheme)
|
||||
@@ -289,7 +289,7 @@ bool tr_isValidTrackerScheme(std::string_view scheme)
|
||||
|
||||
std::optional<tr_url_parsed_t> tr_urlParse(std::string_view url)
|
||||
{
|
||||
url = tr_strvstrip(url);
|
||||
url = tr_strvStrip(url);
|
||||
|
||||
if (!urlCharsAreValid(url))
|
||||
{
|
||||
@@ -300,46 +300,33 @@ std::optional<tr_url_parsed_t> tr_urlParse(std::string_view url)
|
||||
parsed.full = url;
|
||||
|
||||
// scheme
|
||||
auto key = ":"sv;
|
||||
auto pos = url.find(key);
|
||||
if (pos == std::string_view::npos || pos == 0)
|
||||
parsed.scheme = tr_strvSep(&url, ':');
|
||||
if (std::empty(parsed.scheme))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
parsed.scheme = url.substr(0, pos);
|
||||
url.remove_prefix(pos + std::size(key));
|
||||
|
||||
// authority
|
||||
// The authority component is preceded by a double slash ("//") and is
|
||||
// terminated by the next slash ("/"), question mark ("?"), or number
|
||||
// sign ("#") character, or by the end of the URI.
|
||||
key = "//"sv;
|
||||
pos = url.find(key);
|
||||
if (pos == 0)
|
||||
auto key = "//"sv;
|
||||
if (tr_strvStartsWith(url, key))
|
||||
{
|
||||
url.remove_prefix(pos + std::size(key));
|
||||
pos = url.find_first_of("/?#");
|
||||
url.remove_prefix(std::size(key));
|
||||
auto pos = url.find_first_of("/?#");
|
||||
parsed.authority = url.substr(0, pos);
|
||||
url = pos == url.npos ? ""sv : url.substr(pos);
|
||||
|
||||
// host
|
||||
key = ":"sv;
|
||||
pos = parsed.authority.find(key);
|
||||
parsed.host = pos == std::string_view::npos ? parsed.authority : parsed.authority.substr(0, pos);
|
||||
if (std::empty(parsed.host))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
// port
|
||||
parsed.portstr = pos == std::string_view::npos ? getPortForScheme(parsed.scheme) :
|
||||
parsed.authority.substr(pos + std::size(key));
|
||||
auto remain = parsed.authority;
|
||||
parsed.host = tr_strvSep(&remain, ':');
|
||||
parsed.portstr = !std::empty(remain) ? remain : getPortForScheme(parsed.scheme);
|
||||
parsed.port = parsePort(parsed.portstr);
|
||||
}
|
||||
|
||||
// The path is terminated by the first question mark ("?") or
|
||||
// number sign ("#") character, or by the end of the URI.
|
||||
pos = url.find_first_of("?#");
|
||||
auto pos = url.find_first_of("?#");
|
||||
parsed.path = url.substr(0, pos);
|
||||
url = pos == url.npos ? ""sv : url.substr(pos);
|
||||
|
||||
@@ -381,20 +368,9 @@ bool tr_urlIsValid(std::string_view url)
|
||||
|
||||
tr_url_query_view::iterator& tr_url_query_view::iterator::operator++()
|
||||
{
|
||||
// find the next key/value delimiter
|
||||
auto pos = remain.find('&');
|
||||
auto const pair = remain.substr(0, pos);
|
||||
remain = pos == remain.npos ? ""sv : remain.substr(pos + 1);
|
||||
if (std::empty(pair))
|
||||
{
|
||||
keyval.first = keyval.second = remain = ""sv;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// split it into key and value
|
||||
pos = pair.find('=');
|
||||
keyval.first = pair.substr(0, pos);
|
||||
keyval.second = pos == pair.npos ? ""sv : pair.substr(pos + 1);
|
||||
auto pair = tr_strvSep(&remain, '&');
|
||||
keyval.first = tr_strvSep(&pair, '=');
|
||||
keyval.second = pair;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -9,9 +9,11 @@
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
|
||||
#include <libtransmission/crypto-utils.h> // tr_base64_encode()
|
||||
#include <libtransmission/transmission.h>
|
||||
|
||||
#include <libtransmission/crypto-utils.h> // tr_base64_encode()
|
||||
#include <libtransmission/utils.h>
|
||||
|
||||
#include "AddData.h"
|
||||
#include "Utils.h"
|
||||
|
||||
|
||||
@@ -49,34 +49,94 @@ TEST_F(UtilsTest, trStripPositionalArgs)
|
||||
EXPECT_STREQ(expected, out);
|
||||
}
|
||||
|
||||
TEST_F(UtilsTest, trStrstrip)
|
||||
TEST_F(UtilsTest, trStrvContains)
|
||||
{
|
||||
auto* in = tr_strdup(" test ");
|
||||
auto* out = tr_strstrip(in);
|
||||
EXPECT_EQ(in, out);
|
||||
EXPECT_STREQ("test", out);
|
||||
tr_free(in);
|
||||
EXPECT_FALSE(tr_strvContains("a test is this"sv, "TEST"sv));
|
||||
EXPECT_FALSE(tr_strvContains("test"sv, "testt"sv));
|
||||
EXPECT_FALSE(tr_strvContains("test"sv, "this is a test"sv));
|
||||
EXPECT_TRUE(tr_strvContains(" test "sv, "tes"sv));
|
||||
EXPECT_TRUE(tr_strvContains(" test"sv, "test"sv));
|
||||
EXPECT_TRUE(tr_strvContains("a test is this"sv, "test"sv));
|
||||
EXPECT_TRUE(tr_strvContains("test "sv, "test"sv));
|
||||
EXPECT_TRUE(tr_strvContains("test"sv, ""sv));
|
||||
EXPECT_TRUE(tr_strvContains("test"sv, "t"sv));
|
||||
EXPECT_TRUE(tr_strvContains("test"sv, "te"sv));
|
||||
EXPECT_TRUE(tr_strvContains("test"sv, "test"sv));
|
||||
EXPECT_TRUE(tr_strvContains("this is a test"sv, "test"sv));
|
||||
EXPECT_TRUE(tr_strvContains(""sv, ""sv));
|
||||
}
|
||||
|
||||
in = tr_strdup(" test test ");
|
||||
out = tr_strstrip(in);
|
||||
EXPECT_EQ(in, out);
|
||||
EXPECT_STREQ("test test", out);
|
||||
tr_free(in);
|
||||
TEST_F(UtilsTest, trStrvStartsWith)
|
||||
{
|
||||
EXPECT_FALSE(tr_strvStartsWith(""sv, "this is a string"sv));
|
||||
EXPECT_FALSE(tr_strvStartsWith("this is a strin"sv, "this is a string"sv));
|
||||
EXPECT_FALSE(tr_strvStartsWith("this is a strin"sv, "this is a string"sv));
|
||||
EXPECT_FALSE(tr_strvStartsWith("this is a string"sv, " his is a string"sv));
|
||||
EXPECT_FALSE(tr_strvStartsWith("this is a string"sv, "his is a string"sv));
|
||||
EXPECT_FALSE(tr_strvStartsWith("this is a string"sv, "string"sv));
|
||||
EXPECT_TRUE(tr_strvStartsWith(""sv, ""sv));
|
||||
EXPECT_TRUE(tr_strvStartsWith("this is a string"sv, ""sv));
|
||||
EXPECT_TRUE(tr_strvStartsWith("this is a string"sv, "this "sv));
|
||||
EXPECT_TRUE(tr_strvStartsWith("this is a string"sv, "this is"sv));
|
||||
EXPECT_TRUE(tr_strvStartsWith("this is a string"sv, "this"sv));
|
||||
}
|
||||
|
||||
/* strstrip */
|
||||
in = tr_strdup("test");
|
||||
out = tr_strstrip(in);
|
||||
EXPECT_EQ(in, out);
|
||||
EXPECT_STREQ("test", out);
|
||||
tr_free(in);
|
||||
TEST_F(UtilsTest, trStrvEndsWith)
|
||||
{
|
||||
EXPECT_FALSE(tr_strvEndsWith(""sv, "string"sv));
|
||||
EXPECT_FALSE(tr_strvEndsWith("this is a string"sv, "alphabet"sv));
|
||||
EXPECT_FALSE(tr_strvEndsWith("this is a string"sv, "strin"sv));
|
||||
EXPECT_FALSE(tr_strvEndsWith("this is a string"sv, "this is"sv));
|
||||
EXPECT_FALSE(tr_strvEndsWith("this is a string"sv, "this"sv));
|
||||
EXPECT_FALSE(tr_strvEndsWith("tring"sv, "string"sv));
|
||||
EXPECT_TRUE(tr_strvEndsWith(""sv, ""sv));
|
||||
EXPECT_TRUE(tr_strvEndsWith("this is a string"sv, " string"sv));
|
||||
EXPECT_TRUE(tr_strvEndsWith("this is a string"sv, ""sv));
|
||||
EXPECT_TRUE(tr_strvEndsWith("this is a string"sv, "a string"sv));
|
||||
EXPECT_TRUE(tr_strvEndsWith("this is a string"sv, "g"sv));
|
||||
EXPECT_TRUE(tr_strvEndsWith("this is a string"sv, "string"sv));
|
||||
}
|
||||
|
||||
EXPECT_EQ(""sv, tr_strvstrip(" "sv));
|
||||
EXPECT_EQ("test test"sv, tr_strvstrip(" test test "sv));
|
||||
EXPECT_EQ("test"sv, tr_strvstrip(" test "sv));
|
||||
EXPECT_EQ("test"sv, tr_strvstrip(" test "sv));
|
||||
EXPECT_EQ("test"sv, tr_strvstrip(" test "sv));
|
||||
EXPECT_EQ("test"sv, tr_strvstrip(" test "sv));
|
||||
EXPECT_EQ("test"sv, tr_strvstrip("test"sv));
|
||||
TEST_F(UtilsTest, trStrvSep)
|
||||
{
|
||||
auto constexpr Delim = ',';
|
||||
|
||||
auto sv = "token1,token2,token3"sv;
|
||||
EXPECT_EQ("token1"sv, tr_strvSep(&sv, Delim));
|
||||
EXPECT_EQ("token2"sv, tr_strvSep(&sv, Delim));
|
||||
EXPECT_EQ("token3"sv, tr_strvSep(&sv, Delim));
|
||||
EXPECT_EQ(""sv, tr_strvSep(&sv, Delim));
|
||||
|
||||
sv = " token1,token2"sv;
|
||||
EXPECT_EQ(" token1"sv, tr_strvSep(&sv, Delim));
|
||||
EXPECT_EQ("token2"sv, tr_strvSep(&sv, Delim));
|
||||
|
||||
sv = "token1;token2"sv;
|
||||
EXPECT_EQ("token1;token2"sv, tr_strvSep(&sv, Delim));
|
||||
EXPECT_EQ(""sv, tr_strvSep(&sv, Delim));
|
||||
|
||||
sv = ""sv;
|
||||
EXPECT_EQ(""sv, tr_strvSep(&sv, Delim));
|
||||
}
|
||||
|
||||
TEST_F(UtilsTest, trStrvStrip)
|
||||
{
|
||||
EXPECT_EQ(""sv, tr_strvStrip(" "sv));
|
||||
EXPECT_EQ("test test"sv, tr_strvStrip(" test test "sv));
|
||||
EXPECT_EQ("test"sv, tr_strvStrip(" test "sv));
|
||||
EXPECT_EQ("test"sv, tr_strvStrip(" test "sv));
|
||||
EXPECT_EQ("test"sv, tr_strvStrip(" test "sv));
|
||||
EXPECT_EQ("test"sv, tr_strvStrip(" test "sv));
|
||||
EXPECT_EQ("test"sv, tr_strvStrip("test"sv));
|
||||
}
|
||||
|
||||
TEST_F(UtilsTest, trStrvDup)
|
||||
{
|
||||
auto constexpr Key = "this is a test"sv;
|
||||
char* str = tr_strvDup(Key);
|
||||
EXPECT_NE(nullptr, str);
|
||||
EXPECT_EQ(Key, str);
|
||||
tr_free(str);
|
||||
}
|
||||
|
||||
TEST_F(UtilsTest, trBuildpath)
|
||||
@@ -124,7 +184,48 @@ TEST_F(UtilsTest, trUtf8clean)
|
||||
EXPECT_TRUE(tr_utf8_validate(out.c_str(), out.size(), nullptr));
|
||||
}
|
||||
|
||||
TEST_F(UtilsTest, numbers)
|
||||
TEST_F(UtilsTest, trStrvUtf8Clean)
|
||||
{
|
||||
auto in = "hello world"sv;
|
||||
auto out = tr_strvUtf8Clean(in);
|
||||
EXPECT_EQ(in, out);
|
||||
|
||||
in = "hello world"sv;
|
||||
out = tr_strvUtf8Clean(in.substr(0, 5));
|
||||
EXPECT_EQ("hello"sv, out);
|
||||
|
||||
// this version is not utf-8 (but cp866)
|
||||
in = "\x92\xE0\xE3\xA4\xAD\xAE \xA1\xEB\xE2\xEC \x81\xAE\xA3\xAE\xAC"sv;
|
||||
out = tr_strvUtf8Clean(in);
|
||||
EXPECT_TRUE(std::size(out) == 17 || std::size(out) == 33);
|
||||
EXPECT_TRUE(tr_utf8_validate(out.c_str(), out.size(), nullptr));
|
||||
|
||||
// same string, but utf-8 clean
|
||||
in = "Трудно быть Богом"sv;
|
||||
out = tr_strvUtf8Clean(in);
|
||||
EXPECT_NE(nullptr, out.data());
|
||||
EXPECT_TRUE(tr_utf8_validate(out.c_str(), out.size(), nullptr));
|
||||
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
|
||||
// is resolved before revisiting this.
|
||||
in = "\xF4\x00\x81\x82"sv;
|
||||
out = tr_strvUtf8Clean(in);
|
||||
EXPECT_NE(nullptr, out.data());
|
||||
EXPECT_TRUE(out.size() == 1 || out.size() == 2);
|
||||
EXPECT_TRUE(tr_utf8_validate(out.c_str(), out.size(), nullptr));
|
||||
|
||||
in = "\xF4\x33\x81\x82"sv;
|
||||
out = tr_strvUtf8Clean(in);
|
||||
EXPECT_NE(nullptr, out.data());
|
||||
EXPECT_TRUE(out.size() == 4 || out.size() == 7);
|
||||
EXPECT_TRUE(tr_utf8_validate(out.c_str(), out.size(), nullptr));
|
||||
}
|
||||
|
||||
TEST_F(UtilsTest, trParseNumberRange)
|
||||
{
|
||||
auto const tostring = [](std::vector<int> const& v)
|
||||
{
|
||||
@@ -190,18 +291,6 @@ TEST_F(UtilsTest, trMemmem)
|
||||
EXPECT_EQ(nullptr, tr_memmem(needle.data(), needle.size(), haystack.data(), haystack.size()));
|
||||
}
|
||||
|
||||
TEST_F(UtilsTest, trBinaryHex)
|
||||
{
|
||||
auto const hex_in = std::string{ "fb5ef5507427b17e04b69cef31fa3379b456735a" };
|
||||
|
||||
auto binary = std::array<uint8_t, SHA_DIGEST_LENGTH>{};
|
||||
tr_hex_to_binary(hex_in.data(), binary.data(), hex_in.size() / 2);
|
||||
|
||||
auto hex_out = std::array<uint8_t, SHA_DIGEST_LENGTH * 2 + 1>{};
|
||||
tr_binary_to_hex(binary.data(), hex_out.data(), 20);
|
||||
EXPECT_EQ(hex_in, reinterpret_cast<char const*>(hex_out.data()));
|
||||
}
|
||||
|
||||
TEST_F(UtilsTest, array)
|
||||
{
|
||||
auto array = std::array<size_t, 10>{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
|
||||
+5
-13
@@ -674,7 +674,7 @@ static void addDays(tr_variant* args, tr_quark const key, char const* arg)
|
||||
}
|
||||
}
|
||||
|
||||
static void addLabels(tr_variant* args, char const* arg)
|
||||
static void addLabels(tr_variant* args, std::string_view comma_delimited_labels)
|
||||
{
|
||||
tr_variant* labels;
|
||||
if (!tr_variantDictFindList(args, TR_KEY_labels, &labels))
|
||||
@@ -682,19 +682,11 @@ static void addLabels(tr_variant* args, char const* arg)
|
||||
labels = tr_variantDictAddList(args, TR_KEY_labels, 10);
|
||||
}
|
||||
|
||||
char* argcpy = tr_strdup(arg);
|
||||
char* const tmp = argcpy; /* save copied string start pointer to free later */
|
||||
char* token;
|
||||
while ((token = tr_strsep(&argcpy, ",")) != nullptr)
|
||||
auto label = std::string_view{};
|
||||
while (tr_strvSep(&comma_delimited_labels, &label, ','))
|
||||
{
|
||||
tr_strstrip(token);
|
||||
if (!tr_str_is_empty(token))
|
||||
{
|
||||
tr_variantListAddStr(labels, token);
|
||||
}
|
||||
tr_variantListAddStr(labels, label);
|
||||
}
|
||||
|
||||
tr_free(tmp);
|
||||
}
|
||||
|
||||
static void addFiles(tr_variant* args, tr_quark const key, char const* arg)
|
||||
@@ -2759,7 +2751,7 @@ static int processArgs(char const* rpcurl, int argc, char const* const* argv)
|
||||
switch (c)
|
||||
{
|
||||
case 'L':
|
||||
addLabels(args, optarg);
|
||||
addLabels(args, optarg ? optarg : "");
|
||||
break;
|
||||
|
||||
case 712:
|
||||
|
||||
Reference in New Issue
Block a user