From bdf1bb6d17836ca22e3a1606739342e83def25be Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 9 Nov 2021 18:13:47 -0600 Subject: [PATCH] refactor: tr_strv*() util functions (#2123) * feat: add tr_strv utils --- libtransmission/announcer-http.cc | 6 +- libtransmission/announcer.cc | 2 +- libtransmission/crypto-utils.cc | 50 ++++++++ libtransmission/crypto-utils.h | 11 +- libtransmission/magnet.cc | 6 +- libtransmission/makemeta.cc | 18 +-- libtransmission/metainfo.cc | 20 ++-- libtransmission/peer-io.h | 1 + libtransmission/resume.cc | 8 +- libtransmission/rpc-server.cc | 2 +- libtransmission/rpcimpl.cc | 10 +- libtransmission/torrent.cc | 2 +- libtransmission/utils.cc | 177 ++++++---------------------- libtransmission/utils.h | 51 ++++++-- libtransmission/web-utils.cc | 56 +++------ qt/AddData.cc | 4 +- tests/libtransmission/utils-test.cc | 163 +++++++++++++++++++------ utils/remote.cc | 18 +-- 18 files changed, 314 insertions(+), 291 deletions(-) diff --git a/libtransmission/announcer-http.cc b/libtransmission/announcer-http.cc index 8ca7d104b..fca85394b 100644 --- a/libtransmission/announcer-http.cc +++ b/libtransmission/announcer-http.cc @@ -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)) diff --git a/libtransmission/announcer.cc b/libtransmission/announcer.cc index d578fe91b..ebb3f882c 100644 --- a/libtransmission/announcer.cc +++ b/libtransmission/announcer.cc @@ -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; diff --git a/libtransmission/crypto-utils.cc b/libtransmission/crypto-utils.cc index 9e9f1e21d..232a25459 100644 --- a/libtransmission/crypto-utils.cc +++ b/libtransmission/crypto-utils.cc @@ -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(vinput); + auto* output = static_cast(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(vinput); + auto* output = static_cast(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); +} diff --git a/libtransmission/crypto-utils.h b/libtransmission/crypto-utils.h index d2dc83b4f..7a00801ce 100644 --- a/libtransmission/crypto-utils.h +++ b/libtransmission/crypto-utils.h @@ -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); /** @} */ diff --git a/libtransmission/magnet.cc b/libtransmission/magnet.cc index 1bd5aa4e3..f50e73912 100644 --- a/libtransmission/magnet.cc +++ b/libtransmission/magnet.cc @@ -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) { diff --git a/libtransmission/makemeta.cc b/libtransmission/makemeta.cc index e6db13ea6..c37439883 100644 --- a/libtransmission/makemeta.cc +++ b/libtransmission/makemeta.cc @@ -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); } } diff --git a/libtransmission/metainfo.cc b/libtransmission/metainfo.cc index ae833d000..ffe35dc81 100644 --- a/libtransmission/metainfo.cc +++ b/libtransmission/metainfo.cc @@ -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) diff --git a/libtransmission/peer-io.h b/libtransmission/peer-io.h index 24d6ece41..5776825ba 100644 --- a/libtransmission/peer-io.h +++ b/libtransmission/peer-io.h @@ -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; diff --git a/libtransmission/resume.cc b/libtransmission/resume.cc index 3761d8633..606ec45b5 100644 --- a/libtransmission/resume.cc +++ b/libtransmission/resume.cc @@ -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) { diff --git a/libtransmission/rpc-server.cc b/libtransmission/rpc-server.cc index 8babb2fb0..5e04936d1 100644 --- a/libtransmission/rpc-server.cc +++ b/libtransmission/rpc-server.cc @@ -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; diff --git a/libtransmission/rpcimpl.cc b/libtransmission/rpcimpl.cc index b06aab20d..b13ee562c 100644 --- a/libtransmission/rpcimpl.cc +++ b/libtransmission/rpcimpl.cc @@ -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) { diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index 277ec6664..c29ffe38b 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -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)) { diff --git a/libtransmission/utils.cc b/libtransmission/utils.cc index 160b4efb2..324c92d7a 100644 --- a/libtransmission/utils.cc +++ b/libtransmission/utils.cc @@ -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(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(vinput); - auto* output = static_cast(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(vinput); - auto* output = static_cast(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{ "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 tr_parseNumberRange(std::string_view str) { auto values = std::set{}; - - 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) }; diff --git a/libtransmission/utils.h b/libtransmission/utils.h index 39cb91ba4..b2b6f2a7a 100644 --- a/libtransmission/utils.h +++ b/libtransmission/utils.h @@ -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 +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); diff --git a/libtransmission/web-utils.cc b/libtransmission/web-utils.cc index d3aed24e5..b57e77960 100644 --- a/libtransmission/web-utils.cc +++ b/libtransmission/web-utils.cc @@ -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_urlParse(std::string_view url) { - url = tr_strvstrip(url); + url = tr_strvStrip(url); if (!urlCharsAreValid(url)) { @@ -300,46 +300,33 @@ std::optional 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; } diff --git a/qt/AddData.cc b/qt/AddData.cc index 967bd5cf9..388096a79 100644 --- a/qt/AddData.cc +++ b/qt/AddData.cc @@ -9,9 +9,11 @@ #include #include -#include // tr_base64_encode() #include +#include // tr_base64_encode() +#include + #include "AddData.h" #include "Utils.h" diff --git a/tests/libtransmission/utils-test.cc b/tests/libtransmission/utils-test.cc index 81a245131..6063fb31f 100644 --- a/tests/libtransmission/utils-test.cc +++ b/tests/libtransmission/utils-test.cc @@ -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 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{}; - tr_hex_to_binary(hex_in.data(), binary.data(), hex_in.size() / 2); - - auto hex_out = std::array{}; - tr_binary_to_hex(binary.data(), hex_out.data(), 20); - EXPECT_EQ(hex_in, reinterpret_cast(hex_out.data())); -} - TEST_F(UtilsTest, array) { auto array = std::array{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; diff --git a/utils/remote.cc b/utils/remote.cc index 027492dd4..5732b22a2 100644 --- a/utils/remote.cc +++ b/utils/remote.cc @@ -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: