Audacity 3.2.0
CurlHandleManager.cpp
Go to the documentation of this file.
1/*!********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file CurlHandleManager.cpp
6 @brief Define a class responsible for reuse of CURL handles.
7
8 Dmitry Vedenko
9 **********************************************************************/
10
11#include "CurlHandleManager.h"
12
13#include <algorithm>
14#include <sstream>
15
16#include <wx/platinfo.h>
17
18#include "Prefs.h"
19#include "CodeConversions.h"
20
21namespace audacity
22{
23namespace network_manager
24{
25namespace
26{
27
28void GetOSString (std::ostringstream& output, const wxPlatformInfo& platformInfo)
29{
30 const wxOperatingSystemId osID = platformInfo.GetOperatingSystemId ();
31
32 if (osID & wxOS_WINDOWS)
33 output << "Windows ";
34 else if (osID & wxOS_MAC)
35 output << "MacOS ";
36 else if (osID & wxOS_UNIX_LINUX)
37 output << "Linux ";
38 else if (osID & wxOS_UNIX_FREEBSD)
39 output << "FreeBSD ";
40 else if (osID & wxOS_UNIX_OPENBSD)
41 output << "OpenBSD ";
42 else
43 output << "Other ";
44
45 output <<
46 platformInfo.GetOSMajorVersion () <<
47 "_" <<
48 platformInfo.GetOSMinorVersion () <<
49 "_" <<
50 platformInfo.GetOSMicroVersion() <<
51 "; ";
52
53#if defined(__amd64__) || defined(__x86_64__) || defined(_M_X64)
54 output << "x64";
55#elif defined(__i386__) || defined(i386) || defined(_M_IX86) || defined(_X86_) || defined(__THW_INTEL)
56 output << "x86";
57#elif defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64)
58 output << "arm64";
59#elif defined(arm) || defined(__arm__) || defined(ARM) || defined(_ARM_)
60 output << "arm";
61#else
62 output << "unknown";
63#endif
64}
65
66BoolSetting EnableSSLValidationPref { "/CURL/EnableSSLValidation", true };
67StringSetting ProxyStringPref { "/CURL/Proxy", "" };
68
69struct CurlConfig final : private PrefsListener
70{
71 std::string Proxy;
72 bool SSLValidation { true };
73
74 void UpdatePrefs() override
75 {
76 SSLValidation = EnableSSLValidationPref.Read();
78 }
79};
80
82}
83
84constexpr std::chrono::milliseconds CurlHandleManager::KEEP_ALIVE_IDLE;
85constexpr std::chrono::milliseconds CurlHandleManager::KEEP_ALIVE_PROBE;
86
87CurlHandleManager::Handle::Handle(CurlHandleManager* owner, CURL* handle, RequestVerb verb, std::string url) noexcept
88 : mHandle (handle),
89 mOwner (owner),
90 mVerb (verb),
91 mUrl (std::move (url)),
92 mHandleFromCache (handle != nullptr)
93{
94 if (mHandle == nullptr)
95 mHandle = curl_easy_init ();
96
97 setOption (CURLOPT_URL, mUrl);
98
99 switch (verb)
100 {
102 setOption (CURLOPT_NOBODY, 1);
103 break;
104 case RequestVerb::Get:
105 // This is a default, no additional setup is needed
106 // We cache handles by the verb, so there is no need to
107 // reset the handle state
108 break;
110 setOption (CURLOPT_POST, 1);
111 break;
112 case RequestVerb::Put:
113 setOption (CURLOPT_UPLOAD, 1);
114 break;
116 setOption (CURLOPT_CUSTOMREQUEST, "DELETE");
117 break;
119 setOption(CURLOPT_UPLOAD, 1);
120 setOption(CURLOPT_CUSTOMREQUEST, "PATCH");
121 break;
122 }
123
124 setOption (CURLOPT_NOSIGNAL, 1L);
125
127 enableSSLValidation();
128 else
129 disableSSLValidation();
130
131 setOption (CURLOPT_ACCEPT_ENCODING, "");
132}
133
135{
136 *this = std::move (rhs);
137}
138
140{
141 if (mReuse)
142 mOwner->cacheHandle (*this);
143 else
144 curl_easy_cleanup (mHandle);
145}
146
148{
149 std::swap (mHandle, rhs.mHandle);
150 std::swap (mOwner, rhs.mOwner);
151
152 std::swap (mVerb, rhs.mVerb);
153
154 mUrl = std::move (rhs.mUrl);
155 mHeaders = std::move (rhs.mHeaders);
156
157 mReuse = rhs.mReuse;
158 rhs.mReuse = false;
159
160 return *this;
161}
162
163CURLcode CurlHandleManager::Handle::setOption (CURLoption option, const std::string& value) noexcept
164{
165 return setOption (option, value.c_str ());
166}
167
168CURLcode CurlHandleManager::Handle::appendCookie (const Cookie& cookie) noexcept
169{
170 return setOption(CURLOPT_COOKIE, "Set-Cookie: " + cookie.Name + "=" + cookie.Value);
171}
172
174{
175 for (const Cookie& cookie : cookies)
176 {
177 const CURLcode result = appendCookie (cookie);
178
179 if (result != CURLE_OK)
180 return result;
181 }
182
183 return CURLE_OK;
184}
185
187{
188 if (header.hasSameName ("User-Agent"))
189 mUserAgentSet = true;
190
191 mHeaders.append(header.Name + ": " + header.Value);
192}
193
195{
196 for (const Header& header : headers)
197 appendHeader (header);
198}
199
201{
202 if (!mUserAgentSet)
203 mHeaders.append ("User-Agent: " + mOwner->getUserAgent ());
204
205 CURLcode result = setOption (CURLOPT_HTTPHEADER, mHeaders.getCurlList ());
206
207 if (result != CURLE_OK)
208 return { result, std::string () };
209
210 char currentError[CURL_ERROR_SIZE] = {};
211 setOption(CURLOPT_ERRORBUFFER, currentError);
212
213 result = curl_easy_perform (mHandle);
214
215 mReuse = mReuse && result == CURLE_OK;
216
217 return { result, std::string (currentError) };
218}
219
221{
222 mReuse = true;
223}
224
226{
227 return mHandleFromCache;
228}
229
231{
232 long code = 0;
233
234 if (CURLE_OK != curl_easy_getinfo(mHandle, CURLINFO_RESPONSE_CODE, &code))
235 return 0;
236
237 return code;
238}
239
241{
242 setOption (CURLOPT_COOKIELIST, nullptr);
243 setOption (CURLOPT_PROXY, nullptr);
244 setOption (CURLOPT_SSL_OPTIONS, 0);
245
246 mUserAgentSet = false;
247}
248
250{
251 return mHandle;
252}
253
255{
256 setOption(CURLOPT_SSL_VERIFYPEER, 0L);
257 setOption(CURLOPT_SSL_VERIFYHOST, 0L);
258}
259
261{
262 setOption(CURLOPT_SSL_VERIFYPEER, 1L);
263 setOption(CURLOPT_SSL_VERIFYHOST, 2L);
264}
265
267{
268 std::ostringstream ss;
269
270 ss << "Audacity/" <<
271 AUDACITY_VERSION << "." <<
272 AUDACITY_RELEASE << "." <<
273 AUDACITY_REVISION <<
274 " (";
275
277
278 ss << ")";
279
280 mUserAgent = ss.str ();
281
283}
284
286{
287 std::lock_guard<std::mutex> lock (mHandleCacheLock);
288
289 for (auto& cachedHandle : mHandleCache)
290 curl_easy_cleanup (cachedHandle.Handle);
291}
292
293void CurlHandleManager::setProxy (std::string proxy)
294{
295 mProxy = std::move (proxy);
296}
297
299{
300 Handle handle (this, getCurlHandleFromCache (verb, url), verb, url);
301
302 if (!mProxy.empty ())
303 {
304 handle.setOption (CURLOPT_PROXY, mProxy);
305 // If we use proxy, checking the CRL will likely break the SSL proxying
306 handle.setOption (CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE);
307 }
308
309 handle.setOption (CURLOPT_TCP_KEEPALIVE, 1L);
310
311 handle.setOption (CURLOPT_TCP_KEEPIDLE,
312 std::chrono::duration_cast<std::chrono::seconds> (KEEP_ALIVE_IDLE).count ()
313 );
314
315 handle.setOption (CURLOPT_TCP_KEEPINTVL,
316 std::chrono::duration_cast<std::chrono::seconds> (KEEP_ALIVE_PROBE).count ()
317 );
318
319 return handle;
320}
321
323{
324 return mUserAgent;
325}
326
327CURL* CurlHandleManager::getCurlHandleFromCache (RequestVerb verb, const std::string& url)
328{
329 std::lock_guard<std::mutex> lock (mHandleCacheLock);
330
332
333 const std::string schemeAndDomain = GetSchemeAndDomain (url);
334
335 auto it = std::find_if (mHandleCache.begin (), mHandleCache.end (), [verb, schemeAndDomain](const CachedHandle& handle) {
336 return handle.Verb == verb && handle.SchemeAndDomain == schemeAndDomain;
337 });
338
339 if (it == mHandleCache.end ())
340 return nullptr;
341
342 CURL* handle = it->Handle;
343
344 mHandleCache.erase (it);
345
346 return handle;
347}
348
350{
351 // Reset the state to the safe defaults
352 handle.reset ();
353
354 std::lock_guard<std::mutex> lock (mHandleCacheLock);
355
357
358 mHandleCache.push_back ({
359 handle.mVerb,
360 GetSchemeAndDomain (handle.mUrl),
361 handle.mHandle,
362 RequestClock::now ()
363 });
364}
365
367{
368 const RequestTimePoint timePoint = RequestClock::now ();
369
370 mHandleCache.erase (std::remove_if (mHandleCache.begin (), mHandleCache.end (), [timePoint](const CachedHandle& cachedHandle) {
371 return (timePoint - cachedHandle.RequestTime) >= KEEP_ALIVE_IDLE;
372 }), mHandleCache.end ());
373}
374
375std::string CurlHandleManager::GetSchemeAndDomain (const std::string& url)
376{
377 const size_t schemeEndPosition = url.find ("://");
378
379 if (schemeEndPosition == std::string::npos) // Is url even valid?
380 return url;
381
382 const size_t domainStartPosition = schemeEndPosition + 3;
383
384 const size_t slashLocation = url.find ('/', domainStartPosition);
385
386 if (slashLocation == std::string::npos)
387 return url;
388
389 return url.substr (domainStartPosition, slashLocation - domainStartPosition);
390}
391
392}
393}
Declare functions to perform UTF-8 to std::wstring conversions.
Declare a class responsible for reuse of CURL handles.
This specialization of Setting for bool adds a Toggle method to negate the saved value.
Definition: Prefs.h:346
A listener notified of changes in preferences.
Definition: Prefs.h:652
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:207
Specialization of Setting for strings.
Definition: Prefs.h:370
Handle(CurlHandleManager *owner, CURL *handle, RequestVerb verb, std::string url) noexcept
CURLcode appendCookie(const Cookie &cookie) noexcept
CURLcode appendCookies(const CookiesList &cookie) noexcept
CURLcode setOption(CURLoption option, Args... value) noexcept
static constexpr std::chrono::milliseconds KEEP_ALIVE_PROBE
Handle getHandle(RequestVerb verb, const std::string &url)
CURL * getCurlHandleFromCache(RequestVerb verb, const std::string &url)
static std::string GetSchemeAndDomain(const std::string &url)
static constexpr std::chrono::milliseconds KEEP_ALIVE_IDLE
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:196
constexpr size_t npos(-1)
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:628
void GetOSString(std::ostringstream &output, const wxPlatformInfo &platformInfo)
std::string ToUTF8(const std::wstring &wstr)
bool hasSameName(const Header &header) const
Definition: HeadersList.cpp:66