mirror of
https://github.com/cookiengineer/audacity
synced 2025-05-17 17:26:48 +02:00
350 lines
8.5 KiB
C++
350 lines
8.5 KiB
C++
/*!********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
@file CurlHandleManager.cpp
|
|
@brief Define a class responsible for reuse of CURL hanldes.
|
|
|
|
Dmitry Vedenko
|
|
**********************************************************************/
|
|
|
|
#include "CurlHandleManager.h"
|
|
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
|
|
#include <wx/platinfo.h>
|
|
|
|
namespace audacity
|
|
{
|
|
namespace network_manager
|
|
{
|
|
namespace
|
|
{
|
|
|
|
void GetOSString (std::ostringstream& output, const wxPlatformInfo& platformInfo)
|
|
{
|
|
const wxOperatingSystemId osID = platformInfo.GetOperatingSystemId ();
|
|
|
|
if (osID & wxOS_WINDOWS)
|
|
output << "Windows ";
|
|
else if (osID & wxOS_MAC)
|
|
output << "MacOS ";
|
|
else if (osID & wxOS_UNIX_LINUX)
|
|
output << "Linux ";
|
|
else if (osID & wxOS_UNIX_FREEBSD)
|
|
output << "FreeBSD ";
|
|
else if (osID & wxOS_UNIX_OPENBSD)
|
|
output << "OpenBSD ";
|
|
else
|
|
output << "Other ";
|
|
|
|
output <<
|
|
platformInfo.GetOSMajorVersion () <<
|
|
"_" <<
|
|
platformInfo.GetOSMinorVersion () <<
|
|
"_" <<
|
|
platformInfo.GetOSMicroVersion() <<
|
|
"; ";
|
|
|
|
#if defined(__amd64__) || defined(__x86_64__) || defined(_M_X64)
|
|
output << "x64";
|
|
#elif defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(_X86_) || defined(__THW_INTEL)
|
|
output << "x86";
|
|
#elif defined(__arm64__) || defined(__aarch64__)
|
|
output << "arm64";
|
|
#elif defined(arm) || defined(__arm__) || defined(ARM) || defined(_ARM_)
|
|
output << "arm";
|
|
#else
|
|
output << "unknown";
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
constexpr std::chrono::milliseconds CurlHandleManager::KEEP_ALIVE_IDLE;
|
|
constexpr std::chrono::milliseconds CurlHandleManager::KEEP_ALIVE_PROBE;
|
|
|
|
CurlHandleManager::Handle::Handle(CurlHandleManager* owner, CURL* handle, RequestVerb verb, std::string url) noexcept
|
|
: mHandle (handle),
|
|
mOwner (owner),
|
|
mVerb (verb),
|
|
mUrl (std::move (url)),
|
|
mHandleFromCache (handle != nullptr)
|
|
{
|
|
if (mHandle == nullptr)
|
|
mHandle = curl_easy_init ();
|
|
|
|
setOption (CURLOPT_URL, mUrl);
|
|
|
|
switch (verb)
|
|
{
|
|
case RequestVerb::Head:
|
|
setOption (CURLOPT_NOBODY, 1);
|
|
break;
|
|
case RequestVerb::Get:
|
|
// This is a default, no additional setup is needed
|
|
// We cache handles by the verb, so there is no need to
|
|
// reset the handle state
|
|
break;
|
|
case RequestVerb::Post:
|
|
setOption (CURLOPT_POST, 1);
|
|
break;
|
|
case RequestVerb::Put:
|
|
setOption (CURLOPT_UPLOAD, 1);
|
|
break;
|
|
case RequestVerb::Delete:
|
|
setOption (CURLOPT_CUSTOMREQUEST, "DELETE");
|
|
break;
|
|
}
|
|
|
|
setOption (CURLOPT_NOSIGNAL, 1L);
|
|
|
|
setOption (CURLOPT_SSL_VERIFYPEER, 1L);
|
|
setOption (CURLOPT_SSL_VERIFYHOST, 2L);
|
|
|
|
setOption (CURLOPT_ACCEPT_ENCODING, "");
|
|
}
|
|
|
|
CurlHandleManager::Handle::Handle (Handle&& rhs) noexcept
|
|
{
|
|
*this = std::move (rhs);
|
|
}
|
|
|
|
CurlHandleManager::Handle::~Handle () noexcept
|
|
{
|
|
if (mReuse)
|
|
mOwner->cacheHandle (*this);
|
|
else
|
|
curl_easy_cleanup (mHandle);
|
|
}
|
|
|
|
CurlHandleManager::Handle& CurlHandleManager::Handle::operator=(Handle&& rhs) noexcept
|
|
{
|
|
std::swap (mHandle, rhs.mHandle);
|
|
std::swap (mOwner, rhs.mOwner);
|
|
|
|
std::swap (mVerb, rhs.mVerb);
|
|
|
|
mUrl = std::move (rhs.mUrl);
|
|
mHeaders = std::move (rhs.mHeaders);
|
|
|
|
mReuse = rhs.mReuse;
|
|
rhs.mReuse = false;
|
|
|
|
return *this;
|
|
}
|
|
|
|
CURLcode CurlHandleManager::Handle::setOption (CURLoption option, const std::string& value) noexcept
|
|
{
|
|
return setOption (option, value.c_str ());
|
|
}
|
|
|
|
CURLcode CurlHandleManager::Handle::appendCookie (const Cookie& cookie) noexcept
|
|
{
|
|
return setOption(CURLOPT_COOKIE, "Set-Cookie: " + cookie.Name + "=" + cookie.Value);
|
|
}
|
|
|
|
CURLcode CurlHandleManager::Handle::appendCookies (const CookiesList& cookies)noexcept
|
|
{
|
|
for (const Cookie& cookie : cookies)
|
|
{
|
|
const CURLcode result = appendCookie (cookie);
|
|
|
|
if (result != CURLE_OK)
|
|
return result;
|
|
}
|
|
|
|
return CURLE_OK;
|
|
}
|
|
|
|
void CurlHandleManager::Handle::appendHeader (const Header& header)
|
|
{
|
|
if (header.hasSameName ("User-Agent"))
|
|
mUserAgentSet = true;
|
|
|
|
mHeaders.append(header.Name + ": " + header.Value);
|
|
}
|
|
|
|
void CurlHandleManager::Handle::appendHeaders (const HeadersList& headers)
|
|
{
|
|
for (const Header& header : headers)
|
|
appendHeader (header);
|
|
}
|
|
|
|
CurlHandleManager::Handle::Result CurlHandleManager::Handle::perform ()
|
|
{
|
|
if (!mUserAgentSet)
|
|
mHeaders.append ("User-Agent: " + mOwner->getUserAgent ());
|
|
|
|
CURLcode result = setOption (CURLOPT_HTTPHEADER, mHeaders.getCurlList ());
|
|
|
|
if (result != CURLE_OK)
|
|
return { result, std::string () };
|
|
|
|
char currentError[CURL_ERROR_SIZE] = {};
|
|
setOption(CURLOPT_ERRORBUFFER, currentError);
|
|
|
|
result = curl_easy_perform (mHandle);
|
|
|
|
mReuse = mReuse && result == CURLE_OK;
|
|
|
|
return { result, std::string (currentError) };
|
|
}
|
|
|
|
void CurlHandleManager::Handle::markKeepAlive ()
|
|
{
|
|
mReuse = true;
|
|
}
|
|
|
|
bool CurlHandleManager::Handle::isHandleFromCache () const noexcept
|
|
{
|
|
return mHandleFromCache;
|
|
}
|
|
|
|
unsigned CurlHandleManager::Handle::getHTTPCode () const noexcept
|
|
{
|
|
long code = 0;
|
|
|
|
if (CURLE_OK != curl_easy_getinfo(mHandle, CURLINFO_RESPONSE_CODE, &code))
|
|
return 0;
|
|
|
|
return code;
|
|
}
|
|
|
|
void CurlHandleManager::Handle::reset () noexcept
|
|
{
|
|
setOption (CURLOPT_COOKIELIST, nullptr);
|
|
setOption (CURLOPT_PROXY, nullptr);
|
|
setOption (CURLOPT_SSL_OPTIONS, 0);
|
|
|
|
mUserAgentSet = false;
|
|
}
|
|
|
|
CurlHandleManager::CurlHandleManager ()
|
|
{
|
|
std::ostringstream ss;
|
|
|
|
ss << "Audacity/" <<
|
|
AUDACITY_VERSION << "." <<
|
|
AUDACITY_RELEASE << "." <<
|
|
AUDACITY_REVISION <<
|
|
" (";
|
|
|
|
GetOSString (ss, wxPlatformInfo::Get ());
|
|
|
|
ss << ")";
|
|
|
|
mUserAgent = ss.str ();
|
|
}
|
|
|
|
CurlHandleManager::~CurlHandleManager ()
|
|
{
|
|
std::lock_guard<std::mutex> lock (mHandleCacheLock);
|
|
|
|
for (auto& cachedHandle : mHandleCache)
|
|
curl_easy_cleanup (cachedHandle.Handle);
|
|
}
|
|
|
|
void CurlHandleManager::setProxy (std::string proxy)
|
|
{
|
|
mProxy = std::move (proxy);
|
|
}
|
|
|
|
CurlHandleManager::Handle CurlHandleManager::getHandle (RequestVerb verb, const std::string& url)
|
|
{
|
|
Handle handle (this, getCurlHandleFromCache (verb, url), verb, url);
|
|
|
|
if (!mProxy.empty ())
|
|
{
|
|
handle.setOption (CURLOPT_PROXY, mProxy);
|
|
// If we use proxy, checking the CRL will likely break the SSL proxying
|
|
handle.setOption (CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE);
|
|
}
|
|
|
|
handle.setOption (CURLOPT_TCP_KEEPALIVE, 1L);
|
|
|
|
handle.setOption (CURLOPT_TCP_KEEPIDLE,
|
|
std::chrono::duration_cast<std::chrono::seconds> (KEEP_ALIVE_IDLE).count ()
|
|
);
|
|
|
|
handle.setOption (CURLOPT_TCP_KEEPINTVL,
|
|
std::chrono::duration_cast<std::chrono::seconds> (KEEP_ALIVE_PROBE).count ()
|
|
);
|
|
|
|
return handle;
|
|
}
|
|
|
|
std::string CurlHandleManager::getUserAgent () const
|
|
{
|
|
return mUserAgent;
|
|
}
|
|
|
|
CURL* CurlHandleManager::getCurlHandleFromCache (RequestVerb verb, const std::string& url)
|
|
{
|
|
std::lock_guard<std::mutex> lock (mHandleCacheLock);
|
|
|
|
cleanupHandlesCache ();
|
|
|
|
const std::string schemeAndDomain = GetSchemeAndDomain (url);
|
|
|
|
auto it = std::find_if (mHandleCache.begin (), mHandleCache.end (), [verb, schemeAndDomain](const CachedHandle& handle) {
|
|
return handle.Verb == verb && handle.SchemeAndDomain == schemeAndDomain;
|
|
});
|
|
|
|
if (it == mHandleCache.end ())
|
|
return nullptr;
|
|
|
|
CURL* handle = it->Handle;
|
|
|
|
mHandleCache.erase (it);
|
|
|
|
return handle;
|
|
}
|
|
|
|
void CurlHandleManager::cacheHandle (Handle& handle)
|
|
{
|
|
// Reset the state to the safe defaults
|
|
handle.reset ();
|
|
|
|
std::lock_guard<std::mutex> lock (mHandleCacheLock);
|
|
|
|
cleanupHandlesCache ();
|
|
|
|
mHandleCache.push_back ({
|
|
handle.mVerb,
|
|
GetSchemeAndDomain (handle.mUrl),
|
|
handle.mHandle,
|
|
RequestClock::now ()
|
|
});
|
|
}
|
|
|
|
void CurlHandleManager::cleanupHandlesCache ()
|
|
{
|
|
const RequestTimePoint timePoint = RequestClock::now ();
|
|
|
|
mHandleCache.erase (std::remove_if (mHandleCache.begin (), mHandleCache.end (), [timePoint](const CachedHandle& cachedHandle) {
|
|
return (timePoint - cachedHandle.RequestTime) >= KEEP_ALIVE_IDLE;
|
|
}), mHandleCache.end ());
|
|
}
|
|
|
|
std::string CurlHandleManager::GetSchemeAndDomain (const std::string& url)
|
|
{
|
|
const size_t schemeEndPosition = url.find ("://");
|
|
|
|
if (schemeEndPosition == std::string::npos) // Is url even valid?
|
|
return url;
|
|
|
|
const size_t domainStartPosition = schemeEndPosition + 3;
|
|
|
|
const size_t slashLocation = url.find ('/', domainStartPosition);
|
|
|
|
if (slashLocation == std::string::npos)
|
|
return url;
|
|
|
|
return url.substr (domainStartPosition, slashLocation - domainStartPosition);
|
|
}
|
|
|
|
}
|
|
}
|