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"
19#include "Connection_libcurl.h"
24using namespace Stroika::Foundation::IO;
27using namespace Stroika::Foundation::Time;
31using Stroika::Foundation::IO::Network::Transfer::Connection::IRep;
32using Stroika::Foundation::IO::Network::Transfer::Connection::Options;
42#if qStroika_HasComponent_libcurl
43using namespace Stroika::Foundation::IO::Network::Transfer::LibCurl;
46 void AssureLibCurlInitialized_ ()
48 static once_flag sOnceFlag_;
49 call_once (sOnceFlag_, [] () {
50 atexit ([] () { ::curl_global_cleanup (); });
58 Verify (::curl_global_init (CURL_GLOBAL_SSL | CURL_GLOBAL_ACK_EINTR) == 0);
68const std::error_category& Transfer::LibCurl::error_category () noexcept
70 class LibCurl_error_category_ :
public error_category {
72 virtual const char* name () const noexcept
override
74 return "LibCurl error";
76 virtual error_condition default_error_condition ([[maybe_unused]]
int ev)
const noexcept override
79 case CURLE_OUT_OF_MEMORY:
80 return errc::not_enough_memory;
81 case CURLE_OPERATION_TIMEDOUT:
82 return errc::timed_out;
83 case CURLE_LOGIN_DENIED:
84 return errc::permission_denied;
85 case CURLE_SEND_ERROR:
86 return errc::io_error;
87 case CURLE_RECV_ERROR:
88 return errc::io_error;
93 return error_condition{errc::bad_message};
95 virtual string message (
int ccode)
const override
97 return ::curl_easy_strerror (
static_cast<CURLcode
> (ccode));
100 return Common::Immortalize<LibCurl_error_category_> ();
104 class Rep_ :
public Transfer::LibCurl::Connection::IRep {
109 Rep_ (
const Options& options)
112 AssureLibCurlInitialized_ ();
114 Rep_ (
const Rep_&) =
delete;
117 if (fCurlHandle_ !=
nullptr) {
118 ::curl_easy_cleanup (fCurlHandle_);
120 if (fSavedHeaders_ !=
nullptr) {
121 ::curl_slist_free_all (fSavedHeaders_);
122 fSavedHeaders_ =
nullptr;
127 nonvirtual Rep_& operator= (
const Rep_&) =
delete;
130 virtual Options GetOptions ()
const override
141 MakeHandleIfNeeded_ ();
142 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_TIMEOUT_MS,
static_cast<int> (timeout.count () * 1000)));
143 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CONNECTTIMEOUT_MS,
static_cast<int> (timeout.count () * 1000)));
145 virtual URI GetSchemeAndAuthority ()
const override
149 virtual void SetSchemeAndAuthority (
const URI& schemeAndAuthority)
override
151#if USE_NOISY_TRACE_IN_THIS_MODULE_
152 DbgTrace (
"Connection_LibCurl::Rep_::SetSchemeAndAuthority ('{}')"_f, schemeAndAuthority);
154 fURL_.SetScheme (schemeAndAuthority.
GetScheme ());
155 fURL_.SetAuthority (schemeAndAuthority.
GetAuthority ());
156 if (fCurlHandle_ !=
nullptr) {
157 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_URL, fURL_.As<
string> ().c_str ()));
160 virtual void Close ()
override
162 if (fCurlHandle_ !=
nullptr) {
163 ::curl_easy_cleanup (fCurlHandle_);
164 fCurlHandle_ =
nullptr;
169#if USE_NOISY_TRACE_IN_THIS_MODULE_
174 SetAuthorityRelativeURL_ (useRequest.fAuthorityRelativeURL);
177 if (fOptions_.fCache !=
nullptr) {
178 if (
auto r = fOptions_.fCache->OnBeforeFetch (&cacheContext, fURL_.GetSchemeAndAuthority (), &useRequest)) {
184 MakeHandleIfNeeded_ ();
185 fUploadData_ = useRequest.fData.As<vector<byte>> ();
186 fUploadDataCursor_ = 0;
187 fResponseData_.clear ();
188 fResponseHeaders_.clear ();
190 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_USERAGENT, fOptions_.fUserAgent.AsUTF8 ().c_str ()));
192 if (fOptions_.fSupportSessionCookies) {
194 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_COOKIEFILE,
"/dev/null"));
198 if (fOptions_.fAssumeLowestCommonDenominatorHTTPServer) {
200 {pair<String, String>{
"Expect"sv, {}}, pair<String, String>{
"Transfer-Encoding"sv, {}}}};
201 overrideHeaders = kSilenceTheseHeaders_ + overrideHeaders;
203 if (fOptions_.fAuthentication and
204 fOptions_.fAuthentication->GetOptions () == Connection::Options::Authentication::Options::eProactivelySendAuthentication) {
205 overrideHeaders.
Add (String::FromStringConstant (HeaderName::kAuthorization), fOptions_.fAuthentication->GetAuthToken ());
208 constexpr bool kDefault_FailConnectionIfSSLCertificateInvalid{
false};
210 (void)::curl_easy_setopt (
211 fCurlHandle_, CURLOPT_SSL_VERIFYPEER,
212 fOptions_.fFailConnectionIfSSLCertificateInvalid.value_or (kDefault_FailConnectionIfSSLCertificateInvalid) ? 1L : 0L);
213 (void)::curl_easy_setopt (
214 fCurlHandle_, CURLOPT_SSL_VERIFYHOST,
215 fOptions_.fFailConnectionIfSSLCertificateInvalid.value_or (kDefault_FailConnectionIfSSLCertificateInvalid) ? 2L : 0L);
218 if (useRequest.fMethod == HTTP::Methods::kGet) {
219 if (fDidCustomMethod_) {
220 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST,
nullptr));
221 fDidCustomMethod_ =
false;
223 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HTTPGET, 1));
225 else if (useRequest.fMethod == HTTP::Methods::kPost) {
226 if (fDidCustomMethod_) {
227 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST,
nullptr));
228 fDidCustomMethod_ =
false;
230 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POST, 1));
231 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POSTFIELDSIZE, fUploadData_.size ()));
233 else if (useRequest.fMethod == HTTP::Methods::kPut) {
234 if (fDidCustomMethod_) {
235 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST,
nullptr));
236 fDidCustomMethod_ =
false;
238 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_UPLOAD, fUploadData_.empty () ? 0 : 1));
239 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_INFILESIZE, fUploadData_.size ()));
242 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HTTPGET, 0));
243 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POST, 0));
244 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST, useRequest.fMethod.AsUTF8 ().c_str ()));
247 if (fOptions_.fAuthentication and
248 fOptions_.fAuthentication->GetOptions () == Connection::Options::Authentication::Options::eRespondToWWWAuthenticate) {
249 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HTTPAUTH, (
long)CURLAUTH_ANY));
250 auto nameAndPassword = *fOptions_.fAuthentication->GetUsernameAndPassword ();
251 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_USERNAME, nameAndPassword.first.AsUTF8 ().c_str ()));
252 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_PASSWORD, nameAndPassword.second.AsUTF8 ().c_str ()));
256 curl_slist* tmpH =
nullptr;
257 for (
const auto& i : overrideHeaders) {
258 tmpH = ::curl_slist_append (tmpH, (i.fKey +
": "sv + i.fValue).AsUTF8<
string> ().c_str ());
261 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HTTPHEADER, tmpH));
262 if (fSavedHeaders_ !=
nullptr) {
263 ::curl_slist_free_all (fSavedHeaders_);
264 fSavedHeaders_ =
nullptr;
266 fSavedHeaders_ = tmpH;
268 ThrowIfError (::curl_easy_perform (fCurlHandle_));
271 ThrowIfError (::curl_easy_getinfo (fCurlHandle_, CURLINFO_RESPONSE_CODE, &resultCode));
273 if (fOptions_.fCache !=
nullptr) {
274 fOptions_.fCache->OnAfterFetch (cacheContext, &result);
276#if USE_NOISY_TRACE_IN_THIS_MODULE_
277 DbgTrace (
"returning status = {}, dataLen = {}"_f, result.GetStatus (), result.GetData ().size ());
283 nonvirtual
void SetAuthorityRelativeURL_ (
const URI& url)
285#if USE_NOISY_TRACE_IN_THIS_MODULE_
286 DbgTrace (
"Connection_LibCurl::Rep_::SetAuthorityRelativeURL_ ({})"_f, url);
290 newURL.SetAuthority (fURL_.GetAuthority ());
291 if (fCurlHandle_ !=
nullptr) {
292 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_URL, newURL.
As<
string> ().c_str ()));
298 nonvirtual
void MakeHandleIfNeeded_ ()
300 if (fCurlHandle_ ==
nullptr) {
306 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_URL, fURL_.As<
string> ().c_str ()));
308#if USE_LIBCURL_VERBOSE_
309 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_VERBOSE, 1));
317 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_NOSIGNAL, 1));
319#if qStroika_Foundation_Debug_AssertionsChecked && qStroika_Foundation_Common_Platform_POSIX
321 struct sigaction oldact;
322 (void)::sigaction (SIGPIPE, NULL, &oldact);
323 if (oldact.sa_handler == SIG_DFL) {
324 DbgTrace (
"Warning: no override of SIGPIPE. This is a risk factor with curl. Often just ignore"_f);
329 if (fOptions_.fMaxAutomaticRedirects == 0) {
330 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_FOLLOWLOCATION, 0L));
333 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_FOLLOWLOCATION, 1L));
334 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_MAXREDIRS, fOptions_.fMaxAutomaticRedirects));
337 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POSTREDIR, CURL_REDIR_POST_301));
340 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_READFUNCTION, &s_RequestPayloadReadHandler_));
341 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_READDATA,
this));
342 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_WRITEFUNCTION, &s_ResponseWriteHandler_));
343 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_WRITEDATA,
this));
344 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HEADERFUNCTION, &s_ResponseHeaderWriteHandler_));
345 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_WRITEHEADER,
this));
347 if (fOptions_.fTCPKeepAlives) {
348 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_TCP_KEEPALIVE, fOptions_.fTCPKeepAlives->fEnabled));
349#if qStroika_Foundation_Common_Platform_Linux
350 if (fOptions_.fTCPKeepAlives->fTimeIdleBeforeSendingKeepalives) {
351 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_TCP_KEEPIDLE, *fOptions_.fTCPKeepAlives->fTimeIdleBeforeSendingKeepalives));
353 if (fOptions_.fTCPKeepAlives->fTimeBetweenIndividualKeepaliveProbes) {
354 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_TCP_KEEPINTVL,
355 *fOptions_.fTCPKeepAlives->fTimeBetweenIndividualKeepaliveProbes));
363 static size_t s_RequestPayloadReadHandler_ (
char* buffer,
size_t size,
size_t nitems,
void* userP)
365 return reinterpret_cast<Rep_*
> (userP)->RequestPayloadReadHandler_ (
reinterpret_cast<byte*
> (buffer), size * nitems);
368 nonvirtual
size_t RequestPayloadReadHandler_ (
byte* buffer,
size_t bufSize)
370#if USE_NOISY_TRACE_IN_THIS_MODULE_
373 size_t bytes2Copy = fUploadData_.size () - fUploadDataCursor_;
374 bytes2Copy = min (bytes2Copy, bufSize);
375 ::memcpy (buffer, Traversal::Iterator2Pointer (begin (fUploadData_)) + fUploadDataCursor_, bytes2Copy);
376 fUploadDataCursor_ += bytes2Copy;
377#if USE_NOISY_TRACE_IN_THIS_MODULE_
378 DbgTrace (
"bufSize = {}, bytes2Copy={}"_f, bufSize, bytes2Copy);
384 static size_t s_ResponseWriteHandler_ (
char* ptr,
size_t size,
size_t nmemb,
void* userP)
386#if qStroika_FeatureSupported_Valgrind
387 VALGRIND_MAKE_MEM_DEFINED (ptr, size * nmemb);
389 return reinterpret_cast<Rep_*
> (userP)->ResponseWriteHandler_ (
reinterpret_cast<const byte*
> (ptr), size * nmemb);
392 nonvirtual
size_t ResponseWriteHandler_ (
const byte* ptr,
size_t nBytes)
394 fResponseData_.insert (fResponseData_.end (), ptr, ptr + nBytes);
399 static size_t s_ResponseHeaderWriteHandler_ (
char* ptr,
size_t size,
size_t nmemb,
void* userP)
401#if qStroika_FeatureSupported_Valgrind
402 VALGRIND_MAKE_MEM_DEFINED (ptr, size * nmemb);
404 return reinterpret_cast<Rep_*
> (userP)->ResponseHeaderWriteHandler_ (
reinterpret_cast<const byte*
> (ptr), size * nmemb);
407 nonvirtual
size_t ResponseHeaderWriteHandler_ (
const byte* ptr,
size_t nBytes)
409 String from = String::FromUTF8 (span{
reinterpret_cast<const char*
> (ptr), nBytes});
411 size_t i = from.
find (
':');
412 if (i != string::npos) {
418 fResponseHeaders_.Add (from, to);
423 void* fCurlHandle_{
nullptr};
425 bool fDidCustomMethod_{
false};
426 vector<byte> fUploadData_;
427 size_t fUploadDataCursor_{};
428 vector<byte> fResponseData_;
430 curl_slist* fSavedHeaders_{
nullptr};
439Connection::Ptr Transfer::LibCurl::Connection::New (
const Options& options)
441 return Connection::Ptr{make_shared<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
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...