1#include "http.h"
2
3#include <base/log.h>
4#include <base/math.h>
5#include <base/system.h>
6#include <engine/external/json-parser/json.h>
7#include <engine/shared/config.h>
8#include <engine/storage.h>
9#include <game/version.h>
10
11#include <limits>
12#include <thread>
13
14#if !defined(CONF_FAMILY_WINDOWS)
15#include <csignal>
16#endif
17
18#include <curl/curl.h>
19
20// There is a stray constant on Windows/MSVC...
21#ifdef ERROR
22#undef ERROR
23#endif
24
25int CurlDebug(CURL *pHandle, curl_infotype Type, char *pData, size_t DataSize, void *pUser)
26{
27 char TypeChar;
28 switch(Type)
29 {
30 case CURLINFO_TEXT:
31 TypeChar = '*';
32 break;
33 case CURLINFO_HEADER_OUT:
34 TypeChar = '<';
35 break;
36 case CURLINFO_HEADER_IN:
37 TypeChar = '>';
38 break;
39 default:
40 return 0;
41 }
42 while(const char *pLineEnd = (const char *)memchr(s: pData, c: '\n', n: DataSize))
43 {
44 int LineLength = pLineEnd - pData;
45 log_debug("curl", "%c %.*s", TypeChar, LineLength, pData);
46 pData += LineLength + 1;
47 DataSize -= LineLength + 1;
48 }
49 return 0;
50}
51
52void EscapeUrl(char *pBuf, int Size, const char *pStr)
53{
54 char *pEsc = curl_easy_escape(handle: 0, string: pStr, length: 0);
55 str_copy(dst: pBuf, src: pEsc, dst_size: Size);
56 curl_free(p: pEsc);
57}
58
59bool HttpHasIpresolveBug()
60{
61 // curl < 7.77.0 doesn't use CURLOPT_IPRESOLVE correctly wrt.
62 // connection caches.
63 return curl_version_info(CURLVERSION_NOW)->version_num < 0x074d00;
64}
65
66CHttpRequest::CHttpRequest(const char *pUrl)
67{
68 str_copy(dst&: m_aUrl, src: pUrl);
69 sha256_init(ctxt: &m_ActualSha256Ctx);
70}
71
72CHttpRequest::~CHttpRequest()
73{
74 dbg_assert(m_File == nullptr, "HTTP request file was not closed");
75 free(ptr: m_pBuffer);
76 curl_slist_free_all(list: (curl_slist *)m_pHeaders);
77 free(ptr: m_pBody);
78}
79
80bool CHttpRequest::BeforeInit()
81{
82 if(m_WriteToFile)
83 {
84 if(fs_makedir_rec_for(path: m_aDestAbsolute) < 0)
85 {
86 log_error("http", "i/o error, cannot create folder for: %s", m_aDest);
87 return false;
88 }
89
90 m_File = io_open(filename: m_aDestAbsolute, flags: IOFLAG_WRITE);
91 if(!m_File)
92 {
93 log_error("http", "i/o error, cannot open file: %s", m_aDest);
94 return false;
95 }
96 }
97 return true;
98}
99
100bool CHttpRequest::ConfigureHandle(void *pHandle)
101{
102 CURL *pH = (CURL *)pHandle;
103 if(!BeforeInit())
104 {
105 return false;
106 }
107
108 if(g_Config.m_DbgCurl)
109 {
110 curl_easy_setopt(pH, CURLOPT_VERBOSE, 1L);
111 curl_easy_setopt(pH, CURLOPT_DEBUGFUNCTION, CurlDebug);
112 }
113 long Protocols = CURLPROTO_HTTPS;
114 if(g_Config.m_HttpAllowInsecure)
115 {
116 Protocols |= CURLPROTO_HTTP;
117 }
118
119 curl_easy_setopt(pH, CURLOPT_ERRORBUFFER, m_aErr);
120
121 curl_easy_setopt(pH, CURLOPT_CONNECTTIMEOUT_MS, m_Timeout.ConnectTimeoutMs);
122 curl_easy_setopt(pH, CURLOPT_TIMEOUT_MS, m_Timeout.TimeoutMs);
123 curl_easy_setopt(pH, CURLOPT_LOW_SPEED_LIMIT, m_Timeout.LowSpeedLimit);
124 curl_easy_setopt(pH, CURLOPT_LOW_SPEED_TIME, m_Timeout.LowSpeedTime);
125 if(m_MaxResponseSize >= 0)
126 {
127 curl_easy_setopt(pH, CURLOPT_MAXFILESIZE_LARGE, (curl_off_t)m_MaxResponseSize);
128 }
129
130 // ‘CURLOPT_PROTOCOLS’ is deprecated: since 7.85.0. Use CURLOPT_PROTOCOLS_STR
131 // Wait until all platforms have 7.85.0
132#ifdef __GNUC__
133#pragma GCC diagnostic push
134#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
135#endif
136 curl_easy_setopt(pH, CURLOPT_PROTOCOLS, Protocols);
137#ifdef __GNUC__
138#pragma GCC diagnostic pop
139#endif
140 curl_easy_setopt(pH, CURLOPT_FOLLOWLOCATION, 1L);
141 curl_easy_setopt(pH, CURLOPT_MAXREDIRS, 4L);
142 if(m_FailOnErrorStatus)
143 {
144 curl_easy_setopt(pH, CURLOPT_FAILONERROR, 1L);
145 }
146 curl_easy_setopt(pH, CURLOPT_URL, m_aUrl);
147 curl_easy_setopt(pH, CURLOPT_NOSIGNAL, 1L);
148 curl_easy_setopt(pH, CURLOPT_USERAGENT, GAME_NAME " " GAME_RELEASE_VERSION " (" CONF_PLATFORM_STRING "; " CONF_ARCH_STRING ")");
149 curl_easy_setopt(pH, CURLOPT_ACCEPT_ENCODING, ""); // Use any compression algorithm supported by libcurl.
150
151 curl_easy_setopt(pH, CURLOPT_HEADERDATA, this);
152 curl_easy_setopt(pH, CURLOPT_HEADERFUNCTION, HeaderCallback);
153 curl_easy_setopt(pH, CURLOPT_WRITEDATA, this);
154 curl_easy_setopt(pH, CURLOPT_WRITEFUNCTION, WriteCallback);
155 curl_easy_setopt(pH, CURLOPT_NOPROGRESS, 0L);
156 curl_easy_setopt(pH, CURLOPT_PROGRESSDATA, this);
157 // ‘CURLOPT_PROGRESSFUNCTION’ is deprecated: since 7.32.0. Use CURLOPT_XFERINFOFUNCTION
158 // See problems with curl_off_t type in header file in https://github.com/ddnet/ddnet/pull/6185/
159#ifdef __GNUC__
160#pragma GCC diagnostic push
161#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
162#endif
163 curl_easy_setopt(pH, CURLOPT_PROGRESSFUNCTION, ProgressCallback);
164#ifdef __GNUC__
165#pragma GCC diagnostic pop
166#endif
167 curl_easy_setopt(pH, CURLOPT_IPRESOLVE, m_IpResolve == IPRESOLVE::V4 ? CURL_IPRESOLVE_V4 : m_IpResolve == IPRESOLVE::V6 ? CURL_IPRESOLVE_V6 : CURL_IPRESOLVE_WHATEVER);
168 if(g_Config.m_Bindaddr[0] != '\0')
169 {
170 curl_easy_setopt(pH, CURLOPT_INTERFACE, g_Config.m_Bindaddr);
171 }
172
173 if(curl_version_info(CURLVERSION_NOW)->version_num < 0x074400)
174 {
175 // Causes crashes, see https://github.com/ddnet/ddnet/issues/4342.
176 // No longer a problem in curl 7.68 and above, and 0x44 = 68.
177 curl_easy_setopt(pH, CURLOPT_FORBID_REUSE, 1L);
178 }
179
180#ifdef CONF_PLATFORM_ANDROID
181 curl_easy_setopt(pH, CURLOPT_CAINFO, "data/cacert.pem");
182#endif
183
184 switch(m_Type)
185 {
186 case REQUEST::GET:
187 break;
188 case REQUEST::HEAD:
189 curl_easy_setopt(pH, CURLOPT_NOBODY, 1L);
190 break;
191 case REQUEST::POST:
192 case REQUEST::POST_JSON:
193 if(m_Type == REQUEST::POST_JSON)
194 {
195 Header(pNameColonValue: "Content-Type: application/json");
196 }
197 else
198 {
199 Header(pNameColonValue: "Content-Type:");
200 }
201 curl_easy_setopt(pH, CURLOPT_POSTFIELDS, m_pBody);
202 curl_easy_setopt(pH, CURLOPT_POSTFIELDSIZE, m_BodyLength);
203 break;
204 }
205
206 curl_easy_setopt(pH, CURLOPT_HTTPHEADER, m_pHeaders);
207
208 return true;
209}
210
211size_t CHttpRequest::OnHeader(char *pHeader, size_t HeaderSize)
212{
213 // `pHeader` is NOT null-terminated.
214 // `pHeader` has a trailing newline.
215
216 if(HeaderSize <= 1)
217 {
218 m_HeadersEnded = true;
219 return HeaderSize;
220 }
221 if(m_HeadersEnded)
222 {
223 // redirect, clear old headers
224 m_HeadersEnded = false;
225 m_ResultDate = {};
226 m_ResultLastModified = {};
227 }
228
229 static const char DATE[] = "Date: ";
230 static const char LAST_MODIFIED[] = "Last-Modified: ";
231
232 // Trailing newline and null termination evens out.
233 if(HeaderSize - 1 >= sizeof(DATE) - 1 && str_startswith_nocase(str: pHeader, prefix: DATE))
234 {
235 char aValue[128];
236 str_truncate(dst: aValue, dst_size: sizeof(aValue), src: pHeader + (sizeof(DATE) - 1), truncation_len: HeaderSize - (sizeof(DATE) - 1) - 1);
237 int64_t Value = curl_getdate(p: aValue, unused: nullptr);
238 if(Value != -1)
239 {
240 m_ResultDate = Value;
241 }
242 }
243 if(HeaderSize - 1 >= sizeof(LAST_MODIFIED) - 1 && str_startswith_nocase(str: pHeader, prefix: LAST_MODIFIED))
244 {
245 char aValue[128];
246 str_truncate(dst: aValue, dst_size: sizeof(aValue), src: pHeader + (sizeof(LAST_MODIFIED) - 1), truncation_len: HeaderSize - (sizeof(LAST_MODIFIED) - 1) - 1);
247 int64_t Value = curl_getdate(p: aValue, unused: nullptr);
248 if(Value != -1)
249 {
250 m_ResultLastModified = Value;
251 }
252 }
253
254 return HeaderSize;
255}
256
257size_t CHttpRequest::OnData(char *pData, size_t DataSize)
258{
259 // Need to check for the maximum response size here as curl can only
260 // guarantee it if the server sets a Content-Length header.
261 if(m_MaxResponseSize >= 0 && m_ResponseLength + DataSize > (uint64_t)m_MaxResponseSize)
262 {
263 return 0;
264 }
265
266 sha256_update(ctxt: &m_ActualSha256Ctx, data: pData, data_len: DataSize);
267
268 if(!m_WriteToFile)
269 {
270 if(DataSize == 0)
271 {
272 return DataSize;
273 }
274 size_t NewBufferSize = maximum(a: (size_t)1024, b: m_BufferSize);
275 while(m_ResponseLength + DataSize > NewBufferSize)
276 {
277 NewBufferSize *= 2;
278 }
279 if(NewBufferSize != m_BufferSize)
280 {
281 m_pBuffer = (unsigned char *)realloc(ptr: m_pBuffer, size: NewBufferSize);
282 m_BufferSize = NewBufferSize;
283 }
284 mem_copy(dest: m_pBuffer + m_ResponseLength, source: pData, size: DataSize);
285 m_ResponseLength += DataSize;
286 return DataSize;
287 }
288 else
289 {
290 m_ResponseLength += DataSize;
291 return io_write(io: m_File, buffer: pData, size: DataSize);
292 }
293}
294
295size_t CHttpRequest::HeaderCallback(char *pData, size_t Size, size_t Number, void *pUser)
296{
297 dbg_assert(Size == 1, "invalid size parameter passed to header callback");
298 return ((CHttpRequest *)pUser)->OnHeader(pHeader: pData, HeaderSize: Number);
299}
300
301size_t CHttpRequest::WriteCallback(char *pData, size_t Size, size_t Number, void *pUser)
302{
303 return ((CHttpRequest *)pUser)->OnData(pData, DataSize: Size * Number);
304}
305
306int CHttpRequest::ProgressCallback(void *pUser, double DlTotal, double DlCurr, double UlTotal, double UlCurr)
307{
308 CHttpRequest *pTask = (CHttpRequest *)pUser;
309 pTask->m_Current.store(i: DlCurr, m: std::memory_order_relaxed);
310 pTask->m_Size.store(i: DlTotal, m: std::memory_order_relaxed);
311 pTask->m_Progress.store(i: (100 * DlCurr) / (DlTotal ? DlTotal : 1), m: std::memory_order_relaxed);
312 pTask->OnProgress();
313 return pTask->m_Abort ? -1 : 0;
314}
315
316void CHttpRequest::OnCompletionInternal(void *pHandle, unsigned int Result)
317{
318 if(pHandle)
319 {
320 CURL *pH = (CURL *)pHandle;
321 long StatusCode;
322 curl_easy_getinfo(pH, CURLINFO_RESPONSE_CODE, &StatusCode);
323 m_StatusCode = StatusCode;
324 }
325
326 EHttpState State;
327 const CURLcode Code = static_cast<CURLcode>(Result);
328 if(Code != CURLE_OK)
329 {
330 if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::FAILURE)
331 {
332 log_error("http", "%s failed. libcurl error (%u): %s", m_aUrl, Code, m_aErr);
333 }
334 State = (Code == CURLE_ABORTED_BY_CALLBACK) ? EHttpState::ABORTED : EHttpState::ERROR;
335 }
336 else
337 {
338 if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::ALL)
339 {
340 log_info("http", "task done: %s", m_aUrl);
341 }
342 State = EHttpState::DONE;
343 }
344
345 if(State == EHttpState::DONE)
346 {
347 m_ActualSha256 = sha256_finish(ctxt: &m_ActualSha256Ctx);
348 if(m_ExpectedSha256 != SHA256_ZEROED && m_ActualSha256 != m_ExpectedSha256)
349 {
350 if(g_Config.m_DbgCurl || m_LogProgress >= HTTPLOG::FAILURE)
351 {
352 char aActualSha256[SHA256_MAXSTRSIZE];
353 sha256_str(digest: m_ActualSha256, str: aActualSha256, max_len: sizeof(aActualSha256));
354 char aExpectedSha256[SHA256_MAXSTRSIZE];
355 sha256_str(digest: m_ExpectedSha256, str: aExpectedSha256, max_len: sizeof(aExpectedSha256));
356 log_error("http", "SHA256 mismatch: got=%s, expected=%s, url=%s", aActualSha256, aExpectedSha256, m_aUrl);
357 }
358 State = EHttpState::ERROR;
359 }
360 }
361
362 if(m_WriteToFile)
363 {
364 if(m_File && io_close(io: m_File) != 0)
365 {
366 log_error("http", "i/o error, cannot close file: %s", m_aDest);
367 State = EHttpState::ERROR;
368 }
369 m_File = nullptr;
370
371 if(State == EHttpState::ERROR || State == EHttpState::ABORTED)
372 {
373 fs_remove(filename: m_aDestAbsolute);
374 }
375 }
376
377 // The globally visible state must be updated after OnCompletion has finished,
378 // or other threads may try to access the result of a completed HTTP request,
379 // before the result has been initialized/updated in OnCompletion.
380 OnCompletion(State);
381 m_State = State;
382}
383
384void CHttpRequest::WriteToFile(IStorage *pStorage, const char *pDest, int StorageType)
385{
386 m_WriteToFile = true;
387 str_copy(dst&: m_aDest, src: pDest);
388 if(StorageType == -2)
389 {
390 pStorage->GetBinaryPath(pFilename: m_aDest, pBuffer: m_aDestAbsolute, BufferSize: sizeof(m_aDestAbsolute));
391 }
392 else
393 {
394 pStorage->GetCompletePath(Type: StorageType, pDir: m_aDest, pBuffer: m_aDestAbsolute, BufferSize: sizeof(m_aDestAbsolute));
395 }
396}
397
398void CHttpRequest::Header(const char *pNameColonValue)
399{
400 m_pHeaders = curl_slist_append(list: (curl_slist *)m_pHeaders, data: pNameColonValue);
401}
402
403void CHttpRequest::Wait()
404{
405 using namespace std::chrono_literals;
406
407 // This is so uncommon that polling just might work
408 for(;;)
409 {
410 EHttpState State = m_State.load(m: std::memory_order_seq_cst);
411 if(State != EHttpState::QUEUED && State != EHttpState::RUNNING)
412 {
413 return;
414 }
415 std::this_thread::sleep_for(rtime: 10ms);
416 }
417}
418
419void CHttpRequest::Result(unsigned char **ppResult, size_t *pResultLength) const
420{
421 dbg_assert(State() == EHttpState::DONE, "Request not done");
422 dbg_assert(!m_WriteToFile, "Result not usable together with WriteToFile");
423 *ppResult = m_pBuffer;
424 *pResultLength = m_ResponseLength;
425}
426
427json_value *CHttpRequest::ResultJson() const
428{
429 unsigned char *pResult;
430 size_t ResultLength;
431 Result(ppResult: &pResult, pResultLength: &ResultLength);
432 return json_parse(json: (char *)pResult, length: ResultLength);
433}
434
435const SHA256_DIGEST &CHttpRequest::ResultSha256() const
436{
437 dbg_assert(State() == EHttpState::DONE, "Request not done");
438 return m_ActualSha256;
439}
440
441int CHttpRequest::StatusCode() const
442{
443 dbg_assert(State() == EHttpState::DONE, "Request not done");
444 return m_StatusCode;
445}
446
447std::optional<int64_t> CHttpRequest::ResultAgeSeconds() const
448{
449 dbg_assert(State() == EHttpState::DONE, "Request not done");
450 if(!m_ResultDate || !m_ResultLastModified)
451 {
452 return {};
453 }
454 return *m_ResultDate - *m_ResultLastModified;
455}
456
457std::optional<int64_t> CHttpRequest::ResultLastModified() const
458{
459 dbg_assert(State() == EHttpState::DONE, "Request not done");
460 return m_ResultLastModified;
461}
462
463bool CHttp::Init(std::chrono::milliseconds ShutdownDelay)
464{
465 m_ShutdownDelay = ShutdownDelay;
466
467#if !defined(CONF_FAMILY_WINDOWS)
468 // As a multithreaded application we have to tell curl to not install signal
469 // handlers and instead ignore SIGPIPE from OpenSSL ourselves.
470 signal(SIGPIPE, SIG_IGN);
471#endif
472 m_pThread = thread_init(threadfunc: CHttp::ThreadMain, user: this, name: "http");
473
474 std::unique_lock Lock(m_Lock);
475 m_Cv.wait(lock&: Lock, p: [this]() { return m_State != CHttp::UNINITIALIZED; });
476 if(m_State != CHttp::RUNNING)
477 {
478 return false;
479 }
480
481 return true;
482}
483
484void CHttp::ThreadMain(void *pUser)
485{
486 CHttp *pHttp = static_cast<CHttp *>(pUser);
487 pHttp->RunLoop();
488}
489
490void CHttp::RunLoop()
491{
492 std::unique_lock Lock(m_Lock);
493 if(curl_global_init(CURL_GLOBAL_DEFAULT))
494 {
495 log_error("http", "curl_global_init failed");
496 m_State = CHttp::ERROR;
497 m_Cv.notify_all();
498 return;
499 }
500
501 m_pMultiH = curl_multi_init();
502 if(!m_pMultiH)
503 {
504 log_error("http", "curl_multi_init failed");
505 m_State = CHttp::ERROR;
506 m_Cv.notify_all();
507 return;
508 }
509
510 // print curl version
511 {
512 curl_version_info_data *pVersion = curl_version_info(CURLVERSION_NOW);
513 log_info("http", "libcurl version %s (compiled = " LIBCURL_VERSION ")", pVersion->version);
514 }
515
516 m_State = CHttp::RUNNING;
517 m_Cv.notify_all();
518 Lock.unlock();
519
520 while(m_State == CHttp::RUNNING)
521 {
522 static int s_NextTimeout = std::numeric_limits<int>::max();
523 int Events = 0;
524 const CURLMcode PollCode = curl_multi_poll(multi_handle: m_pMultiH, extra_fds: nullptr, extra_nfds: 0, timeout_ms: s_NextTimeout, ret: &Events);
525
526 // We may have been woken up for a shutdown
527 if(m_Shutdown)
528 {
529 auto Now = std::chrono::steady_clock::now();
530 if(!m_ShutdownTime.has_value())
531 {
532 m_ShutdownTime = Now + m_ShutdownDelay;
533 s_NextTimeout = m_ShutdownDelay.count();
534 }
535 else if(m_ShutdownTime < Now || m_RunningRequests.empty())
536 {
537 break;
538 }
539 }
540
541 if(PollCode != CURLM_OK)
542 {
543 Lock.lock();
544 log_error("http", "curl_multi_poll failed: %s", curl_multi_strerror(PollCode));
545 m_State = CHttp::ERROR;
546 break;
547 }
548
549 const CURLMcode PerformCode = curl_multi_perform(multi_handle: m_pMultiH, running_handles: &Events);
550 if(PerformCode != CURLM_OK)
551 {
552 Lock.lock();
553 log_error("http", "curl_multi_perform failed: %s", curl_multi_strerror(PerformCode));
554 m_State = CHttp::ERROR;
555 break;
556 }
557
558 struct CURLMsg *pMsg;
559 while((pMsg = curl_multi_info_read(multi_handle: m_pMultiH, msgs_in_queue: &Events)))
560 {
561 if(pMsg->msg == CURLMSG_DONE)
562 {
563 auto RequestIt = m_RunningRequests.find(x: pMsg->easy_handle);
564 dbg_assert(RequestIt != m_RunningRequests.end(), "Running handle not added to map");
565 auto pRequest = std::move(RequestIt->second);
566 m_RunningRequests.erase(position: RequestIt);
567
568 pRequest->OnCompletionInternal(pHandle: pMsg->easy_handle, Result: pMsg->data.result);
569 curl_multi_remove_handle(multi_handle: m_pMultiH, curl_handle: pMsg->easy_handle);
570 curl_easy_cleanup(curl: pMsg->easy_handle);
571 }
572 }
573
574 decltype(m_PendingRequests) NewRequests = {};
575 Lock.lock();
576 std::swap(x&: m_PendingRequests, y&: NewRequests);
577 Lock.unlock();
578
579 while(!NewRequests.empty())
580 {
581 auto &pRequest = NewRequests.front();
582 if(g_Config.m_DbgCurl)
583 log_debug("http", "task: %s %s", CHttpRequest::GetRequestType(pRequest->m_Type), pRequest->m_aUrl);
584
585 CURL *pEH = curl_easy_init();
586 if(!pEH)
587 {
588 log_error("http", "curl_easy_init failed");
589 goto error_init;
590 }
591
592 if(!pRequest->ConfigureHandle(pHandle: pEH))
593 {
594 curl_easy_cleanup(curl: pEH);
595 str_copy(dst&: pRequest->m_aErr, src: "Failed to initialize request");
596 pRequest->OnCompletionInternal(pHandle: nullptr, Result: CURLE_ABORTED_BY_CALLBACK);
597 NewRequests.pop_front();
598 continue;
599 }
600
601 if(curl_multi_add_handle(multi_handle: m_pMultiH, curl_handle: pEH) != CURLM_OK)
602 {
603 log_error("http", "curl_multi_add_handle failed");
604 goto error_configure;
605 }
606
607 m_RunningRequests.emplace(args&: pEH, args: std::move(pRequest));
608 NewRequests.pop_front();
609 continue;
610
611 error_configure:
612 curl_easy_cleanup(curl: pEH);
613 error_init:
614 Lock.lock();
615 m_State = CHttp::ERROR;
616 break;
617 }
618
619 // Only happens if m_State == ERROR, thus we already hold the lock
620 if(!NewRequests.empty())
621 {
622 m_PendingRequests.insert(position: m_PendingRequests.end(), first: std::make_move_iterator(i: NewRequests.begin()), last: std::make_move_iterator(i: NewRequests.end()));
623 break;
624 }
625 }
626
627 if(!Lock.owns_lock())
628 Lock.lock();
629
630 bool Cleanup = m_State != CHttp::ERROR;
631 for(auto &pRequest : m_PendingRequests)
632 {
633 str_copy(dst&: pRequest->m_aErr, src: "Shutting down");
634 pRequest->OnCompletionInternal(pHandle: nullptr, Result: CURLE_ABORTED_BY_CALLBACK);
635 }
636
637 for(auto &ReqPair : m_RunningRequests)
638 {
639 auto &[pHandle, pRequest] = ReqPair;
640
641 str_copy(dst&: pRequest->m_aErr, src: "Shutting down");
642 pRequest->OnCompletionInternal(pHandle, Result: CURLE_ABORTED_BY_CALLBACK);
643
644 if(Cleanup)
645 {
646 curl_multi_remove_handle(multi_handle: m_pMultiH, curl_handle: pHandle);
647 curl_easy_cleanup(curl: pHandle);
648 }
649 }
650
651 if(Cleanup)
652 {
653 curl_multi_cleanup(multi_handle: m_pMultiH);
654 curl_global_cleanup();
655 }
656}
657
658void CHttp::Run(std::shared_ptr<IHttpRequest> pRequest)
659{
660 std::shared_ptr<CHttpRequest> pRequestImpl = std::static_pointer_cast<CHttpRequest>(r: pRequest);
661 std::unique_lock Lock(m_Lock);
662 if(m_Shutdown || m_State == CHttp::ERROR)
663 {
664 str_copy(dst&: pRequestImpl->m_aErr, src: "Shutting down");
665 pRequestImpl->OnCompletionInternal(pHandle: nullptr, Result: CURLE_ABORTED_BY_CALLBACK);
666 return;
667 }
668 m_Cv.wait(lock&: Lock, p: [this]() { return m_State != CHttp::UNINITIALIZED; });
669 m_PendingRequests.emplace_back(args&: pRequestImpl);
670 curl_multi_wakeup(multi_handle: m_pMultiH);
671}
672
673void CHttp::Shutdown()
674{
675 std::unique_lock Lock(m_Lock);
676 if(m_Shutdown || m_State != CHttp::RUNNING)
677 return;
678
679 m_Shutdown = true;
680 curl_multi_wakeup(multi_handle: m_pMultiH);
681}
682
683CHttp::~CHttp()
684{
685 if(!m_pThread)
686 return;
687
688 Shutdown();
689 thread_wait(thread: m_pThread);
690}
691