Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
Connection_libcurl.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <csignal>
7
8#if qStroika_HasComponent_libcurl
9#include <curl/curl.h>
10#endif
11
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
20#include "Connection_libcurl.h"
21
22using namespace Stroika::Foundation;
24using namespace Stroika::Foundation::Execution;
25using namespace Stroika::Foundation::IO;
28using namespace Stroika::Foundation::Time;
29
32using Stroika::Foundation::IO::Network::Transfer::Connection::IRep;
33using Stroika::Foundation::IO::Network::Transfer::Connection::Options;
34
35// Comment this in to turn on aggressive noisy DbgTrace in this module
36//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
37
38// Uncomment this line to enable libcurl to print diagnostics to stderr
39//#define USE_LIBCURL_VERBOSE_ 1
40
41CompileTimeFlagChecker_SOURCE (Stroika::Foundation::IO::Network::Transfer, qStroika_HasComponent_libcurl, qStroika_HasComponent_libcurl);
42
43#if qStroika_HasComponent_libcurl
44using namespace Stroika::Foundation::IO::Network::Transfer::LibCurl;
45
46namespace {
47 void AssureLibCurlInitialized_ ()
48 {
49 static once_flag sOnceFlag_;
50 call_once (sOnceFlag_, [] () {
51 atexit ([] () { ::curl_global_cleanup (); });
52 /*
53 * @todo review CURL_GLOBAL_SSL
54 *
55 * @see http://curl.haxx.se/libcurl/c/curl_global_init.html
56 * not 100% sure what flags to send
57 * CURL_GLOBAL_SSL needed for now, but could interfere with other openssl uses
58 */
59 Verify (::curl_global_init (CURL_GLOBAL_SSL | CURL_GLOBAL_ACK_EINTR) == 0);
60 });
61 }
62}
63
64/*
65 ********************************************************************************
66 *********************** Transfer::LibCurl::error_category **********************
67 ********************************************************************************
68 */
69const std::error_category& Transfer::LibCurl::error_category () noexcept
70{
71 class LibCurl_error_category_ : public error_category {
72 public:
73 virtual const char* name () const noexcept override
74 {
75 return "LibCurl error";
76 }
77 virtual error_condition default_error_condition ([[maybe_unused]] int ev) const noexcept override
78 {
79 switch (ev) {
80 // the network itself, or the server at the other end
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; // as DNS maps EAI_NONAME - there is no 'name not found' errc
88 case CURLE_GOT_NOTHING:
89 return errc::connection_reset; // the server closed the connection without replying
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;
96 case CURLE_AGAIN:
97 return errc::resource_unavailable_try_again;
98 case CURLE_WEIRD_SERVER_REPLY:
99 return errc::protocol_error;
100
101 // SSL
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; //?
108
109 // the request, or what it asked for
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;
127
128 case CURLE_OUT_OF_MEMORY:
129 return errc::not_enough_memory;
130 }
131 // @todo - not sure how todo this - except by defining new conditions
132 //switch (ev) {
133 //}
134 return error_condition{errc::bad_message}; // no idea what to return here
135 }
136 virtual string message (int ccode) const override
137 {
138 return ::curl_easy_strerror (static_cast<CURLcode> (ccode));
139 }
140 };
141 return Common::Immortalize<LibCurl_error_category_> ();
142}
143
144namespace {
145 class Rep_ : public Transfer::LibCurl::Connection::IRep {
146 private:
147 Options fOptions_;
148
149 public:
150 Rep_ (const Options& options)
151 : fOptions_{options}
152 {
153 AssureLibCurlInitialized_ ();
154 }
155 Rep_ (const Rep_&) = delete;
156 virtual ~Rep_ ()
157 {
158 if (fCurlHandle_ != nullptr) {
159 ::curl_easy_cleanup (fCurlHandle_);
160 }
161 if (fSavedHeaders_ != nullptr) {
162 ::curl_slist_free_all (fSavedHeaders_);
163 fSavedHeaders_ = nullptr;
164 }
165 }
166
167 public:
168 nonvirtual Rep_& operator= (const Rep_&) = delete;
169
170 public:
171 virtual Options GetOptions () const override
172 {
173 return fOptions_;
174 }
175 virtual Time::DurationSeconds GetTimeout () const override
176 {
178 return 0s;
179 }
180 virtual void SetTimeout (Time::DurationSeconds timeout) override
181 {
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)));
185 }
186 virtual URI GetSchemeAndAuthority () const override
187 {
188 return fURL_.GetSchemeAndAuthority ();
189 }
190 virtual void SetSchemeAndAuthority (const URI& schemeAndAuthority) override
191 {
192#if USE_NOISY_TRACE_IN_THIS_MODULE_
193 DbgTrace ("Connection_LibCurl::Rep_::SetSchemeAndAuthority ('{}')"_f, schemeAndAuthority);
194#endif
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 ()));
199 }
200 }
201 virtual void Close () override
202 {
203 if (fCurlHandle_ != nullptr) {
204 ::curl_easy_cleanup (fCurlHandle_);
205 fCurlHandle_ = nullptr;
206 }
207 }
208 virtual Response Send (const Request& request) override
209 {
210#if USE_NOISY_TRACE_IN_THIS_MODULE_
211 Debug::TraceContextBumper ctx{"Connection_LibCurl::Rep_::Send", L"method='{}'", request.fMethod};
212#endif
213 Request useRequest = request;
214
215 SetAuthorityRelativeURL_ (useRequest.fAuthorityRelativeURL);
216
218 if (fOptions_.fCache != nullptr) {
219 if (auto r = fOptions_.fCache->OnBeforeFetch (&cacheContext, fURL_.GetSchemeAndAuthority (), &useRequest)) {
220 // shortcut - we already have a cached answer
221 return *r;
222 }
223 }
224
225 MakeHandleIfNeeded_ ();
226 fUploadData_ = useRequest.fData.As<vector<byte>> ();
227 fUploadDataCursor_ = 0;
228 fResponseData_.clear ();
229 fResponseHeaders_.clear ();
230 fCapturedCallbackException_ = nullptr;
231
232 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_USERAGENT, fOptions_.fUserAgent.AsUTF8 ().c_str ()));
233
234 if (fOptions_.fSupportSessionCookies) {
235 //@todo horrible kludge, but I couldnt find a better way. Will need to use soemthing else for windows, probably!
236 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_COOKIEFILE, "/dev/null"));
237 }
238
239 Mapping<String, String> overrideHeaders = useRequest.fOverrideHeaders;
240 if (fOptions_.fAssumeLowestCommonDenominatorHTTPServer) {
241 static const Mapping<String, String> kSilenceTheseHeaders_{
242 {pair<String, String>{"Expect"sv, {}}, pair<String, String>{"Transfer-Encoding"sv, {}}}};
243 overrideHeaders = kSilenceTheseHeaders_ + overrideHeaders;
244 }
245 if (fOptions_.fAuthentication and
246 fOptions_.fAuthentication->GetOptions () == Connection::Options::Authentication::Options::eProactivelySendAuthentication) {
247 overrideHeaders.Add (String::FromStringConstant (HeaderName::kAuthorization), fOptions_.fAuthentication->GetAuthToken ());
248 }
249 {
250 constexpr bool kDefault_FailConnectionIfSSLCertificateInvalid{false};
251 // ignore error if compiled without ssl
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);
258 }
259
260 if (useRequest.fMethod == HTTP::Methods::kGet) {
261 if (fDidCustomMethod_) {
262 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST, nullptr));
263 fDidCustomMethod_ = false;
264 }
265 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HTTPGET, 1));
266 }
267 else if (useRequest.fMethod == HTTP::Methods::kPost) {
268 if (fDidCustomMethod_) {
269 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST, nullptr));
270 fDidCustomMethod_ = false;
271 }
272 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POST, 1));
273 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POSTFIELDSIZE, fUploadData_.size ()));
274 }
275 else if (useRequest.fMethod == HTTP::Methods::kPut) {
276 if (fDidCustomMethod_) {
277 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST, nullptr));
278 fDidCustomMethod_ = false;
279 }
280 // CURLOPT_UPLOAD 1 even with no body (then INFILESIZE 0 - nothing to read): 0 means 'not an upload', which leaves
281 // the handle its default method, so the PUT went out as a GET
282 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_UPLOAD, 1));
283 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_INFILESIZE, fUploadData_.size ()));
284 }
285 else {
286 // any other method - DELETE, or UPnP's NOTIFY, say - is sent as POST is, by its own name: with its body, if it has one
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 ()));
291 }
292 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_CUSTOMREQUEST, useRequest.fMethod.AsUTF8 ().c_str ()));
293 fDidCustomMethod_ = true; // so the next GET, POST or PUT is sent by its own name, not this one
294 }
295
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)); // tell libcurl we can use "any" auth, which lets the lib pick one, but it also costs one extra round-trip and possibly sending of all the PUT data twice!
299 auto nameAndPassword = *fOptions_.fAuthentication->GetUsernameAndPassword (); // if eRespondToWWWAuthenticate we must have username/password (Options CTOR requirement)
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 ()));
302 }
303
304 // grab initial headers and do POST/etc based on args in request...
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 ());
308 }
309 AssertNotNull (fCurlHandle_);
310 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_HTTPHEADER, tmpH));
311 if (fSavedHeaders_ != nullptr) {
312 ::curl_slist_free_all (fSavedHeaders_);
313 fSavedHeaders_ = nullptr;
314 }
315 fSavedHeaders_ = tmpH;
316
317 // a callback that threw aborted the transfer itself, so its exception is the error - not whatever libcurl made of being aborted
318 CURLcode performResult = ::curl_easy_perform (fCurlHandle_);
319 ThrowIfCapturedCallbackException_ ();
320 ThrowIfError (performResult);
321
322 long resultCode = 0;
323 ThrowIfError (::curl_easy_getinfo (fCurlHandle_, CURLINFO_RESPONSE_CODE, &resultCode));
324 Response result{fResponseData_, static_cast<HTTP::Status> (resultCode), fResponseHeaders_};
325 if (fOptions_.fCache != nullptr) {
326 fOptions_.fCache->OnAfterFetch (cacheContext, &result);
327 }
328#if USE_NOISY_TRACE_IN_THIS_MODULE_
329 DbgTrace ("returning status = {}, dataLen = {}"_f, result.GetStatus (), result.GetData ().size ());
330#endif
331 return result;
332 }
333
334 private:
335 nonvirtual void SetAuthorityRelativeURL_ (const URI& url)
336 {
337#if USE_NOISY_TRACE_IN_THIS_MODULE_
338 DbgTrace ("Connection_LibCurl::Rep_::SetAuthorityRelativeURL_ ({})"_f, url);
339#endif
340 URI newURL = url; // almost but not quite the same as fURL_.Combine (url)
341 newURL.SetScheme (fURL_.GetScheme ());
342 newURL.SetAuthority (fURL_.GetAuthority ());
343 if (fCurlHandle_ != nullptr) {
344 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_URL, newURL.As<string> ().c_str ()));
345 }
346 fURL_ = newURL;
347 }
348
349 private:
350 nonvirtual void MakeHandleIfNeeded_ ()
351 {
352 if (fCurlHandle_ == nullptr) {
353 ThrowIfNull (fCurlHandle_ = ::curl_easy_init ());
354
355 /*
356 * Now setup COMMON options we ALWAYS set.
357 */
358 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_URL, fURL_.As<string> ().c_str ()));
359
360#if USE_LIBCURL_VERBOSE_
361 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_VERBOSE, 1));
362#endif
363
364 /*
365 * We may want this to be optional? Or use ares, for resolution (may want that anyhow).
366 * But for now, this is most likely to avoid untoward interactions with other libraries (guess)
367 * --LGP 2015-11-12
368 */
369 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_NOSIGNAL, 1));
370
371#if qStroika_Foundation_Debug_AssertionsChecked && qStroika_Platform_POSIX
372 {
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);
377 }
378 }
379#endif
380
381 if (fOptions_.fMaxAutomaticRedirects == 0) {
382 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_FOLLOWLOCATION, 0L));
383 }
384 else {
385 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_FOLLOWLOCATION, 1L));
386 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_MAXREDIRS, fOptions_.fMaxAutomaticRedirects));
387 // violate dejure standard but follow defacto standard and only senisble behavior
388 // https://curl.haxx.se/libcurl/c/CURLOPT_POSTREDIR.html
389 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_POSTREDIR, CURL_REDIR_POST_301)); // could have used CURL_REDIR_POST_ALL?
390 }
391
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));
398
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));
404 }
405 if (fOptions_.fTCPKeepAlives->fTimeBetweenIndividualKeepaliveProbes) {
406 ThrowIfError (::curl_easy_setopt (fCurlHandle_, CURLOPT_TCP_KEEPINTVL,
407 *fOptions_.fTCPKeepAlives->fTimeBetweenIndividualKeepaliveProbes));
408 }
409#endif
410 }
411 }
412 }
413
414 private:
415 /**
416 * Call one of the handlers below from a libcurl callback. They can throw - ResponseHeaderWriteHandler_ does, on a
417 * header that is not valid UTF-8 - and the exception must not escape into libcurl, whose frames lie between here
418 * and curl_easy_perform. libcurl is C, so its functions are nounwind, and with -flto the unwind tables for them go
419 * away: the unwinder then finds no handler at all and terminates. So catch it here, tell libcurl to give up on the
420 * transfer, and rethrow once its frames are gone.
421 *
422 * Returning fewer bytes than were handed to a write callback is how libcurl is told to abort one.
423 */
424 template <typename CALL_HANDLER>
425 static size_t Invoke_ (void* userP, size_t abortResult, CALL_HANDLER&& callHandler) noexcept
426 {
427 Rep_* rep = reinterpret_cast<Rep_*> (userP);
428 if (rep->fCapturedCallbackException_ == nullptr) [[likely]] {
429 try {
430 return callHandler (*rep);
431 }
432 catch (...) {
433 rep->fCapturedCallbackException_ = current_exception ();
434 }
435 }
436 return abortResult;
437 }
438 nonvirtual void ThrowIfCapturedCallbackException_ ()
439 {
440 if (fCapturedCallbackException_ != nullptr) [[unlikely]] {
441 rethrow_exception (fCapturedCallbackException_);
442 }
443 }
444
445 static size_t s_RequestPayloadReadHandler_ (char* buffer, size_t size, size_t nitems, void* userP)
446 {
447 return Invoke_ (userP, CURL_READFUNC_ABORT,
448 [&] (Rep_& rep) { return rep.RequestPayloadReadHandler_ (reinterpret_cast<byte*> (buffer), size * nitems); });
449 }
450
451 nonvirtual size_t RequestPayloadReadHandler_ (byte* buffer, size_t bufSize)
452 {
453#if USE_NOISY_TRACE_IN_THIS_MODULE_
454 Debug::TraceContextBumper ctx{"Connection_LibCurl::Rep_::RequestPayloadReadHandler_"};
455#endif
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);
462#endif
463 return bytes2Copy;
464 }
465
466 private:
467 static size_t s_ResponseWriteHandler_ (char* ptr, size_t size, size_t nmemb, void* userP)
468 {
469#if qStroika_FeatureSupported_Valgrind
470 VALGRIND_MAKE_MEM_DEFINED (ptr, size * nmemb); // Handle OpenSSL if not built with purify
471#endif
472 return Invoke_ (userP, 0,
473 [&] (Rep_& rep) { return rep.ResponseWriteHandler_ (reinterpret_cast<const byte*> (ptr), size * nmemb); });
474 }
475
476 nonvirtual size_t ResponseWriteHandler_ (const byte* ptr, size_t nBytes)
477 {
478 fResponseData_.insert (fResponseData_.end (), ptr, ptr + nBytes);
479 return nBytes;
480 }
481
482 private:
483 static size_t s_ResponseHeaderWriteHandler_ (char* ptr, size_t size, size_t nmemb, void* userP)
484 {
485#if qStroika_FeatureSupported_Valgrind
486 VALGRIND_MAKE_MEM_DEFINED (ptr, size * nmemb); // Handle OpenSSL if not built with purify
487#endif
488 return Invoke_ (userP, 0,
489 [&] (Rep_& rep) { return rep.ResponseHeaderWriteHandler_ (reinterpret_cast<const byte*> (ptr), size * nmemb); });
490 }
491
492 nonvirtual size_t ResponseHeaderWriteHandler_ (const byte* ptr, size_t nBytes)
493 {
494 String from = String::FromUTF8 (span{reinterpret_cast<const char*> (ptr), nBytes});
495 String to;
496 size_t i = from.find (':');
497 if (i != string::npos) {
498 to = from.SubString (i + 1);
499 from = from.SubString (0, i);
500 }
501 from = from.Trim ();
502 to = to.Trim ();
503 fResponseHeaders_.Add (from, to);
504 return nBytes;
505 }
506
507 private:
508 void* fCurlHandle_{nullptr};
509 URI fURL_;
510 bool fDidCustomMethod_{false};
511 vector<byte> fUploadData_;
512 size_t fUploadDataCursor_{};
513 vector<byte> fResponseData_;
514 Mapping<String, String> fResponseHeaders_;
515 curl_slist* fSavedHeaders_{nullptr};
516 // a callback's exception, as thrown - so rethrown whole: a throw of *pointer-to-base would slice it
517 exception_ptr fCapturedCallbackException_;
518 };
519}
520
521/*
522 ********************************************************************************
523 ****************** Transfer::LibCurl::Connection::New **************************
524 ********************************************************************************
525 */
526Connection::Ptr Transfer::LibCurl::Connection::New (const Options& options)
527{
528 return Connection::Ptr{Memory::MakeSharedPtr<Rep_> (options)};
529}
530#endif
#define AssertNotNull(p)
Definition Assertions.h:336
#define AssertNotImplemented()
Definition Assertions.h:404
#define Verify(c)
Definition Assertions.h:422
#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.
Definition Realtime.h:57
#define DbgTrace
Definition Trace.h:327
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
nonvirtual String SubString(SZ from) const
nonvirtual String Trim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
Definition String.cpp:1651
nonvirtual size_t find(Character c, size_t startAt=0) const
Definition String.inl:1065
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)
Definition Mapping.inl:186
nonvirtual URI GetSchemeAndAuthority() const
Definition URI.inl:90
nonvirtual T As(optional< StringPCTEncodedFlag > pctEncoded={}) const
nonvirtual optional< SchemeType > GetScheme() const
Definition URI.inl:36
nonvirtual optional< Authority > GetAuthority() const
Definition URI.inl:55
nonvirtual void SetScheme(const optional< SchemeType > &scheme)
Definition URI.inl:41
void ThrowIfNull(const Private_::ConstVoidStar &p, const HRESULT &hr)
Template specialization for ThrowIfNull (), for thing being thrown HRESULT - really throw HRESULTErro...