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
80 case CURLE_OUT_OF_MEMORY:
81 return errc::not_enough_memory;
82 case CURLE_OPERATION_TIMEDOUT:
83 return errc::timed_out;
84 case CURLE_LOGIN_DENIED:
85 return errc::permission_denied;
86 case CURLE_SEND_ERROR:
87 return errc::io_error;
88 case CURLE_RECV_ERROR:
89 return errc::io_error;
94 return error_condition{errc::bad_message};
96 virtual string message (
int ccode)
const override
98 return ::curl_easy_strerror (
static_cast<CURLcode
> (ccode));
101 return Common::Immortalize<LibCurl_error_category_> ();
105 class Rep_ :
public Transfer::LibCurl::Connection::IRep {
110 Rep_ (
const Options& options)
113 AssureLibCurlInitialized_ ();
115 Rep_ (
const Rep_&) =
delete;
118 if (fCurlHandle_ !=
nullptr) {
119 ::curl_easy_cleanup (fCurlHandle_);
121 if (fSavedHeaders_ !=
nullptr) {
122 ::curl_slist_free_all (fSavedHeaders_);
123 fSavedHeaders_ =
nullptr;
128 nonvirtual Rep_& operator= (
const Rep_&) =
delete;
131 virtual Options GetOptions ()
const override
142 MakeHandleIfNeeded_ ();
143 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_TIMEOUT_MS,
static_cast<int> (timeout.count () * 1000)));
144 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CONNECTTIMEOUT_MS,
static_cast<int> (timeout.count () * 1000)));
146 virtual URI GetSchemeAndAuthority ()
const override
150 virtual void SetSchemeAndAuthority (
const URI& schemeAndAuthority)
override
152#if USE_NOISY_TRACE_IN_THIS_MODULE_
153 DbgTrace (
"Connection_LibCurl::Rep_::SetSchemeAndAuthority ('{}')"_f, schemeAndAuthority);
155 fURL_.SetScheme (schemeAndAuthority.
GetScheme ());
156 fURL_.SetAuthority (schemeAndAuthority.
GetAuthority ());
157 if (fCurlHandle_ !=
nullptr) {
158 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_URL, fURL_.As<
string> ().c_str ()));
161 virtual void Close ()
override
163 if (fCurlHandle_ !=
nullptr) {
164 ::curl_easy_cleanup (fCurlHandle_);
165 fCurlHandle_ =
nullptr;
170#if USE_NOISY_TRACE_IN_THIS_MODULE_
175 SetAuthorityRelativeURL_ (useRequest.fAuthorityRelativeURL);
178 if (fOptions_.fCache !=
nullptr) {
179 if (
auto r = fOptions_.fCache->OnBeforeFetch (&cacheContext, fURL_.GetSchemeAndAuthority (), &useRequest)) {
185 MakeHandleIfNeeded_ ();
186 fUploadData_ = useRequest.fData.As<vector<byte>> ();
187 fUploadDataCursor_ = 0;
188 fResponseData_.clear ();
189 fResponseHeaders_.clear ();
191 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_USERAGENT, fOptions_.fUserAgent.AsUTF8 ().c_str ()));
193 if (fOptions_.fSupportSessionCookies) {
195 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_COOKIEFILE,
"/dev/null"));
199 if (fOptions_.fAssumeLowestCommonDenominatorHTTPServer) {
201 {pair<String, String>{
"Expect"sv, {}}, pair<String, String>{
"Transfer-Encoding"sv, {}}}};
202 overrideHeaders = kSilenceTheseHeaders_ + overrideHeaders;
204 if (fOptions_.fAuthentication and
205 fOptions_.fAuthentication->GetOptions () == Connection::Options::Authentication::Options::eProactivelySendAuthentication) {
206 overrideHeaders.
Add (String::FromStringConstant (HeaderName::kAuthorization), fOptions_.fAuthentication->GetAuthToken ());
209 constexpr bool kDefault_FailConnectionIfSSLCertificateInvalid{
false};
211 (void)::curl_easy_setopt (
212 fCurlHandle_, CURLOPT_SSL_VERIFYPEER,
213 fOptions_.fFailConnectionIfSSLCertificateInvalid.value_or (kDefault_FailConnectionIfSSLCertificateInvalid) ? 1L : 0L);
214 (void)::curl_easy_setopt (
215 fCurlHandle_, CURLOPT_SSL_VERIFYHOST,
216 fOptions_.fFailConnectionIfSSLCertificateInvalid.value_or (kDefault_FailConnectionIfSSLCertificateInvalid) ? 2L : 0L);
219 if (useRequest.fMethod == HTTP::Methods::kGet) {
220 if (fDidCustomMethod_) {
221 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST,
nullptr));
222 fDidCustomMethod_ =
false;
224 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HTTPGET, 1));
226 else if (useRequest.fMethod == HTTP::Methods::kPost) {
227 if (fDidCustomMethod_) {
228 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST,
nullptr));
229 fDidCustomMethod_ =
false;
231 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POST, 1));
232 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POSTFIELDSIZE, fUploadData_.size ()));
234 else if (useRequest.fMethod == HTTP::Methods::kPut) {
235 if (fDidCustomMethod_) {
236 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST,
nullptr));
237 fDidCustomMethod_ =
false;
239 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_UPLOAD, fUploadData_.empty () ? 0 : 1));
240 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_INFILESIZE, fUploadData_.size ()));
243 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HTTPGET, 0));
244 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POST, 0));
245 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST, useRequest.fMethod.AsUTF8 ().c_str ()));
248 if (fOptions_.fAuthentication and
249 fOptions_.fAuthentication->GetOptions () == Connection::Options::Authentication::Options::eRespondToWWWAuthenticate) {
250 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HTTPAUTH, (
long)CURLAUTH_ANY));
251 auto nameAndPassword = *fOptions_.fAuthentication->GetUsernameAndPassword ();
252 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_USERNAME, nameAndPassword.first.AsUTF8 ().c_str ()));
253 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_PASSWORD, nameAndPassword.second.AsUTF8 ().c_str ()));
257 curl_slist* tmpH =
nullptr;
258 for (
const auto& i : overrideHeaders) {
259 tmpH = ::curl_slist_append (tmpH, (i.fKey +
": "sv + i.fValue).AsUTF8<
string> ().c_str ());
262 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HTTPHEADER, tmpH));
263 if (fSavedHeaders_ !=
nullptr) {
264 ::curl_slist_free_all (fSavedHeaders_);
265 fSavedHeaders_ =
nullptr;
267 fSavedHeaders_ = tmpH;
269 ThrowIfError (::curl_easy_perform (fCurlHandle_));
272 ThrowIfError (::curl_easy_getinfo (fCurlHandle_, CURLINFO_RESPONSE_CODE, &resultCode));
274 if (fOptions_.fCache !=
nullptr) {
275 fOptions_.fCache->OnAfterFetch (cacheContext, &result);
277#if USE_NOISY_TRACE_IN_THIS_MODULE_
278 DbgTrace (
"returning status = {}, dataLen = {}"_f, result.GetStatus (), result.GetData ().size ());
284 nonvirtual
void SetAuthorityRelativeURL_ (
const URI& url)
286#if USE_NOISY_TRACE_IN_THIS_MODULE_
287 DbgTrace (
"Connection_LibCurl::Rep_::SetAuthorityRelativeURL_ ({})"_f, url);
291 newURL.SetAuthority (fURL_.GetAuthority ());
292 if (fCurlHandle_ !=
nullptr) {
293 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_URL, newURL.
As<
string> ().c_str ()));
299 nonvirtual
void MakeHandleIfNeeded_ ()
301 if (fCurlHandle_ ==
nullptr) {
307 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_URL, fURL_.As<
string> ().c_str ()));
309#if USE_LIBCURL_VERBOSE_
310 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_VERBOSE, 1));
318 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_NOSIGNAL, 1));
320#if qStroika_Foundation_Debug_AssertionsChecked && qStroika_Foundation_Common_Platform_POSIX
322 struct sigaction oldact;
323 (void)::sigaction (SIGPIPE, NULL, &oldact);
324 if (oldact.sa_handler == SIG_DFL) {
325 DbgTrace (
"Warning: no override of SIGPIPE. This is a risk factor with curl. Often just ignore"_f);
330 if (fOptions_.fMaxAutomaticRedirects == 0) {
331 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_FOLLOWLOCATION, 0L));
334 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_FOLLOWLOCATION, 1L));
335 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_MAXREDIRS, fOptions_.fMaxAutomaticRedirects));
338 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POSTREDIR, CURL_REDIR_POST_301));
341 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_READFUNCTION, &s_RequestPayloadReadHandler_));
342 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_READDATA,
this));
343 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_WRITEFUNCTION, &s_ResponseWriteHandler_));
344 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_WRITEDATA,
this));
345 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HEADERFUNCTION, &s_ResponseHeaderWriteHandler_));
346 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_WRITEHEADER,
this));
348 if (fOptions_.fTCPKeepAlives) {
349 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_TCP_KEEPALIVE, fOptions_.fTCPKeepAlives->fEnabled));
350#if qStroika_Foundation_Common_Platform_Linux
351 if (fOptions_.fTCPKeepAlives->fTimeIdleBeforeSendingKeepalives) {
352 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_TCP_KEEPIDLE, *fOptions_.fTCPKeepAlives->fTimeIdleBeforeSendingKeepalives));
354 if (fOptions_.fTCPKeepAlives->fTimeBetweenIndividualKeepaliveProbes) {
355 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_TCP_KEEPINTVL,
356 *fOptions_.fTCPKeepAlives->fTimeBetweenIndividualKeepaliveProbes));
364 static size_t s_RequestPayloadReadHandler_ (
char* buffer,
size_t size,
size_t nitems,
void* userP)
366 return reinterpret_cast<Rep_*
> (userP)->RequestPayloadReadHandler_ (
reinterpret_cast<byte*
> (buffer), size * nitems);
369 nonvirtual
size_t RequestPayloadReadHandler_ (
byte* buffer,
size_t bufSize)
371#if USE_NOISY_TRACE_IN_THIS_MODULE_
374 size_t bytes2Copy = fUploadData_.size () - fUploadDataCursor_;
375 bytes2Copy = min (bytes2Copy, bufSize);
376 ::memcpy (buffer, Traversal::Iterator2Pointer (begin (fUploadData_)) + fUploadDataCursor_, bytes2Copy);
377 fUploadDataCursor_ += bytes2Copy;
378#if USE_NOISY_TRACE_IN_THIS_MODULE_
379 DbgTrace (
"bufSize = {}, bytes2Copy={}"_f, bufSize, bytes2Copy);
385 static size_t s_ResponseWriteHandler_ (
char* ptr,
size_t size,
size_t nmemb,
void* userP)
387#if qStroika_FeatureSupported_Valgrind
388 VALGRIND_MAKE_MEM_DEFINED (ptr, size * nmemb);
390 return reinterpret_cast<Rep_*
> (userP)->ResponseWriteHandler_ (
reinterpret_cast<const byte*
> (ptr), size * nmemb);
393 nonvirtual
size_t ResponseWriteHandler_ (
const byte* ptr,
size_t nBytes)
395 fResponseData_.insert (fResponseData_.end (), ptr, ptr + nBytes);
400 static size_t s_ResponseHeaderWriteHandler_ (
char* ptr,
size_t size,
size_t nmemb,
void* userP)
402#if qStroika_FeatureSupported_Valgrind
403 VALGRIND_MAKE_MEM_DEFINED (ptr, size * nmemb);
405 return reinterpret_cast<Rep_*
> (userP)->ResponseHeaderWriteHandler_ (
reinterpret_cast<const byte*
> (ptr), size * nmemb);
408 nonvirtual
size_t ResponseHeaderWriteHandler_ (
const byte* ptr,
size_t nBytes)
410 String from = String::FromUTF8 (span{
reinterpret_cast<const char*
> (ptr), nBytes});
412 size_t i = from.
find (
':');
413 if (i != string::npos) {
419 fResponseHeaders_.Add (from, to);
424 void* fCurlHandle_{
nullptr};
426 bool fDidCustomMethod_{
false};
427 vector<byte> fUploadData_;
428 size_t fUploadDataCursor_{};
429 vector<byte> fResponseData_;
431 curl_slist* fSavedHeaders_{
nullptr};
440Connection::Ptr Transfer::LibCurl::Connection::New (
const Options& options)
442 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
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...