diff --git a/Transmission.xcodeproj/project.pbxproj b/Transmission.xcodeproj/project.pbxproj index 8300dc3e1..23a70e59e 100644 --- a/Transmission.xcodeproj/project.pbxproj +++ b/Transmission.xcodeproj/project.pbxproj @@ -346,6 +346,8 @@ C1639A791A55F56600E42033 /* cencode.c in Sources */ = {isa = PBXBuildFile; fileRef = C1639A771A55F56600E42033 /* cencode.c */; }; C1639A7C1A55F57200E42033 /* cdecode.h in Headers */ = {isa = PBXBuildFile; fileRef = C1639A7A1A55F57200E42033 /* cdecode.h */; }; C1639A7D1A55F57200E42033 /* cencode.h in Headers */ = {isa = PBXBuildFile; fileRef = C1639A7B1A55F57200E42033 /* cencode.h */; }; + C17740D5273A002C00E455D2 /* web-utils.cc in Sources */ = {isa = PBXBuildFile; fileRef = C17740D3273A002C00E455D2 /* web-utils.cc */; }; + C17740D6273A002C00E455D2 /* web-utils.h in Headers */ = {isa = PBXBuildFile; fileRef = C17740D4273A002C00E455D2 /* web-utils.h */; }; C1A7517526ED048C0038B90A /* libarc4.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C1A7516426ED03140038B90A /* libarc4.a */; }; C1A751E526ED09A30038B90A /* arc4.c in Sources */ = {isa = PBXBuildFile; fileRef = C1A751E326ED09A30038B90A /* arc4.c */; }; C1A751E626ED09A30038B90A /* arc4.h in Headers */ = {isa = PBXBuildFile; fileRef = C1A751E426ED09A30038B90A /* arc4.h */; }; @@ -1001,6 +1003,8 @@ C1639A771A55F56600E42033 /* cencode.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = cencode.c; path = src/cencode.c; sourceTree = ""; }; C1639A7A1A55F57200E42033 /* cdecode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = cdecode.h; path = include/b64/cdecode.h; sourceTree = ""; }; C1639A7B1A55F57200E42033 /* cencode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = cencode.h; path = include/b64/cencode.h; sourceTree = ""; }; + C17740D3273A002C00E455D2 /* web-utils.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "web-utils.cc"; sourceTree = ""; }; + C17740D4273A002C00E455D2 /* web-utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "web-utils.h"; sourceTree = ""; }; C1A7516426ED03140038B90A /* libarc4.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libarc4.a; sourceTree = BUILT_PRODUCTS_DIR; }; C1A751E326ED09A30038B90A /* arc4.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = arc4.c; path = "third-party/arc4/src/arc4.c"; sourceTree = SOURCE_ROOT; }; C1A751E426ED09A30038B90A /* arc4.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = arc4.h; path = "third-party/arc4/src/arc4.h"; sourceTree = SOURCE_ROOT; }; @@ -1362,6 +1366,8 @@ 4D1838DC09DEC04A0047D688 /* libtransmission */ = { isa = PBXGroup; children = ( + C17740D3273A002C00E455D2 /* web-utils.cc */, + C17740D4273A002C00E455D2 /* web-utils.h */, CAB35C62252F6F5E00552A55 /* mime-types.h */, C1077A4A183EB29600634C22 /* error.cc */, C1077A4B183EB29600634C22 /* error.h */, @@ -1846,6 +1852,7 @@ C11DEA171FCD31C0009E22B9 /* subprocess.h in Headers */, A25D2CBE0CF4C73E0096A262 /* stats.h in Headers */, C1033E0A1A3279B800EF44D8 /* crypto-utils.h in Headers */, + C17740D6273A002C00E455D2 /* web-utils.h in Headers */, A29DF8BA0DB2544C00D04E5A /* resume.h in Headers */, A29DF8BB0DB2544C00D04E5A /* torrent.h in Headers */, A29DF8BE0DB2545F00D04E5A /* verify.h in Headers */, @@ -2457,6 +2464,7 @@ C1FEE57A1C3223CC00D62832 /* watchdir.cc in Sources */, A23547E211CD0B090046EAE6 /* cache.cc in Sources */, A284214412DA663E00FBDDBB /* tr-udp.cc in Sources */, + C17740D5273A002C00E455D2 /* web-utils.cc in Sources */, C1425B351EE9C5F5001DB85F /* tr-assert.cc in Sources */, A2679294130E00A000CB7464 /* tr-utp.cc in Sources */, A23F29A2132A447400E9A83B /* announcer-http.cc in Sources */, diff --git a/gtk/DetailsDialog.cc b/gtk/DetailsDialog.cc index 01d976b69..086f01c37 100644 --- a/gtk/DetailsDialog.cc +++ b/gtk/DetailsDialog.cc @@ -19,6 +19,7 @@ #include #include /* tr_free */ +#include #include "Actions.h" #include "DetailsDialog.h" diff --git a/gtk/FilterBar.cc b/gtk/FilterBar.cc index 78cd17ffd..4f3e82c4a 100644 --- a/gtk/FilterBar.cc +++ b/gtk/FilterBar.cc @@ -15,6 +15,7 @@ #include #include +#include #include "FaviconCache.h" /* gtr_get_favicon() */ #include "FilterBar.h" diff --git a/gtk/PrefsDialog.cc b/gtk/PrefsDialog.cc index 45e5e0e7a..c0fc0c78b 100644 --- a/gtk/PrefsDialog.cc +++ b/gtk/PrefsDialog.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include "FreeSpaceLabel.h" #include "HigWorkarea.h" @@ -429,8 +430,7 @@ void onBlocklistUpdate(Gtk::Button* w, std::shared_ptr const& da void on_blocklist_url_changed(Gtk::Editable* e, Gtk::Button* button) { auto const url = e->get_chars(0, -1); - bool const is_url_valid = tr_urlParse(url.c_str(), TR_BAD_SIZE, nullptr, nullptr, nullptr, nullptr); - button->set_sensitive(is_url_valid); + button->set_sensitive(tr_urlIsValid(url.c_str())); } void onIntComboChanged(Gtk::ComboBox* combo_box, tr_quark const key, Glib::RefPtr const& core) diff --git a/gtk/Utils.cc b/gtk/Utils.cc index eb1e44148..23a9efbe4 100644 --- a/gtk/Utils.cc +++ b/gtk/Utils.cc @@ -19,7 +19,7 @@ #include /* TR_RATIO_NA, TR_RATIO_INF */ #include #include /* tr_strratio() */ -#include /* tr_webResponseStr() */ +#include #include /* SHORT_VERSION_STRING */ #include "HigWorkarea.h" diff --git a/libtransmission/CMakeLists.txt b/libtransmission/CMakeLists.txt index 6a99fe8a7..2dd29d4ac 100644 --- a/libtransmission/CMakeLists.txt +++ b/libtransmission/CMakeLists.txt @@ -73,6 +73,7 @@ set(PROJECT_FILES watchdir-kqueue.cc watchdir-win32.cc web.cc + web-utils.cc webseed.cc ) @@ -118,8 +119,9 @@ else() endif() set(${PROJECT_NAME}_PUBLIC_HEADERS - error.h + ${PROJECT_BINARY_DIR}/version.h error-types.h + error.h file.h log.h makemeta.h @@ -133,11 +135,12 @@ set(${PROJECT_NAME}_PUBLIC_HEADERS utils.h variant.h watchdir.h + web-utils.h web.h - ${PROJECT_BINARY_DIR}/version.h ) set(${PROJECT_NAME}_PRIVATE_HEADERS + ConvertUTF.h announcer-common.h announcer.h bandwidth.h @@ -146,9 +149,8 @@ set(${PROJECT_NAME}_PRIVATE_HEADERS cache.h clients.h completion.h - ConvertUTF.h - crypto.h crypto-utils.h + crypto.h fdlimit.h handshake.h history.h @@ -163,22 +165,22 @@ set(${PROJECT_NAME}_PRIVATE_HEADERS peer-mgr.h peer-msgs.h peer-socket.h - platform.h platform-quota.h + platform.h port-forwarding.h ptrarray.h resume.h rpc-server.h session.h - subprocess.h stats.h - torrent.h + subprocess.h torrent-magnet.h + torrent.h tr-dht.h - trevent.h tr-lpd.h tr-udp.h tr-utp.h + trevent.h upnp.h variant-common.h verify.h diff --git a/libtransmission/announcer-common.h b/libtransmission/announcer-common.h index 0f252f875..b8e6deb92 100644 --- a/libtransmission/announcer-common.h +++ b/libtransmission/announcer-common.h @@ -17,7 +17,7 @@ #include "transmission.h" #include "quark.h" -#include "utils.h" +#include "web-utils.h" /*** **** SCRAPE @@ -238,12 +238,17 @@ void tr_tracker_udp_announce( void tr_tracker_udp_start_shutdown(tr_session* session); -tr_quark tr_announcerGetKey(tr_parsed_url_t const& parsed); +tr_quark tr_announcerGetKey(tr_url_parsed_t const& parsed); inline tr_quark tr_announcerGetKey(std::string_view url) { auto const parsed = tr_urlParseTracker(url); - return parsed ? tr_announcerGetKey(*parsed) : TR_KEY_NONE; + if (!parsed) + { + return TR_KEY_NONE; + } + + return tr_announcerGetKey(*parsed); } inline tr_quark tr_announcerGetKey(tr_quark url) diff --git a/libtransmission/announcer-http.cc b/libtransmission/announcer-http.cc index 1671dc415..8ca7d104b 100644 --- a/libtransmission/announcer-http.cc +++ b/libtransmission/announcer-http.cc @@ -24,7 +24,8 @@ #include "trevent.h" /* tr_runInEventThread() */ #include "utils.h" #include "variant.h" -#include "web.h" /* tr_http_escape() */ +#include "web.h" +#include "web-utils.h" #define dbgmsg(name, ...) tr_logAddDeepNamed(name, __VA_ARGS__) diff --git a/libtransmission/announcer.cc b/libtransmission/announcer.cc index 7560e5648..d578fe91b 100644 --- a/libtransmission/announcer.cc +++ b/libtransmission/announcer.cc @@ -32,6 +32,7 @@ #include "torrent.h" #include "tr-assert.h" #include "utils.h" +#include "web-utils.h" using namespace std::literals; @@ -241,7 +242,7 @@ struct tr_tracker }; // format: `${host}:${port}` -tr_quark tr_announcerGetKey(tr_parsed_url_t const& parsed) +tr_quark tr_announcerGetKey(tr_url_parsed_t const& parsed) { std::string buf; tr_buildBuf(buf, parsed.host, ":"sv, parsed.portstr); @@ -536,14 +537,14 @@ static void publishPeersPex(tr_tier* tier, int seeders, int leechers, tr_pex con struct AnnTrackerInfo { - AnnTrackerInfo(tr_tracker_info info_in, tr_parsed_url_t url_in) + AnnTrackerInfo(tr_tracker_info info_in, tr_url_parsed_t url_in) : info{ info_in } , url{ url_in } { } tr_tracker_info info; - tr_parsed_url_t url; + tr_url_parsed_t url; /* primary key: tier * secondary key: udp comes before http */ diff --git a/libtransmission/bandwidth.cc b/libtransmission/bandwidth.cc index 91ec442da..e763517f7 100644 --- a/libtransmission/bandwidth.cc +++ b/libtransmission/bandwidth.cc @@ -8,6 +8,7 @@ #include #include /* memset() */ +#include #include "transmission.h" #include "bandwidth.h" diff --git a/libtransmission/bandwidth.h b/libtransmission/bandwidth.h index 4585923f3..cc78b4676 100644 --- a/libtransmission/bandwidth.h +++ b/libtransmission/bandwidth.h @@ -14,10 +14,10 @@ #include #include +#include #include "transmission.h" #include "tr-assert.h" -#include "utils.h" /* tr_new(), tr_free() */ class tr_peerIo; diff --git a/libtransmission/clients.cc b/libtransmission/clients.cc index 8bfdc581d..4e6343926 100644 --- a/libtransmission/clients.cc +++ b/libtransmission/clients.cc @@ -10,13 +10,14 @@ #include #include -#include -#include -#include #include /* isprint() */ #include /* strtol() */ #include +#include +#include +#include #include +#include #include "transmission.h" #include "clients.h" diff --git a/libtransmission/completion.cc b/libtransmission/completion.cc index 752382e63..fecb4eeb7 100644 --- a/libtransmission/completion.cc +++ b/libtransmission/completion.cc @@ -6,6 +6,8 @@ * */ +#include + #include "transmission.h" #include "completion.h" #include "torrent.h" diff --git a/libtransmission/completion.h b/libtransmission/completion.h index f571faf1b..cdcc1e1c7 100644 --- a/libtransmission/completion.h +++ b/libtransmission/completion.h @@ -12,9 +12,10 @@ #error only libtransmission should #include this header. #endif +#include + #include "transmission.h" #include "bitfield.h" -#include "utils.h" /* tr_getRatio() */ struct tr_completion { diff --git a/libtransmission/crypto-utils.h b/libtransmission/crypto-utils.h index d9d49fe8a..d2dc83b4f 100644 --- a/libtransmission/crypto-utils.h +++ b/libtransmission/crypto-utils.h @@ -15,7 +15,7 @@ #include "transmission.h" /* SHA_DIGEST_LENGTH */ #include "tr-macros.h" -#include "utils.h" /* TR_GNUC_MALLOC, TR_GNUC_NULL_TERMINATED */ +#include "utils.h" // tr_binary_to_hex(), tr_hex_to_binary() /** *** @addtogroup utils Utilities diff --git a/libtransmission/crypto.h b/libtransmission/crypto.h index 9618876d7..3fd5e6e0a 100644 --- a/libtransmission/crypto.h +++ b/libtransmission/crypto.h @@ -18,7 +18,6 @@ #include "crypto-utils.h" #include "tr-macros.h" -#include "utils.h" /* TR_GNUC_NULL_TERMINATED */ /** *** @addtogroup peers diff --git a/libtransmission/inout.cc b/libtransmission/inout.cc index c00501141..5affdad8f 100644 --- a/libtransmission/inout.cc +++ b/libtransmission/inout.cc @@ -11,6 +11,7 @@ #include /* bsearch() */ #include /* memcmp() */ #include +#include #include "transmission.h" #include "cache.h" /* tr_cacheReadBlock() */ diff --git a/libtransmission/log.h b/libtransmission/log.h index b7821b559..157876546 100644 --- a/libtransmission/log.h +++ b/libtransmission/log.h @@ -12,7 +12,6 @@ #include "file.h" /* tr_sys_file_t */ #include "tr-macros.h" -#include "utils.h" /* TR_GNUC_PRINTF, TR_GNUC_NONNULL */ #define TR_LOG_MAX_QUEUE_LENGTH 10000 diff --git a/libtransmission/magnet.cc b/libtransmission/magnet.cc index 07b77c077..1bd5aa4e3 100644 --- a/libtransmission/magnet.cc +++ b/libtransmission/magnet.cc @@ -6,6 +6,7 @@ * */ +#include #include /* strchr() */ #include /* sscanf() */ @@ -14,8 +15,11 @@ #include "magnet.h" #include "tr-assert.h" #include "utils.h" +#include "utils.h" #include "variant.h" -#include "web.h" +#include "web-utils.h" + +using namespace std::literals; /*** **** @@ -104,127 +108,74 @@ static void base32_to_sha1(uint8_t* out, char const* in, size_t const inlen) **** ***/ -#define MAX_TRACKERS 64 -#define MAX_WEBSEEDS 64 - -tr_magnet_info* tr_magnetParse(char const* uri) +tr_magnet_info* tr_magnetParse(std::string_view magnet_link) { + auto const parsed = tr_urlParse(magnet_link); + if (!parsed || parsed->scheme != "magnet"sv) + { + return nullptr; + } + bool got_checksum = false; - int trCount = 0; - int wsCount = 0; - char* tr[MAX_TRACKERS]; - char* ws[MAX_WEBSEEDS]; - char* displayName = nullptr; + auto tr = std::vector{}; + auto ws = std::vector{}; + char* display_name = nullptr; uint8_t sha1[SHA_DIGEST_LENGTH]; - tr_magnet_info* info = nullptr; - if (uri != nullptr && strncmp(uri, "magnet:?", 8) == 0) + for (auto const& [key, value] : tr_url_query_view{ parsed->query }) { - for (char const* walk = uri + 8; !tr_str_is_empty(walk);) + if (key == "dn"sv) { - char const* key = walk; - char const* delim = strchr(key, '='); - char const* val = delim == nullptr ? nullptr : delim + 1; - char const* next = strchr(delim == nullptr ? key : val, '&'); - - auto keylen = size_t{}; - if (delim != nullptr) + display_name = tr_strvdup(tr_urlPercentDecode(value)); + } + else if (key == "tr"sv || key.find("tr.") == 0) + { + // "tr." explanation @ https://trac.transmissionbt.com/ticket/3341 + tr.push_back(tr_strvdup(tr_urlPercentDecode(value))); + } + else if (key == "ws"sv) + { + ws.push_back(tr_strvdup(tr_urlPercentDecode(value))); + } + else if (key == "xt"sv) + { + auto constexpr ValPrefix = "urn:btih:"sv; + if (value.find(ValPrefix) == 0) { - keylen = (size_t)(delim - key); - } - else if (next != nullptr) - { - keylen = (size_t)(next - key); - } - else - { - keylen = strlen(key); - } - - auto vallen = size_t{}; - if (val == nullptr) - { - vallen = 0; - } - else if (next != nullptr) - { - vallen = (size_t)(next - val); - } - else - { - vallen = strlen(val); - } - - if (keylen == 2 && memcmp(key, "xt", 2) == 0 && val != nullptr && strncmp(val, "urn:btih:", 9) == 0) - { - char const* hash = val + 9; - size_t const hashlen = vallen - 9; - - if (hashlen == 40) + auto const hash = value.substr(std::size(ValPrefix)); + switch (std::size(hash)) { - tr_hex_to_sha1(sha1, hash); + case 40: + tr_hex_to_sha1(sha1, std::data(hash)); got_checksum = true; - } - else if (hashlen == 32) - { - base32_to_sha1(sha1, hash, hashlen); + break; + + case 32: + base32_to_sha1(sha1, std::data(hash), std::size(hash)); got_checksum = true; + break; + + default: + break; } } - - if (displayName == nullptr && vallen > 0 && keylen == 2 && memcmp(key, "dn", 2) == 0) - { - displayName = tr_http_unescape(val, vallen); - } - - if (vallen > 0 && trCount < MAX_TRACKERS) - { - auto i = int{}; - - if (keylen == 2 && memcmp(key, "tr", 2) == 0) - { - tr[trCount++] = tr_http_unescape(val, vallen); - } - else if (sscanf(key, "tr.%d=", &i) == 1 && i >= 0) /* ticket #3341 and #5134 */ - { - tr[trCount++] = tr_http_unescape(val, vallen); - } - } - - if (vallen > 0 && keylen == 2 && memcmp(key, "ws", 2) == 0 && wsCount < MAX_WEBSEEDS) - { - ws[wsCount++] = tr_http_unescape(val, vallen); - } - - walk = next != nullptr ? next + 1 : nullptr; } } - if (got_checksum) + if (!got_checksum) { - info = tr_new0(tr_magnet_info, 1); - info->displayName = displayName; - info->trackerCount = trCount; - info->trackers = static_cast(tr_memdup(tr, sizeof(char*) * trCount)); - info->webseedCount = wsCount; - info->webseeds = static_cast(tr_memdup(ws, sizeof(char*) * wsCount)); - memcpy(info->hash, sha1, sizeof(uint8_t) * SHA_DIGEST_LENGTH); - } - else - { - for (int i = 0; i < trCount; i++) - { - tr_free(tr[i]); - } - - for (int i = 0; i < wsCount; i++) - { - tr_free(ws[i]); - } - - tr_free(displayName); + std::for_each(std::begin(tr), std::end(tr), tr_free); + std::for_each(std::begin(ws), std::end(ws), tr_free); + return nullptr; } + auto* const info = tr_new0(tr_magnet_info, 1); + info->displayName = display_name; + info->trackerCount = std::size(tr); + info->trackers = reinterpret_cast(tr_memdup(std::data(tr), std::size(tr) * sizeof(char*))); + info->webseedCount = std::size(ws); + info->webseeds = reinterpret_cast(tr_memdup(std::data(ws), std::size(ws) * sizeof(char*))); + std::copy_n(sha1, SHA_DIGEST_LENGTH, info->hash); return info; } @@ -232,20 +183,10 @@ void tr_magnetFree(tr_magnet_info* info) { if (info != nullptr) { - for (int i = 0; i < info->trackerCount; ++i) - { - tr_free(info->trackers[i]); - } - + std::for_each(info->trackers, info->trackers + info->trackerCount, tr_free); + std::for_each(info->webseeds, info->webseeds + info->webseedCount, tr_free); tr_free(info->trackers); - - for (int i = 0; i < info->webseedCount; ++i) - { - tr_free(info->webseeds[i]); - } - tr_free(info->webseeds); - tr_free(info->displayName); tr_free(info); } diff --git a/libtransmission/magnet.h b/libtransmission/magnet.h index 085e656a3..5d2207713 100644 --- a/libtransmission/magnet.h +++ b/libtransmission/magnet.h @@ -12,9 +12,11 @@ #error only libtransmission should #include this header. #endif -#include "tr-macros.h" +#include + #include "transmission.h" -#include "variant.h" + +struct tr_variant; struct tr_magnet_info { @@ -29,9 +31,7 @@ struct tr_magnet_info char** webseeds; }; -tr_magnet_info* tr_magnetParse(char const* uri); - -struct tr_variant; +tr_magnet_info* tr_magnetParse(std::string_view magnet_link); void tr_magnetCreateMetainfo(tr_magnet_info const*, tr_variant*); diff --git a/libtransmission/makemeta.cc b/libtransmission/makemeta.cc index 2566dec70..e6db13ea6 100644 --- a/libtransmission/makemeta.cc +++ b/libtransmission/makemeta.cc @@ -14,17 +14,19 @@ #include /* evutil_ascii_strcasecmp() */ #include "transmission.h" + #include "crypto-utils.h" /* tr_sha1 */ #include "error.h" #include "file.h" #include "log.h" -#include "session.h" #include "makemeta.h" #include "platform.h" /* threads, locks */ +#include "session.h" #include "tr-assert.h" #include "utils.h" /* buildpath */ #include "variant.h" #include "version.h" +#include "web-utils.h" /**** ***** diff --git a/libtransmission/metainfo.cc b/libtransmission/metainfo.cc index d63fb8230..ae833d000 100644 --- a/libtransmission/metainfo.cc +++ b/libtransmission/metainfo.cc @@ -12,8 +12,6 @@ #include #include -#include - #include "transmission.h" #include "crypto-utils.h" /* tr_sha1 */ @@ -25,6 +23,7 @@ #include "tr-assert.h" #include "utils.h" #include "variant.h" +#include "web-utils.h" using namespace std::literals; diff --git a/libtransmission/metainfo.h b/libtransmission/metainfo.h index 278ad60ae..73f7726be 100644 --- a/libtransmission/metainfo.h +++ b/libtransmission/metainfo.h @@ -12,9 +12,12 @@ #error only libtransmission should #include this header. #endif +#include +#include + #include "transmission.h" -#include "variant.h" -#include "tr-macros.h" + +struct tr_variant; enum tr_metainfo_basename_format { diff --git a/libtransmission/peer-io.h b/libtransmission/peer-io.h index 5fd9af316..24d6ece41 100644 --- a/libtransmission/peer-io.h +++ b/libtransmission/peer-io.h @@ -23,7 +23,6 @@ #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/peer-msgs.cc b/libtransmission/peer-msgs.cc index 0bbda2364..b6337e201 100644 --- a/libtransmission/peer-msgs.cc +++ b/libtransmission/peer-msgs.cc @@ -18,6 +18,7 @@ #include #include "transmission.h" + #include "cache.h" #include "completion.h" #include "file.h" @@ -25,9 +26,10 @@ #include "peer-io.h" #include "peer-mgr.h" #include "peer-msgs.h" +#include "ptrarray.h" #include "session.h" -#include "torrent.h" #include "torrent-magnet.h" +#include "torrent.h" #include "tr-assert.h" #include "tr-dht.h" #include "utils.h" diff --git a/libtransmission/platform.cc b/libtransmission/platform.cc index e38164b6a..8175a0020 100644 --- a/libtransmission/platform.cc +++ b/libtransmission/platform.cc @@ -9,7 +9,6 @@ #include #include #include -#include #include #ifndef _XOPEN_SOURCE diff --git a/libtransmission/ptrarray.cc b/libtransmission/ptrarray.cc index 844746cd3..b1c059eef 100644 --- a/libtransmission/ptrarray.cc +++ b/libtransmission/ptrarray.cc @@ -16,6 +16,44 @@ #define FLOOR 32 +int tr_lowerBound( + void const* key, + void const* base, + size_t nmemb, + size_t size, + tr_voidptr_compare_func compar, + bool* exact_match) +{ + size_t first = 0; + auto const* cbase = static_cast(base); + bool exact = false; + + while (nmemb != 0) + { + size_t const half = nmemb / 2; + size_t const middle = first + half; + int const c = (*compar)(key, cbase + size * middle); + + if (c <= 0) + { + if (c == 0) + { + exact = true; + } + + nmemb = half; + } + else + { + first = middle + 1; + nmemb = nmemb - half - 1; + } + } + + *exact_match = exact; + return first; +} + void tr_ptrArrayDestruct(tr_ptrArray* p, PtrArrayForeachFunc func) { TR_ASSERT(p != nullptr); diff --git a/libtransmission/ptrarray.h b/libtransmission/ptrarray.h index 5a3df8585..eeae211d7 100644 --- a/libtransmission/ptrarray.h +++ b/libtransmission/ptrarray.h @@ -30,6 +30,8 @@ struct tr_ptrArray int n_alloc; }; +using tr_voidptr_compare_func = int (*)(void const* lhs, void const* rhs); + using PtrArrayCompareFunc = tr_voidptr_compare_func; using PtrArrayForeachFunc = void (*)(void*); @@ -121,4 +123,13 @@ void tr_ptrArrayRemoveSortedPointer(tr_ptrArray* t, void const* ptr, tr_voidptr_ @return the matching pointer, or nullptr if no match was found */ void* tr_ptrArrayFindSorted(tr_ptrArray* array, void const* key, tr_voidptr_compare_func compare); +/** @brief similar to bsearch() but returns the index of the lower bound */ +int tr_lowerBound( + void const* key, + void const* base, + size_t nmemb, + size_t size, + tr_voidptr_compare_func compar, + bool* exact_match) TR_GNUC_HOT TR_GNUC_NONNULL(1, 5, 6); + /* @} */ diff --git a/libtransmission/resume.cc b/libtransmission/resume.cc index 790feff6f..3761d8633 100644 --- a/libtransmission/resume.cc +++ b/libtransmission/resume.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include "transmission.h" #include "completion.h" diff --git a/libtransmission/rpc-server.cc b/libtransmission/rpc-server.cc index c79452ac7..8babb2fb0 100644 --- a/libtransmission/rpc-server.cc +++ b/libtransmission/rpc-server.cc @@ -11,6 +11,7 @@ #include /* memcpy */ #include #include +#include #include @@ -20,21 +21,23 @@ #include /* TODO: eventually remove this */ #include "transmission.h" -#include "crypto.h" /* tr_ssha1_matches() */ + #include "crypto-utils.h" /* tr_rand_buffer() */ +#include "crypto.h" /* tr_ssha1_matches() */ #include "error.h" #include "fdlimit.h" #include "log.h" #include "net.h" #include "platform.h" /* tr_getWebClientDir() */ -#include "rpcimpl.h" #include "rpc-server.h" -#include "session.h" +#include "rpcimpl.h" #include "session-id.h" +#include "session.h" #include "tr-assert.h" #include "trevent.h" #include "utils.h" #include "variant.h" +#include "web-utils.h" #include "web.h" /* session-id is used to make cross-site request forgery attacks difficult. diff --git a/libtransmission/rpc-server.h b/libtransmission/rpc-server.h index 7d15c4356..d24d397c7 100644 --- a/libtransmission/rpc-server.h +++ b/libtransmission/rpc-server.h @@ -12,9 +12,8 @@ #error only libtransmission should #include this header. #endif -#include "variant.h" - struct tr_rpc_server; +struct tr_variant; tr_rpc_server* tr_rpcInit(tr_session* session, tr_variant* settings); diff --git a/libtransmission/rpcimpl.cc b/libtransmission/rpcimpl.cc index e98a8ee2a..b06aab20d 100644 --- a/libtransmission/rpcimpl.cc +++ b/libtransmission/rpcimpl.cc @@ -21,8 +21,6 @@ #endif #include -#include - #include "transmission.h" #include "completion.h" #include "crypto-utils.h" @@ -42,6 +40,7 @@ #include "variant.h" #include "version.h" #include "web.h" +#include "web-utils.h" #define RPC_VERSION 17 #define RPC_VERSION_MIN 14 diff --git a/libtransmission/rpcimpl.h b/libtransmission/rpcimpl.h index f922beebc..2a3849ea7 100644 --- a/libtransmission/rpcimpl.h +++ b/libtransmission/rpcimpl.h @@ -12,12 +12,13 @@ #include "transmission.h" #include "tr-macros.h" -#include "variant.h" /*** **** RPC processing ***/ +struct tr_variant; + using tr_rpc_response_func = void (*)(tr_session* session, tr_variant* response, void* user_data); /* http://www.json.org/ */ diff --git a/libtransmission/session.cc b/libtransmission/session.cc index 866681801..6e5844df8 100644 --- a/libtransmission/session.cc +++ b/libtransmission/session.cc @@ -16,6 +16,7 @@ #include // std::back_inserter #include #include // std::acumulate() +#include #include #ifndef _WIN32 @@ -30,32 +31,33 @@ // #define TR_SHOW_DEPRECATED #include "transmission.h" + #include "announcer.h" #include "bandwidth.h" #include "blocklist.h" #include "cache.h" #include "crypto-utils.h" -#include "error.h" #include "error-types.h" +#include "error.h" #include "fdlimit.h" #include "file.h" #include "log.h" #include "net.h" #include "peer-io.h" #include "peer-mgr.h" -#include "platform.h" /* tr_lock, tr_getTorrentDir() */ #include "platform-quota.h" /* tr_device_info_free() */ +#include "platform.h" /* tr_lock, tr_getTorrentDir() */ #include "port-forwarding.h" #include "rpc-server.h" -#include "session.h" #include "session-id.h" +#include "session.h" #include "stats.h" #include "torrent.h" #include "tr-assert.h" #include "tr-dht.h" /* tr_dhtUpkeep() */ +#include "tr-lpd.h" #include "tr-udp.h" #include "tr-utp.h" -#include "tr-lpd.h" #include "trevent.h" #include "utils.h" #include "variant.h" diff --git a/libtransmission/session.h b/libtransmission/session.h index f47967cef..0be23b57d 100644 --- a/libtransmission/session.h +++ b/libtransmission/session.h @@ -23,13 +23,12 @@ #include #include -#include // evutil_ascii_strcasecmp() +#include // evutil_ascii_strncasecmp() #include "bandwidth.h" -#include "bitfield.h" #include "net.h" #include "tr-macros.h" -#include "utils.h" +#include "utils.h" // tr_speed_K enum tr_auto_switch_state_t { @@ -43,14 +42,15 @@ tr_peer_id_t tr_peerIdInit(); struct event_base; struct evdns_base; +class tr_bitfield; struct tr_address; struct tr_announcer; struct tr_announcer_udp; struct tr_bindsockets; struct tr_blocklistFile; struct tr_cache; -struct tr_fdInfo; struct tr_device_info; +struct tr_fdInfo; struct tr_turtle_info { diff --git a/libtransmission/torrent-ctor.cc b/libtransmission/torrent-ctor.cc index b515dad10..15ecdbf14 100644 --- a/libtransmission/torrent-ctor.cc +++ b/libtransmission/torrent-ctor.cc @@ -93,7 +93,7 @@ char const* tr_ctorGetSourceFile(tr_ctor const* ctor) int tr_ctorSetMetainfoFromMagnetLink(tr_ctor* ctor, char const* magnet_link) { - tr_magnet_info* magnet_info = tr_magnetParse(magnet_link); + tr_magnet_info* magnet_info = magnet_link ? tr_magnetParse(magnet_link) : nullptr; if (magnet_info == nullptr) { return -1; diff --git a/libtransmission/torrent-magnet.cc b/libtransmission/torrent-magnet.cc index ab949ddde..331e3a794 100644 --- a/libtransmission/torrent-magnet.cc +++ b/libtransmission/torrent-magnet.cc @@ -12,18 +12,19 @@ #include #include "transmission.h" + #include "crypto-utils.h" /* tr_sha1() */ #include "file.h" #include "log.h" #include "magnet.h" #include "metainfo.h" #include "resume.h" -#include "torrent.h" #include "torrent-magnet.h" +#include "torrent.h" #include "tr-assert.h" #include "utils.h" #include "variant.h" -#include "web.h" +#include "web-utils.h" #define dbgmsg(tor, ...) tr_logAddDeepNamed(tr_torrentName(tor), __VA_ARGS__) diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index 904726040..277ec6664 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -30,6 +30,7 @@ #include /* evutil_vsnprintf() */ #include "transmission.h" + #include "announcer.h" #include "bandwidth.h" #include "cache.h" @@ -48,14 +49,15 @@ #include "resume.h" #include "session.h" #include "subprocess.h" -#include "torrent.h" #include "torrent-magnet.h" +#include "torrent.h" #include "tr-assert.h" #include "trevent.h" /* tr_runInEventThread() */ #include "utils.h" #include "variant.h" #include "verify.h" #include "version.h" +#include "web-utils.h" /*** **** @@ -112,11 +114,11 @@ tr_torrent* tr_torrentFindFromHash(tr_session* session, tr_sha1_digest_t const& return tr_torrentFindFromHash(session, reinterpret_cast(std::data(info_dict_hash))); } -tr_torrent* tr_torrentFindFromMagnetLink(tr_session* session, char const* magnet) +tr_torrent* tr_torrentFindFromMagnetLink(tr_session* session, char const* magnet_link) { tr_torrent* tor = nullptr; - tr_magnet_info* const info = tr_magnetParse(magnet); + tr_magnet_info* const info = magnet_link ? tr_magnetParse(magnet_link) : nullptr; if (info != nullptr) { tor = tr_torrentFindFromHash(session, info->hash); diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index 450e2bc2f..3e09bddac 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -19,20 +19,20 @@ #include #include -#include "bandwidth.h" /* tr_bandwidth */ +#include "bandwidth.h" #include "bitfield.h" -#include "completion.h" /* tr_completion */ +#include "completion.h" #include "file.h" #include "quark.h" -#include "session.h" /* tr_sessionLock(), tr_sessionUnlock() */ +#include "session.h" #include "tr-assert.h" #include "tr-macros.h" -#include "utils.h" /* TR_GNUC_PRINTF */ class tr_swarm; +struct tr_magnet_info; +struct tr_session; struct tr_torrent; struct tr_torrent_tiers; -struct tr_magnet_info; /** *** Package-visible ctor API diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h index bd5a2b652..4af2de0bc 100644 --- a/libtransmission/transmission.h +++ b/libtransmission/transmission.h @@ -43,8 +43,6 @@ struct tr_variant; using tr_priority_t = int8_t; -using tr_voidptr_compare_func = int (*)(void const* lhs, void const* rhs); - #define TR_RPC_SESSION_ID_HEADER "X-Transmission-Session-Id" enum tr_preallocation_mode diff --git a/libtransmission/utils.cc b/libtransmission/utils.cc index 738a71bb3..160b4efb2 100644 --- a/libtransmission/utils.cc +++ b/libtransmission/utils.cc @@ -803,229 +803,6 @@ void tr_hex_to_binary(void const* vinput, void* voutput, size_t byte_length) **** ***/ -bool tr_addressIsIP(char const* str) -{ - tr_address tmp; - return tr_address_from_string(&tmp, str); -} - -static int parsePort(std::string_view port) -{ - auto tmp = std::array{}; - - if (std::size(port) >= std::size(tmp)) - { - return -1; - } - - std::copy(std::begin(port), std::end(port), std::begin(tmp)); - char* end = nullptr; - long port_num = strtol(std::data(tmp), &end, 10); - if (*end != '\0' || port_num <= 0 || port_num >= 65536) - { - port_num = -1; - } - - return int(port_num); -} - -static std::string_view getPortForScheme(std::string_view scheme) -{ - auto constexpr KnownSchemes = std::array, 5>{ { - { "udp"sv, "80"sv }, - { "ftp"sv, "21"sv }, - { "sftp"sv, "22"sv }, - { "http"sv, "80"sv }, - { "https"sv, "443"sv }, - } }; - - for (auto const& [known_scheme, port] : KnownSchemes) - { - if (scheme == known_scheme) - { - return port; - } - } - - return "-1"sv; -} - -static bool urlCharsAreValid(std::string_view url) -{ - // rfc2396 - auto constexpr ValidChars = std::string_view{ - "abcdefghijklmnopqrstuvwxyz" // lowalpha - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" // upalpha - "0123456789" // digit - "-_.!~*'()" // mark - ";/?:@&=+$," // reserved - "<>#%<\"" // delims - "{}|\\^[]`" // unwise - }; - - return !std::empty(url) && - std::all_of(std::begin(url), std::end(url), [&ValidChars](auto ch) { return ValidChars.find(ch) != ValidChars.npos; }); -} - -std::optional tr_urlParse(std::string_view url) -{ - url = tr_strvstrip(url); - - if (!urlCharsAreValid(url)) - { - return {}; - } - - // scheme - auto key = "://"sv; - auto pos = url.find(key); - if (pos == std::string_view::npos || pos == 0) - { - return {}; - } - auto const scheme = url.substr(0, pos); - url.remove_prefix(pos + std::size(key)); - - // authority - key = "/"sv; - pos = url.find(key); - if (pos == 0) - { - return {}; - } - auto const authority = url.substr(0, pos); - url.remove_prefix(std::size(authority)); - auto const path = std::empty(url) ? "/"sv : url; - - // host - key = ":"sv; - pos = authority.find(key); - auto const host = pos == std::string_view::npos ? authority : authority.substr(0, pos); - if (std::empty(host)) - { - return {}; - } - - // port - auto const portstr = pos == std::string_view::npos ? getPortForScheme(scheme) : authority.substr(pos + std::size(key)); - auto const port = parsePort(portstr); - - return tr_parsed_url_t{ scheme, host, path, portstr, port }; -} - -static bool tr_isValidTrackerScheme(std::string_view scheme) -{ - auto constexpr Schemes = std::array{ "http"sv, "https"sv, "udp"sv }; - return std::find(std::begin(Schemes), std::end(Schemes), scheme) != std::end(Schemes); -} - -std::optional tr_urlParseTracker(std::string_view url) -{ - auto const parsed = tr_urlParse(url); - return parsed && tr_isValidTrackerScheme(parsed->scheme) ? *parsed : std::optional{}; -} - -bool tr_urlIsValidTracker(std::string_view url) -{ - return !!tr_urlParseTracker(url); -} - -bool tr_urlIsValid(std::string_view url) -{ - auto constexpr Schemes = std::array{ "http"sv, "https"sv, "ftp"sv, "sftp"sv, "udp"sv }; - auto const parsed = tr_urlParse(url); - return parsed && std::find(std::begin(Schemes), std::end(Schemes), parsed->scheme) != std::end(Schemes); -} - -bool tr_urlParse(char const* url, size_t url_len, char** setme_scheme, char** setme_host, int* setme_port, char** setme_path) -{ - if (url_len == TR_BAD_SIZE) - { - url_len = strlen(url); - } - - char const* scheme = url; - char const* scheme_end = tr_memmem(scheme, url_len, "://", 3); - - if (scheme_end == nullptr) - { - return false; - } - - size_t const scheme_len = scheme_end - scheme; - - if (scheme_len == 0) - { - return false; - } - - url += scheme_len + 3; - url_len -= scheme_len + 3; - - char const* authority = url; - auto const* authority_end = static_cast(memchr(authority, '/', url_len)); - - if (authority_end == nullptr) - { - authority_end = authority + url_len; - } - - size_t const authority_len = authority_end - authority; - - if (authority_len == 0) - { - return false; - } - - url += authority_len; - url_len -= authority_len; - - auto const* host_end = static_cast(memchr(authority, ':', authority_len)); - - size_t const host_len = host_end != nullptr ? (size_t)(host_end - authority) : authority_len; - - if (host_len == 0) - { - return false; - } - - size_t const port_len = host_end != nullptr ? authority_end - host_end - 1 : 0; - - if (setme_scheme != nullptr) - { - *setme_scheme = tr_strndup(scheme, scheme_len); - } - - if (setme_host != nullptr) - { - *setme_host = tr_strndup(authority, host_len); - } - - if (setme_port != nullptr) - { - auto const tmp = port_len > 0 ? std::string_view{ host_end + 1, port_len } : getPortForScheme({ scheme, scheme_len }); - *setme_port = parsePort(tmp); - } - - if (setme_path != nullptr) - { - if (url[0] == '\0') - { - *setme_path = tr_strdup("/"); - } - else - { - *setme_path = tr_strndup(url, url_len); - } - } - - return true; -} - -/*** -**** -***/ - void tr_removeElementFromArray(void* array, size_t index_to_remove, size_t sizeof_element, size_t nmemb) { auto* a = static_cast(array); @@ -1036,44 +813,6 @@ void tr_removeElementFromArray(void* array, size_t index_to_remove, size_t sizeo sizeof_element * (--nmemb - index_to_remove)); } -int tr_lowerBound( - void const* key, - void const* base, - size_t nmemb, - size_t size, - tr_voidptr_compare_func compar, - bool* exact_match) -{ - size_t first = 0; - auto const* cbase = static_cast(base); - bool exact = false; - - while (nmemb != 0) - { - size_t const half = nmemb / 2; - size_t const middle = first + half; - int const c = (*compar)(key, cbase + size * middle); - - if (c <= 0) - { - if (c == 0) - { - exact = true; - } - - nmemb = half; - } - else - { - first = middle + 1; - nmemb = nmemb - half - 1; - } - } - - *exact_match = exact; - return first; -} - /*** **** ***/ diff --git a/libtransmission/utils.h b/libtransmission/utils.h index cff059104..39cb91ba4 100644 --- a/libtransmission/utils.h +++ b/libtransmission/utils.h @@ -8,13 +8,13 @@ #pragma once -#include +#include +#include +#include +#include #include -#include -#include /* size_t */ #include #include -#include /* time_t */ #include #include @@ -229,15 +229,6 @@ constexpr bool tr_str_is_empty(char const* value) char* evbuffer_free_to_str(struct evbuffer* buf, size_t* result_len); -/** @brief similar to bsearch() but returns the index of the lower bound */ -int tr_lowerBound( - void const* key, - void const* base, - size_t nmemb, - size_t size, - tr_voidptr_compare_func compar, - bool* exact_match) TR_GNUC_HOT TR_GNUC_NONNULL(1, 5, 6); - /** * @brief sprintf() a string into a newly-allocated buffer large enough to hold it * @return a newly-allocated string that can be freed with tr_free() @@ -280,36 +271,6 @@ char* tr_strsep(char** str, char const* delim); 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 convenience function to determine if an address is an IP address (IPv4 or IPv6) */ -bool tr_addressIsIP(char const* address); - -/** @brief return true if the url is a http or https or UDP url that Transmission understands */ -bool tr_urlIsValidTracker(std::string_view url); - -/** @brief return true if the url is a [ http, https, ftp, sftp ] url that Transmission understands */ -bool tr_urlIsValid(std::string_view url); - -// TODO: move this to types.h -struct tr_parsed_url_t -{ - std::string_view scheme; - std::string_view host; - std::string_view path; - std::string_view portstr; - int port = -1; -}; - -std::optional tr_urlParse(std::string_view url); - -// like tr_urlParse(), but with the added constraint that 'scheme' -// must be one we that Transmission supports for announce and scrape -std::optional tr_urlParseTracker(std::string_view url); - -/** @brief parse a URL into its component parts - @return True on success or false if an error occurred */ -bool tr_urlParse(char const* url, size_t url_len, char** setme_scheme, char** setme_host, int* setme_port, char** setme_path) - TR_GNUC_NONNULL(1); - /** @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 new file mode 100644 index 000000000..d3aed24e5 --- /dev/null +++ b/libtransmission/web-utils.cc @@ -0,0 +1,439 @@ +/* + * This file Copyright (C) Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "transmission.h" + +#include "net.h" +#include "web-utils.h" +#include "utils.h" + +using namespace std::literals; + +/*** +**** +***/ + +bool tr_addressIsIP(char const* str) +{ + tr_address tmp; + return tr_address_from_string(&tmp, str); +} + +char const* tr_webGetResponseStr(long code) +{ + switch (code) + { + case 0: + return "No Response"; + + case 101: + return "Switching Protocols"; + + case 200: + return "OK"; + + case 201: + return "Created"; + + case 202: + return "Accepted"; + + case 203: + return "Non-Authoritative Information"; + + case 204: + return "No Content"; + + case 205: + return "Reset Content"; + + case 206: + return "Partial Content"; + + case 300: + return "Multiple Choices"; + + case 301: + return "Moved Permanently"; + + case 302: + return "Found"; + + case 303: + return "See Other"; + + case 304: + return "Not Modified"; + + case 305: + return "Use Proxy"; + + case 306: + return " (Unused)"; + + case 307: + return "Temporary Redirect"; + + case 400: + return "Bad Request"; + + case 401: + return "Unauthorized"; + + case 402: + return "Payment Required"; + + case 403: + return "Forbidden"; + + case 404: + return "Not Found"; + + case 405: + return "Method Not Allowed"; + + case 406: + return "Not Acceptable"; + + case 407: + return "Proxy Authentication Required"; + + case 408: + return "Request Timeout"; + + case 409: + return "Conflict"; + + case 410: + return "Gone"; + + case 411: + return "Length Required"; + + case 412: + return "Precondition Failed"; + + case 413: + return "Request Entity Too Large"; + + case 414: + return "Request-URI Too Long"; + + case 415: + return "Unsupported Media Type"; + + case 416: + return "Requested Range Not Satisfiable"; + + case 417: + return "Expectation Failed"; + + case 421: + return "Misdirected Request"; + + case 500: + return "Internal Server Error"; + + case 501: + return "Not Implemented"; + + case 502: + return "Bad Gateway"; + + case 503: + return "Service Unavailable"; + + case 504: + return "Gateway Timeout"; + + case 505: + return "HTTP Version Not Supported"; + + default: + return "Unknown Error"; + } +} + +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& ch : str) + { + if ((UnescapedChars.find(ch) != std::string_view::npos) || (ReservedChars.find(ch) && !escape_reserved)) + { + evbuffer_add_printf(out, "%c", ch); + } + else + { + evbuffer_add_printf(out, "%%%02X", (unsigned)(ch & 0xFF)); + } + } +} + +static bool is_rfc2396_alnum(uint8_t ch) +{ + return ('0' <= ch && ch <= '9') || ('A' <= ch && ch <= 'Z') || ('a' <= ch && ch <= 'z') || ch == '.' || ch == '-' || + ch == '_' || ch == '~'; +} + +void tr_http_escape_sha1(char* out, tr_sha1_digest_t const& digest) +{ + for (auto const b : digest) + { + if (is_rfc2396_alnum(uint8_t(b))) + { + *out++ = (char)b; + } + else + { + out += tr_snprintf(out, 4, "%%%02x", (unsigned int)b); + } + } + + *out = '\0'; +} + +void tr_http_escape_sha1(char* out, uint8_t const* sha1_digest) +{ + auto digest = tr_sha1_digest_t{}; + std::copy_n(reinterpret_cast(sha1_digest), std::size(digest), std::begin(digest)); + tr_http_escape_sha1(out, digest); +} + +//// URLs + +namespace +{ + +int parsePort(std::string_view port) +{ + auto tmp = std::array{}; + + if (std::size(port) >= std::size(tmp)) + { + return -1; + } + + std::copy(std::begin(port), std::end(port), std::begin(tmp)); + char* end = nullptr; + long port_num = strtol(std::data(tmp), &end, 10); + if (*end != '\0' || port_num <= 0 || port_num >= 65536) + { + port_num = -1; + } + + return int(port_num); +} + +constexpr std::string_view getPortForScheme(std::string_view scheme) +{ + auto constexpr KnownSchemes = std::array, 5>{ { + { "ftp"sv, "21"sv }, + { "http"sv, "80"sv }, + { "https"sv, "443"sv }, + { "sftp"sv, "22"sv }, + { "udp"sv, "80"sv }, + } }; + + for (auto const& [known_scheme, port] : KnownSchemes) + { + if (scheme == known_scheme) + { + return port; + } + } + + return "-1"sv; +} + +bool urlCharsAreValid(std::string_view url) +{ + // rfc2396 + auto constexpr ValidChars = std::string_view{ + "abcdefghijklmnopqrstuvwxyz" // lowalpha + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" // upalpha + "0123456789" // digit + "-_.!~*'()" // mark + ";/?:@&=+$," // reserved + "<>#%<\"" // delims + "{}|\\^[]`" // unwise + }; + + return !std::empty(url) && + std::all_of(std::begin(url), std::end(url), [&ValidChars](auto ch) { return ValidChars.find(ch) != ValidChars.npos; }); +} + +bool tr_isValidTrackerScheme(std::string_view scheme) +{ + auto constexpr Schemes = std::array{ "http"sv, "https"sv, "udp"sv }; + return std::find(std::begin(Schemes), std::end(Schemes), scheme) != std::end(Schemes); +} + +} // namespace + +std::optional tr_urlParse(std::string_view url) +{ + url = tr_strvstrip(url); + + if (!urlCharsAreValid(url)) + { + return {}; + } + + auto parsed = tr_url_parsed_t{}; + parsed.full = url; + + // scheme + auto key = ":"sv; + auto pos = url.find(key); + if (pos == std::string_view::npos || pos == 0) + { + 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) + { + url.remove_prefix(pos + std::size(key)); + 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)); + 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("?#"); + parsed.path = url.substr(0, pos); + url = pos == url.npos ? ""sv : url.substr(pos); + + // query + if (url.find('?') == 0) + { + url.remove_prefix(1); + pos = url.find('#'); + parsed.query = url.substr(0, pos); + url = pos == url.npos ? ""sv : url.substr(pos); + } + + // fragment + if (url.find('#') == 0) + { + parsed.fragment = url.substr(1); + } + + return parsed; +} + +std::optional tr_urlParseTracker(std::string_view url) +{ + auto const parsed = tr_urlParse(url); + return parsed && tr_isValidTrackerScheme(parsed->scheme) ? *parsed : std::optional{}; +} + +bool tr_urlIsValidTracker(std::string_view url) +{ + return !!tr_urlParseTracker(url); +} + +bool tr_urlIsValid(std::string_view url) +{ + auto constexpr Schemes = std::array{ "http"sv, "https"sv, "ftp"sv, "sftp"sv, "udp"sv }; + auto const parsed = tr_urlParse(url); + return parsed && std::find(std::begin(Schemes), std::end(Schemes), parsed->scheme) != std::end(Schemes); +} + +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); + return *this; +} + +tr_url_query_view::iterator tr_url_query_view::begin() const +{ + auto it = iterator{}; + it.remain = query; + ++it; + return it; +} + +std::string tr_urlPercentDecode(std::string_view in) +{ + auto out = std::string{}; + out.reserve(std::size(in)); + + for (;;) + { + auto pos = in.find('%'); + out += in.substr(0, pos); + if (pos == in.npos) + { + break; + } + + in.remove_prefix(pos); + if (std::size(in) >= 3 && in[0] == '%' && std::isxdigit(in[1]) && std::isxdigit(in[2])) + { + auto hexstr = std::array{ in[1], in[2], '\0' }; + auto const hex = strtoul(std::data(hexstr), nullptr, 16); + out += char(hex); + in.remove_prefix(3); + } + else + { + out += in.front(); + in.remove_prefix(1); + } + } + + return out; +} diff --git a/libtransmission/web-utils.h b/libtransmission/web-utils.h new file mode 100644 index 000000000..fa78369a7 --- /dev/null +++ b/libtransmission/web-utils.h @@ -0,0 +1,102 @@ +/* + * This file Copyright (C) Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#pragma once + +#include +#include +#include + +struct evbuffer; + +#include "transmission.h" // tr_sha1_digest_t + +/** @brief convenience function to determine if an address is an IP address (IPv4 or IPv6) */ +bool tr_addressIsIP(char const* address); + +/** @brief return true if the url is a http or https or UDP url that Transmission understands */ +bool tr_urlIsValidTracker(std::string_view url); + +/** @brief return true if the url is a [ http, https, ftp, sftp ] url that Transmission understands */ +bool tr_urlIsValid(std::string_view url); + +struct tr_url_parsed_t +{ + std::string_view scheme; + std::string_view authority; + std::string_view host; + std::string_view path; + std::string_view portstr; + std::string_view query; + std::string_view fragment; + std::string_view full; + int port = -1; +}; + +std::optional tr_urlParse(std::string_view url); + +// like tr_urlParse(), but with the added constraint that 'scheme' +// must be one we that Transmission supports for announce and scrape +std::optional tr_urlParseTracker(std::string_view url); + +// example use: `for (auto const [key, val] : tr_url_query_view{ querystr })` +struct tr_url_query_view +{ + std::string_view const query; + + explicit tr_url_query_view(std::string_view query_in) + : query{ query_in } + { + } + + struct iterator + { + std::pair keyval = std::make_pair(std::string_view{ "" }, std::string_view{ "" }); + std::string_view remain = std::string_view{ "" }; + + iterator& operator++(); + + constexpr auto const& operator*() const + { + return keyval; + } + + constexpr auto const* operator->() const + { + return &keyval; + } + + constexpr bool operator==(iterator const& that) const + { + return this->remain == that.remain && this->keyval == that.keyval; + } + + constexpr bool operator!=(iterator const& that) const + { + return !(*this == that); + } + }; + + iterator begin() const; + + constexpr iterator end() const + { + return iterator{}; + } +}; + +// TODO: replace evbuffer* with std::string& +void tr_http_escape(struct evbuffer* out, std::string_view str, bool escape_reserved); + +void tr_http_escape_sha1(char* out, uint8_t const* sha1_digest); + +void tr_http_escape_sha1(char* out, tr_sha1_digest_t const& digest); + +char const* tr_webGetResponseStr(long response_code); + +std::string tr_urlPercentDecode(std::string_view); diff --git a/libtransmission/web.cc b/libtransmission/web.cc index 04df963fe..0f26c1ac3 100644 --- a/libtransmission/web.cc +++ b/libtransmission/web.cc @@ -602,199 +602,3 @@ char const* tr_webGetTaskRealUrl(struct tr_web_task* task) curl_easy_getinfo(task->curl_easy, CURLINFO_EFFECTIVE_URL, &url); return url; } - -/***** -****** -****** -*****/ - -char const* tr_webGetResponseStr(long code) -{ - switch (code) - { - case 0: - return "No Response"; - - case 101: - return "Switching Protocols"; - - case 200: - return "OK"; - - case 201: - return "Created"; - - case 202: - return "Accepted"; - - case 203: - return "Non-Authoritative Information"; - - case 204: - return "No Content"; - - case 205: - return "Reset Content"; - - case 206: - return "Partial Content"; - - case 300: - return "Multiple Choices"; - - case 301: - return "Moved Permanently"; - - case 302: - return "Found"; - - case 303: - return "See Other"; - - case 304: - return "Not Modified"; - - case 305: - return "Use Proxy"; - - case 306: - return " (Unused)"; - - case 307: - return "Temporary Redirect"; - - case 400: - return "Bad Request"; - - case 401: - return "Unauthorized"; - - case 402: - return "Payment Required"; - - case 403: - return "Forbidden"; - - case 404: - return "Not Found"; - - case 405: - return "Method Not Allowed"; - - case 406: - return "Not Acceptable"; - - case 407: - return "Proxy Authentication Required"; - - case 408: - return "Request Timeout"; - - case 409: - return "Conflict"; - - case 410: - return "Gone"; - - case 411: - return "Length Required"; - - case 412: - return "Precondition Failed"; - - case 413: - return "Request Entity Too Large"; - - case 414: - return "Request-URI Too Long"; - - case 415: - return "Unsupported Media Type"; - - case 416: - return "Requested Range Not Satisfiable"; - - case 417: - return "Expectation Failed"; - - case 421: - return "Misdirected Request"; - - case 500: - return "Internal Server Error"; - - case 501: - return "Not Implemented"; - - case 502: - return "Bad Gateway"; - - case 503: - return "Service Unavailable"; - - case 504: - return "Gateway Timeout"; - - case 505: - return "HTTP Version Not Supported"; - - default: - return "Unknown Error"; - } -} - -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& ch : str) - { - if ((UnescapedChars.find(ch) != std::string_view::npos) || (ReservedChars.find(ch) && !escape_reserved)) - { - evbuffer_add_printf(out, "%c", ch); - } - else - { - evbuffer_add_printf(out, "%%%02X", (unsigned)(ch & 0xFF)); - } - } -} - -char* tr_http_unescape(char const* str, size_t len) -{ - char* tmp = curl_unescape(str, len); - char* ret = tr_strdup(tmp); - curl_free(tmp); - return ret; -} - -static bool is_rfc2396_alnum(uint8_t ch) -{ - return ('0' <= ch && ch <= '9') || ('A' <= ch && ch <= 'Z') || ('a' <= ch && ch <= 'z') || ch == '.' || ch == '-' || - ch == '_' || ch == '~'; -} - -void tr_http_escape_sha1(char* out, tr_sha1_digest_t const& digest) -{ - for (auto const b : digest) - { - if (is_rfc2396_alnum(uint8_t(b))) - { - *out++ = (char)b; - } - else - { - out += tr_snprintf(out, 4, "%%%02x", (unsigned int)b); - } - } - - *out = '\0'; -} - -void tr_http_escape_sha1(char* out, uint8_t const* sha1_digest) -{ - auto digest = tr_sha1_digest_t{}; - std::copy_n(reinterpret_cast(sha1_digest), std::size(digest), std::begin(digest)); - tr_http_escape_sha1(out, digest); -} diff --git a/libtransmission/web.h b/libtransmission/web.h index 879199af6..1057e6327 100644 --- a/libtransmission/web.h +++ b/libtransmission/web.h @@ -1,5 +1,5 @@ /* - * This file Copyright (C) 2008-2014 Mnemosyne LLC + * This file Copyright (C) Mnemosyne LLC * * It may be used under the GNU GPL versions 2 or 3 * or any future license endorsed by Mnemosyne LLC. @@ -9,10 +9,10 @@ #pragma once #include -#include -#include "tr-macros.h" +#include "transmission.h" +struct evbuffer; struct tr_address; struct tr_web_task; @@ -33,8 +33,6 @@ using tr_web_done_func = void (*)( size_t response_byte_count, void* user_data); -char const* tr_webGetResponseStr(long response_code); - struct tr_web_task* tr_webRun(tr_session* session, char const* url, tr_web_done_func done_func, void* done_func_user_data); struct tr_web_task* tr_webRunWithCookies( @@ -44,8 +42,6 @@ struct tr_web_task* tr_webRunWithCookies( tr_web_done_func done_func, void* done_func_user_data); -struct evbuffer; - struct tr_web_task* tr_webRunWebseed( tr_torrent* tor, char const* url, @@ -57,11 +53,3 @@ struct tr_web_task* tr_webRunWebseed( long tr_webGetTaskResponseCode(struct tr_web_task* task); char const* tr_webGetTaskRealUrl(struct tr_web_task* task); - -void tr_http_escape(struct evbuffer* out, std::string_view str, bool escape_reserved); - -void tr_http_escape_sha1(char* out, uint8_t const* sha1_digest); - -void tr_http_escape_sha1(char* out, tr_sha1_digest_t const& digest); - -char* tr_http_unescape(char const* str, size_t len); diff --git a/libtransmission/webseed.cc b/libtransmission/webseed.cc index da493ec8c..164780e70 100644 --- a/libtransmission/webseed.cc +++ b/libtransmission/webseed.cc @@ -9,11 +9,13 @@ #include #include /* strlen() */ #include +#include #include #include #include "transmission.h" + #include "bandwidth.h" #include "cache.h" #include "inout.h" /* tr_ioFindFileLocation() */ @@ -21,6 +23,7 @@ #include "torrent.h" #include "trevent.h" /* tr_runInEventThread() */ #include "utils.h" +#include "web-utils.h" #include "web.h" #include "webseed.h" diff --git a/macosx/CreatorWindowController.mm b/macosx/CreatorWindowController.mm index 0130f314f..9eb9eeee9 100644 --- a/macosx/CreatorWindowController.mm +++ b/macosx/CreatorWindowController.mm @@ -21,7 +21,8 @@ *****************************************************************************/ #include -#include // tr_urlIsValidTracker() +#include +#include // tr_urlIsValidTracker() #import "CreatorWindowController.h" #import "Controller.h" diff --git a/macosx/TrackerCell.mm b/macosx/TrackerCell.mm index 77eba8d07..eb12f219f 100644 --- a/macosx/TrackerCell.mm +++ b/macosx/TrackerCell.mm @@ -21,7 +21,7 @@ *****************************************************************************/ #include -#include //tr_addressIsIP() +#include //tr_addressIsIP() #import "TrackerCell.h" #import "TrackerNode.h" diff --git a/tests/libtransmission/CMakeLists.txt b/tests/libtransmission/CMakeLists.txt index 50fa53b59..134d00d0a 100644 --- a/tests/libtransmission/CMakeLists.txt +++ b/tests/libtransmission/CMakeLists.txt @@ -24,7 +24,8 @@ add_executable(libtransmission-test test-fixtures.h utils-test.cc variant-test.cc - watchdir-test.cc) + watchdir-test.cc + web-utils-test.cc) target_compile_definitions(libtransmission-test PRIVATE diff --git a/tests/libtransmission/magnet-test.cc b/tests/libtransmission/magnet-test.cc index 3c2d43099..587cb8c0b 100644 --- a/tests/libtransmission/magnet-test.cc +++ b/tests/libtransmission/magnet-test.cc @@ -13,31 +13,34 @@ #include "gtest/gtest.h" #include +#include + +using namespace std::literals; TEST(Magnet, magnetParse) { - auto const expected_hash = std::array{ + auto constexpr ExpectedHash = std::array{ 210, 53, 64, 16, 163, 202, 74, 222, 91, 116, // 39, 187, 9, 58, 98, 163, 137, 159, 243, 129, // }; - char const* const uri_hex = + auto constexpr UriHex = "magnet:?xt=urn:btih:" "d2354010a3ca4ade5b7427bb093a62a3899ff381" "&dn=Display%20Name" "&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce" "&tr=http%3A%2F%2Ftracker.opentracker.org%2Fannounce" - "&ws=http%3A%2F%2Fserver.webseed.org%2Fpath%2Fto%2Ffile"; + "&ws=http%3A%2F%2Fserver.webseed.org%2Fpath%2Fto%2Ffile"sv; - char const* const uri_base32 = + auto constexpr UriBase32 = "magnet:?xt=urn:btih:" "2I2UAEFDZJFN4W3UE65QSOTCUOEZ744B" "&dn=Display%20Name" "&tr=http%3A%2F%2Ftracker.openbittorrent.com%2Fannounce" "&ws=http%3A%2F%2Fserver.webseed.org%2Fpath%2Fto%2Ffile" - "&tr=http%3A%2F%2Ftracker.opentracker.org%2Fannounce"; + "&tr=http%3A%2F%2Ftracker.opentracker.org%2Fannounce"sv; - for (auto const& uri : { uri_hex, uri_base32 }) + for (auto const& uri : { UriHex, UriBase32 }) { auto* info = tr_magnetParse(uri); EXPECT_NE(nullptr, info); @@ -47,8 +50,8 @@ TEST(Magnet, magnetParse) EXPECT_EQ(1, info->webseedCount); EXPECT_STREQ("http://server.webseed.org/path/to/file", info->webseeds[0]); EXPECT_STREQ("Display Name", info->displayName); - EXPECT_EQ(expected_hash.size(), sizeof(info->hash)); - EXPECT_EQ(0, memcmp(info->hash, expected_hash.data(), expected_hash.size())); + EXPECT_EQ(std::size(ExpectedHash), sizeof(info->hash)); + EXPECT_EQ(0, memcmp(info->hash, std::data(ExpectedHash), std::size(ExpectedHash))); tr_magnetFree(info); } } diff --git a/tests/libtransmission/utils-test.cc b/tests/libtransmission/utils-test.cc index c0da3604b..81a245131 100644 --- a/tests/libtransmission/utils-test.cc +++ b/tests/libtransmission/utils-test.cc @@ -15,11 +15,12 @@ #endif #include "transmission.h" + #include "ConvertUTF.h" // tr_utf8_validate() -#include "platform.h" #include "crypto-utils.h" // tr_rand_int_weak() +#include "platform.h" +#include "ptrarray.h" #include "utils.h" -#include "web.h" // tr_http_unescape() #include "test-fixtures.h" @@ -231,93 +232,6 @@ TEST_F(UtilsTest, array) } } -TEST_F(UtilsTest, url) -{ - auto const* url = "http://1"; - int port; - char* scheme = nullptr; - char* host = nullptr; - char* path = nullptr; - EXPECT_TRUE(tr_urlParse(url, TR_BAD_SIZE, &scheme, &host, &port, &path)); - EXPECT_STREQ("http", scheme); - EXPECT_STREQ("1", host); - EXPECT_STREQ("/", path); - EXPECT_EQ(80, port); - tr_free(scheme); - tr_free(path); - tr_free(host); - - auto parsed = tr_urlParse(url); - EXPECT_TRUE(parsed); - EXPECT_EQ("http"sv, parsed->scheme); - EXPECT_EQ("1"sv, parsed->host); - EXPECT_EQ("/"sv, parsed->path); - EXPECT_EQ("80"sv, parsed->portstr); - EXPECT_EQ(80, parsed->port); - - url = "http://www.some-tracker.org/some/path"; - scheme = nullptr; - host = nullptr; - path = nullptr; - EXPECT_TRUE(tr_urlParse(url, TR_BAD_SIZE, &scheme, &host, &port, &path)); - EXPECT_STREQ("http", scheme); - EXPECT_STREQ("www.some-tracker.org", host); - EXPECT_STREQ("/some/path", path); - EXPECT_EQ(80, port); - tr_free(scheme); - tr_free(path); - tr_free(host); - - parsed = tr_urlParse(url); - EXPECT_TRUE(parsed); - EXPECT_EQ("http"sv, parsed->scheme); - EXPECT_EQ("www.some-tracker.org"sv, parsed->host); - EXPECT_EQ("/some/path"sv, parsed->path); - EXPECT_EQ("80"sv, parsed->portstr); - EXPECT_EQ(80, parsed->port); - - url = "http://www.some-tracker.org:8080/some/path"; - scheme = nullptr; - host = nullptr; - path = nullptr; - EXPECT_TRUE(tr_urlParse(url, TR_BAD_SIZE, &scheme, &host, &port, &path)); - EXPECT_STREQ("http", scheme); - EXPECT_STREQ("www.some-tracker.org", host); - EXPECT_STREQ("/some/path", path); - EXPECT_EQ(8080, port); - tr_free(scheme); - tr_free(path); - tr_free(host); - - parsed = tr_urlParse(url); - EXPECT_TRUE(parsed); - EXPECT_EQ("http"sv, parsed->scheme); - EXPECT_EQ("www.some-tracker.org"sv, parsed->host); - EXPECT_EQ("/some/path"sv, parsed->path); - EXPECT_EQ("8080"sv, parsed->portstr); - EXPECT_EQ(8080, parsed->port); - - EXPECT_FALSE(tr_urlIsValid("hello world"sv)); - EXPECT_FALSE(tr_urlIsValid("http://www.💩.com/announce/"sv)); - EXPECT_TRUE(tr_urlIsValid("http://www.example.com/announce/"sv)); - EXPECT_FALSE(tr_urlIsValid(""sv)); - EXPECT_FALSE(tr_urlIsValid("com"sv)); - EXPECT_FALSE(tr_urlIsValid("www.example.com"sv)); - EXPECT_FALSE(tr_urlIsValid("://www.example.com"sv)); - EXPECT_FALSE(tr_urlIsValid("zzz://www.example.com"sv)); // syntactically valid, but unsupported scheme - EXPECT_TRUE(tr_urlIsValid("https://www.example.com"sv)); - - EXPECT_TRUE(tr_urlIsValid("sftp://www.example.com"sv)); - EXPECT_FALSE(tr_urlIsValidTracker("sftp://www.example.com"sv)); // unsupported tracker scheme -} - -TEST_F(UtilsTest, trHttpUnescape) -{ - auto const url = std::string{ "http%3A%2F%2Fwww.example.com%2F~user%2F%3Ftest%3D1%26test1%3D2" }; - auto str = makeString(tr_http_unescape(url.data(), url.size())); - EXPECT_EQ("http://www.example.com/~user/?test=1&test1=2", str); -} - TEST_F(UtilsTest, truncd) { auto buf = std::array{}; diff --git a/tests/libtransmission/web-utils-test.cc b/tests/libtransmission/web-utils-test.cc new file mode 100644 index 000000000..5f619c98b --- /dev/null +++ b/tests/libtransmission/web-utils-test.cc @@ -0,0 +1,180 @@ +/* + * This file Copyright (C) 2013-2014 Mnemosyne LLC + * + * It may be used under the GNU GPL versions 2 or 3 + * or any future license endorsed by Mnemosyne LLC. + * + */ + +#include + +#ifdef _WIN32 +#include +#define setenv(key, value, unused) SetEnvironmentVariableA(key, value) +#define unsetenv(key) SetEnvironmentVariableA(key, nullptr) +#endif + +#include "transmission.h" +#include "platform.h" +#include "web-utils.h" + +#include "test-fixtures.h" + +using namespace std::literals; + +using WebUtilsTest = ::testing::Test; +using namespace std::literals; + +TEST_F(WebUtilsTest, urlParse) +{ + auto url = "http://1"sv; + auto parsed = tr_urlParse(url); + EXPECT_TRUE(parsed); + EXPECT_EQ("http"sv, parsed->scheme); + EXPECT_EQ("1"sv, parsed->host); + EXPECT_EQ(""sv, parsed->path); + EXPECT_EQ("80"sv, parsed->portstr); + EXPECT_EQ(""sv, parsed->query); + EXPECT_EQ(""sv, parsed->fragment); + EXPECT_EQ(80, parsed->port); + + url = "http://www.some-tracker.org/some/path"sv; + parsed = tr_urlParse(url); + EXPECT_TRUE(parsed); + EXPECT_EQ("http"sv, parsed->scheme); + EXPECT_EQ("www.some-tracker.org"sv, parsed->host); + EXPECT_EQ("/some/path"sv, parsed->path); + EXPECT_EQ(""sv, parsed->query); + EXPECT_EQ(""sv, parsed->fragment); + EXPECT_EQ("80"sv, parsed->portstr); + EXPECT_EQ(80, parsed->port); + + url = "http://www.some-tracker.org:8080/some/path"sv; + parsed = tr_urlParse(url); + EXPECT_TRUE(parsed); + EXPECT_EQ("http"sv, parsed->scheme); + EXPECT_EQ("www.some-tracker.org"sv, parsed->host); + EXPECT_EQ("/some/path"sv, parsed->path); + EXPECT_EQ(""sv, parsed->query); + EXPECT_EQ(""sv, parsed->fragment); + EXPECT_EQ("8080"sv, parsed->portstr); + EXPECT_EQ(8080, parsed->port); + + url = "http://www.some-tracker.org:8080/some/path?key=val&foo=bar#fragment"sv; + parsed = tr_urlParse(url); + EXPECT_TRUE(parsed); + EXPECT_EQ("http"sv, parsed->scheme); + EXPECT_EQ("www.some-tracker.org"sv, parsed->host); + EXPECT_EQ("/some/path"sv, parsed->path); + EXPECT_EQ("key=val&foo=bar"sv, parsed->query); + EXPECT_EQ("fragment"sv, parsed->fragment); + EXPECT_EQ("8080"sv, parsed->portstr); + EXPECT_EQ(8080, parsed->port); + + url = + "magnet:" + "?xt=urn:btih:14ffe5dd23188fd5cb53a1d47f1289db70abf31e" + "&dn=ubuntu_12_04_1_desktop_32_bit" + "&tr=http%3A%2F%2Ftracker.publicbt.com%2Fannounce" + "&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80" + "&ws=http%3A%2F%2Ftransmissionbt.com"sv; + parsed = tr_urlParse(url); + EXPECT_TRUE(parsed); + EXPECT_EQ("magnet"sv, parsed->scheme); + EXPECT_EQ(""sv, parsed->host); + EXPECT_EQ(""sv, parsed->path); + EXPECT_EQ( + "xt=urn:btih:14ffe5dd23188fd5cb53a1d47f1289db70abf31e" + "&dn=ubuntu_12_04_1_desktop_32_bit" + "&tr=http%3A%2F%2Ftracker.publicbt.com%2Fannounce" + "&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80" + "&ws=http%3A%2F%2Ftransmissionbt.com"sv, + parsed->query); + EXPECT_EQ(""sv, parsed->portstr); +} + +TEST_F(WebUtilsTest, urlNextQueryPair) +{ + auto constexpr Query = "a=1&b=two&c=si&d_has_no_val&e=&f&g=gee"sv; + auto const query_view = tr_url_query_view{ Query }; + auto const end = std::end(query_view); + + auto it = std::begin(query_view); + EXPECT_NE(end, it); + EXPECT_EQ("a"sv, it->first); + EXPECT_EQ("1"sv, it->second); + + ++it; + EXPECT_NE(end, it); + EXPECT_EQ("b"sv, it->first); + EXPECT_EQ("two"sv, it->second); + + ++it; + EXPECT_NE(end, it); + EXPECT_EQ("c"sv, it->first); + EXPECT_EQ("si"sv, it->second); + + ++it; + EXPECT_NE(end, it); + EXPECT_EQ("d_has_no_val"sv, it->first); + EXPECT_EQ(""sv, it->second); + + ++it; + EXPECT_NE(end, it); + EXPECT_EQ("e"sv, it->first); + EXPECT_EQ(""sv, it->second); + + ++it; + EXPECT_NE(end, it); + EXPECT_EQ("f"sv, it->first); + EXPECT_EQ(""sv, it->second); + + ++it; + EXPECT_NE(end, it); + EXPECT_EQ("g"sv, it->first); + EXPECT_EQ("gee"sv, it->second); + + ++it; + EXPECT_EQ(end, it); +} + +TEST_F(WebUtilsTest, urlIsValid) +{ + EXPECT_FALSE(tr_urlIsValid("hello world"sv)); + EXPECT_FALSE(tr_urlIsValid("http://www.💩.com/announce/"sv)); + EXPECT_TRUE(tr_urlIsValid("http://www.example.com/announce/"sv)); + EXPECT_FALSE(tr_urlIsValid(""sv)); + EXPECT_FALSE(tr_urlIsValid("com"sv)); + EXPECT_FALSE(tr_urlIsValid("www.example.com"sv)); + EXPECT_FALSE(tr_urlIsValid("://www.example.com"sv)); + EXPECT_FALSE(tr_urlIsValid("zzz://www.example.com"sv)); // syntactically valid, but unsupported scheme + EXPECT_TRUE(tr_urlIsValid("https://www.example.com"sv)); + + EXPECT_TRUE(tr_urlIsValid("sftp://www.example.com"sv)); + EXPECT_FALSE(tr_urlIsValidTracker("sftp://www.example.com"sv)); // unsupported tracker scheme +} + +TEST_F(WebUtilsTest, urlPercentDecode) +{ + auto constexpr Tests = std::array, 13>{ { + { "%-2"sv, "%-2"sv }, + { "%6 1"sv, "%6 1"sv }, + { "%6"sv, "%6"sv }, + { "%6%a"sv, "%6%a"sv }, + { "%61 "sv, "a "sv }, + { "%61"sv, "a"sv }, + { "%61a"sv, "aa"sv }, + { "%61b"sv, "ab"sv }, + { "%6a"sv, "j"sv }, + { "%FF"sv, "\xff"sv }, + { "%FF%00%ff"sv, "\xff\x00\xff"sv }, + { "%FG"sv, "%FG"sv }, + { "http%3A%2F%2Fwww.example.com%2F~user%2F%3Ftest%3D1%26test1%3D2"sv, + "http://www.example.com/~user/?test=1&test1=2"sv }, + } }; + + for (auto const& test : Tests) + { + EXPECT_EQ(test.second, tr_urlPercentDecode(test.first)); + } +} diff --git a/utils/show.cc b/utils/show.cc index 79d2df45c..f129000de 100644 --- a/utils/show.cc +++ b/utils/show.cc @@ -18,7 +18,7 @@ #include #include #include -#include /* tr_webGetResponseStr() */ +#include /* tr_webGetResponseStr() */ #include #include