mirror of
https://github.com/transmission/transmission
synced 2025-02-20 21:26:53 +00:00
test: add tests for parsing http tracker scrape responses (#2527)
This commit is contained in:
parent
4c36ba35bc
commit
3c5442dd7b
3 changed files with 241 additions and 95 deletions
|
@ -233,4 +233,6 @@ void tr_tracker_udp_start_shutdown(tr_session* session);
|
|||
|
||||
void tr_announcerParseHttpAnnounceResponse(tr_announce_response& response, std::string_view msg);
|
||||
|
||||
void tr_announcerParseHttpScrapeResponse(tr_scrape_response& response, std::string_view msg);
|
||||
|
||||
tr_interned_string tr_announcerGetKey(tr_url_parsed_t const& parsed);
|
||||
|
|
|
@ -194,7 +194,7 @@ static void on_announce_done_eventthread(void* vdata)
|
|||
delete data;
|
||||
}
|
||||
|
||||
static void maybeLogMessage(std::string_view description, tr_direction direction, std::string_view message)
|
||||
static void verboseLog(std::string_view description, tr_direction direction, std::string_view message)
|
||||
{
|
||||
auto& out = std::cerr;
|
||||
static bool const verbose = tr_env_key_exists("TR_CURL_VERBOSE");
|
||||
|
@ -222,12 +222,16 @@ static void maybeLogMessage(std::string_view description, tr_direction direction
|
|||
|
||||
void tr_announcerParseHttpAnnounceResponse(tr_announce_response& response, std::string_view msg)
|
||||
{
|
||||
maybeLogMessage("Announce response:", TR_DOWN, msg);
|
||||
verboseLog("Announce response:", TR_DOWN, msg);
|
||||
|
||||
auto benc = tr_variant{};
|
||||
auto const variant_loaded = tr_variantFromBuf(&benc, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, msg);
|
||||
if (!variant_loaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (variant_loaded && tr_variantIsDict(&benc))
|
||||
if (tr_variantIsDict(&benc))
|
||||
{
|
||||
auto i = int64_t{};
|
||||
auto sv = std::string_view{};
|
||||
|
@ -288,10 +292,7 @@ void tr_announcerParseHttpAnnounceResponse(tr_announce_response& response, std::
|
|||
}
|
||||
}
|
||||
|
||||
if (variant_loaded)
|
||||
{
|
||||
tr_variantFree(&benc);
|
||||
}
|
||||
tr_variantFree(&benc);
|
||||
}
|
||||
|
||||
static void on_announce_done(
|
||||
|
@ -375,6 +376,77 @@ static void on_scrape_done_eventthread(void* vdata)
|
|||
delete data;
|
||||
}
|
||||
|
||||
void tr_announcerParseHttpScrapeResponse(tr_scrape_response& response, std::string_view msg)
|
||||
{
|
||||
verboseLog("Scrape response:", TR_DOWN, msg);
|
||||
|
||||
auto top = tr_variant{};
|
||||
auto const variant_loaded = tr_variantFromBuf(&top, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, msg);
|
||||
if (!variant_loaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto sv = std::string_view{}; tr_variantDictFindStrView(&top, TR_KEY_failure_reason, &sv))
|
||||
{
|
||||
response.errmsg = sv;
|
||||
}
|
||||
|
||||
tr_variant* flags = nullptr;
|
||||
auto intVal = int64_t{};
|
||||
if (tr_variantDictFindDict(&top, TR_KEY_flags, &flags) &&
|
||||
tr_variantDictFindInt(flags, TR_KEY_min_request_interval, &intVal))
|
||||
{
|
||||
response.min_request_interval = intVal;
|
||||
}
|
||||
|
||||
tr_variant* files = nullptr;
|
||||
if (tr_variantDictFindDict(&top, TR_KEY_files, &files))
|
||||
{
|
||||
auto key = tr_quark{};
|
||||
tr_variant* val = nullptr;
|
||||
|
||||
for (int i = 0; tr_variantDictChild(files, i, &key, &val); ++i)
|
||||
{
|
||||
/* populate the corresponding row in our response array */
|
||||
for (int j = 0; j < response.row_count; ++j)
|
||||
{
|
||||
struct tr_scrape_response_row* row = &response.rows[j];
|
||||
|
||||
// TODO(ckerr): ugh, interning info dict hashes is awful
|
||||
auto const& hash = row->info_hash;
|
||||
auto const key_sv = tr_quark_get_string_view(key);
|
||||
if (std::size(hash) == std::size(key_sv) && memcmp(std::data(hash), std::data(key_sv), std::size(hash)) == 0)
|
||||
{
|
||||
if (tr_variantDictFindInt(val, TR_KEY_complete, &intVal))
|
||||
{
|
||||
row->seeders = intVal;
|
||||
}
|
||||
|
||||
if (tr_variantDictFindInt(val, TR_KEY_incomplete, &intVal))
|
||||
{
|
||||
row->leechers = intVal;
|
||||
}
|
||||
|
||||
if (tr_variantDictFindInt(val, TR_KEY_downloaded, &intVal))
|
||||
{
|
||||
row->downloads = intVal;
|
||||
}
|
||||
|
||||
if (tr_variantDictFindInt(val, TR_KEY_downloaders, &intVal))
|
||||
{
|
||||
row->downloaders = intVal;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr_variantFree(&top);
|
||||
}
|
||||
|
||||
static void on_scrape_done(
|
||||
tr_session* session,
|
||||
bool did_connect,
|
||||
|
@ -385,11 +457,11 @@ static void on_scrape_done(
|
|||
{
|
||||
auto* data = static_cast<struct scrape_data*>(vdata);
|
||||
|
||||
tr_scrape_response* response = &data->response;
|
||||
response->did_connect = did_connect;
|
||||
response->did_timeout = did_timeout;
|
||||
tr_scrape_response& response = data->response;
|
||||
response.did_connect = did_connect;
|
||||
response.did_timeout = did_timeout;
|
||||
|
||||
auto const scrape_url_sv = response->scrape_url.sv();
|
||||
auto const scrape_url_sv = response.scrape_url.sv();
|
||||
dbgmsg(data->log_name, "Got scrape response for \"%" TR_PRIsv "\"", TR_PRIsv_ARG(scrape_url_sv));
|
||||
|
||||
if (response_code != HTTP_OK)
|
||||
|
@ -398,93 +470,11 @@ static void on_scrape_done(
|
|||
char const* response_str = tr_webGetResponseStr(response_code);
|
||||
char buf[512];
|
||||
tr_snprintf(buf, sizeof(buf), fmt, response_code, response_str);
|
||||
response->errmsg = buf;
|
||||
response.errmsg = buf;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto top = tr_variant{};
|
||||
|
||||
auto const variant_loaded = tr_variantFromBuf(&top, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, msg);
|
||||
|
||||
if (tr_env_key_exists("TR_CURL_VERBOSE"))
|
||||
{
|
||||
if (!variant_loaded)
|
||||
{
|
||||
fprintf(stderr, "%s", "Scrape response was not in benc format\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "%s", "Scrape response:\n< ");
|
||||
for (auto const ch : tr_variantToStr(&top, TR_VARIANT_FMT_JSON))
|
||||
{
|
||||
fputc(ch, stderr);
|
||||
}
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
}
|
||||
|
||||
if (variant_loaded)
|
||||
{
|
||||
if (auto sv = std::string_view{}; tr_variantDictFindStrView(&top, TR_KEY_failure_reason, &sv))
|
||||
{
|
||||
response->errmsg = sv;
|
||||
}
|
||||
|
||||
tr_variant* flags = nullptr;
|
||||
auto intVal = int64_t{};
|
||||
if (tr_variantDictFindDict(&top, TR_KEY_flags, &flags) &&
|
||||
tr_variantDictFindInt(flags, TR_KEY_min_request_interval, &intVal))
|
||||
{
|
||||
response->min_request_interval = intVal;
|
||||
}
|
||||
|
||||
tr_variant* files = nullptr;
|
||||
if (tr_variantDictFindDict(&top, TR_KEY_files, &files))
|
||||
{
|
||||
auto key = tr_quark{};
|
||||
tr_variant* val = nullptr;
|
||||
|
||||
for (int i = 0; tr_variantDictChild(files, i, &key, &val); ++i)
|
||||
{
|
||||
/* populate the corresponding row in our response array */
|
||||
for (int j = 0; j < response->row_count; ++j)
|
||||
{
|
||||
struct tr_scrape_response_row* row = &response->rows[j];
|
||||
|
||||
// TODO(ckerr): ugh, interning info dict hashes is awful
|
||||
auto const& hash = row->info_hash;
|
||||
auto const key_sv = tr_quark_get_string_view(key);
|
||||
if (std::size(hash) == std::size(key_sv) &&
|
||||
memcmp(std::data(hash), std::data(key_sv), std::size(hash)) == 0)
|
||||
{
|
||||
if (tr_variantDictFindInt(val, TR_KEY_complete, &intVal))
|
||||
{
|
||||
row->seeders = intVal;
|
||||
}
|
||||
|
||||
if (tr_variantDictFindInt(val, TR_KEY_incomplete, &intVal))
|
||||
{
|
||||
row->leechers = intVal;
|
||||
}
|
||||
|
||||
if (tr_variantDictFindInt(val, TR_KEY_downloaded, &intVal))
|
||||
{
|
||||
row->downloads = intVal;
|
||||
}
|
||||
|
||||
if (tr_variantDictFindInt(val, TR_KEY_downloaders, &intVal))
|
||||
{
|
||||
row->downloaders = intVal;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr_variantFree(&top);
|
||||
}
|
||||
tr_announcerParseHttpScrapeResponse(response, msg);
|
||||
}
|
||||
|
||||
tr_runInEventThread(session, on_scrape_done_eventthread, data);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
|
||||
|
@ -146,3 +147,156 @@ TEST_F(AnnouncerTest, parseHttpAnnounceResponseFailureReason)
|
|||
EXPECT_EQ("foobar"sv, response.errmsg);
|
||||
EXPECT_EQ(""sv, response.warning);
|
||||
}
|
||||
|
||||
TEST_F(AnnouncerTest, parseHttpScrapeResponseMulti)
|
||||
{
|
||||
// clang-format off
|
||||
auto constexpr ResponseBenc =
|
||||
"d"
|
||||
"5:files"
|
||||
"d"
|
||||
"20:aaaaaaaaaaaaaaaaaaaa"
|
||||
"d"
|
||||
"8:complete" "i1e"
|
||||
"10:incomplete" "i2e"
|
||||
"10:downloaded" "i3e"
|
||||
"e"
|
||||
|
||||
"20:bbbbbbbbbbbbbbbbbbbb"
|
||||
"d"
|
||||
"8:complete" "i4e"
|
||||
"10:incomplete" "i5e"
|
||||
"10:downloaded" "i6e"
|
||||
"e"
|
||||
|
||||
"20:cccccccccccccccccccc"
|
||||
"d"
|
||||
"8:complete" "i7e"
|
||||
"10:incomplete" "i8e"
|
||||
"10:downloaded" "i9e"
|
||||
"e"
|
||||
"e"
|
||||
"e"sv;
|
||||
// clang-format on
|
||||
|
||||
auto response = tr_scrape_response{};
|
||||
std::fill_n(std::data(response.rows[0].info_hash), std::size(response.rows[0].info_hash), std::byte{ 'a' });
|
||||
std::fill_n(std::data(response.rows[1].info_hash), std::size(response.rows[1].info_hash), std::byte{ 'b' });
|
||||
std::fill_n(std::data(response.rows[2].info_hash), std::size(response.rows[2].info_hash), std::byte{ 'c' });
|
||||
response.row_count = 3;
|
||||
tr_announcerParseHttpScrapeResponse(response, ResponseBenc);
|
||||
|
||||
EXPECT_EQ(1, response.rows[0].seeders);
|
||||
EXPECT_EQ(2, response.rows[0].leechers);
|
||||
EXPECT_EQ(3, response.rows[0].downloads);
|
||||
|
||||
EXPECT_EQ(4, response.rows[1].seeders);
|
||||
EXPECT_EQ(5, response.rows[1].leechers);
|
||||
EXPECT_EQ(6, response.rows[1].downloads);
|
||||
|
||||
EXPECT_EQ(7, response.rows[2].seeders);
|
||||
EXPECT_EQ(8, response.rows[2].leechers);
|
||||
EXPECT_EQ(9, response.rows[2].downloads);
|
||||
}
|
||||
|
||||
TEST_F(AnnouncerTest, parseHttpScrapeResponseMultiWithExcess)
|
||||
{
|
||||
// clang-format off
|
||||
auto constexpr ResponseBenc =
|
||||
"d"
|
||||
"5:files"
|
||||
"d"
|
||||
"20:aaaaaaaaaaaaaaaaaaaa"
|
||||
"d"
|
||||
"8:complete" "i1e"
|
||||
"10:incomplete" "i2e"
|
||||
"10:downloaded" "i3e"
|
||||
"e"
|
||||
|
||||
"20:bbbbbbbbbbbbbbbbbbbb"
|
||||
"d"
|
||||
"8:complete" "i4e"
|
||||
"10:incomplete" "i5e"
|
||||
"10:downloaded" "i6e"
|
||||
"e"
|
||||
|
||||
"20:cccccccccccccccccccc"
|
||||
"d"
|
||||
"8:complete" "i7e"
|
||||
"10:incomplete" "i8e"
|
||||
"10:downloaded" "i9e"
|
||||
"e"
|
||||
|
||||
"20:dddddddddddddddddddd"
|
||||
"d"
|
||||
"8:complete" "i7e"
|
||||
"10:incomplete" "i8e"
|
||||
"10:downloaded" "i9e"
|
||||
"e"
|
||||
"e"
|
||||
"e"sv;
|
||||
// clang-format on
|
||||
|
||||
auto response = tr_scrape_response{};
|
||||
std::fill_n(std::data(response.rows[0].info_hash), std::size(response.rows[0].info_hash), std::byte{ 'a' });
|
||||
std::fill_n(std::data(response.rows[1].info_hash), std::size(response.rows[1].info_hash), std::byte{ 'b' });
|
||||
std::fill_n(std::data(response.rows[2].info_hash), std::size(response.rows[2].info_hash), std::byte{ 'c' });
|
||||
response.row_count = 3;
|
||||
tr_announcerParseHttpScrapeResponse(response, ResponseBenc);
|
||||
|
||||
EXPECT_EQ(1, response.rows[0].seeders);
|
||||
EXPECT_EQ(2, response.rows[0].leechers);
|
||||
EXPECT_EQ(3, response.rows[0].downloads);
|
||||
|
||||
EXPECT_EQ(4, response.rows[1].seeders);
|
||||
EXPECT_EQ(5, response.rows[1].leechers);
|
||||
EXPECT_EQ(6, response.rows[1].downloads);
|
||||
|
||||
EXPECT_EQ(7, response.rows[2].seeders);
|
||||
EXPECT_EQ(8, response.rows[2].leechers);
|
||||
EXPECT_EQ(9, response.rows[2].downloads);
|
||||
}
|
||||
|
||||
TEST_F(AnnouncerTest, parseHttpScrapeResponseMultiWithMissing)
|
||||
{
|
||||
// clang-format off
|
||||
auto constexpr ResponseBenc =
|
||||
"d"
|
||||
"5:files"
|
||||
"d"
|
||||
"20:aaaaaaaaaaaaaaaaaaaa"
|
||||
"d"
|
||||
"8:complete" "i1e"
|
||||
"10:incomplete" "i2e"
|
||||
"10:downloaded" "i3e"
|
||||
"e"
|
||||
|
||||
"20:cccccccccccccccccccc"
|
||||
"d"
|
||||
"8:complete" "i7e"
|
||||
"10:incomplete" "i8e"
|
||||
"10:downloaded" "i9e"
|
||||
"e"
|
||||
"e"
|
||||
"e"sv;
|
||||
// clang-format on
|
||||
|
||||
auto response = tr_scrape_response{};
|
||||
std::fill_n(std::data(response.rows[0].info_hash), std::size(response.rows[0].info_hash), std::byte{ 'a' });
|
||||
std::fill_n(std::data(response.rows[1].info_hash), std::size(response.rows[1].info_hash), std::byte{ 'b' });
|
||||
std::fill_n(std::data(response.rows[2].info_hash), std::size(response.rows[2].info_hash), std::byte{ 'c' });
|
||||
response.row_count = 3;
|
||||
tr_announcerParseHttpScrapeResponse(response, ResponseBenc);
|
||||
|
||||
EXPECT_EQ(1, response.rows[0].seeders);
|
||||
EXPECT_EQ(2, response.rows[0].leechers);
|
||||
EXPECT_EQ(3, response.rows[0].downloads);
|
||||
|
||||
EXPECT_EQ(0, response.rows[1].seeders);
|
||||
EXPECT_EQ(0, response.rows[1].leechers);
|
||||
EXPECT_EQ(0, response.rows[1].downloads);
|
||||
|
||||
EXPECT_EQ(7, response.rows[2].seeders);
|
||||
EXPECT_EQ(8, response.rows[2].leechers);
|
||||
EXPECT_EQ(9, response.rows[2].downloads);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue