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 "RequestPayload.h"
19
20#include "MemoryX.h"
21
22namespace audacity
23{
24namespace network_manager
25{
26namespace
27{
28
29static const std::map<CURLcode, NetworkError> errorsMap = {
30 { CURLE_OK, NetworkError::NoError },
31 { CURLE_URL_MALFORMAT, NetworkError::BadURL },
32 { CURLE_COULDNT_RESOLVE_PROXY, NetworkError::ProxyNotFound },
33 { CURLE_COULDNT_RESOLVE_HOST, NetworkError::HostNotFound },
34 { CURLE_COULDNT_CONNECT, NetworkError::ConnectionRefused },
35 { CURLE_HTTP_RETURNED_ERROR, NetworkError::HTTPError },
36 { CURLE_WRITE_ERROR, NetworkError::OperationCancelled },
37 { CURLE_READ_ERROR, NetworkError::OperationCancelled },
38 { CURLE_OPERATION_TIMEDOUT, NetworkError::Timeout },
39 { CURLE_RANGE_ERROR, NetworkError::HTTPError },
40 { CURLE_HTTP_POST_ERROR, NetworkError::HTTPError },
41 { CURLE_SSL_CONNECT_ERROR, NetworkError::SSLHandshakeFailed },
42 { CURLE_ABORTED_BY_CALLBACK, NetworkError::OperationCancelled },
43 { CURLE_TOO_MANY_REDIRECTS, NetworkError::OperationCancelled },
44 { CURLE_PEER_FAILED_VERIFICATION, NetworkError::SSLHandshakeFailed },
45 { CURLE_GOT_NOTHING, NetworkError::RemoteHostClosed },
46 { CURLE_SSL_ENGINE_NOTFOUND, NetworkError::SSLHandshakeFailed },
47 { CURLE_SSL_ENGINE_SETFAILED, NetworkError::SSLHandshakeFailed },
48 { CURLE_SEND_ERROR, NetworkError::RemoteHostClosed },
49 { CURLE_RECV_ERROR, NetworkError::RemoteHostClosed },
50 { CURLE_SSL_CERTPROBLEM, NetworkError::SSLHandshakeFailed },
51 { CURLE_SSL_CIPHER, NetworkError::SSLHandshakeFailed },
52 { CURLE_SSL_CACERT, NetworkError::SSLHandshakeFailed },
53 { CURLE_USE_SSL_FAILED, NetworkError::SSLHandshakeFailed },
54 { CURLE_SSL_ENGINE_INITFAILED, NetworkError::SSLHandshakeFailed },
55 { CURLE_SSL_CACERT_BADFILE, NetworkError::SSLHandshakeFailed },
56 { CURLE_SSL_SHUTDOWN_FAILED, NetworkError::SSLHandshakeFailed },
57 { CURLE_SSL_CRL_BADFILE, NetworkError::SSLHandshakeFailed },
58 { CURLE_SSL_ISSUER_ERROR, NetworkError::SSLHandshakeFailed },
59 { CURLE_CHUNK_FAILED, NetworkError::HTTPError },
60 { CURLE_NO_CONNECTION_AVAILABLE, NetworkError::ConnectionFailed },
61 { CURLE_SSL_PINNEDPUBKEYNOTMATCH, NetworkError::SSLHandshakeFailed },
62 { CURLE_SSL_INVALIDCERTSTATUS, NetworkError::SSLHandshakeFailed },
63 { CURLE_PARTIAL_FILE, NetworkError::RemoteHostClosed }
64};
65
66size_t DataStreamRead (char* ptr, size_t size, size_t nmemb, RequestPayloadStream* stream)
67{
68 return stream->Read (ptr, size * nmemb);
69}
70
71int DataStreamSeek(RequestPayloadStream* stream, curl_off_t offs, int origin)
72{
73 const auto direction =
77
78 return stream->Seek(offs, direction) ? CURL_SEEKFUNC_OK :
79 CURL_SEEKFUNC_FAIL;
80}
81
82size_t MimePartRead(char* ptr, size_t size, size_t nmemb, MultipartData::Part* stream)
83{
84 return stream->Read(ptr, size * nmemb);
85}
86
87int MimePartSeek(MultipartData::Part* stream, curl_off_t offs, int origin) noexcept
88{
89 return stream->Seek(offs, origin) ? CURL_SEEKFUNC_OK : CURL_SEEKFUNC_FAIL;
90}
91
92}
93
94CurlResponse::CurlResponse (RequestVerb verb, const Request& request, CurlHandleManager* handleManager) noexcept
95 : mVerb(verb),
96 mRequest(request),
97 mHandleManager (handleManager)
98{
99}
100
101bool CurlResponse::isFinished () const noexcept
102{
103 std::lock_guard<std::recursive_mutex> lock (mStatusMutex);
104 return mRequestFinished;
105}
106
107unsigned CurlResponse::getHTTPCode () const noexcept
108{
109 std::lock_guard<std::recursive_mutex> lock (mStatusMutex);
110 return mHttpCode;
111}
112
114{
115 std::lock_guard<std::recursive_mutex> lock (mStatusMutex);
116 return mNetworkError;
117}
118
120{
121 std::lock_guard<std::recursive_mutex> lock (mStatusMutex);
122 return mErrorString;
123}
124
125bool CurlResponse::headersReceived () const noexcept
126{
127 std::lock_guard<std::recursive_mutex> lock (mStatusMutex);
128 return mHeadersReceived;
129}
130
131bool CurlResponse::hasHeader (const std::string& headerName) const noexcept
132{
133 std::lock_guard<std::mutex> lock (mHeadersMutex);
134 return mResponseHeaders.hasHeader (headerName);
135}
136
137std::string CurlResponse::getHeader (const std::string& headerName) const
138{
139 std::lock_guard<std::mutex> lock (mHeadersMutex);
140 return mResponseHeaders.getHeaderValue (headerName);
141}
142
143const HeadersList& CurlResponse::getHeaders () const noexcept
144{
145 std::lock_guard<std::mutex> lock (mHeadersMutex);
146 return mResponseHeaders;
147}
148
149const CookiesList& CurlResponse::getCookies () const noexcept
150{
151 std::lock_guard<std::mutex> lock (mHeadersMutex);
152 return mResponseCookies;
153}
154
155const Request& CurlResponse::getRequest () const noexcept
156{
157 return mRequest;
158}
159
160std::string CurlResponse::getURL () const
161{
162 return mRequest.getURL ();
163}
164
165void CurlResponse::abort () noexcept
166{
167 std::lock_guard<std::recursive_mutex> lock (mStatusMutex);
168 mAbortRequested = true;
169}
170
172{
173 std::lock_guard<std::mutex> lock (mCallbackMutex);
174
175 mOnDataReceivedCallback = std::move (callback);
176
179}
180
182{
183 std::lock_guard<std::mutex> callbackLock (mCallbackMutex);
184
185 mRequestFinishedCallback = std::move (callback);
186
187 std::lock_guard<std::recursive_mutex> statusLock (mStatusMutex);
188
191}
192
194{
195 std::lock_guard<std::mutex> callbackLock(mCallbackMutex);
196 mDownloadProgressCallback = std::move(callback);
197}
198
200{
201 std::lock_guard<std::mutex> callbackLock(mCallbackMutex);
202 mUploadProgressCallback = std::move(callback);
203}
204
205uint64_t CurlResponse::getBytesAvailable () const noexcept
206{
207 std::lock_guard<std::mutex> lock (mDataBufferMutex);
208 return mDataBuffer.size ();
209}
210
211uint64_t CurlResponse::readData (void* buffer, uint64_t maxBytesCount)
212{
213 if (buffer == nullptr || maxBytesCount == 0)
214 return 0;
215
216 std::lock_guard<std::mutex> lock (mDataBufferMutex);
217
218 if (mDataBuffer.empty ())
219 return 0;
220
221 maxBytesCount = std::min<uint64_t> (maxBytesCount, mDataBuffer.size ());
222
223 const auto begin = mDataBuffer.begin ();
224 const auto end = begin + maxBytesCount;
225
226 std::copy (begin, end, static_cast<uint8_t*> (buffer));
227
228 mDataBuffer.erase (begin, end);
229
230 return maxBytesCount;
231}
232
234{
235 mPayload = std::move(payload);
236}
237
238void CurlResponse::setForm(std::unique_ptr<MultipartData> form)
239{
240 mForm = std::move(form);
241}
242
244{
246
247 handle.setOption (CURLOPT_WRITEFUNCTION, WriteCallback);
248 handle.setOption (CURLOPT_WRITEDATA, this);
249
250 handle.setOption (CURLOPT_HEADERFUNCTION, HeaderCallback);
251 handle.setOption (CURLOPT_HEADERDATA, this);
252
253 handle.setOption (CURLOPT_XFERINFOFUNCTION, CurlProgressCallback);
254 handle.setOption (CURLOPT_XFERINFODATA, this);
255
256 handle.setOption (CURLOPT_FOLLOWLOCATION, mRequest.getMaxRedirects () == 0 ? 0 : 1);
257 handle.setOption (CURLOPT_MAXREDIRS, mRequest.getMaxRedirects ());
258
259 handle.setOption (CURLOPT_NOPROGRESS, 0L);
260
261 handle.setOption (CURLOPT_CONNECTTIMEOUT_MS,
262 std::chrono::duration_cast<std::chrono::milliseconds> (mRequest.getTimeout()).count ()
263 );
264
266
267 curl_mime* mimeList = nullptr;
268
269 if (mForm != nullptr)
270 {
271 mimeList = curl_mime_init(handle.getCurlHandle());
272
273 for (size_t i = 0; i < mForm->GetPartsCount(); ++i)
274 {
275 auto part = mForm->GetPart(i);
276
277 curl_mimepart* curlPart = curl_mime_addpart(mimeList);
278
279 const auto& headers = part->GetHeaders();
280
281 if (headers.getHeadersCount() > 0)
282 {
283 curl_slist* partHeaders = nullptr;
284
285 for (auto header : headers)
286 {
287 partHeaders = curl_slist_append(
288 partHeaders, (header.Name + ": " + header.Value).c_str());
289 }
290
291 curl_mime_headers(curlPart, partHeaders, 1);
292 }
293
294 curl_mime_data_cb(
295 curlPart, part->GetSize(), curl_read_callback(MimePartRead),
296 curl_seek_callback(MimePartSeek), nullptr, part);
297 }
298
299 curl_easy_setopt(handle.getCurlHandle(), CURLOPT_MIMEPOST, mimeList);
300 }
301 else if (mPayload != nullptr && mPayload->HasData())
302 {
303 if (const auto payloadSize = mPayload->GetDataSize(); payloadSize > 0)
304 {
305 handle.appendHeader({ "Transfer-Encoding", std::string() });
306 handle.appendHeader(
307 { "Content-Length", std::to_string(payloadSize) });
308
310 handle.setOption(CURLOPT_POSTFIELDSIZE_LARGE, payloadSize);
311 else
312 handle.setOption(CURLOPT_INFILESIZE_LARGE, payloadSize);
313 }
314
315 handle.setOption (CURLOPT_READFUNCTION, DataStreamRead);
316 handle.setOption (CURLOPT_READDATA, mPayload.get());
317
318 handle.setOption (CURLOPT_SEEKFUNCTION, DataStreamSeek);
319 handle.setOption (CURLOPT_SEEKDATA, mPayload.get());
320 }
322 {
323 handle.setOption (CURLOPT_POSTFIELDS, "");
324 handle.setOption (CURLOPT_POSTFIELDSIZE, 0);
325 }
326
327 auto cleanupMime = finally(
328 [mimeList]() {
329 if (mimeList != nullptr)
330 curl_mime_free(mimeList);
331 });
332
334
335 mCurrentHandle = &handle;
336 const auto result = handle.perform ();
337 mCurrentHandle = nullptr;
338
339 {
340 std::lock_guard<std::recursive_mutex> lock (mStatusMutex);
341
342 if (result.Code != CURLE_OK)
343 {
344 const auto it = errorsMap.find (result.Code);
345
346 mNetworkError = it != errorsMap.end () ? it->second : NetworkError::UnknownError;
347 mErrorString = result.Message;
348 }
349 else
350 {
351 if (mHttpCode == 0)
352 mHttpCode = handle.getHTTPCode ();
353 if (mHttpCode >= 400)
355 }
356
357 mRequestFinished = true;
358 }
359
360 std::lock_guard<std::mutex> lock (mCallbackMutex);
361
364
369}
370
371
372size_t CurlResponse::WriteCallback (const uint8_t* ptr, size_t size, size_t nmemb, CurlResponse* request) noexcept
373{
374 {
375 std::lock_guard<std::recursive_mutex> lock (request->mStatusMutex);
376
377 if (request->mAbortRequested)
378 return 0;
379
380 if (!request->mHeadersReceived)
381 {
382 request->mHeadersReceived = true;
383
384 // WriteCallback is called by the handle
385 assert (request->mCurrentHandle != nullptr);
386
387 if (request->mCurrentHandle != nullptr)
388 request->mHttpCode = request->mCurrentHandle->getHTTPCode ();
389 }
390 }
391
392 size *= nmemb;
393
394 {
395 std::lock_guard<std::mutex> lock (request->mDataBufferMutex);
396 request->mDataBuffer.insert (request->mDataBuffer.end (), ptr, ptr + size);
397 }
398
399 std::lock_guard<std::mutex> lock (request->mCallbackMutex);
400
401 if (request->mOnDataReceivedCallback)
402 request->mOnDataReceivedCallback (request);
403
404 return size;
405}
406
407size_t CurlResponse::HeaderCallback (const char* buffer, size_t size, size_t nitems, CurlResponse* request) noexcept
408{
409 {
410 std::lock_guard<std::recursive_mutex> lock (request->mStatusMutex);
411
412 if (request->mAbortRequested)
413 return 0;
414
415 // HeaderCallback is called by the handle
416 assert (request->mCurrentHandle != nullptr);
417
418 if (request->mCurrentHandle != nullptr)
419 request->mHttpCode = request->mCurrentHandle->getHTTPCode ();
420 }
421
422 size = size * nitems;
423
424 if (size < 2)
425 return 0;
426
427 const Header header = Header::Parse (std::string (buffer, size - 2));
428
429 std::lock_guard<std::mutex> lock (request->mHeadersMutex);
430
431 if (header.hasSameName ("Set-Cookie"))
432 {
433 request->mResponseCookies.addCookie (Cookie::Parse (header.Value));
434 }
435 else
436 {
437 if (header.hasSameName ("Keep-Alive"))
438 request->mCurrentHandle->markKeepAlive ();
439
440 request->mResponseHeaders.addHeader (header);
441 }
442
443 return size;
444}
445
447 CurlResponse* clientp, curl_off_t dltotal, curl_off_t dlnow,
448 curl_off_t ultotal, curl_off_t ulnow) noexcept
449{
450 {
451 std::lock_guard<std::recursive_mutex> lock(clientp->mStatusMutex);
452
453 if (clientp->mAbortRequested)
454 return -1;
455 }
456
457 std::lock_guard<std::mutex> callbackLock(clientp->mCallbackMutex);
458
459 if (dltotal > 0 && clientp->mDownloadProgressCallback)
460 clientp->mDownloadProgressCallback(dlnow, dltotal);
461
462 if (ultotal > 0 && clientp->mUploadProgressCallback)
463 clientp->mUploadProgressCallback(ulnow, ultotal);
464
465 return CURLE_OK;
466}
467
468}
469}
Declare an implementation of IResponse using libcurl.
Declare a class for constructing HTTP requests.
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.
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:108
NetworkError getError() const noexcept override
void setPayload(RequestPayloadStreamPtr payload)
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:85
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:118
std::function< void(IResponse *)> RequestCallback
Definition: IResponse.h:115
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
virtual bool Seek(int64_t offset, SeekDirection direction)=0
returns true on success
virtual int64_t Read(void *buffer, int64_t size)=0
returns number of bytes read
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(RequestPayloadStream *stream, curl_off_t offs, int origin)
size_t DataStreamRead(char *ptr, size_t size, size_t nmemb, RequestPayloadStream *stream)
static const std::map< CURLcode, NetworkError > errorsMap
std::shared_ptr< RequestPayloadStream > RequestPayloadStreamPtr
Definition: CurlResponse.h:33
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
const char * begin(const char *str) noexcept
Definition: StringUtils.h:101
void copy(const T *src, T *dst, int32_t n)
Definition: VectorOps.h:40
static Header Parse(const std::string &header)
Definition: HeadersList.cpp:81
bool hasSameName(const Header &header) const
Definition: HeadersList.cpp:66