Audacity 3.2.0
CurlResponse.cpp
Go to the documentation of this file.
1/*!********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file CurlResponse.cpp
6 @brief Define an implementation of IResponse using libcurl.
7
8 Dmitry Vedenko
9 **********************************************************************/
10
11#include "CurlResponse.h"
12
13#include <cassert>
14#include <map>
15#include <algorithm>
16
17#include "MultipartData.h"
18#include "MemoryX.h"
19
20namespace audacity
21{
22namespace network_manager
23{
24namespace
25{
26
27static const std::map<CURLcode, NetworkError> errorsMap = {
28 { CURLE_OK, NetworkError::NoError },
29 { CURLE_URL_MALFORMAT, NetworkError::BadURL },
30 { CURLE_COULDNT_RESOLVE_PROXY, NetworkError::ProxyNotFound },
31 { CURLE_COULDNT_RESOLVE_HOST, NetworkError::HostNotFound },
32 { CURLE_COULDNT_CONNECT, NetworkError::ConnectionRefused },
33 { CURLE_HTTP_RETURNED_ERROR, NetworkError::HTTPError },
34 { CURLE_WRITE_ERROR, NetworkError::OperationCancelled },
35 { CURLE_READ_ERROR, NetworkError::OperationCancelled },
36 { CURLE_OPERATION_TIMEDOUT, NetworkError::Timeout },
37 { CURLE_RANGE_ERROR, NetworkError::HTTPError },
38 { CURLE_HTTP_POST_ERROR, NetworkError::HTTPError },
39 { CURLE_SSL_CONNECT_ERROR, NetworkError::SSLHandshakeFailed },
40 { CURLE_ABORTED_BY_CALLBACK, NetworkError::OperationCancelled },
41 { CURLE_TOO_MANY_REDIRECTS, NetworkError::OperationCancelled },
42 { CURLE_PEER_FAILED_VERIFICATION, NetworkError::SSLHandshakeFailed },
43 { CURLE_GOT_NOTHING, NetworkError::RemoteHostClosed },
44 { CURLE_SSL_ENGINE_NOTFOUND, NetworkError::SSLHandshakeFailed },
45 { CURLE_SSL_ENGINE_SETFAILED, NetworkError::SSLHandshakeFailed },
46 { CURLE_SEND_ERROR, NetworkError::RemoteHostClosed },
47 { CURLE_RECV_ERROR, NetworkError::RemoteHostClosed },
48 { CURLE_SSL_CERTPROBLEM, NetworkError::SSLHandshakeFailed },
49 { CURLE_SSL_CIPHER, NetworkError::SSLHandshakeFailed },
50 { CURLE_SSL_CACERT, NetworkError::SSLHandshakeFailed },
51 { CURLE_USE_SSL_FAILED, NetworkError::SSLHandshakeFailed },
52 { CURLE_SSL_ENGINE_INITFAILED, NetworkError::SSLHandshakeFailed },
53 { CURLE_SSL_CACERT_BADFILE, NetworkError::SSLHandshakeFailed },
54 { CURLE_SSL_SHUTDOWN_FAILED, NetworkError::SSLHandshakeFailed },
55 { CURLE_SSL_CRL_BADFILE, NetworkError::SSLHandshakeFailed },
56 { CURLE_SSL_ISSUER_ERROR, NetworkError::SSLHandshakeFailed },
57 { CURLE_CHUNK_FAILED, NetworkError::HTTPError },
58 { CURLE_NO_CONNECTION_AVAILABLE, NetworkError::ConnectionFailed },
59 { CURLE_SSL_PINNEDPUBKEYNOTMATCH, NetworkError::SSLHandshakeFailed },
60 { CURLE_SSL_INVALIDCERTSTATUS, NetworkError::SSLHandshakeFailed },
61 { CURLE_PARTIAL_FILE, NetworkError::RemoteHostClosed }
62};
63
64struct DataStream final
65{
66 const char* Buffer;
67 size_t Size;
68
69 size_t Offset { 0 };
70};
71
72size_t DataStreamRead (char* ptr, size_t size, size_t nmemb, DataStream* stream) noexcept
73{
74 size = std::min (size * nmemb, stream->Size - stream->Offset);
75
76 const char* start = stream->Buffer + stream->Offset;
77 const char* end = start + size;
78
79 std::copy (start, end, ptr);
80
81 stream->Offset += size;
82
83 return size;
84}
85
86int DataStreamSeek (DataStream* stream, curl_off_t offs, int origin) noexcept
87{
88 int64_t offset = offs;
89
90 switch (origin)
91 {
92 case SEEK_CUR:
93 offset += stream->Offset;
94 break;
95 case SEEK_END:
96 offset += stream->Size;
97 break;
98 }
99
100 if (offset < 0 || offset >= stream->Size)
101 return CURL_SEEKFUNC_FAIL;
102
103 stream->Offset = offset;
104
105 return CURL_SEEKFUNC_OK;
106}
107
108size_t MimePartRead(char* ptr, size_t size, size_t nmemb, MultipartData::Part* stream)
109{
110 return stream->Read(ptr, size * nmemb);
111}
112
113int MimePartSeek(MultipartData::Part* stream, curl_off_t offs, int origin) noexcept
114{
115 return stream->Seek(offs, origin) ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_FAIL;
116}
117
118}
119
120CurlResponse::CurlResponse (RequestVerb verb, const Request& request, CurlHandleManager* handleManager) noexcept
121 : mVerb(verb),
122 mRequest(request),
123 mHandleManager (handleManager)
124{
125}
126
127bool CurlResponse::isFinished () const noexcept
128{
129 std::lock_guard<std::recursive_mutex> lock (mStatusMutex);
130 return mRequestFinished;
131}
132
133unsigned CurlResponse::getHTTPCode () const noexcept
134{
135 std::lock_guard<std::recursive_mutex> lock (mStatusMutex);
136 return mHttpCode;
137}
138
140{
141 std::lock_guard<std::recursive_mutex> lock (mStatusMutex);
142 return mNetworkError;
143}
144
146{
147 std::lock_guard<std::recursive_mutex> lock (mStatusMutex);
148 return mErrorString;
149}
150
151bool CurlResponse::headersReceived () const noexcept
152{
153 std::lock_guard<std::recursive_mutex> lock (mStatusMutex);
154 return mHeadersReceived;
155}
156
157bool CurlResponse::hasHeader (const std::string& headerName) const noexcept
158{
159 std::lock_guard<std::mutex> lock (mHeadersMutex);
160 return mResponseHeaders.hasHeader (headerName);
161}
162
163std::string CurlResponse::getHeader (const std::string& headerName) const
164{
165 std::lock_guard<std::mutex> lock (mHeadersMutex);
166 return mResponseHeaders.getHeaderValue (headerName);
167}
168
169const HeadersList& CurlResponse::getHeaders () const noexcept
170{
171 std::lock_guard<std::mutex> lock (mHeadersMutex);
172 return mResponseHeaders;
173}
174
175const CookiesList& CurlResponse::getCookies () const noexcept
176{
177 std::lock_guard<std::mutex> lock (mHeadersMutex);
178 return mResponseCookies;
179}
180
181const Request& CurlResponse::getRequest () const noexcept
182{
183 return mRequest;
184}
185
186std::string CurlResponse::getURL () const
187{
188 return mRequest.getURL ();
189}
190
191void CurlResponse::abort () noexcept
192{
193 std::lock_guard<std::recursive_mutex> lock (mStatusMutex);
194 mAbortRequested = true;
195}
196
198{
199 std::lock_guard<std::mutex> lock (mCallbackMutex);
200
201 mOnDataReceivedCallback = std::move (callback);
202
205}
206
208{
209 std::lock_guard<std::mutex> callbackLock (mCallbackMutex);
210
211 mRequestFinishedCallback = std::move (callback);
212
213 std::lock_guard<std::recursive_mutex> statusLock (mStatusMutex);
214
217}
218
220{
221 std::lock_guard<std::mutex> callbackLock(mCallbackMutex);
222 mDownloadProgressCallback = std::move(callback);
223}
224
226{
227 std::lock_guard<std::mutex> callbackLock(mCallbackMutex);
228 mUploadProgressCallback = std::move(callback);
229}
230
231uint64_t CurlResponse::getBytesAvailable () const noexcept
232{
233 std::lock_guard<std::mutex> lock (mDataBufferMutex);
234 return mDataBuffer.size ();
235}
236
237uint64_t CurlResponse::readData (void* buffer, uint64_t maxBytesCount)
238{
239 if (buffer == nullptr || maxBytesCount == 0)
240 return 0;
241
242 std::lock_guard<std::mutex> lock (mDataBufferMutex);
243
244 if (mDataBuffer.empty ())
245 return 0;
246
247 maxBytesCount = std::min<uint64_t> (maxBytesCount, mDataBuffer.size ());
248
249 const auto begin = mDataBuffer.begin ();
250 const auto end = begin + maxBytesCount;
251
252 std::copy (begin, end, static_cast<uint8_t*> (buffer));
253
254 mDataBuffer.erase (begin, end);
255
256 return maxBytesCount;
257}
258
259void CurlResponse::setPayload(const void* ptr, size_t size)
260{
261 mPayload = ptr;
263}
264
265void CurlResponse::setForm(std::unique_ptr<MultipartData> form)
266{
267 mForm = std::move(form);
268}
269
271{
273
274 handle.setOption (CURLOPT_WRITEFUNCTION, WriteCallback);
275 handle.setOption (CURLOPT_WRITEDATA, this);
276
277 handle.setOption (CURLOPT_HEADERFUNCTION, HeaderCallback);
278 handle.setOption (CURLOPT_HEADERDATA, this);
279
280 handle.setOption (CURLOPT_XFERINFOFUNCTION, CurlProgressCallback);
281 handle.setOption (CURLOPT_XFERINFODATA, this);
282
283 handle.setOption (CURLOPT_FOLLOWLOCATION, mRequest.getMaxRedirects () == 0 ? 0 : 1);
284 handle.setOption (CURLOPT_MAXREDIRS, mRequest.getMaxRedirects ());
285
286 handle.setOption (CURLOPT_NOPROGRESS, 0L);
287
288 handle.setOption (CURLOPT_CONNECTTIMEOUT_MS,
289 std::chrono::duration_cast<std::chrono::milliseconds> (mRequest.getTimeout()).count ()
290 );
291
293
294 DataStream ds { reinterpret_cast<const char*>(mPayload), mPayloadSize };
295 curl_mime* mimeList = nullptr;
296
297 if (mForm != nullptr)
298 {
299 mimeList = curl_mime_init(handle.getCurlHandle());
300
301 for (size_t i = 0; i < mForm->GetPartsCount(); ++i)
302 {
303 auto part = mForm->GetPart(i);
304
305 curl_mimepart* curlPart = curl_mime_addpart(mimeList);
306
307 const auto& headers = part->GetHeaders();
308
309 if (headers.getHeadersCount() > 0)
310 {
311 curl_slist* partHeaders = nullptr;
312
313 for (auto header : headers)
314 {
315 partHeaders = curl_slist_append(
316 partHeaders, (header.Name + ": " + header.Value).c_str());
317 }
318
319 curl_mime_headers(curlPart, partHeaders, 1);
320 }
321
322 curl_mime_data_cb(
323 curlPart, part->GetSize(), curl_read_callback(MimePartRead),
324 curl_seek_callback(MimePartSeek), nullptr, part);
325 }
326
327 curl_easy_setopt(handle.getCurlHandle(), CURLOPT_MIMEPOST, mimeList);
328 }
329 else if (mPayload != nullptr && mPayloadSize != 0)
330 {
331 handle.appendHeader ({ "Transfer-Encoding", std::string () });
332 handle.appendHeader({ "Content-Length", std::to_string(mPayloadSize) });
333
335 handle.setOption(CURLOPT_POSTFIELDSIZE_LARGE, mPayloadSize);
336 else
337 handle.setOption(CURLOPT_INFILESIZE_LARGE, mPayloadSize);
338
339 handle.setOption (CURLOPT_READFUNCTION, DataStreamRead);
340 handle.setOption (CURLOPT_READDATA, &ds);
341
342 handle.setOption (CURLOPT_SEEKFUNCTION, DataStreamSeek);
343 handle.setOption (CURLOPT_SEEKDATA, &ds);
344 }
346 {
347 handle.setOption (CURLOPT_POSTFIELDS, "");
348 handle.setOption (CURLOPT_POSTFIELDSIZE, 0);
349 }
350
351 auto cleanupMime = finally(
352 [mimeList]() {
353 if (mimeList != nullptr)
354 curl_mime_free(mimeList);
355 });
356
358
359 mCurrentHandle = &handle;
360 const auto result = handle.perform ();
361 mCurrentHandle = nullptr;
362
363 {
364 std::lock_guard<std::recursive_mutex> lock (mStatusMutex);
365
366 if (result.Code != CURLE_OK)
367 {
368 const auto it = errorsMap.find (result.Code);
369
370 mNetworkError = it != errorsMap.end () ? it->second : NetworkError::UnknownError;
371 mErrorString = result.Message;
372 }
373 else
374 {
375 if (mHttpCode == 0)
376 mHttpCode = handle.getHTTPCode ();
377 }
378
379 mRequestFinished = true;
380 }
381
382 std::lock_guard<std::mutex> lock (mCallbackMutex);
383
386
391}
392
393
394size_t CurlResponse::WriteCallback (const uint8_t* ptr, size_t size, size_t nmemb, CurlResponse* request) noexcept
395{
396 {
397 std::lock_guard<std::recursive_mutex> lock (request->mStatusMutex);
398
399 if (request->mAbortRequested)
400 return 0;
401
402 if (!request->mHeadersReceived)
403 {
404 request->mHeadersReceived = true;
405
406 // WriteCallback is called by the handle
407 assert (request->mCurrentHandle != nullptr);
408
409 if (request->mCurrentHandle != nullptr)
410 request->mHttpCode = request->mCurrentHandle->getHTTPCode ();
411 }
412 }
413
414 size *= nmemb;
415
416 {
417 std::lock_guard<std::mutex> lock (request->mDataBufferMutex);
418 request->mDataBuffer.insert (request->mDataBuffer.end (), ptr, ptr + size);
419 }
420
421 std::lock_guard<std::mutex> lock (request->mCallbackMutex);
422
423 if (request->mOnDataReceivedCallback)
424 request->mOnDataReceivedCallback (request);
425
426 return size;
427}
428
429size_t CurlResponse::HeaderCallback (const char* buffer, size_t size, size_t nitems, CurlResponse* request) noexcept
430{
431 {
432 std::lock_guard<std::recursive_mutex> lock (request->mStatusMutex);
433
434 if (request->mAbortRequested)
435 return 0;
436
437 // HeaderCallback is called by the handle
438 assert (request->mCurrentHandle != nullptr);
439
440 if (request->mCurrentHandle != nullptr)
441 request->mHttpCode = request->mCurrentHandle->getHTTPCode ();
442 }
443
444 size = size * nitems;
445
446 if (size < 2)
447 return 0;
448
449 const Header header = Header::Parse (std::string (buffer, size - 2));
450
451 std::lock_guard<std::mutex> lock (request->mHeadersMutex);
452
453 if (header.hasSameName ("Set-Cookie"))
454 {
455 request->mResponseCookies.addCookie (Cookie::Parse (header.Value));
456 }
457 else
458 {
459 if (header.hasSameName ("Keep-Alive"))
460 request->mCurrentHandle->markKeepAlive ();
461
462 request->mResponseHeaders.addHeader (header);
463 }
464
465 return size;
466}
467
469 CurlResponse* clientp, curl_off_t dltotal, curl_off_t dlnow,
470 curl_off_t ultotal, curl_off_t ulnow) noexcept
471{
472 {
473 std::lock_guard<std::recursive_mutex> lock(clientp->mStatusMutex);
474
475 if (clientp->mAbortRequested)
476 return -1;
477 }
478
479 std::lock_guard<std::mutex> callbackLock(clientp->mCallbackMutex);
480
481 if (dltotal > 0 && clientp->mDownloadProgressCallback)
482 clientp->mDownloadProgressCallback(dlnow, dltotal);
483
484 if (ultotal > 0 && clientp->mUploadProgressCallback)
485 clientp->mUploadProgressCallback(ulnow, ultotal);
486
487 return CURLE_OK;
488}
489
490}
491}
int min(int a, int b)
Declare an implementation of IResponse using libcurl.
CURLcode appendCookies(const CookiesList &cookie) noexcept
CURLcode setOption(CURLoption option, Args... value) noexcept
Handle getHandle(RequestVerb verb, const std::string &url)
virtual void setUploadProgressCallback(ProgressCallback callback) override
Set the upload progress callback.
void setForm(std::unique_ptr< MultipartData > form)
virtual void setDownloadProgressCallback(ProgressCallback callback) override
Set the download progress callback.
void setPayload(const void *ptr, size_t size)
static size_t WriteCallback(const uint8_t *ptr, size_t size, size_t nmemb, CurlResponse *userdata) noexcept
uint64_t getBytesAvailable() const noexcept override
const CookiesList & getCookies() const noexcept override
std::unique_ptr< MultipartData > mForm
Definition: CurlResponse.h:106
NetworkError getError() const noexcept override
const Request & getRequest() const noexcept override
static int CurlProgressCallback(CurlResponse *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) noexcept
bool isFinished() const noexcept override
void setOnDataReceivedCallback(RequestCallback callback) override
unsigned getHTTPCode() const noexcept override
CurlResponse(RequestVerb verb, const Request &request, CurlHandleManager *handleManager) noexcept
std::string getErrorString() const override
bool hasHeader(const std::string &headerName) const noexcept override
std::string getHeader(const std::string &headerName) const override
const HeadersList & getHeaders() const noexcept override
static size_t HeaderCallback(const char *buffer, size_t size, size_t nitems, CurlResponse *userdata) noexcept
CurlHandleManager::Handle * mCurrentHandle
Definition: CurlResponse.h:82
void setRequestFinishedCallback(RequestCallback callback) override
uint64_t readData(void *buffer, uint64_t maxBytesCount) override
bool headersReceived() const noexcept override
std::string getURL() const override
std::string getHeaderValue(const std::string &headerName) const
std::function< void(int64_t current, int64_t expected)> ProgressCallback
Definition: IResponse.h:55
std::function< void(IResponse *)> RequestCallback
Definition: IResponse.h:52
virtual size_t Read(void *buffer, size_t maxBytes)=0
Timeout getTimeout() const noexcept
Definition: Request.cpp:98
const HeadersList & getHeaders() const noexcept
Definition: Request.cpp:58
const CookiesList & getCookies() noexcept
Definition: Request.cpp:75
size_t getMaxRedirects() const noexcept
Definition: Request.cpp:87
const std::string & getURL() const noexcept
Definition: Request.cpp:41
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150
size_t MimePartRead(char *ptr, size_t size, size_t nmemb, MultipartData::Part *stream)
int MimePartSeek(MultipartData::Part *stream, curl_off_t offs, int origin) noexcept
int DataStreamSeek(DataStream *stream, curl_off_t offs, int origin) noexcept
size_t DataStreamRead(char *ptr, size_t size, size_t nmemb, DataStream *stream) noexcept
static const std::map< CURLcode, NetworkError > errorsMap
void copy(const T *src, T *dst, int32_t n)
Definition: VectorOps.h:40
static Header Parse(const std::string &header)
Definition: HeadersList.cpp:79
bool hasSameName(const Header &header) const
Definition: HeadersList.cpp:64