4#include "Stroika/Foundation/StroikaPreComp.h"
8#if qStroika_HasComponent_libcurl
15#include "Stroika/Foundation/Execution/Throw.h"
16#include "Stroika/Foundation/IO/Network/HTTP/Headers.h"
17#include "Stroika/Foundation/IO/Network/HTTP/Methods.h"
20#include "Connection_libcurl.h"
25using namespace Stroika::Foundation::IO;
28using namespace Stroika::Foundation::Time;
32using Stroika::Foundation::IO::Network::Transfer::Connection::IRep;
33using Stroika::Foundation::IO::Network::Transfer::Connection::Options;
43#if qStroika_HasComponent_libcurl
44using namespace Stroika::Foundation::IO::Network::Transfer::LibCurl;
47 void AssureLibCurlInitialized_ ()
49 static once_flag sOnceFlag_;
50 call_once (sOnceFlag_, [] () {
51 atexit ([] () { ::curl_global_cleanup (); });
59 Verify (::curl_global_init (CURL_GLOBAL_SSL | CURL_GLOBAL_ACK_EINTR) == 0);
69const std::error_category& Transfer::LibCurl::error_category () noexcept
71 class LibCurl_error_category_ :
public error_category {
73 virtual const char* name () const noexcept
override
75 return "LibCurl error";
77 virtual error_condition default_error_condition ([[maybe_unused]]
int ev)
const noexcept override
81 case CURLE_OPERATION_TIMEDOUT:
82 return errc::timed_out;
83 case CURLE_COULDNT_CONNECT:
84 return errc::connection_refused;
85 case CURLE_COULDNT_RESOLVE_HOST:
86 case CURLE_COULDNT_RESOLVE_PROXY:
87 return errc::no_such_device;
88 case CURLE_GOT_NOTHING:
89 return errc::connection_reset;
90 case CURLE_SEND_ERROR:
91 case CURLE_RECV_ERROR:
92 case CURLE_PARTIAL_FILE:
93 case CURLE_WRITE_ERROR:
94 case CURLE_READ_ERROR:
95 return errc::io_error;
97 return errc::resource_unavailable_try_again;
98 case CURLE_WEIRD_SERVER_REPLY:
99 return errc::protocol_error;
102 case CURLE_SSL_ENGINE_NOTFOUND:
103 return errc::protocol_not_supported;
104 case CURLE_USE_SSL_FAILED:
105 case CURLE_SSL_CONNECT_ERROR:
106 case CURLE_PEER_FAILED_VERIFICATION:
107 return errc::protocol_error;
110 case CURLE_UNSUPPORTED_PROTOCOL:
111 return errc::protocol_not_supported;
112 case CURLE_NOT_BUILT_IN:
113 return errc::not_supported;
114 case CURLE_URL_MALFORMAT:
115 case CURLE_BAD_FUNCTION_ARGUMENT:
116 return errc::invalid_argument;
117 case CURLE_AUTH_ERROR:
118 case CURLE_LOGIN_DENIED:
119 case CURLE_REMOTE_ACCESS_DENIED:
120 return errc::permission_denied;
121 case CURLE_REMOTE_FILE_NOT_FOUND:
122 return errc::no_such_file_or_directory;
123 case CURLE_FILESIZE_EXCEEDED:
124 return errc::file_too_large;
125 case CURLE_ABORTED_BY_CALLBACK:
126 return errc::operation_canceled;
128 case CURLE_OUT_OF_MEMORY:
129 return errc::not_enough_memory;
134 return error_condition{errc::bad_message};
136 virtual string message (
int ccode)
const override
138 return ::curl_easy_strerror (
static_cast<CURLcode
> (ccode));
141 return Common::Immortalize<LibCurl_error_category_> ();
145 class Rep_ :
public Transfer::LibCurl::Connection::IRep {
150 Rep_ (
const Options& options)
153 AssureLibCurlInitialized_ ();
155 Rep_ (
const Rep_&) =
delete;
158 if (fCurlHandle_ !=
nullptr) {
159 ::curl_easy_cleanup (fCurlHandle_);
161 if (fSavedHeaders_ !=
nullptr) {
162 ::curl_slist_free_all (fSavedHeaders_);
163 fSavedHeaders_ =
nullptr;
168 nonvirtual Rep_& operator= (
const Rep_&) =
delete;
171 virtual Options GetOptions ()
const override
182 MakeHandleIfNeeded_ ();
183 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_TIMEOUT_MS,
static_cast<int> (timeout.count () * 1000)));
184 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CONNECTTIMEOUT_MS,
static_cast<int> (timeout.count () * 1000)));
186 virtual URI GetSchemeAndAuthority ()
const override
190 virtual void SetSchemeAndAuthority (
const URI& schemeAndAuthority)
override
192#if USE_NOISY_TRACE_IN_THIS_MODULE_
193 DbgTrace (
"Connection_LibCurl::Rep_::SetSchemeAndAuthority ('{}')"_f, schemeAndAuthority);
195 fURL_.SetScheme (schemeAndAuthority.
GetScheme ());
196 fURL_.SetAuthority (schemeAndAuthority.
GetAuthority ());
197 if (fCurlHandle_ !=
nullptr) {
198 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_URL, fURL_.As<
string> ().c_str ()));
201 virtual void Close ()
override
203 if (fCurlHandle_ !=
nullptr) {
204 ::curl_easy_cleanup (fCurlHandle_);
205 fCurlHandle_ =
nullptr;
210#if USE_NOISY_TRACE_IN_THIS_MODULE_
215 SetAuthorityRelativeURL_ (useRequest.fAuthorityRelativeURL);
218 if (fOptions_.fCache !=
nullptr) {
219 if (
auto r = fOptions_.fCache->OnBeforeFetch (&cacheContext, fURL_.GetSchemeAndAuthority (), &useRequest)) {
225 MakeHandleIfNeeded_ ();
226 fUploadData_ = useRequest.fData.As<vector<byte>> ();
227 fUploadDataCursor_ = 0;
228 fResponseData_.clear ();
229 fResponseHeaders_.clear ();
230 fCapturedCallbackException_ =
nullptr;
232 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_USERAGENT, fOptions_.fUserAgent.AsUTF8 ().c_str ()));
234 if (fOptions_.fSupportSessionCookies) {
236 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_COOKIEFILE,
"/dev/null"));
240 if (fOptions_.fAssumeLowestCommonDenominatorHTTPServer) {
242 {pair<String, String>{
"Expect"sv, {}}, pair<String, String>{
"Transfer-Encoding"sv, {}}}};
243 overrideHeaders = kSilenceTheseHeaders_ + overrideHeaders;
245 if (fOptions_.fAuthentication and
246 fOptions_.fAuthentication->GetOptions () == Connection::Options::Authentication::Options::eProactivelySendAuthentication) {
247 overrideHeaders.
Add (String::FromStringConstant (HeaderName::kAuthorization), fOptions_.fAuthentication->GetAuthToken ());
250 constexpr bool kDefault_FailConnectionIfSSLCertificateInvalid{
false};
252 (void)::curl_easy_setopt (
253 fCurlHandle_, CURLOPT_SSL_VERIFYPEER,
254 fOptions_.fFailConnectionIfSSLCertificateInvalid.value_or (kDefault_FailConnectionIfSSLCertificateInvalid) ? 1L : 0L);
255 (void)::curl_easy_setopt (
256 fCurlHandle_, CURLOPT_SSL_VERIFYHOST,
257 fOptions_.fFailConnectionIfSSLCertificateInvalid.value_or (kDefault_FailConnectionIfSSLCertificateInvalid) ? 2L : 0L);
260 if (useRequest.fMethod == HTTP::Methods::kGet) {
261 if (fDidCustomMethod_) {
262 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST,
nullptr));
263 fDidCustomMethod_ =
false;
265 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HTTPGET, 1));
267 else if (useRequest.fMethod == HTTP::Methods::kPost) {
268 if (fDidCustomMethod_) {
269 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST,
nullptr));
270 fDidCustomMethod_ =
false;
272 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POST, 1));
273 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POSTFIELDSIZE, fUploadData_.size ()));
275 else if (useRequest.fMethod == HTTP::Methods::kPut) {
276 if (fDidCustomMethod_) {
277 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST,
nullptr));
278 fDidCustomMethod_ =
false;
282 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_UPLOAD, 1));
283 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_INFILESIZE, fUploadData_.size ()));
287 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HTTPGET, 0));
288 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POST, fUploadData_.empty () ? 0 : 1));
289 if (not fUploadData_.empty ()) {
290 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POSTFIELDSIZE, fUploadData_.size ()));
292 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST, useRequest.fMethod.AsUTF8 ().c_str ()));
293 fDidCustomMethod_ =
true;
296 if (fOptions_.fAuthentication and
297 fOptions_.fAuthentication->GetOptions () == Connection::Options::Authentication::Options::eRespondToWWWAuthenticate) {
298 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HTTPAUTH, (
long)CURLAUTH_ANY));
299 auto nameAndPassword = *fOptions_.fAuthentication->GetUsernameAndPassword ();
300 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_USERNAME, nameAndPassword.first.AsUTF8 ().c_str ()));
301 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_PASSWORD, nameAndPassword.second.AsUTF8 ().c_str ()));
305 curl_slist* tmpH =
nullptr;
306 for (
const auto& i : overrideHeaders) {
307 tmpH = ::curl_slist_append (tmpH, (i.fKey +
": "sv + i.fValue).AsUTF8<
string> ().c_str ());
310 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HTTPHEADER, tmpH));
311 if (fSavedHeaders_ !=
nullptr) {
312 ::curl_slist_free_all (fSavedHeaders_);
313 fSavedHeaders_ =
nullptr;
315 fSavedHeaders_ = tmpH;
318 CURLcode performResult = ::curl_easy_perform (fCurlHandle_);
319 ThrowIfCapturedCallbackException_ ();
320 ThrowIfError (performResult);
323 ThrowIfError (::curl_easy_getinfo (fCurlHandle_, CURLINFO_RESPONSE_CODE, &resultCode));
325 if (fOptions_.fCache !=
nullptr) {
326 fOptions_.fCache->OnAfterFetch (cacheContext, &result);
328#if USE_NOISY_TRACE_IN_THIS_MODULE_
329 DbgTrace (
"returning status = {}, dataLen = {}"_f, result.GetStatus (), result.GetData ().size ());
335 nonvirtual
void SetAuthorityRelativeURL_ (
const URI& url)
337#if USE_NOISY_TRACE_IN_THIS_MODULE_
338 DbgTrace (
"Connection_LibCurl::Rep_::SetAuthorityRelativeURL_ ({})"_f, url);
342 newURL.SetAuthority (fURL_.GetAuthority ());
343 if (fCurlHandle_ !=
nullptr) {
344 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_URL, newURL.
As<
string> ().c_str ()));
350 nonvirtual
void MakeHandleIfNeeded_ ()
352 if (fCurlHandle_ ==
nullptr) {
358 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_URL, fURL_.As<
string> ().c_str ()));
360#if USE_LIBCURL_VERBOSE_
361 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_VERBOSE, 1));
369 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_NOSIGNAL, 1));
371#if qStroika_Foundation_Debug_AssertionsChecked && qStroika_Platform_POSIX
373 struct sigaction oldact;
374 (void)::sigaction (SIGPIPE, NULL, &oldact);
375 if (oldact.sa_handler == SIG_DFL) {
376 DbgTrace (
"Warning: no override of SIGPIPE. This is a risk factor with curl. Often just ignore"_f);
381 if (fOptions_.fMaxAutomaticRedirects == 0) {
382 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_FOLLOWLOCATION, 0L));
385 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_FOLLOWLOCATION, 1L));
386 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_MAXREDIRS, fOptions_.fMaxAutomaticRedirects));
389 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POSTREDIR, CURL_REDIR_POST_301));
392 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_READFUNCTION, &s_RequestPayloadReadHandler_));
393 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_READDATA,
this));
394 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_WRITEFUNCTION, &s_ResponseWriteHandler_));
395 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_WRITEDATA,
this));
396 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HEADERFUNCTION, &s_ResponseHeaderWriteHandler_));
397 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_WRITEHEADER,
this));
399 if (fOptions_.fTCPKeepAlives) {
400 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_TCP_KEEPALIVE, fOptions_.fTCPKeepAlives->fEnabled));
401#if qStroika_Platform_Linux
402 if (fOptions_.fTCPKeepAlives->fTimeIdleBeforeSendingKeepalives) {
403 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_TCP_KEEPIDLE, *fOptions_.fTCPKeepAlives->fTimeIdleBeforeSendingKeepalives));
405 if (fOptions_.fTCPKeepAlives->fTimeBetweenIndividualKeepaliveProbes) {
406 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_TCP_KEEPINTVL,
407 *fOptions_.fTCPKeepAlives->fTimeBetweenIndividualKeepaliveProbes));
424 template <
typename CALL_HANDLER>
425 static size_t Invoke_ (
void* userP,
size_t abortResult, CALL_HANDLER&& callHandler)
noexcept
427 Rep_* rep =
reinterpret_cast<Rep_*
> (userP);
428 if (rep->fCapturedCallbackException_ ==
nullptr) [[likely]] {
430 return callHandler (*rep);
433 rep->fCapturedCallbackException_ = current_exception ();
438 nonvirtual
void ThrowIfCapturedCallbackException_ ()
440 if (fCapturedCallbackException_ !=
nullptr) [[unlikely]] {
441 rethrow_exception (fCapturedCallbackException_);
445 static size_t s_RequestPayloadReadHandler_ (
char* buffer,
size_t size,
size_t nitems,
void* userP)
447 return Invoke_ (userP, CURL_READFUNC_ABORT,
448 [&] (Rep_& rep) {
return rep.RequestPayloadReadHandler_ (
reinterpret_cast<byte*
> (buffer), size * nitems); });
451 nonvirtual
size_t RequestPayloadReadHandler_ (
byte* buffer,
size_t bufSize)
453#if USE_NOISY_TRACE_IN_THIS_MODULE_
456 size_t bytes2Copy = fUploadData_.size () - fUploadDataCursor_;
457 bytes2Copy = min (bytes2Copy, bufSize);
458 ::memcpy (buffer, Traversal::Iterator2Pointer (begin (fUploadData_)) + fUploadDataCursor_, bytes2Copy);
459 fUploadDataCursor_ += bytes2Copy;
460#if USE_NOISY_TRACE_IN_THIS_MODULE_
461 DbgTrace (
"bufSize = {}, bytes2Copy={}"_f, bufSize, bytes2Copy);
467 static size_t s_ResponseWriteHandler_ (
char* ptr,
size_t size,
size_t nmemb,
void* userP)
469#if qStroika_FeatureSupported_Valgrind
470 VALGRIND_MAKE_MEM_DEFINED (ptr, size * nmemb);
472 return Invoke_ (userP, 0,
473 [&] (Rep_& rep) {
return rep.ResponseWriteHandler_ (
reinterpret_cast<const byte*
> (ptr), size * nmemb); });
476 nonvirtual
size_t ResponseWriteHandler_ (
const byte* ptr,
size_t nBytes)
478 fResponseData_.insert (fResponseData_.end (), ptr, ptr + nBytes);
483 static size_t s_ResponseHeaderWriteHandler_ (
char* ptr,
size_t size,
size_t nmemb,
void* userP)
485#if qStroika_FeatureSupported_Valgrind
486 VALGRIND_MAKE_MEM_DEFINED (ptr, size * nmemb);
488 return Invoke_ (userP, 0,
489 [&] (Rep_& rep) {
return rep.ResponseHeaderWriteHandler_ (
reinterpret_cast<const byte*
> (ptr), size * nmemb); });
492 nonvirtual
size_t ResponseHeaderWriteHandler_ (
const byte* ptr,
size_t nBytes)
494 String from = String::FromUTF8 (span{
reinterpret_cast<const char*
> (ptr), nBytes});
496 size_t i = from.
find (
':');
497 if (i != string::npos) {
503 fResponseHeaders_.Add (from, to);
508 void* fCurlHandle_{
nullptr};
510 bool fDidCustomMethod_{
false};
511 vector<byte> fUploadData_;
512 size_t fUploadDataCursor_{};
513 vector<byte> fResponseData_;
515 curl_slist* fSavedHeaders_{
nullptr};
517 exception_ptr fCapturedCallbackException_;
526Connection::Ptr Transfer::LibCurl::Connection::New (
const Options& options)
528 return Connection::Ptr{Memory::MakeSharedPtr<Rep_> (options)};
#define AssertNotImplemented()
#define CompileTimeFlagChecker_SOURCE(NS_PREFIX, NAME, VALUE)
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
String is like std::u32string, except it is much easier to use, often much more space efficient,...
nonvirtual String SubString(SZ from) const
nonvirtual String Trim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
nonvirtual size_t find(Character c, size_t startAt=0) const
A Mapping uniquely associates two elements: a key and a value (use Assocation to allow multiple value...
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
nonvirtual URI GetSchemeAndAuthority() const
nonvirtual T As(optional< StringPCTEncodedFlag > pctEncoded={}) const
nonvirtual optional< SchemeType > GetScheme() const
nonvirtual optional< Authority > GetAuthority() const
nonvirtual void SetScheme(const optional< SchemeType > &scheme)
void ThrowIfNull(const Private_::ConstVoidStar &p, const HRESULT &hr)
Template specialization for ThrowIfNull (), for thing being thrown HRESULT - really throw HRESULTErro...