refactor: web_utils (#2121)

* chore: move web utils from web, utils to web-utils
This commit is contained in:
Charles Kerr 2021-11-08 21:30:03 -06:00 committed by GitHub
parent 5df3505832
commit d8b57fe4dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 958 additions and 800 deletions

View File

@ -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 = "<group>"; };
C1639A7A1A55F57200E42033 /* cdecode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = cdecode.h; path = include/b64/cdecode.h; sourceTree = "<group>"; };
C1639A7B1A55F57200E42033 /* cencode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = cencode.h; path = include/b64/cencode.h; sourceTree = "<group>"; };
C17740D3273A002C00E455D2 /* web-utils.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "web-utils.cc"; sourceTree = "<group>"; };
C17740D4273A002C00E455D2 /* web-utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "web-utils.h"; sourceTree = "<group>"; };
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 */,

View File

@ -19,6 +19,7 @@
#include <libtransmission/transmission.h>
#include <libtransmission/utils.h> /* tr_free */
#include <libtransmission/web-utils.h>
#include "Actions.h"
#include "DetailsDialog.h"

View File

@ -15,6 +15,7 @@
#include <libtransmission/transmission.h>
#include <libtransmission/utils.h>
#include <libtransmission/web-utils.h>
#include "FaviconCache.h" /* gtr_get_favicon() */
#include "FilterBar.h"

View File

@ -17,6 +17,7 @@
#include <libtransmission/transmission.h>
#include <libtransmission/utils.h>
#include <libtransmission/version.h>
#include <libtransmission/web-utils.h>
#include "FreeSpaceLabel.h"
#include "HigWorkarea.h"
@ -429,8 +430,7 @@ void onBlocklistUpdate(Gtk::Button* w, std::shared_ptr<blocklist_data> 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<Session> const& core)

View File

@ -19,7 +19,7 @@
#include <libtransmission/transmission.h> /* TR_RATIO_NA, TR_RATIO_INF */
#include <libtransmission/error.h>
#include <libtransmission/utils.h> /* tr_strratio() */
#include <libtransmission/web.h> /* tr_webResponseStr() */
#include <libtransmission/web-utils.h>
#include <libtransmission/version.h> /* SHORT_VERSION_STRING */
#include "HigWorkarea.h"

View File

@ -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

View File

@ -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)

View File

@ -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__)

View File

@ -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 */

View File

@ -8,6 +8,7 @@
#include <algorithm>
#include <cstring> /* memset() */
#include <vector>
#include "transmission.h"
#include "bandwidth.h"

View File

@ -14,10 +14,10 @@
#include <array>
#include <unordered_set>
#include <vector>
#include "transmission.h"
#include "tr-assert.h"
#include "utils.h" /* tr_new(), tr_free() */
class tr_peerIo;

View File

@ -10,13 +10,14 @@
#include <algorithm>
#include <array>
#include <iterator>
#include <optional>
#include <string_view>
#include <cctype> /* isprint() */
#include <cstdlib> /* strtol() */
#include <cstring>
#include <iterator>
#include <optional>
#include <string_view>
#include <tuple>
#include <vector>
#include "transmission.h"
#include "clients.h"

View File

@ -6,6 +6,8 @@
*
*/
#include <vector>
#include "transmission.h"
#include "completion.h"
#include "torrent.h"

View File

@ -12,9 +12,10 @@
#error only libtransmission should #include this header.
#endif
#include <vector>
#include "transmission.h"
#include "bitfield.h"
#include "utils.h" /* tr_getRatio() */
struct tr_completion
{

View File

@ -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

View File

@ -18,7 +18,6 @@
#include "crypto-utils.h"
#include "tr-macros.h"
#include "utils.h" /* TR_GNUC_NULL_TERMINATED */
/**
*** @addtogroup peers

View File

@ -11,6 +11,7 @@
#include <cstdlib> /* bsearch() */
#include <cstring> /* memcmp() */
#include <optional>
#include <vector>
#include "transmission.h"
#include "cache.h" /* tr_cacheReadBlock() */

View File

@ -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

View File

@ -6,6 +6,7 @@
*
*/
#include <algorithm>
#include <cstring> /* strchr() */
#include <cstdio> /* 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<char*>{};
auto ws = std::vector<char*>{};
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<char**>(tr_memdup(tr, sizeof(char*) * trCount));
info->webseedCount = wsCount;
info->webseeds = static_cast<char**>(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<char**>(tr_memdup(std::data(tr), std::size(tr) * sizeof(char*)));
info->webseedCount = std::size(ws);
info->webseeds = reinterpret_cast<char**>(tr_memdup(std::data(ws), std::size(ws) * sizeof(char*)));
std::copy_n(sha1, SHA_DIGEST_LENGTH, info->hash);
return info;
}
@ -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);
}

View File

@ -12,9 +12,11 @@
#error only libtransmission should #include this header.
#endif
#include "tr-macros.h"
#include <string_view>
#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*);

View File

@ -14,17 +14,19 @@
#include <event2/util.h> /* 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"
/****
*****

View File

@ -12,8 +12,6 @@
#include <iterator>
#include <string_view>
#include <event2/buffer.h>
#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;

View File

@ -12,9 +12,12 @@
#error only libtransmission should #include this header.
#endif
#include <string>
#include <string_view>
#include "transmission.h"
#include "variant.h"
#include "tr-macros.h"
struct tr_variant;
enum tr_metainfo_basename_format
{

View File

@ -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;

View File

@ -18,6 +18,7 @@
#include <event2/event.h>
#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"

View File

@ -9,7 +9,6 @@
#include <cstdlib>
#include <cstring>
#include <list>
#include <map>
#include <string>
#ifndef _XOPEN_SOURCE

View File

@ -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<char const*>(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);

View File

@ -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);
/* @} */

View File

@ -9,6 +9,7 @@
#include <algorithm>
#include <cstring>
#include <string_view>
#include <vector>
#include "transmission.h"
#include "completion.h"

View File

@ -11,6 +11,7 @@
#include <cstring> /* memcpy */
#include <list>
#include <string>
#include <vector>
#include <zlib.h>
@ -20,21 +21,23 @@
#include <event2/http_struct.h> /* 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.

View File

@ -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);

View File

@ -21,8 +21,6 @@
#endif
#include <zlib.h>
#include <event2/buffer.h>
#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

View File

@ -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/ */

View File

@ -16,6 +16,7 @@
#include <iterator> // std::back_inserter
#include <list>
#include <numeric> // std::acumulate()
#include <unordered_set>
#include <vector>
#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"

View File

@ -23,13 +23,12 @@
#include <unordered_set>
#include <vector>
#include <event2/util.h> // evutil_ascii_strcasecmp()
#include <event2/util.h> // 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
{

View File

@ -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;

View File

@ -12,18 +12,19 @@
#include <event2/buffer.h>
#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__)

View File

@ -30,6 +30,7 @@
#include <event2/util.h> /* 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<uint8_t const*>(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);

View File

@ -19,20 +19,20 @@
#include <unordered_set>
#include <vector>
#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

View File

@ -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

View File

@ -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<char, 16>{};
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<std::pair<std::string_view, std::string_view>, 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_parsed_url_t> 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<std::string_view, 3>{ "http"sv, "https"sv, "udp"sv };
return std::find(std::begin(Schemes), std::end(Schemes), scheme) != std::end(Schemes);
}
std::optional<tr_parsed_url_t> tr_urlParseTracker(std::string_view url)
{
auto const parsed = tr_urlParse(url);
return parsed && tr_isValidTrackerScheme(parsed->scheme) ? *parsed : std::optional<tr_parsed_url_t>{};
}
bool tr_urlIsValidTracker(std::string_view url)
{
return !!tr_urlParseTracker(url);
}
bool tr_urlIsValid(std::string_view url)
{
auto constexpr Schemes = std::array<std::string_view, 5>{ "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<char const*>(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<char const*>(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<char*>(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<char const*>(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;
}
/***
****
***/

View File

@ -8,13 +8,13 @@
#pragma once
#include <inttypes.h>
#include <cinttypes>
#include <cstdarg>
#include <cstddef>
#include <ctime>
#include <optional>
#include <stdarg.h>
#include <stddef.h> /* size_t */
#include <string>
#include <string_view>
#include <time.h> /* time_t */
#include <type_traits>
#include <vector>
@ -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_parsed_url_t> 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_parsed_url_t> 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);

View File

@ -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 <algorithm>
#include <array>
#include <cctype>
#include <cstddef>
#include <optional>
#include <string_view>
#include <event2/buffer.h>
#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<std::byte const*>(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<char, 16>{};
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<std::pair<std::string_view, std::string_view>, 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<std::string_view, 3>{ "http"sv, "https"sv, "udp"sv };
return std::find(std::begin(Schemes), std::end(Schemes), scheme) != std::end(Schemes);
}
} // namespace
std::optional<tr_url_parsed_t> 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_url_parsed_t> tr_urlParseTracker(std::string_view url)
{
auto const parsed = tr_urlParse(url);
return parsed && tr_isValidTrackerScheme(parsed->scheme) ? *parsed : std::optional<tr_url_parsed_t>{};
}
bool tr_urlIsValidTracker(std::string_view url)
{
return !!tr_urlParseTracker(url);
}
bool tr_urlIsValid(std::string_view url)
{
auto constexpr Schemes = std::array<std::string_view, 5>{ "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<char, 3>{ 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;
}

102
libtransmission/web-utils.h Normal file
View File

@ -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 <cstddef>
#include <optional>
#include <string_view>
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_url_parsed_t> 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_url_parsed_t> 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<std::string_view, std::string_view> 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);

View File

@ -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<std::byte const*>(sha1_digest), std::size(digest), std::begin(digest));
tr_http_escape_sha1(out, digest);
}

View File

@ -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 <cstdint>
#include <string_view>
#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);

View File

@ -9,11 +9,13 @@
#include <algorithm>
#include <cstring> /* strlen() */
#include <set>
#include <vector>
#include <event2/buffer.h>
#include <event2/event.h>
#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"

View File

@ -21,7 +21,8 @@
*****************************************************************************/
#include <libtransmission/transmission.h>
#include <libtransmission/utils.h> // tr_urlIsValidTracker()
#include <libtransmission/utils.h>
#include <libtransmission/web-utils.h> // tr_urlIsValidTracker()
#import "CreatorWindowController.h"
#import "Controller.h"

View File

@ -21,7 +21,7 @@
*****************************************************************************/
#include <libtransmission/transmission.h>
#include <libtransmission/utils.h> //tr_addressIsIP()
#include <libtransmission/web-utils.h> //tr_addressIsIP()
#import "TrackerCell.h"
#import "TrackerNode.h"

View File

@ -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

View File

@ -13,31 +13,34 @@
#include "gtest/gtest.h"
#include <array>
#include <string_view>
using namespace std::literals;
TEST(Magnet, magnetParse)
{
auto const expected_hash = std::array<uint8_t, SHA_DIGEST_LENGTH>{
auto constexpr ExpectedHash = std::array<uint8_t, SHA_DIGEST_LENGTH>{
210, 53, 64, 16, 163, 202, 74, 222, 91, 116, //
39, 187, 9, 58, 98, 163, 137, 159, 243, 129, //
};
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);
}
}

View File

@ -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<char, 32>{};

View File

@ -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 <string_view>
#ifdef _WIN32
#include <windows.h>
#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<std::pair<std::string_view, std::string_view>, 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));
}
}

View File

@ -18,7 +18,7 @@
#include <libtransmission/transmission.h>
#include <libtransmission/tr-getopt.h>
#include <libtransmission/utils.h>
#include <libtransmission/web.h> /* tr_webGetResponseStr() */
#include <libtransmission/web-utils.h> /* tr_webGetResponseStr() */
#include <libtransmission/variant.h>
#include <libtransmission/version.h>