mirror of
https://github.com/transmission/transmission
synced 2024-12-21 23:32:35 +00:00
refactor: tr_web (#2633)
* refactor: fix tr_web rate limiting and dns caching
This commit is contained in:
parent
75e111e581
commit
f1a53840f9
9 changed files with 736 additions and 600 deletions
16
cli/cli.cc
16
cli/cli.cc
|
@ -19,7 +19,7 @@
|
|||
#include <libtransmission/variant.h>
|
||||
#include <libtransmission/version.h>
|
||||
#include <libtransmission/web-utils.h>
|
||||
#include <libtransmission/web.h> /* tr_webRun */
|
||||
#include <libtransmission/web.h> // tr_sessionFetch()
|
||||
|
||||
/***
|
||||
****
|
||||
|
@ -124,16 +124,10 @@ static char* tr_strlratio(char* buf, double ratio, size_t buflen)
|
|||
|
||||
static bool waitingOnWeb;
|
||||
|
||||
static void onTorrentFileDownloaded(
|
||||
tr_session* /*session*/,
|
||||
bool /*did_connect*/,
|
||||
bool /*did_timeout*/,
|
||||
long /*response_code*/,
|
||||
std::string_view response,
|
||||
void* vctor)
|
||||
static void onTorrentFileDownloaded(tr_web::FetchResponse&& response)
|
||||
{
|
||||
auto* ctor = static_cast<tr_ctor*>(vctor);
|
||||
tr_ctorSetMetainfo(ctor, std::data(response), std::size(response), nullptr);
|
||||
auto* ctor = static_cast<tr_ctor*>(response.user_data);
|
||||
tr_ctorSetMetainfo(ctor, std::data(response.body), std::size(response.body), nullptr);
|
||||
waitingOnWeb = false;
|
||||
}
|
||||
|
||||
|
@ -286,7 +280,7 @@ int tr_main(int argc, char* argv[])
|
|||
else if (tr_urlIsValid(torrentPath))
|
||||
{
|
||||
// fetch it
|
||||
tr_webRun(h, { torrentPath, onTorrentFileDownloaded, ctor });
|
||||
tr_sessionFetch(h, { torrentPath, onTorrentFileDownloaded, ctor });
|
||||
waitingOnWeb = true;
|
||||
while (waitingOnWeb)
|
||||
{
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include <glib/gstdio.h> /* g_remove() */
|
||||
|
||||
#include <libtransmission/transmission.h>
|
||||
#include <libtransmission/web.h> /* tr_webRun() */
|
||||
#include <libtransmission/web.h> // tr_sessionFetch()
|
||||
#include <libtransmission/web-utils.h>
|
||||
|
||||
#include "FaviconCache.h"
|
||||
|
@ -73,7 +73,7 @@ Glib::RefPtr<Gdk::Pixbuf> favicon_load_from_cache(std::string const& host)
|
|||
}
|
||||
}
|
||||
|
||||
void favicon_web_done_cb(tr_session*, bool, bool, long, std::string_view, gpointer);
|
||||
void favicon_web_done_cb(tr_web::FetchResponse&& response);
|
||||
|
||||
bool favicon_web_done_idle_cb(std::unique_ptr<favicon_data> fav)
|
||||
{
|
||||
|
@ -90,7 +90,7 @@ bool favicon_web_done_idle_cb(std::unique_ptr<favicon_data> fav)
|
|||
fav->contents.clear();
|
||||
auto* const session = fav->session;
|
||||
auto const next_url = get_url(fav->host, fav->type);
|
||||
tr_webRun(session, { next_url.raw(), favicon_web_done_cb, fav.release() });
|
||||
tr_sessionFetch(session, { next_url.raw(), favicon_web_done_cb, fav.release() });
|
||||
}
|
||||
|
||||
// Not released into the next web request, means we're done trying (even if `pixbuf` is still invalid)
|
||||
|
@ -102,17 +102,10 @@ bool favicon_web_done_idle_cb(std::unique_ptr<favicon_data> fav)
|
|||
return false;
|
||||
}
|
||||
|
||||
void favicon_web_done_cb(
|
||||
tr_session* /*session*/,
|
||||
bool /*did_connect*/,
|
||||
bool /*did_timeout*/,
|
||||
long /*code*/,
|
||||
std::string_view data,
|
||||
gpointer vfav)
|
||||
void favicon_web_done_cb(tr_web::FetchResponse&& response)
|
||||
{
|
||||
auto* fav = static_cast<favicon_data*>(vfav);
|
||||
fav->contents.assign(std::data(data), std::size(data));
|
||||
|
||||
auto* const fav = static_cast<favicon_data*>(response.user_data);
|
||||
fav->contents = response.body;
|
||||
Glib::signal_idle().connect([fav]() { return favicon_web_done_idle_cb(std::unique_ptr<favicon_data>(fav)); });
|
||||
}
|
||||
|
||||
|
@ -136,7 +129,7 @@ void gtr_get_favicon(
|
|||
data->func = pixbuf_ready_func;
|
||||
data->host = host;
|
||||
|
||||
tr_webRun(session, { get_url(host, 0).raw(), favicon_web_done_cb, data.release() });
|
||||
tr_sessionFetch(session, { get_url(host, 0).raw(), favicon_web_done_cb, data.release() });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -121,26 +121,6 @@ static std::string announce_url_new(tr_session const* session, tr_announce_reque
|
|||
return evbuffer_free_to_str(buf);
|
||||
}
|
||||
|
||||
struct announce_data
|
||||
{
|
||||
tr_announce_response response;
|
||||
tr_announce_response_func response_func;
|
||||
void* response_func_user_data;
|
||||
char log_name[128];
|
||||
};
|
||||
|
||||
static void on_announce_done_eventthread(void* vdata)
|
||||
{
|
||||
auto* data = static_cast<struct announce_data*>(vdata);
|
||||
|
||||
if (data->response_func != nullptr)
|
||||
{
|
||||
data->response_func(&data->response, data->response_func_user_data);
|
||||
}
|
||||
|
||||
delete data;
|
||||
}
|
||||
|
||||
static void verboseLog(std::string_view description, tr_direction direction, std::string_view message)
|
||||
{
|
||||
auto& out = std::cerr;
|
||||
|
@ -293,14 +273,17 @@ void tr_announcerParseHttpAnnounceResponse(tr_announce_response& response, std::
|
|||
}
|
||||
}
|
||||
|
||||
static void on_announce_done(
|
||||
tr_session* session,
|
||||
bool did_connect,
|
||||
bool did_timeout,
|
||||
long response_code,
|
||||
std::string_view msg,
|
||||
void* vdata)
|
||||
struct announce_data
|
||||
{
|
||||
tr_announce_response response;
|
||||
tr_announce_response_func response_func;
|
||||
void* response_func_user_data;
|
||||
char log_name[128];
|
||||
};
|
||||
|
||||
static void onAnnounceDone(tr_web::FetchResponse&& web_response)
|
||||
{
|
||||
auto const& [status, body, did_connect, did_timeout, vdata] = web_response;
|
||||
auto* data = static_cast<struct announce_data*>(vdata);
|
||||
|
||||
tr_announce_response* const response = &data->response;
|
||||
|
@ -308,14 +291,14 @@ static void on_announce_done(
|
|||
response->did_timeout = did_timeout;
|
||||
dbgmsg(data->log_name, "Got announce response");
|
||||
|
||||
if (response_code != HTTP_OK)
|
||||
if (status != HTTP_OK)
|
||||
{
|
||||
auto const* const response_str = tr_webGetResponseStr(response_code);
|
||||
response->errmsg = tr_strvJoin("Tracker HTTP response "sv, std::to_string(response_code), " ("sv, response_str, ")"sv);
|
||||
auto const* const response_str = tr_webGetResponseStr(status);
|
||||
response->errmsg = tr_strvJoin("Tracker HTTP response "sv, std::to_string(status), " ("sv, response_str, ")"sv);
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_announcerParseHttpAnnounceResponse(*response, msg);
|
||||
tr_announcerParseHttpAnnounceResponse(*response, body);
|
||||
}
|
||||
|
||||
if (!std::empty(response->pex6))
|
||||
|
@ -328,7 +311,12 @@ static void on_announce_done(
|
|||
dbgmsg(data->log_name, "got a peers length of %zu", std::size(response->pex));
|
||||
}
|
||||
|
||||
tr_runInEventThread(session, on_announce_done_eventthread, data);
|
||||
if (data->response_func != nullptr)
|
||||
{
|
||||
data->response_func(&data->response, data->response_func_user_data);
|
||||
}
|
||||
|
||||
delete data;
|
||||
}
|
||||
|
||||
void tr_tracker_http_announce(
|
||||
|
@ -345,7 +333,12 @@ void tr_tracker_http_announce(
|
|||
|
||||
auto const url = announce_url_new(session, request);
|
||||
dbgmsg(request->log_name, "Sending announce to libcurl: \"%" TR_PRIsv "\"", TR_PRIsv_ARG(url));
|
||||
tr_webRun(session, { url, on_announce_done, d });
|
||||
|
||||
auto options = tr_web::FetchOptions{ url, onAnnounceDone, d };
|
||||
options.timeout_secs = 90L;
|
||||
options.sndbuf = 1024;
|
||||
options.rcvbuf = 3072;
|
||||
session->web->fetch(std::move(options));
|
||||
}
|
||||
|
||||
/****
|
||||
|
@ -354,26 +347,6 @@ void tr_tracker_http_announce(
|
|||
*****
|
||||
****/
|
||||
|
||||
struct scrape_data
|
||||
{
|
||||
tr_scrape_response response;
|
||||
tr_scrape_response_func response_func;
|
||||
void* response_func_user_data;
|
||||
char log_name[128];
|
||||
};
|
||||
|
||||
static void on_scrape_done_eventthread(void* vdata)
|
||||
{
|
||||
auto* data = static_cast<struct scrape_data*>(vdata);
|
||||
|
||||
if (data->response_func != nullptr)
|
||||
{
|
||||
data->response_func(&data->response, data->response_func_user_data);
|
||||
}
|
||||
|
||||
delete data;
|
||||
}
|
||||
|
||||
void tr_announcerParseHttpScrapeResponse(tr_scrape_response& response, std::string_view benc)
|
||||
{
|
||||
verboseLog("Scrape response:", TR_DOWN, benc);
|
||||
|
@ -464,15 +437,18 @@ void tr_announcerParseHttpScrapeResponse(tr_scrape_response& response, std::stri
|
|||
}
|
||||
}
|
||||
|
||||
static void on_scrape_done(
|
||||
tr_session* session,
|
||||
bool did_connect,
|
||||
bool did_timeout,
|
||||
long response_code,
|
||||
std::string_view msg,
|
||||
void* vdata)
|
||||
struct scrape_data
|
||||
{
|
||||
auto* data = static_cast<struct scrape_data*>(vdata);
|
||||
tr_scrape_response response;
|
||||
tr_scrape_response_func response_func;
|
||||
void* response_func_user_data;
|
||||
char log_name[128];
|
||||
};
|
||||
|
||||
static void onScrapeDone(tr_web::FetchResponse&& web_response)
|
||||
{
|
||||
auto const& [status, body, did_connect, did_timeout, vdata] = web_response;
|
||||
auto* const data = static_cast<struct scrape_data*>(vdata);
|
||||
|
||||
tr_scrape_response& response = data->response;
|
||||
response.did_connect = did_connect;
|
||||
|
@ -481,20 +457,22 @@ static void on_scrape_done(
|
|||
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)
|
||||
if (status != HTTP_OK)
|
||||
{
|
||||
char const* fmt = _("Tracker gave HTTP response code %1$ld (%2$s)");
|
||||
char const* response_str = tr_webGetResponseStr(response_code);
|
||||
char buf[512];
|
||||
tr_snprintf(buf, sizeof(buf), fmt, response_code, response_str);
|
||||
response.errmsg = buf;
|
||||
auto const* const response_str = tr_webGetResponseStr(status);
|
||||
response.errmsg = tr_strvJoin("Tracker HTTP response "sv, std::to_string(status), " ("sv, response_str, ")"sv);
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_announcerParseHttpScrapeResponse(response, msg);
|
||||
tr_announcerParseHttpScrapeResponse(response, body);
|
||||
}
|
||||
|
||||
tr_runInEventThread(session, on_scrape_done_eventthread, data);
|
||||
if (data->response_func != nullptr)
|
||||
{
|
||||
data->response_func(&data->response, data->response_func_user_data);
|
||||
}
|
||||
|
||||
delete data;
|
||||
}
|
||||
|
||||
static std::string scrape_url_new(tr_scrape_request const* req)
|
||||
|
@ -540,5 +518,10 @@ void tr_tracker_http_scrape(
|
|||
|
||||
auto const url = scrape_url_new(request);
|
||||
dbgmsg(request->log_name, "Sending scrape to libcurl: \"%" TR_PRIsv "\"", TR_PRIsv_ARG(url));
|
||||
tr_webRun(session, { url, on_scrape_done, d });
|
||||
|
||||
auto options = tr_web::FetchOptions{ url, onScrapeDone, d };
|
||||
options.timeout_secs = 30L;
|
||||
options.sndbuf = 4096;
|
||||
options.rcvbuf = 4096;
|
||||
session->web->fetch(std::move(options));
|
||||
}
|
||||
|
|
|
@ -1340,29 +1340,19 @@ static char const* torrentRenamePath(
|
|||
****
|
||||
***/
|
||||
|
||||
static void portTested(
|
||||
tr_session* /*session*/,
|
||||
bool /*did_connect*/,
|
||||
bool /*did_timeout*/,
|
||||
long response_code,
|
||||
std::string_view response,
|
||||
void* user_data)
|
||||
static void portTested(tr_web::FetchResponse&& web_response)
|
||||
{
|
||||
auto const& [status, body, did_connect, did_tmieout, user_data] = web_response;
|
||||
char result[1024];
|
||||
auto* data = static_cast<struct tr_rpc_idle_data*>(user_data);
|
||||
|
||||
if (response_code != 200)
|
||||
if (status != 200)
|
||||
{
|
||||
tr_snprintf(
|
||||
result,
|
||||
sizeof(result),
|
||||
"portTested: http error %ld: %s",
|
||||
response_code,
|
||||
tr_webGetResponseStr(response_code));
|
||||
tr_snprintf(result, sizeof(result), "portTested: http error %ld: %s", status, tr_webGetResponseStr(status));
|
||||
}
|
||||
else /* success */
|
||||
{
|
||||
bool const isOpen = tr_strvStartsWith(response, '1');
|
||||
bool const isOpen = tr_strvStartsWith(body, '1');
|
||||
tr_variantDictAddBool(data->args_out, TR_KEY_port_is_open, isOpen);
|
||||
tr_snprintf(result, sizeof(result), "success");
|
||||
}
|
||||
|
@ -1378,7 +1368,7 @@ static char const* portTest(
|
|||
{
|
||||
auto const port = tr_sessionGetPeerPort(session);
|
||||
auto const url = tr_strvJoin("https://portcheck.transmissionbt.com/"sv, std::to_string(port));
|
||||
tr_webRun(session, { url, portTested, idle_data });
|
||||
session->web->fetch({ url, portTested, idle_data });
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -1386,28 +1376,18 @@ static char const* portTest(
|
|||
****
|
||||
***/
|
||||
|
||||
static void gotNewBlocklist(
|
||||
tr_session* session,
|
||||
bool /*did_connect*/,
|
||||
bool /*did_timeout*/,
|
||||
long response_code,
|
||||
std::string_view response,
|
||||
void* user_data)
|
||||
static void gotNewBlocklist(tr_web::FetchResponse&& web_response)
|
||||
{
|
||||
char result[1024];
|
||||
auto const& [status, body, did_connect, did_timeout, user_data] = web_response;
|
||||
auto* data = static_cast<struct tr_rpc_idle_data*>(user_data);
|
||||
auto* const session = data->session;
|
||||
|
||||
*result = '\0';
|
||||
char result[1024] = {};
|
||||
|
||||
if (response_code != 200)
|
||||
if (status != 200)
|
||||
{
|
||||
// we failed to download the blocklist...
|
||||
tr_snprintf(
|
||||
result,
|
||||
sizeof(result),
|
||||
"gotNewBlocklist: http error %ld: %s",
|
||||
response_code,
|
||||
tr_webGetResponseStr(response_code));
|
||||
tr_snprintf(result, sizeof(result), "gotNewBlocklist: http error %ld: %s", status, tr_webGetResponseStr(status));
|
||||
tr_idle_function_done(data, result);
|
||||
return;
|
||||
}
|
||||
|
@ -1422,8 +1402,8 @@ static void gotNewBlocklist(
|
|||
auto actual_size = size_t{};
|
||||
auto const decompress_result = libdeflate_gzip_decompress(
|
||||
decompressor.get(),
|
||||
std::data(response),
|
||||
std::size(response),
|
||||
std::data(body),
|
||||
std::size(body),
|
||||
std::data(content),
|
||||
std::size(content),
|
||||
&actual_size);
|
||||
|
@ -1436,7 +1416,7 @@ static void gotNewBlocklist(
|
|||
if (decompress_result == LIBDEFLATE_BAD_DATA)
|
||||
{
|
||||
// couldn't decompress it; maybe we downloaded an uncompressed file
|
||||
content.assign(std::begin(response), std::end(response));
|
||||
content.assign(std::begin(body), std::end(body));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1466,7 +1446,7 @@ static char const* blocklistUpdate(
|
|||
tr_variant* /*args_out*/,
|
||||
struct tr_rpc_idle_data* idle_data)
|
||||
{
|
||||
tr_webRun(session, { session->blocklistUrl(), gotNewBlocklist, idle_data });
|
||||
session->web->fetch({ session->blocklistUrl(), gotNewBlocklist, idle_data });
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -1521,36 +1501,26 @@ struct add_torrent_idle_data
|
|||
tr_ctor* ctor;
|
||||
};
|
||||
|
||||
static void gotMetadataFromURL(
|
||||
tr_session* /*session*/,
|
||||
bool /*did_connect*/,
|
||||
bool /*did_timeout*/,
|
||||
long response_code,
|
||||
std::string_view response,
|
||||
void* user_data)
|
||||
static void gotMetadataFromURL(tr_web::FetchResponse&& web_response)
|
||||
{
|
||||
auto const& [status, body, did_connect, did_timeout, user_data] = web_response;
|
||||
auto* data = static_cast<struct add_torrent_idle_data*>(user_data);
|
||||
|
||||
dbgmsg(
|
||||
"torrentAdd: HTTP response code was %ld (%s); response length was %zu bytes",
|
||||
response_code,
|
||||
tr_webGetResponseStr(response_code),
|
||||
std::size(response));
|
||||
status,
|
||||
tr_webGetResponseStr(status),
|
||||
std::size(body));
|
||||
|
||||
if (response_code == 200 || response_code == 221) /* http or ftp success.. */
|
||||
if (status == 200 || status == 221) /* http or ftp success.. */
|
||||
{
|
||||
tr_ctorSetMetainfo(data->ctor, std::data(response), std::size(response), nullptr);
|
||||
tr_ctorSetMetainfo(data->ctor, std::data(body), std::size(body), nullptr);
|
||||
addTorrentImpl(data->data, data->ctor);
|
||||
}
|
||||
else
|
||||
{
|
||||
char result[1024];
|
||||
tr_snprintf(
|
||||
result,
|
||||
sizeof(result),
|
||||
"gotMetadataFromURL: http error %ld: %s",
|
||||
response_code,
|
||||
tr_webGetResponseStr(response_code));
|
||||
tr_snprintf(result, sizeof(result), "gotMetadataFromURL: http error %ld: %s", status, tr_webGetResponseStr(status));
|
||||
tr_idle_function_done(data->data, result);
|
||||
}
|
||||
|
||||
|
@ -1686,9 +1656,9 @@ static char const* torrentAdd(tr_session* session, tr_variant* args_in, tr_varia
|
|||
d->data = idle_data;
|
||||
d->ctor = ctor;
|
||||
|
||||
auto options = tr_web_options{ filename, gotMetadataFromURL, d };
|
||||
auto options = tr_web::FetchOptions{ filename, gotMetadataFromURL, d };
|
||||
options.cookies = cookies;
|
||||
tr_webRun(session, std::move(options));
|
||||
session->web->fetch(std::move(options));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -118,6 +118,74 @@ tr_peer_id_t tr_peerIdInit()
|
|||
****
|
||||
***/
|
||||
|
||||
std::optional<std::string> tr_session::WebController::cookieFile() const
|
||||
{
|
||||
auto const str = tr_strvPath(session_->config_dir, "cookies.txt");
|
||||
return tr_sys_path_exists(str.c_str(), nullptr) ? std::optional<std::string>{ str } : std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> tr_session::WebController::userAgent() const
|
||||
{
|
||||
return tr_strvJoin(TR_NAME, "/"sv, SHORT_VERSION_STRING);
|
||||
}
|
||||
|
||||
std::optional<std::string> tr_session::WebController::publicAddress() const
|
||||
{
|
||||
for (auto const type : { TR_AF_INET, TR_AF_INET6 })
|
||||
{
|
||||
auto is_default_value = bool{};
|
||||
tr_address const* addr = tr_sessionGetPublicAddress(session_, type, &is_default_value);
|
||||
if (addr != nullptr && !is_default_value)
|
||||
{
|
||||
return tr_address_to_string(addr);
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
unsigned int tr_session::WebController::clamp(int torrent_id, unsigned int byte_count) const
|
||||
{
|
||||
auto const lock = session_->unique_lock();
|
||||
auto const it = session_->torrentsById.find(torrent_id);
|
||||
return it == std::end(session_->torrentsById) ? 0U : it->second->bandwidth->clamp(TR_DOWN, byte_count);
|
||||
}
|
||||
|
||||
void tr_session::WebController::notifyBandwidthConsumed(int torrent_id, size_t byte_count)
|
||||
{
|
||||
auto const lock = session_->unique_lock();
|
||||
auto const it = session_->torrentsById.find(torrent_id);
|
||||
if (it != std::end(session_->torrentsById))
|
||||
{
|
||||
it->second->bandwidth->notifyBandwidthConsumed(TR_DOWN, byte_count, true, tr_time_msec());
|
||||
}
|
||||
}
|
||||
|
||||
void tr_session::WebController::run(tr_web::FetchDoneFunc func, tr_web::FetchResponse&& response) const
|
||||
{
|
||||
// marshall the `func` call into the libtransmission thread
|
||||
|
||||
using wrapper_t = std::pair<tr_web::FetchDoneFunc, tr_web::FetchResponse>;
|
||||
|
||||
auto constexpr callback = [](void* vwrapped)
|
||||
{
|
||||
auto* const wrapped = static_cast<wrapper_t*>(vwrapped);
|
||||
wrapped->first(std::move(wrapped->second));
|
||||
delete wrapped;
|
||||
};
|
||||
|
||||
tr_runInEventThread(session_, callback, new wrapper_t{ func, std::move(response) });
|
||||
}
|
||||
|
||||
void tr_sessionFetch(tr_session* session, tr_web::FetchOptions&& options)
|
||||
{
|
||||
session->web->fetch(std::move(options));
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
tr_encryption_mode tr_sessionGetEncryption(tr_session* session)
|
||||
{
|
||||
TR_ASSERT(session != nullptr);
|
||||
|
@ -693,6 +761,8 @@ static void tr_sessionInitImpl(void* vdata)
|
|||
|
||||
tr_udpInit(session);
|
||||
|
||||
session->web = tr_web::create(session->web_controller);
|
||||
|
||||
if (session->isLPDEnabled)
|
||||
{
|
||||
tr_lpdInit(session, &session->bind_ipv4->addr);
|
||||
|
@ -1799,7 +1869,7 @@ static void sessionCloseImplStart(tr_session* session)
|
|||
|
||||
/* and this goes *after* announcer close so that
|
||||
it won't be idle until the announce events are sent... */
|
||||
tr_webClose(session, TR_WEB_CLOSE_WHEN_IDLE);
|
||||
session->web->closeSoon();
|
||||
|
||||
tr_cacheFree(session->cache);
|
||||
session->cache = nullptr;
|
||||
|
@ -1890,7 +1960,7 @@ void tr_sessionClose(tr_session* session)
|
|||
* so we need to keep the transmission thread alive
|
||||
* for a bit while they tell the router & tracker
|
||||
* that we're closing now */
|
||||
while ((session->shared != nullptr || session->web != nullptr || session->announcer != nullptr ||
|
||||
while ((session->shared != nullptr || !session->web->isClosed() || session->announcer != nullptr ||
|
||||
session->announcer_udp != nullptr) &&
|
||||
!deadlineReached(deadline))
|
||||
{
|
||||
|
@ -1903,7 +1973,7 @@ void tr_sessionClose(tr_session* session)
|
|||
tr_wait_msec(50);
|
||||
}
|
||||
|
||||
tr_webClose(session, TR_WEB_CLOSE_NOW);
|
||||
session->web.reset();
|
||||
|
||||
/* close the libtransmission thread */
|
||||
tr_eventClose(session);
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
#include "net.h" // tr_socket_t
|
||||
#include "quark.h"
|
||||
#include "web.h"
|
||||
|
||||
enum tr_auto_switch_state_t
|
||||
{
|
||||
|
@ -44,6 +45,7 @@ struct evdns_base;
|
|||
|
||||
class tr_bitfield;
|
||||
class tr_rpc_server;
|
||||
class tr_web;
|
||||
struct Bandwidth;
|
||||
struct tr_address;
|
||||
struct tr_announcer;
|
||||
|
@ -334,7 +336,29 @@ public:
|
|||
|
||||
struct tr_cache* cache;
|
||||
|
||||
struct tr_web* web;
|
||||
class WebController final : public tr_web::Controller
|
||||
{
|
||||
public:
|
||||
explicit WebController(tr_session* session)
|
||||
: session_{ session }
|
||||
{
|
||||
}
|
||||
~WebController() override = default;
|
||||
|
||||
[[nodiscard]] std::optional<std::string> cookieFile() const override;
|
||||
[[nodiscard]] std::optional<std::string> publicAddress() const override;
|
||||
[[nodiscard]] std::optional<std::string> userAgent() const override;
|
||||
[[nodiscard]] unsigned int clamp(int bandwidth_tag, unsigned int byte_count) const override;
|
||||
void notifyBandwidthConsumed(int torrent_id, size_t byte_count) override;
|
||||
// runs the tr_web::fetch response callback in the libtransmission thread
|
||||
void run(tr_web::FetchDoneFunc func, tr_web::FetchResponse&& response) const override;
|
||||
|
||||
private:
|
||||
tr_session* const session_;
|
||||
};
|
||||
|
||||
WebController web_controller{ this };
|
||||
std::unique_ptr<tr_web> web;
|
||||
|
||||
struct tr_session_id* session_id;
|
||||
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
|
@ -19,174 +20,24 @@
|
|||
|
||||
#include <event2/buffer.h>
|
||||
|
||||
#include "transmission.h"
|
||||
#include "crypto-utils.h"
|
||||
#include "file.h"
|
||||
#include "log.h"
|
||||
#include "net.h" /* tr_address */
|
||||
#include "torrent.h"
|
||||
#include "session.h"
|
||||
#include "tr-assert.h"
|
||||
#include "tr-macros.h"
|
||||
#include "trevent.h" /* tr_runInEventThread() */
|
||||
#include "utils.h"
|
||||
#include "version.h" /* User-Agent */
|
||||
#include "web.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
#if LIBCURL_VERSION_NUM >= 0x070F06 /* CURLOPT_SOCKOPT* was added in 7.15.6 */
|
||||
#if LIBCURL_VERSION_NUM >= 0x070F06 // CURLOPT_SOCKOPT* was added in 7.15.6
|
||||
#define USE_LIBCURL_SOCKOPT
|
||||
#endif
|
||||
|
||||
static auto constexpr ThreadfuncMaxSleepMsec = int{ 200 };
|
||||
|
||||
#define dbgmsg(...) tr_logAddDeepNamed("web", __VA_ARGS__)
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
struct tr_web_task
|
||||
{
|
||||
private:
|
||||
std::shared_ptr<evbuffer> const privbuf{ evbuffer_new(), evbuffer_free };
|
||||
tr_web_options const options;
|
||||
|
||||
public:
|
||||
tr_web_task(tr_session* session_in, tr_web_options&& options_in)
|
||||
: options{ std::move(options_in) }
|
||||
, session{ session_in }
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto* response() const
|
||||
{
|
||||
return options.buffer != nullptr ? options.buffer : privbuf.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto const& torrent_id() const
|
||||
{
|
||||
return options.torrent_id;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto const& url() const
|
||||
{
|
||||
return options.url;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto const& range() const
|
||||
{
|
||||
return options.range;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto const& cookies() const
|
||||
{
|
||||
return options.cookies;
|
||||
}
|
||||
|
||||
void done() const
|
||||
{
|
||||
if (options.done_func == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto const sv = std::string_view{ reinterpret_cast<char const*>(evbuffer_pullup(response(), -1)),
|
||||
evbuffer_get_length(response()) };
|
||||
options.done_func(session, did_connect, did_timeout, response_code, sv, options.done_func_user_data);
|
||||
}
|
||||
|
||||
tr_session* const session;
|
||||
|
||||
CURL* curl_easy = nullptr;
|
||||
tr_web_task* next = nullptr;
|
||||
|
||||
long response_code = 0;
|
||||
long timeout_secs = 0;
|
||||
bool did_connect = false;
|
||||
bool did_timeout = false;
|
||||
};
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
struct tr_web
|
||||
{
|
||||
bool const curl_verbose = tr_env_key_exists("TR_CURL_VERBOSE");
|
||||
bool const curl_ssl_verify = !tr_env_key_exists("TR_CURL_SSL_NO_VERIFY");
|
||||
bool const curl_proxy_ssl_verify = !tr_env_key_exists("TR_CURL_PROXY_SSL_NO_VERIFY");
|
||||
|
||||
char* curl_ca_bundle;
|
||||
int close_mode = ~0;
|
||||
|
||||
std::recursive_mutex web_tasks_mutex;
|
||||
tr_web_task* tasks = nullptr;
|
||||
|
||||
std::string cookie_filename;
|
||||
std::set<CURL*> paused_easy_handles;
|
||||
};
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
static size_t writeFunc(void* ptr, size_t size, size_t nmemb, void* vtask)
|
||||
{
|
||||
size_t const byteCount = size * nmemb;
|
||||
auto* task = static_cast<struct tr_web_task*>(vtask);
|
||||
|
||||
/* webseed downloads should be speed limited */
|
||||
if (auto const& torrent_id = task->torrent_id(); torrent_id)
|
||||
{
|
||||
tr_torrent const* const tor = tr_torrentFindFromId(task->session, *torrent_id);
|
||||
|
||||
if (tor != nullptr && tor->bandwidth->clamp(TR_DOWN, nmemb) == 0)
|
||||
{
|
||||
task->session->web->paused_easy_handles.insert(task->curl_easy);
|
||||
return CURL_WRITEFUNC_PAUSE;
|
||||
}
|
||||
}
|
||||
|
||||
evbuffer_add(task->response(), ptr, byteCount);
|
||||
dbgmsg("wrote %zu bytes to task %p's buffer", byteCount, (void*)task);
|
||||
return byteCount;
|
||||
}
|
||||
|
||||
#ifdef USE_LIBCURL_SOCKOPT
|
||||
|
||||
static int sockoptfunction(void* vtask, curl_socket_t fd, curlsocktype /*purpose*/)
|
||||
{
|
||||
auto* task = static_cast<struct tr_web_task*>(vtask);
|
||||
|
||||
// Announce and scrape requests have tiny payloads.
|
||||
// Ignore the sockopt() return values -- these are suggestions
|
||||
// rather than hard requirements & it's OK for them to fail
|
||||
|
||||
auto const& url = task->url();
|
||||
|
||||
if (tr_strvContains(url, "scrape"sv))
|
||||
{
|
||||
int const sndbuf = 4096;
|
||||
int const rcvbuf = 4096;
|
||||
(void)setsockopt(fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char const*>(&sndbuf), sizeof(sndbuf));
|
||||
(void)setsockopt(fd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char const*>(&rcvbuf), sizeof(rcvbuf));
|
||||
}
|
||||
else if (tr_strvContains(url, "announce"sv))
|
||||
{
|
||||
int const sndbuf = 1024;
|
||||
int const rcvbuf = 3072;
|
||||
(void)setsockopt(fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char const*>(&sndbuf), sizeof(sndbuf));
|
||||
(void)setsockopt(fd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char const*>(&rcvbuf), sizeof(rcvbuf));
|
||||
}
|
||||
|
||||
/* return nonzero if this function encountered an error */
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static CURLcode ssl_context_func(CURL* /*curl*/, void* ssl_ctx, void* /*user_data*/)
|
||||
{
|
||||
auto const cert_store = tr_ssl_get_x509_store(ssl_ctx);
|
||||
|
@ -244,322 +95,482 @@ static CURLcode ssl_context_func(CURL* /*curl*/, void* ssl_ctx, void* /*user_dat
|
|||
return CURLE_OK;
|
||||
}
|
||||
|
||||
static long getTimeoutFromURL(struct tr_web_task const* task)
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
class tr_web::Impl
|
||||
{
|
||||
if (auto const* const session = task->session; session == nullptr || session->isClosed)
|
||||
public:
|
||||
Impl(Controller& controller_in)
|
||||
: controller{ controller_in }
|
||||
{
|
||||
return 20L;
|
||||
std::call_once(curl_init_flag, curlInit);
|
||||
|
||||
if (auto* bundle = tr_env_get_string("CURL_CA_BUNDLE", nullptr); bundle != nullptr)
|
||||
{
|
||||
curl_ca_bundle = bundle;
|
||||
tr_free(bundle);
|
||||
}
|
||||
|
||||
if (curl_ssl_verify)
|
||||
{
|
||||
auto const* bundle = std::empty(curl_ca_bundle) ? "none" : curl_ca_bundle.c_str();
|
||||
tr_logAddNamedInfo("web", "will verify tracker certs using envvar CURL_CA_BUNDLE: %s", bundle);
|
||||
tr_logAddNamedInfo("web", "NB: this only works if you built against libcurl with openssl or gnutls, NOT nss");
|
||||
tr_logAddNamedInfo("web", "NB: Invalid certs will appear as 'Could not connect to tracker' like many other errors");
|
||||
}
|
||||
|
||||
if (auto const& file = controller.cookieFile(); file)
|
||||
{
|
||||
this->cookie_file = *file;
|
||||
}
|
||||
|
||||
if (auto const& ua = controller.userAgent(); ua)
|
||||
{
|
||||
this->user_agent = *ua;
|
||||
}
|
||||
|
||||
curl_thread = std::make_unique<std::thread>(tr_webThreadFunc, this);
|
||||
}
|
||||
|
||||
if (tr_strvContains(task->url(), "scrape"sv))
|
||||
~Impl()
|
||||
{
|
||||
return 30L;
|
||||
run_mode = RunMode::CloseNow;
|
||||
curl_thread->join();
|
||||
}
|
||||
|
||||
if (tr_strvContains(task->url(), "announce"sv))
|
||||
void closeSoon()
|
||||
{
|
||||
return 90L;
|
||||
run_mode = RunMode::CloseSoon;
|
||||
}
|
||||
|
||||
return 240L;
|
||||
}
|
||||
[[nodiscard]] bool isClosed() const
|
||||
{
|
||||
return is_closed_;
|
||||
}
|
||||
|
||||
static CURL* createEasy(tr_session* s, struct tr_web* web, struct tr_web_task* task)
|
||||
{
|
||||
CURL* const e = curl_easy_init();
|
||||
void fetch(FetchOptions&& options)
|
||||
{
|
||||
if (run_mode != RunMode::Run)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
task->curl_easy = e;
|
||||
task->timeout_secs = getTimeoutFromURL(task);
|
||||
auto const lock = std::unique_lock(web_tasks_mutex);
|
||||
auto* const task = new Task{ *this, std::move(options) };
|
||||
task->next = tasks;
|
||||
tasks = task;
|
||||
}
|
||||
|
||||
curl_easy_setopt(e, CURLOPT_AUTOREFERER, 1L);
|
||||
curl_easy_setopt(e, CURLOPT_ENCODING, "");
|
||||
curl_easy_setopt(e, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(e, CURLOPT_MAXREDIRS, -1L);
|
||||
curl_easy_setopt(e, CURLOPT_NOSIGNAL, 1L);
|
||||
curl_easy_setopt(e, CURLOPT_PRIVATE, task);
|
||||
private:
|
||||
class Task
|
||||
{
|
||||
private:
|
||||
std::shared_ptr<evbuffer> const privbuf{ evbuffer_new(), evbuffer_free };
|
||||
std::shared_ptr<CURL> const easy_handle{ curl_easy_init(), curl_easy_cleanup };
|
||||
tr_web::FetchOptions const options;
|
||||
|
||||
public:
|
||||
Task(tr_web::Impl& impl_in, tr_web::FetchOptions&& options_in)
|
||||
: options{ std::move(options_in) }
|
||||
, impl{ impl_in }
|
||||
{
|
||||
response.user_data = options.done_func_user_data;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto* easy() const
|
||||
{
|
||||
return easy_handle.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto* body() const
|
||||
{
|
||||
return options.buffer != nullptr ? options.buffer : privbuf.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto const& speedLimitTag() const
|
||||
{
|
||||
return options.speed_limit_tag;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto const& url() const
|
||||
{
|
||||
return options.url;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto const& range() const
|
||||
{
|
||||
return options.range;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto const& cookies() const
|
||||
{
|
||||
return options.cookies;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto const& sndbuf() const
|
||||
{
|
||||
return options.sndbuf;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto const& rcvbuf() const
|
||||
{
|
||||
return options.rcvbuf;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto const& timeoutSecs() const
|
||||
{
|
||||
return options.timeout_secs;
|
||||
}
|
||||
|
||||
void done()
|
||||
{
|
||||
if (options.done_func == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
response.body.assign(reinterpret_cast<char const*>(evbuffer_pullup(body(), -1)), evbuffer_get_length(body()));
|
||||
impl.controller.run(options.done_func, std::move(this->response));
|
||||
}
|
||||
|
||||
tr_web::Impl& impl;
|
||||
tr_web::FetchResponse response;
|
||||
Task* next = nullptr;
|
||||
};
|
||||
|
||||
static auto constexpr BandwidthPauseMsec = long{ 500 };
|
||||
static auto constexpr DnsCacheTimeoutSecs = long{ 60 * 60 };
|
||||
|
||||
bool const curl_verbose = tr_env_key_exists("TR_CURL_VERBOSE");
|
||||
bool const curl_ssl_verify = !tr_env_key_exists("TR_CURL_SSL_NO_VERIFY");
|
||||
bool const curl_proxy_ssl_verify = !tr_env_key_exists("TR_CURL_PROXY_SSL_NO_VERIFY");
|
||||
|
||||
Controller& controller;
|
||||
|
||||
std::string curl_ca_bundle;
|
||||
|
||||
std::recursive_mutex web_tasks_mutex;
|
||||
Task* tasks = nullptr;
|
||||
|
||||
std::string cookie_file;
|
||||
std::string user_agent;
|
||||
|
||||
std::unique_ptr<std::thread> curl_thread;
|
||||
|
||||
enum class RunMode
|
||||
{
|
||||
Run,
|
||||
CloseSoon, // no new tasks; exit when running tasks finish
|
||||
CloseNow // exit now even if tasks are running
|
||||
};
|
||||
|
||||
RunMode run_mode = RunMode::Run;
|
||||
|
||||
static size_t onDataReceived(void* data, size_t size, size_t nmemb, void* vtask)
|
||||
{
|
||||
size_t const bytes_used = size * nmemb;
|
||||
auto* task = static_cast<Task*>(vtask);
|
||||
TR_ASSERT(std::this_thread::get_id() == task->impl.curl_thread->get_id());
|
||||
|
||||
if (auto const& tag = task->speedLimitTag(); tag)
|
||||
{
|
||||
// If this is more bandwidth than is allocated for this tag,
|
||||
// then pause the torrent for a tick. curl will deliver `data`
|
||||
// again when the transfer is unpaused.
|
||||
if (task->impl.controller.clamp(*tag, bytes_used) < bytes_used)
|
||||
{
|
||||
task->impl.paused_easy_handles.emplace(tr_time_msec(), task->easy());
|
||||
return CURL_WRITEFUNC_PAUSE;
|
||||
}
|
||||
|
||||
task->impl.controller.notifyBandwidthConsumed(*tag, bytes_used);
|
||||
}
|
||||
|
||||
evbuffer_add(task->body(), data, bytes_used);
|
||||
dbgmsg("wrote %zu bytes to task %p's buffer", bytes_used, (void*)task);
|
||||
return bytes_used;
|
||||
}
|
||||
|
||||
#ifdef USE_LIBCURL_SOCKOPT
|
||||
curl_easy_setopt(e, CURLOPT_SOCKOPTFUNCTION, sockoptfunction);
|
||||
curl_easy_setopt(e, CURLOPT_SOCKOPTDATA, task);
|
||||
static int onSocketCreated(void* vtask, curl_socket_t fd, curlsocktype /*purpose*/)
|
||||
{
|
||||
auto const* const task = static_cast<Task const*>(vtask);
|
||||
TR_ASSERT(std::this_thread::get_id() == task->impl.curl_thread->get_id());
|
||||
|
||||
// Ignore the sockopt() return values -- these are suggestions
|
||||
// rather than hard requirements & it's OK for them to fail
|
||||
|
||||
if (auto const& buf = task->sndbuf(); buf)
|
||||
{
|
||||
(void)setsockopt(fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char const*>(&*buf), sizeof(*buf));
|
||||
}
|
||||
if (auto const& buf = task->rcvbuf(); buf)
|
||||
{
|
||||
(void)setsockopt(fd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char const*>(&*buf), sizeof(*buf));
|
||||
}
|
||||
|
||||
// return nonzero if this function encountered an error
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (web->curl_ssl_verify)
|
||||
static void initEasy(tr_web::Impl* impl, Task* task)
|
||||
{
|
||||
if (web->curl_ca_bundle != nullptr)
|
||||
TR_ASSERT(std::this_thread::get_id() == impl->curl_thread->get_id());
|
||||
auto* const e = task->easy();
|
||||
|
||||
curl_easy_setopt(e, CURLOPT_SHARE, impl->shared());
|
||||
curl_easy_setopt(e, CURLOPT_DNS_CACHE_TIMEOUT, DnsCacheTimeoutSecs);
|
||||
curl_easy_setopt(e, CURLOPT_AUTOREFERER, 1L);
|
||||
curl_easy_setopt(e, CURLOPT_ENCODING, "");
|
||||
curl_easy_setopt(e, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(e, CURLOPT_MAXREDIRS, -1L);
|
||||
curl_easy_setopt(e, CURLOPT_NOSIGNAL, 1L);
|
||||
curl_easy_setopt(e, CURLOPT_PRIVATE, task);
|
||||
|
||||
#ifdef USE_LIBCURL_SOCKOPT
|
||||
curl_easy_setopt(e, CURLOPT_SOCKOPTFUNCTION, onSocketCreated);
|
||||
curl_easy_setopt(e, CURLOPT_SOCKOPTDATA, task);
|
||||
#endif
|
||||
|
||||
if (!impl->curl_ssl_verify)
|
||||
{
|
||||
curl_easy_setopt(e, CURLOPT_CAINFO, web->curl_ca_bundle);
|
||||
curl_easy_setopt(e, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
curl_easy_setopt(e, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
}
|
||||
else if (!std::empty(impl->curl_ca_bundle))
|
||||
{
|
||||
curl_easy_setopt(e, CURLOPT_CAINFO, impl->curl_ca_bundle.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
curl_easy_setopt(e, CURLOPT_SSL_CTX_FUNCTION, ssl_context_func);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
curl_easy_setopt(e, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||
curl_easy_setopt(e, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
}
|
||||
|
||||
if (web->curl_proxy_ssl_verify)
|
||||
{
|
||||
if (web->curl_ca_bundle != nullptr)
|
||||
if (!impl->curl_proxy_ssl_verify)
|
||||
{
|
||||
curl_easy_setopt(e, CURLOPT_PROXY_CAINFO, web->curl_ca_bundle);
|
||||
curl_easy_setopt(e, CURLOPT_PROXY_SSL_VERIFYHOST, 0L);
|
||||
curl_easy_setopt(e, CURLOPT_PROXY_SSL_VERIFYPEER, 0L);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
curl_easy_setopt(e, CURLOPT_PROXY_SSL_VERIFYHOST, 0L);
|
||||
curl_easy_setopt(e, CURLOPT_PROXY_SSL_VERIFYPEER, 0L);
|
||||
}
|
||||
|
||||
curl_easy_setopt(e, CURLOPT_TIMEOUT, task->timeout_secs);
|
||||
curl_easy_setopt(e, CURLOPT_URL, task->url().c_str());
|
||||
curl_easy_setopt(e, CURLOPT_USERAGENT, TR_NAME "/" SHORT_VERSION_STRING);
|
||||
curl_easy_setopt(e, CURLOPT_VERBOSE, (long)(web->curl_verbose ? 1 : 0));
|
||||
curl_easy_setopt(e, CURLOPT_WRITEDATA, task);
|
||||
curl_easy_setopt(e, CURLOPT_WRITEFUNCTION, writeFunc);
|
||||
|
||||
auto is_default_value = bool{};
|
||||
tr_address const* addr = tr_sessionGetPublicAddress(s, TR_AF_INET, &is_default_value);
|
||||
if (addr != nullptr && !is_default_value)
|
||||
{
|
||||
(void)curl_easy_setopt(e, CURLOPT_INTERFACE, tr_address_to_string(addr));
|
||||
}
|
||||
|
||||
addr = tr_sessionGetPublicAddress(s, TR_AF_INET6, &is_default_value);
|
||||
if (addr != nullptr && !is_default_value)
|
||||
{
|
||||
(void)curl_easy_setopt(e, CURLOPT_INTERFACE, tr_address_to_string(addr));
|
||||
}
|
||||
|
||||
if (auto const& cookies = task->cookies(); !std::empty(cookies))
|
||||
{
|
||||
(void)curl_easy_setopt(e, CURLOPT_COOKIE, cookies.c_str());
|
||||
}
|
||||
|
||||
if (auto const& filename = web->cookie_filename; !std::empty(filename))
|
||||
{
|
||||
(void)curl_easy_setopt(e, CURLOPT_COOKIEFILE, filename.c_str());
|
||||
}
|
||||
|
||||
if (auto const& range = task->range(); !std::empty(range))
|
||||
{
|
||||
curl_easy_setopt(e, CURLOPT_RANGE, range.c_str());
|
||||
/* don't bother asking the server to compress webseed fragments */
|
||||
curl_easy_setopt(e, CURLOPT_ENCODING, "identity");
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
static void task_finish_func(void* vtask)
|
||||
{
|
||||
auto* task = static_cast<tr_web_task*>(vtask);
|
||||
task->done();
|
||||
delete task;
|
||||
}
|
||||
|
||||
/****
|
||||
*****
|
||||
****/
|
||||
|
||||
static void tr_webThreadFunc(void* vsession);
|
||||
|
||||
void tr_webRun(tr_session* session, tr_web_options&& options)
|
||||
{
|
||||
if (session->isClosing())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (session->web == nullptr)
|
||||
{
|
||||
std::thread(tr_webThreadFunc, session).detach();
|
||||
while (session->web == nullptr)
|
||||
else if (!std::empty(impl->curl_ca_bundle))
|
||||
{
|
||||
tr_wait_msec(20);
|
||||
curl_easy_setopt(e, CURLOPT_PROXY_CAINFO, impl->curl_ca_bundle.c_str());
|
||||
}
|
||||
|
||||
if (auto const& ua = impl->user_agent; !std::empty(ua))
|
||||
{
|
||||
curl_easy_setopt(e, CURLOPT_USERAGENT, ua.c_str());
|
||||
}
|
||||
|
||||
curl_easy_setopt(e, CURLOPT_TIMEOUT, task->timeoutSecs());
|
||||
curl_easy_setopt(e, CURLOPT_URL, task->url().c_str());
|
||||
curl_easy_setopt(e, CURLOPT_VERBOSE, impl->curl_verbose ? 1L : 0L);
|
||||
curl_easy_setopt(e, CURLOPT_WRITEDATA, task);
|
||||
curl_easy_setopt(e, CURLOPT_WRITEFUNCTION, onDataReceived);
|
||||
|
||||
if (auto const addrstr = impl->controller.publicAddress(); addrstr)
|
||||
{
|
||||
(void)curl_easy_setopt(e, CURLOPT_INTERFACE, addrstr->c_str());
|
||||
}
|
||||
|
||||
if (auto const& cookies = task->cookies(); cookies)
|
||||
{
|
||||
(void)curl_easy_setopt(e, CURLOPT_COOKIE, cookies->c_str());
|
||||
}
|
||||
|
||||
if (auto const& file = impl->cookie_file; !std::empty(file))
|
||||
{
|
||||
(void)curl_easy_setopt(e, CURLOPT_COOKIEFILE, file.c_str());
|
||||
}
|
||||
|
||||
if (auto const& range = task->range(); range)
|
||||
{
|
||||
/* don't bother asking the server to compress webseed fragments */
|
||||
curl_easy_setopt(e, CURLOPT_ENCODING, "identity");
|
||||
curl_easy_setopt(e, CURLOPT_RANGE, range->c_str());
|
||||
}
|
||||
}
|
||||
|
||||
auto const lock = std::unique_lock(session->web->web_tasks_mutex);
|
||||
auto* const task = new tr_web_task{ session, std::move(options) };
|
||||
task->next = session->web->tasks;
|
||||
session->web->tasks = task;
|
||||
}
|
||||
|
||||
static void tr_webThreadFunc(void* vsession)
|
||||
{
|
||||
auto* session = static_cast<tr_session*>(vsession);
|
||||
|
||||
/* try to enable ssl for https support; but if that fails,
|
||||
* try a plain vanilla init */
|
||||
if (curl_global_init(CURL_GLOBAL_SSL) != CURLE_OK)
|
||||
void resumePausedTasks()
|
||||
{
|
||||
curl_global_init(0);
|
||||
}
|
||||
TR_ASSERT(std::this_thread::get_id() == curl_thread->get_id());
|
||||
|
||||
auto* const web = new tr_web{};
|
||||
web->curl_ca_bundle = tr_env_get_string("CURL_CA_BUNDLE", nullptr);
|
||||
|
||||
if (web->curl_ssl_verify)
|
||||
{
|
||||
tr_logAddNamedInfo(
|
||||
"web",
|
||||
"will verify tracker certs using envvar CURL_CA_BUNDLE: %s",
|
||||
web->curl_ca_bundle == nullptr ? "none" : web->curl_ca_bundle);
|
||||
tr_logAddNamedInfo("web", "NB: this only works if you built against libcurl with openssl or gnutls, NOT nss");
|
||||
tr_logAddNamedInfo("web", "NB: invalid certs will show up as 'Could not connect to tracker' like many other errors");
|
||||
}
|
||||
|
||||
auto const str = tr_strvPath(session->config_dir, "cookies.txt");
|
||||
if (tr_sys_path_exists(str.c_str(), nullptr))
|
||||
{
|
||||
web->cookie_filename = str;
|
||||
}
|
||||
|
||||
auto* const multi = curl_multi_init();
|
||||
session->web = web;
|
||||
|
||||
auto repeats = uint32_t{};
|
||||
for (;;)
|
||||
{
|
||||
if (web->close_mode == TR_WEB_CLOSE_NOW)
|
||||
auto& paused = paused_easy_handles;
|
||||
if (std::empty(paused))
|
||||
{
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
if (web->close_mode == TR_WEB_CLOSE_WHEN_IDLE && web->tasks == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
auto const now = tr_time_msec();
|
||||
|
||||
/* add tasks from the queue */
|
||||
for (auto it = std::begin(paused); it != std::end(paused);)
|
||||
{
|
||||
auto const lock = std::unique_lock(web->web_tasks_mutex);
|
||||
|
||||
while (web->tasks != nullptr)
|
||||
if (it->first + BandwidthPauseMsec < now)
|
||||
{
|
||||
/* pop the task */
|
||||
auto* const task = web->tasks;
|
||||
web->tasks = task->next;
|
||||
task->next = nullptr;
|
||||
|
||||
dbgmsg("adding task to curl: [%s]", task->url().c_str());
|
||||
curl_multi_add_handle(multi, createEasy(session, web, task));
|
||||
curl_easy_pause(it->second, CURLPAUSE_CONT);
|
||||
it = paused.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* resume any paused curl handles.
|
||||
swap paused_easy_handles to prevent oscillation
|
||||
between writeFunc this while loop */
|
||||
auto paused = decltype(web->paused_easy_handles){};
|
||||
std::swap(paused, web->paused_easy_handles);
|
||||
std::for_each(std::begin(paused), std::end(paused), [](auto* curl) { curl_easy_pause(curl, CURLPAUSE_CONT); });
|
||||
// the thread started by Impl.curl_thread runs this function
|
||||
static void tr_webThreadFunc(void* vimpl)
|
||||
{
|
||||
auto* impl = static_cast<tr_web::Impl*>(vimpl);
|
||||
TR_ASSERT(std::this_thread::get_id() == impl->curl_thread->get_id());
|
||||
|
||||
/* maybe wait a little while before calling curl_multi_perform() */
|
||||
auto msec = long{};
|
||||
curl_multi_timeout(multi, &msec);
|
||||
if (msec < 0)
|
||||
auto const multi = std::shared_ptr<CURLM>(curl_multi_init(), curl_multi_cleanup);
|
||||
|
||||
auto repeats = unsigned{};
|
||||
for (;;)
|
||||
{
|
||||
msec = ThreadfuncMaxSleepMsec;
|
||||
}
|
||||
|
||||
if (session->isClosed)
|
||||
{
|
||||
msec = 100; /* on shutdown, call perform() more frequently */
|
||||
}
|
||||
|
||||
if (msec > 0)
|
||||
{
|
||||
if (msec > ThreadfuncMaxSleepMsec)
|
||||
if (impl->run_mode == RunMode::CloseNow)
|
||||
{
|
||||
msec = ThreadfuncMaxSleepMsec;
|
||||
break;
|
||||
}
|
||||
|
||||
if (impl->run_mode == RunMode::CloseSoon && impl->tasks == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
/* add tasks from the queue */
|
||||
{
|
||||
auto const lock = std::unique_lock(impl->web_tasks_mutex);
|
||||
|
||||
while (impl->tasks != nullptr)
|
||||
{
|
||||
/* pop the task */
|
||||
auto* const task = impl->tasks;
|
||||
impl->tasks = task->next;
|
||||
task->next = nullptr;
|
||||
|
||||
dbgmsg("adding task to curl: [%s]", task->url().c_str());
|
||||
initEasy(impl, task);
|
||||
curl_multi_add_handle(multi.get(), task->easy());
|
||||
}
|
||||
}
|
||||
|
||||
impl->resumePausedTasks();
|
||||
|
||||
// Adapted from https://curl.se/libcurl/c/curl_multi_wait.html docs.
|
||||
// 'numfds' being zero means either a timeout or no file descriptors to
|
||||
// wait for. Try timeout on first occurrence, then assume no file
|
||||
// descriptors and no file descriptors to wait for means wait for 100
|
||||
// milliseconds.
|
||||
auto numfds = int{};
|
||||
curl_multi_wait(multi, nullptr, 0, msec, &numfds);
|
||||
curl_multi_wait(multi.get(), nullptr, 0, 1000, &numfds);
|
||||
if (numfds == 0)
|
||||
{
|
||||
repeats++;
|
||||
if (repeats > 1)
|
||||
++repeats;
|
||||
if (repeats > 1U)
|
||||
{
|
||||
/* curl_multi_wait() returns immediately if there are
|
||||
* no fds to wait for, so we need an explicit wait here
|
||||
* to emulate select() behavior */
|
||||
tr_wait_msec(std::min(msec, ThreadfuncMaxSleepMsec / 2L));
|
||||
tr_wait_msec(100);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
repeats = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* call curl_multi_perform() */
|
||||
auto mcode = CURLMcode{};
|
||||
auto unused = int{};
|
||||
do
|
||||
{
|
||||
mcode = curl_multi_perform(multi, &unused);
|
||||
} while (mcode == CURLM_CALL_MULTI_PERFORM);
|
||||
/* call curl_multi_perform() */
|
||||
auto unused = int{};
|
||||
curl_multi_perform(multi.get(), &unused);
|
||||
|
||||
/* pump completed tasks from the multi */
|
||||
CURLMsg* msg = nullptr;
|
||||
while ((msg = curl_multi_info_read(multi, &unused)) != nullptr)
|
||||
{
|
||||
if (msg->msg == CURLMSG_DONE && msg->easy_handle != nullptr)
|
||||
/* pump completed tasks from the multi */
|
||||
CURLMsg* msg = nullptr;
|
||||
while ((msg = curl_multi_info_read(multi.get(), &unused)) != nullptr)
|
||||
{
|
||||
auto* const e = msg->easy_handle;
|
||||
if (msg->msg == CURLMSG_DONE && msg->easy_handle != nullptr)
|
||||
{
|
||||
auto* const e = msg->easy_handle;
|
||||
|
||||
tr_web_task* task = nullptr;
|
||||
curl_easy_getinfo(e, CURLINFO_PRIVATE, (void*)&task);
|
||||
TR_ASSERT(e == task->curl_easy);
|
||||
Task* task = nullptr;
|
||||
curl_easy_getinfo(e, CURLINFO_PRIVATE, (void*)&task);
|
||||
|
||||
auto req_bytes_sent = long{};
|
||||
auto total_time = double{};
|
||||
curl_easy_getinfo(e, CURLINFO_RESPONSE_CODE, &task->response_code);
|
||||
curl_easy_getinfo(e, CURLINFO_REQUEST_SIZE, &req_bytes_sent);
|
||||
curl_easy_getinfo(e, CURLINFO_TOTAL_TIME, &total_time);
|
||||
task->did_connect = task->response_code > 0 || req_bytes_sent > 0;
|
||||
task->did_timeout = task->response_code == 0 && total_time >= task->timeout_secs;
|
||||
curl_multi_remove_handle(multi, e);
|
||||
web->paused_easy_handles.erase(e);
|
||||
curl_easy_cleanup(e);
|
||||
tr_runInEventThread(task->session, task_finish_func, task);
|
||||
auto req_bytes_sent = long{};
|
||||
auto total_time = double{};
|
||||
curl_easy_getinfo(e, CURLINFO_REQUEST_SIZE, &req_bytes_sent);
|
||||
curl_easy_getinfo(e, CURLINFO_TOTAL_TIME, &total_time);
|
||||
curl_easy_getinfo(e, CURLINFO_RESPONSE_CODE, &task->response.status);
|
||||
task->response.did_connect = task->response.status > 0 || req_bytes_sent > 0;
|
||||
task->response.did_timeout = task->response.status == 0 && total_time >= task->timeoutSecs();
|
||||
curl_multi_remove_handle(multi.get(), e);
|
||||
task->done();
|
||||
delete task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Discard any remaining tasks.
|
||||
* This is rare, but can happen on shutdown with unresponsive trackers. */
|
||||
while (impl->tasks != nullptr)
|
||||
{
|
||||
auto* const task = impl->tasks;
|
||||
impl->tasks = task->next;
|
||||
dbgmsg("Discarding task \"%s\"", task->url().c_str());
|
||||
delete task;
|
||||
}
|
||||
|
||||
impl->is_closed_ = true;
|
||||
}
|
||||
|
||||
/* Discard any remaining tasks.
|
||||
* This is rare, but can happen on shutdown with unresponsive trackers. */
|
||||
while (web->tasks != nullptr)
|
||||
private:
|
||||
std::shared_ptr<CURLSH> const curlsh_{ curl_share_init(), curl_share_cleanup };
|
||||
|
||||
CURLSH* shared()
|
||||
{
|
||||
auto* const task = web->tasks;
|
||||
web->tasks = task->next;
|
||||
dbgmsg("Discarding task \"%s\"", task->url().c_str());
|
||||
delete task;
|
||||
return curlsh_.get();
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
curl_multi_cleanup(multi);
|
||||
tr_free(web->curl_ca_bundle);
|
||||
delete web;
|
||||
session->web = nullptr;
|
||||
}
|
||||
static std::once_flag curl_init_flag;
|
||||
|
||||
void tr_webClose(tr_session* session, tr_web_close_mode close_mode)
|
||||
bool is_closed_ = false;
|
||||
|
||||
std::multimap<uint64_t /*tr_time_msec()*/, CURL*> paused_easy_handles;
|
||||
|
||||
static void curlInit()
|
||||
{
|
||||
// try to enable ssl for https support;
|
||||
// but if that fails, try a plain vanilla init
|
||||
if (curl_global_init(CURL_GLOBAL_SSL) != CURLE_OK)
|
||||
{
|
||||
curl_global_init(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::once_flag tr_web::Impl::curl_init_flag;
|
||||
|
||||
tr_web::tr_web(Controller& controller)
|
||||
: impl_{ std::make_unique<Impl>(controller) }
|
||||
{
|
||||
if (session->web != nullptr)
|
||||
{
|
||||
session->web->close_mode = close_mode;
|
||||
|
||||
if (close_mode == TR_WEB_CLOSE_NOW)
|
||||
{
|
||||
while (session->web != nullptr)
|
||||
{
|
||||
tr_wait_msec(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr_web::~tr_web() = default;
|
||||
|
||||
std::unique_ptr<tr_web> tr_web::create(Controller& controller)
|
||||
{
|
||||
return std::unique_ptr<tr_web>(new tr_web(controller));
|
||||
}
|
||||
|
||||
void tr_web::fetch(FetchOptions&& options)
|
||||
{
|
||||
impl_->fetch(std::move(options));
|
||||
}
|
||||
|
||||
bool tr_web::isClosed() const
|
||||
{
|
||||
return impl_->isClosed();
|
||||
}
|
||||
|
||||
void tr_web::closeSoon()
|
||||
{
|
||||
impl_->closeSoon();
|
||||
}
|
||||
|
|
|
@ -5,48 +5,142 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
struct evbuffer;
|
||||
|
||||
enum tr_web_close_mode
|
||||
{
|
||||
TR_WEB_CLOSE_WHEN_IDLE,
|
||||
TR_WEB_CLOSE_NOW
|
||||
};
|
||||
|
||||
void tr_webClose(tr_session* session, tr_web_close_mode close_mode);
|
||||
|
||||
using tr_web_done_func = void (*)(
|
||||
tr_session* session,
|
||||
bool did_connect_flag,
|
||||
bool timeout_flag,
|
||||
long response_code,
|
||||
std::string_view response,
|
||||
void* user_data);
|
||||
|
||||
class tr_web_options
|
||||
class tr_web
|
||||
{
|
||||
public:
|
||||
tr_web_options(std::string_view url_in, tr_web_done_func done_func_in, void* done_func_user_data_in)
|
||||
: url{ url_in }
|
||||
, done_func{ done_func_in }
|
||||
, done_func_user_data{ done_func_user_data_in }
|
||||
// The response struct passed to the user's FetchDoneFunc callback
|
||||
// when a fetch() finishes.
|
||||
struct FetchResponse
|
||||
{
|
||||
}
|
||||
long status; // http server response, e.g. 200
|
||||
std::string body;
|
||||
bool did_connect;
|
||||
bool did_timeout;
|
||||
void* user_data;
|
||||
};
|
||||
|
||||
std::string url;
|
||||
std::optional<int> torrent_id;
|
||||
tr_web_done_func done_func = nullptr;
|
||||
void* done_func_user_data = nullptr;
|
||||
std::string range;
|
||||
std::string cookies;
|
||||
evbuffer* buffer = nullptr;
|
||||
using FetchDoneFunc = void (*)(FetchResponse&& response);
|
||||
|
||||
class FetchOptions
|
||||
{
|
||||
public:
|
||||
FetchOptions(std::string_view url_in, FetchDoneFunc done_func_in, void* done_func_user_data_in)
|
||||
: url{ url_in }
|
||||
, done_func{ done_func_in }
|
||||
, done_func_user_data{ done_func_user_data_in }
|
||||
{
|
||||
}
|
||||
|
||||
// the URL to fetch
|
||||
std::string url;
|
||||
|
||||
// Callback to invoke with a FetchResponse when done
|
||||
FetchDoneFunc done_func = nullptr;
|
||||
void* done_func_user_data = nullptr;
|
||||
|
||||
// If you need to set multiple cookies, set them all using a single
|
||||
// option concatenated like this: "name1=content1; name2=content2;"
|
||||
std::optional<std::string> cookies;
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests
|
||||
std::optional<std::string> range;
|
||||
|
||||
// Tag used by tr_web::Controller to limit some transfers' bandwidth
|
||||
std::optional<int> speed_limit_tag;
|
||||
|
||||
// Optionaly set the underlying sockets' send/receive buffers' size.
|
||||
// Can be useful for scrapes / announces where the payload is known
|
||||
// to be small.
|
||||
std::optional<int> sndbuf;
|
||||
std::optional<int> rcvbuf;
|
||||
|
||||
// Maximum time to wait before timeout
|
||||
int timeout_secs = DefaultTimeoutSecs;
|
||||
|
||||
// If provided, this buffer will be used to hold the response body.
|
||||
// Provided for webseeds, which need to set low-level callbacks on
|
||||
// the buffer itself.
|
||||
evbuffer* buffer = nullptr;
|
||||
|
||||
static constexpr int DefaultTimeoutSecs = 120;
|
||||
};
|
||||
|
||||
void fetch(FetchOptions&& options);
|
||||
|
||||
// Notify tr_web that it's going to be destroyed sooon.
|
||||
// New fetch() tasks will be rejected, but already-running tasks
|
||||
// are left alone so that they can finish.
|
||||
void closeSoon();
|
||||
|
||||
// True when tr_web is ready to be destroyed.
|
||||
// Will never be true until after closeSoon() is called.
|
||||
[[nodiscard]] bool isClosed() const;
|
||||
|
||||
// closeSoon() *should* be called first, but OK to destroy tr_web before
|
||||
// isClosed() is true, e.g. there could be a hung fetch task that hasn't
|
||||
// timmed out yet. Deleting the tr_web object will force-terminate any
|
||||
// pending tasks.
|
||||
~tr_web();
|
||||
|
||||
/**
|
||||
* Mediates between tr_web and its clients.
|
||||
*
|
||||
* NB: Note that tr_web calls all these methods in the web thread.
|
||||
*/
|
||||
class Controller
|
||||
{
|
||||
public:
|
||||
virtual ~Controller() = default;
|
||||
|
||||
// Return the location of the cookie file, or nullopt to not use one
|
||||
[[nodiscard]] virtual std::optional<std::string> cookieFile() const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Return the preferred user public address string, or nullopt to not use one
|
||||
[[nodiscard]] virtual std::optional<std::string> publicAddress() const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Return the preferred user aagent, or nullopt to not use one
|
||||
[[nodiscard]] virtual std::optional<std::string> userAgent() const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Notify the system that `byte_count` of download bandwidth was used
|
||||
virtual void notifyBandwidthConsumed([[maybe_unused]] int bandwidth_tag, [[maybe_unused]] size_t byte_count)
|
||||
{
|
||||
}
|
||||
|
||||
// Return the number of bytes that should be allowed. See Bandwidth::clamp()
|
||||
[[nodiscard]] virtual unsigned int clamp([[maybe_unused]] int bandwidth_tag, unsigned int byte_count) const
|
||||
{
|
||||
return byte_count;
|
||||
}
|
||||
|
||||
// Invoke the user-provided fetch callback
|
||||
virtual void run(FetchDoneFunc func, FetchResponse&& response) const
|
||||
{
|
||||
func(std::move(response));
|
||||
}
|
||||
};
|
||||
|
||||
static std::unique_ptr<tr_web> create(Controller& controller);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> const impl_;
|
||||
explicit tr_web(Controller& controller);
|
||||
};
|
||||
|
||||
void tr_webRun(tr_session* session, tr_web_options&& options);
|
||||
void tr_sessionFetch(struct tr_session* session, tr_web::FetchOptions&& options);
|
||||
|
|
|
@ -405,17 +405,14 @@ void on_idle(tr_webseed* w)
|
|||
}
|
||||
}
|
||||
|
||||
void web_response_func(
|
||||
tr_session* session,
|
||||
bool /*did_connect*/,
|
||||
bool /*did_timeout*/,
|
||||
long response_code,
|
||||
std::string_view /*response*/,
|
||||
void* vtask)
|
||||
void onWebResponse(tr_web::FetchResponse&& web_response)
|
||||
{
|
||||
auto const& [status, body, did_connect, did_timeout, vtask] = web_response;
|
||||
bool const success = status == 206;
|
||||
|
||||
auto* const t = static_cast<tr_webseed_task*>(vtask);
|
||||
bool const success = response_code == 206;
|
||||
tr_webseed* w = t->webseed;
|
||||
auto* const session = t->session;
|
||||
auto* const w = t->webseed;
|
||||
|
||||
w->connection_limiter.taskFinished(success);
|
||||
|
||||
|
@ -511,11 +508,11 @@ void task_request_next_chunk(tr_webseed_task* t)
|
|||
uint64_t this_pass = std::min(remain, tor->fileSize(file_index) - file_offset);
|
||||
|
||||
auto const url = make_url(t->webseed, tor->fileSubpath(file_index));
|
||||
auto options = tr_web_options{ url, web_response_func, t };
|
||||
auto options = tr_web::FetchOptions{ url, onWebResponse, t };
|
||||
options.range = tr_strvJoin(std::to_string(file_offset), "-"sv, std::to_string(file_offset + this_pass - 1));
|
||||
options.torrent_id = tor->uniqueId;
|
||||
options.speed_limit_tag = tor->uniqueId;
|
||||
options.buffer = t->content();
|
||||
tr_webRun(tor->session, std::move(options));
|
||||
tor->session->web->fetch(std::move(options));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
Loading…
Reference in a new issue