1#include "cpr/session.h"
2
3#include <algorithm>
4#include <cstdlib>
5#include <cstring>
6#include <fstream>
7#include <functional>
8#include <iostream>
9#include <stdexcept>
10#include <string>
11
12#include <curl/curl.h>
13
14#include "cpr/async.h"
15#include "cpr/cprtypes.h"
16#include "cpr/interceptor.h"
17#include "cpr/util.h"
18
19#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION
20#include "cpr/ssl_ctx.h"
21#endif
22
23
24namespace cpr {
25// Ignored here since libcurl reqires a long:
26// NOLINTNEXTLINE(google-runtime-int)
27constexpr long ON = 1L;
28// Ignored here since libcurl reqires a long:
29// NOLINTNEXTLINE(google-runtime-int)
30constexpr long OFF = 0L;
31
32CURLcode Session::DoEasyPerform() {
33 if (isUsedInMultiPerform) {
34 std::cerr << "curl_easy_perform cannot be executed if the CURL handle is used in a MultiPerform." << std::endl;
35 return CURLcode::CURLE_FAILED_INIT;
36 }
37 return curl_easy_perform(curl: curl_->handle);
38}
39
40void Session::SetHeaderInternal() {
41 curl_slist* chunk = nullptr;
42 for (const std::pair<const std::string, std::string>& item : header_) {
43 std::string header_string = item.first;
44 if (item.second.empty()) {
45 header_string += ";";
46 } else {
47 header_string += ": " + item.second;
48 }
49
50 curl_slist* temp = curl_slist_append(list: chunk, data: header_string.c_str());
51 if (temp) {
52 chunk = temp;
53 }
54 }
55
56 // Set the chunked transfer encoding in case it does not already exist:
57 if (chunkedTransferEncoding_ && header_.find(x: "Transfer-Encoding") == header_.end()) {
58 curl_slist* temp = curl_slist_append(list: chunk, data: "Transfer-Encoding:chunked");
59 if (temp) {
60 chunk = temp;
61 }
62 }
63
64 // libcurl would prepare the header "Expect: 100-continue" by default when uploading files larger than 1 MB.
65 // Here we would like to disable this feature:
66 curl_slist* temp = curl_slist_append(list: chunk, data: "Expect:");
67 if (temp) {
68 chunk = temp;
69 }
70
71 curl_easy_setopt(curl_->handle, CURLOPT_HTTPHEADER, chunk);
72
73 curl_slist_free_all(list: curl_->chunk);
74 curl_->chunk = chunk;
75}
76
77// Only supported with libcurl >= 7.61.0.
78// As an alternative use SetHeader and add the token manually.
79#if LIBCURL_VERSION_NUM >= 0x073D00
80void Session::SetBearer(const Bearer& token) {
81 // Ignore here since this has been defined by libcurl.
82 curl_easy_setopt(curl_->handle, CURLOPT_HTTPAUTH, CURLAUTH_BEARER);
83 curl_easy_setopt(curl_->handle, CURLOPT_XOAUTH2_BEARER, token.GetToken());
84}
85#endif
86
87Session::Session() : curl_(new CurlHolder()) {
88 // Set up some sensible defaults
89 curl_version_info_data* version_info = curl_version_info(CURLVERSION_NOW);
90 const std::string version = "curl/" + std::string{version_info->version};
91 curl_easy_setopt(curl_->handle, CURLOPT_USERAGENT, version.c_str());
92 SetRedirect(Redirect());
93 curl_easy_setopt(curl_->handle, CURLOPT_NOPROGRESS, 1L);
94 curl_easy_setopt(curl_->handle, CURLOPT_ERRORBUFFER, curl_->error.data());
95 curl_easy_setopt(curl_->handle, CURLOPT_COOKIEFILE, "");
96#ifdef CPR_CURL_NOSIGNAL
97 curl_easy_setopt(curl_->handle, CURLOPT_NOSIGNAL, 1L);
98#endif
99
100#if LIBCURL_VERSION_NUM >= 0x071900
101 curl_easy_setopt(curl_->handle, CURLOPT_TCP_KEEPALIVE, 1L);
102#endif
103}
104
105Response Session::makeDownloadRequest() {
106 if (!interceptors_.empty()) {
107 return intercept();
108 }
109
110 const CURLcode curl_error = DoEasyPerform();
111
112 return CompleteDownload(curl_error);
113}
114
115void Session::prepareCommon() {
116 assert(curl_->handle);
117
118 // Set Header:
119 SetHeaderInternal();
120
121 const std::string parametersContent = parameters_.GetContent(*curl_);
122 if (!parametersContent.empty()) {
123 const Url new_url{url_ + "?" + parametersContent};
124 curl_easy_setopt(curl_->handle, CURLOPT_URL, new_url.c_str());
125 } else {
126 curl_easy_setopt(curl_->handle, CURLOPT_URL, url_.c_str());
127 }
128
129 // Proxy:
130 const std::string protocol = url_.str().substr(pos: 0, n: url_.str().find(c: ':'));
131 if (proxies_.has(protocol)) {
132 curl_easy_setopt(curl_->handle, CURLOPT_PROXY, proxies_[protocol].c_str());
133 if (proxyAuth_.has(protocol)) {
134 curl_easy_setopt(curl_->handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
135 curl_easy_setopt(curl_->handle, CURLOPT_PROXYUSERPWD, proxyAuth_[protocol]);
136 }
137 }
138
139#if LIBCURL_VERSION_NUM >= 0x072100
140 if (acceptEncoding_.empty()) {
141 // Enable all supported built-in compressions
142 curl_easy_setopt(curl_->handle, CURLOPT_ACCEPT_ENCODING, "");
143 } else if (acceptEncoding_.disabled()) {
144 // Disable curl adding the 'Accept-Encoding' header
145 curl_easy_setopt(curl_->handle, CURLOPT_ACCEPT_ENCODING, nullptr);
146 } else {
147 curl_easy_setopt(curl_->handle, CURLOPT_ACCEPT_ENCODING, acceptEncoding_.getString().c_str());
148 }
149#endif
150
151#if LIBCURL_VERSION_NUM >= 0x077100
152#if SUPPORT_SSL_NO_REVOKE
153 // NOLINTNEXTLINE (google-runtime-int)
154 long bitmask{0};
155 curl_easy_setopt(curl_->handle, CURLOPT_SSL_OPTIONS, &bitmask);
156 const bool noRevoke = bitmask & CURLSSLOPT_NO_REVOKE;
157#endif
158
159 // Fix loading certs from Windows cert store when using OpenSSL:
160 curl_easy_setopt(curl_->handle, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
161
162// Ensure SSL no revoke is still set
163#if SUPPORT_SSL_NO_REVOKE
164 if (noRevoke) {
165 curl_easy_setopt(curl_->handle, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE);
166 }
167#endif
168#endif
169
170 curl_->error[0] = '\0';
171
172 response_string_.clear();
173 if (response_string_reserve_size_ > 0) {
174 response_string_.reserve(res_arg: response_string_reserve_size_);
175 }
176 header_string_.clear();
177 if (!this->writecb_.callback) {
178 curl_easy_setopt(curl_->handle, CURLOPT_WRITEFUNCTION, cpr::util::writeFunction);
179 curl_easy_setopt(curl_->handle, CURLOPT_WRITEDATA, &response_string_);
180 }
181 if (!this->headercb_.callback) {
182 curl_easy_setopt(curl_->handle, CURLOPT_HEADERFUNCTION, cpr::util::writeFunction);
183 curl_easy_setopt(curl_->handle, CURLOPT_HEADERDATA, &header_string_);
184 }
185
186 // Enable so we are able to retrive certificate information:
187 curl_easy_setopt(curl_->handle, CURLOPT_CERTINFO, 1L);
188}
189
190void Session::prepareCommonDownload() {
191 assert(curl_->handle);
192
193 // Set Header:
194 SetHeaderInternal();
195
196 const std::string parametersContent = parameters_.GetContent(*curl_);
197 if (!parametersContent.empty()) {
198 const Url new_url{url_ + "?" + parametersContent};
199 curl_easy_setopt(curl_->handle, CURLOPT_URL, new_url.c_str());
200 } else {
201 curl_easy_setopt(curl_->handle, CURLOPT_URL, url_.c_str());
202 }
203
204 const std::string protocol = url_.str().substr(pos: 0, n: url_.str().find(c: ':'));
205 if (proxies_.has(protocol)) {
206 curl_easy_setopt(curl_->handle, CURLOPT_PROXY, proxies_[protocol].c_str());
207 if (proxyAuth_.has(protocol)) {
208 curl_easy_setopt(curl_->handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
209 curl_easy_setopt(curl_->handle, CURLOPT_PROXYUSERPWD, proxyAuth_[protocol]);
210 }
211 }
212
213 curl_->error[0] = '\0';
214
215 header_string_.clear();
216 if (headercb_.callback) {
217 curl_easy_setopt(curl_->handle, CURLOPT_HEADERFUNCTION, cpr::util::headerUserFunction);
218 curl_easy_setopt(curl_->handle, CURLOPT_HEADERDATA, &headercb_);
219 } else {
220 curl_easy_setopt(curl_->handle, CURLOPT_HEADERFUNCTION, cpr::util::writeFunction);
221 curl_easy_setopt(curl_->handle, CURLOPT_HEADERDATA, &header_string_);
222 }
223}
224
225Response Session::makeRequest() {
226 if (!interceptors_.empty()) {
227 return intercept();
228 }
229
230 const CURLcode curl_error = DoEasyPerform();
231 return Complete(curl_error);
232}
233
234void Session::SetLimitRate(const LimitRate& limit_rate) {
235 curl_easy_setopt(curl_->handle, CURLOPT_MAX_RECV_SPEED_LARGE, limit_rate.downrate);
236 curl_easy_setopt(curl_->handle, CURLOPT_MAX_SEND_SPEED_LARGE, limit_rate.uprate);
237}
238
239void Session::SetReadCallback(const ReadCallback& read) {
240 readcb_ = read;
241 curl_easy_setopt(curl_->handle, CURLOPT_INFILESIZE_LARGE, read.size);
242 curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, read.size);
243 curl_easy_setopt(curl_->handle, CURLOPT_READFUNCTION, cpr::util::readUserFunction);
244 curl_easy_setopt(curl_->handle, CURLOPT_READDATA, &readcb_);
245 chunkedTransferEncoding_ = read.size == -1;
246}
247
248void Session::SetHeaderCallback(const HeaderCallback& header) {
249 curl_easy_setopt(curl_->handle, CURLOPT_HEADERFUNCTION, cpr::util::headerUserFunction);
250 headercb_ = header;
251 curl_easy_setopt(curl_->handle, CURLOPT_HEADERDATA, &headercb_);
252}
253
254void Session::SetWriteCallback(const WriteCallback& write) {
255 curl_easy_setopt(curl_->handle, CURLOPT_WRITEFUNCTION, cpr::util::writeUserFunction);
256 writecb_ = write;
257 curl_easy_setopt(curl_->handle, CURLOPT_WRITEDATA, &writecb_);
258}
259
260void Session::SetProgressCallback(const ProgressCallback& progress) {
261 progresscb_ = progress;
262 if (isCancellable) {
263 cancellationcb_.SetProgressCallback(progresscb_);
264 return;
265 }
266#if LIBCURL_VERSION_NUM < 0x072000
267 curl_easy_setopt(curl_->handle, CURLOPT_PROGRESSFUNCTION, cpr::util::progressUserFunction<ProgressCallback>);
268 curl_easy_setopt(curl_->handle, CURLOPT_PROGRESSDATA, &progresscb_);
269#else
270 curl_easy_setopt(curl_->handle, CURLOPT_XFERINFOFUNCTION, cpr::util::progressUserFunction<ProgressCallback>);
271 curl_easy_setopt(curl_->handle, CURLOPT_XFERINFODATA, &progresscb_);
272#endif
273 curl_easy_setopt(curl_->handle, CURLOPT_NOPROGRESS, 0L);
274}
275
276void Session::SetDebugCallback(const DebugCallback& debug) {
277 curl_easy_setopt(curl_->handle, CURLOPT_DEBUGFUNCTION, cpr::util::debugUserFunction);
278 debugcb_ = debug;
279 curl_easy_setopt(curl_->handle, CURLOPT_DEBUGDATA, &debugcb_);
280 curl_easy_setopt(curl_->handle, CURLOPT_VERBOSE, 1L);
281}
282
283void Session::SetUrl(const Url& url) {
284 url_ = url;
285}
286
287void Session::SetResolve(const Resolve& resolve) {
288 SetResolves({resolve});
289}
290
291void Session::SetResolves(const std::vector<Resolve>& resolves) {
292 curl_slist_free_all(list: curl_->resolveCurlList);
293 curl_->resolveCurlList = nullptr;
294 for (const Resolve& resolve : resolves) {
295 for (const uint16_t port : resolve.ports) {
296 curl_->resolveCurlList = curl_slist_append(list: curl_->resolveCurlList, data: (resolve.host + ":" + std::to_string(val: port) + ":" + resolve.addr).c_str());
297 }
298 }
299 curl_easy_setopt(curl_->handle, CURLOPT_RESOLVE, curl_->resolveCurlList);
300}
301
302void Session::SetParameters(const Parameters& parameters) {
303 parameters_ = parameters;
304}
305
306void Session::SetParameters(Parameters&& parameters) {
307 parameters_ = std::move(parameters);
308}
309
310void Session::SetHeader(const Header& header) {
311 header_ = header;
312}
313
314void Session::UpdateHeader(const Header& header) {
315 for (const std::pair<const std::string, std::string>& item : header) {
316 header_[item.first] = item.second;
317 }
318}
319
320void Session::SetTimeout(const Timeout& timeout) {
321 curl_easy_setopt(curl_->handle, CURLOPT_TIMEOUT_MS, timeout.Milliseconds());
322}
323
324void Session::SetConnectTimeout(const ConnectTimeout& timeout) {
325 curl_easy_setopt(curl_->handle, CURLOPT_CONNECTTIMEOUT_MS, timeout.Milliseconds());
326}
327
328void Session::SetAuth(const Authentication& auth) {
329 // Ignore here since this has been defined by libcurl.
330 switch (auth.GetAuthMode()) {
331 case AuthMode::BASIC:
332 curl_easy_setopt(curl_->handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
333 curl_easy_setopt(curl_->handle, CURLOPT_USERPWD, auth.GetAuthString());
334 break;
335 case AuthMode::DIGEST:
336 curl_easy_setopt(curl_->handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
337 curl_easy_setopt(curl_->handle, CURLOPT_USERPWD, auth.GetAuthString());
338 break;
339 case AuthMode::NTLM:
340 curl_easy_setopt(curl_->handle, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
341 curl_easy_setopt(curl_->handle, CURLOPT_USERPWD, auth.GetAuthString());
342 break;
343 }
344}
345
346void Session::SetUserAgent(const UserAgent& ua) {
347 curl_easy_setopt(curl_->handle, CURLOPT_USERAGENT, ua.c_str());
348}
349
350void Session::SetPayload(const Payload& payload) {
351 hasBodyOrPayload_ = true;
352 const std::string content = payload.GetContent(*curl_);
353 curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(content.length()));
354 curl_easy_setopt(curl_->handle, CURLOPT_COPYPOSTFIELDS, content.c_str());
355}
356
357void Session::SetPayload(Payload&& payload) {
358 hasBodyOrPayload_ = true;
359 const std::string content = payload.GetContent(*curl_);
360 curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(content.length()));
361 curl_easy_setopt(curl_->handle, CURLOPT_COPYPOSTFIELDS, content.c_str());
362}
363
364void Session::SetProxies(const Proxies& proxies) {
365 proxies_ = proxies;
366}
367
368void Session::SetProxies(Proxies&& proxies) {
369 proxies_ = std::move(proxies);
370}
371
372void Session::SetProxyAuth(ProxyAuthentication&& proxy_auth) {
373 proxyAuth_ = std::move(proxy_auth);
374}
375
376void Session::SetProxyAuth(const ProxyAuthentication& proxy_auth) {
377 proxyAuth_ = proxy_auth;
378}
379
380void Session::SetMultipart(const Multipart& multipart) {
381 // Make sure, we have a empty multipart to start with:
382 if (curl_->multipart) {
383 curl_mime_free(mime: curl_->multipart);
384 }
385 curl_->multipart = curl_mime_init(easy: curl_->handle);
386
387 // Add all multipart pieces:
388 for (const Part& part : multipart.parts) {
389 if (part.is_file) {
390 for (const File& file : part.files) {
391 curl_mimepart* mimePart = curl_mime_addpart(mime: curl_->multipart);
392 if (!part.content_type.empty()) {
393 curl_mime_type(part: mimePart, mimetype: part.content_type.c_str());
394 }
395
396 curl_mime_filedata(part: mimePart, filename: file.filepath.c_str());
397 curl_mime_name(part: mimePart, name: part.name.c_str());
398
399 if (file.hasOverridenFilename()) {
400 curl_mime_filename(part: mimePart, filename: file.overriden_filename.c_str());
401 }
402 }
403 } else {
404 curl_mimepart* mimePart = curl_mime_addpart(mime: curl_->multipart);
405 if (!part.content_type.empty()) {
406 curl_mime_type(part: mimePart, mimetype: part.content_type.c_str());
407 }
408 if (part.is_buffer) {
409 // Do not use formdata, to prevent having to use reinterpreter_cast:
410 curl_mime_name(part: mimePart, name: part.name.c_str());
411 curl_mime_data(part: mimePart, data: part.data, datasize: part.datalen);
412 curl_mime_filename(part: mimePart, filename: part.value.c_str());
413 } else {
414 curl_mime_name(part: mimePart, name: part.name.c_str());
415 curl_mime_data(part: mimePart, data: part.value.c_str(), CURL_ZERO_TERMINATED);
416 }
417 }
418 }
419
420 curl_easy_setopt(curl_->handle, CURLOPT_MIMEPOST, curl_->multipart);
421 hasBodyOrPayload_ = true;
422}
423
424void Session::SetMultipart(Multipart&& multipart) {
425 SetMultipart(multipart);
426}
427
428void Session::SetRedirect(const Redirect& redirect) {
429 curl_easy_setopt(curl_->handle, CURLOPT_FOLLOWLOCATION, redirect.follow ? 1L : 0L);
430 curl_easy_setopt(curl_->handle, CURLOPT_MAXREDIRS, redirect.maximum);
431 curl_easy_setopt(curl_->handle, CURLOPT_UNRESTRICTED_AUTH, redirect.cont_send_cred ? 1L : 0L);
432
433 // NOLINTNEXTLINE (google-runtime-int)
434 long mask = 0;
435 if (any(flag: redirect.post_flags & PostRedirectFlags::POST_301)) {
436 mask |= CURL_REDIR_POST_301;
437 }
438 if (any(flag: redirect.post_flags & PostRedirectFlags::POST_302)) {
439 mask |= CURL_REDIR_POST_302;
440 }
441 if (any(flag: redirect.post_flags & PostRedirectFlags::POST_303)) {
442 mask |= CURL_REDIR_POST_303;
443 }
444 curl_easy_setopt(curl_->handle, CURLOPT_POSTREDIR, mask);
445}
446
447void Session::SetCookies(const Cookies& cookies) {
448 curl_easy_setopt(curl_->handle, CURLOPT_COOKIELIST, "ALL");
449 curl_easy_setopt(curl_->handle, CURLOPT_COOKIE, cookies.GetEncoded(*curl_).c_str());
450}
451
452void Session::SetBody(const Body& body) {
453 hasBodyOrPayload_ = true;
454 curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(body.str().length()));
455 curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDS, body.c_str());
456}
457
458void Session::SetBody(Body&& body) {
459 hasBodyOrPayload_ = true;
460 curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(body.str().length()));
461 curl_easy_setopt(curl_->handle, CURLOPT_COPYPOSTFIELDS, body.c_str());
462}
463
464void Session::SetLowSpeed(const LowSpeed& low_speed) {
465 curl_easy_setopt(curl_->handle, CURLOPT_LOW_SPEED_LIMIT, low_speed.limit);
466 curl_easy_setopt(curl_->handle, CURLOPT_LOW_SPEED_TIME, low_speed.time);
467}
468
469void Session::SetVerifySsl(const VerifySsl& verify) {
470 curl_easy_setopt(curl_->handle, CURLOPT_SSL_VERIFYPEER, verify ? ON : OFF);
471 curl_easy_setopt(curl_->handle, CURLOPT_SSL_VERIFYHOST, verify ? 2L : 0L);
472}
473
474void Session::SetUnixSocket(const UnixSocket& unix_socket) {
475 curl_easy_setopt(curl_->handle, CURLOPT_UNIX_SOCKET_PATH, unix_socket.GetUnixSocketString());
476}
477
478void Session::SetSslOptions(const SslOptions& options) {
479 if (!options.cert_file.empty()) {
480 curl_easy_setopt(curl_->handle, CURLOPT_SSLCERT, options.cert_file.c_str());
481 if (!options.cert_type.empty()) {
482 curl_easy_setopt(curl_->handle, CURLOPT_SSLCERTTYPE, options.cert_type.c_str());
483 }
484 }
485 if (!options.key_file.empty()) {
486 curl_easy_setopt(curl_->handle, CURLOPT_SSLKEY, options.key_file.c_str());
487 if (!options.key_type.empty()) {
488 curl_easy_setopt(curl_->handle, CURLOPT_SSLKEYTYPE, options.key_type.c_str());
489 }
490 if (!options.key_pass.empty()) {
491 curl_easy_setopt(curl_->handle, CURLOPT_KEYPASSWD, options.key_pass.c_str());
492 }
493#if SUPPORT_CURLOPT_SSLKEY_BLOB
494 } else if (!options.key_blob.empty()) {
495 std::string key_blob(options.key_blob);
496 curl_blob blob{};
497 // NOLINTNEXTLINE (readability-container-data-pointer)
498 blob.data = &key_blob[0];
499 blob.len = key_blob.length();
500 curl_easy_setopt(curl_->handle, CURLOPT_SSLKEY_BLOB, &blob);
501 if (!options.key_type.empty()) {
502 curl_easy_setopt(curl_->handle, CURLOPT_SSLKEYTYPE, options.key_type.c_str());
503 }
504 if (!options.key_pass.empty()) {
505 curl_easy_setopt(curl_->handle, CURLOPT_KEYPASSWD, options.key_pass.c_str());
506 }
507#endif
508 }
509 if (!options.pinned_public_key.empty()) {
510 curl_easy_setopt(curl_->handle, CURLOPT_PINNEDPUBLICKEY, options.pinned_public_key.c_str());
511 }
512#if SUPPORT_ALPN
513 curl_easy_setopt(curl_->handle, CURLOPT_SSL_ENABLE_ALPN, options.enable_alpn ? ON : OFF);
514#endif
515#if SUPPORT_NPN
516 curl_easy_setopt(curl_->handle, CURLOPT_SSL_ENABLE_NPN, options.enable_npn ? ON : OFF);
517#endif
518 curl_easy_setopt(curl_->handle, CURLOPT_SSL_VERIFYPEER, options.verify_peer ? ON : OFF);
519 curl_easy_setopt(curl_->handle, CURLOPT_SSL_VERIFYHOST, options.verify_host ? 2L : 0L);
520#if LIBCURL_VERSION_NUM >= 0x072900
521 curl_easy_setopt(curl_->handle, CURLOPT_SSL_VERIFYSTATUS, options.verify_status ? ON : OFF);
522#endif
523
524 int maxTlsVersion = options.ssl_version;
525#if SUPPORT_MAX_TLS_VERSION
526 maxTlsVersion |= options.max_version;
527#endif
528
529 curl_easy_setopt(curl_->handle, CURLOPT_SSLVERSION,
530 // Ignore here since this has been defined by libcurl.
531 maxTlsVersion);
532#if SUPPORT_SSL_NO_REVOKE
533 if (options.ssl_no_revoke) {
534 curl_easy_setopt(curl_->handle, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE);
535 }
536#endif
537 if (!options.ca_info.empty()) {
538 curl_easy_setopt(curl_->handle, CURLOPT_CAINFO, options.ca_info.c_str());
539 }
540 if (!options.ca_path.empty()) {
541 curl_easy_setopt(curl_->handle, CURLOPT_CAPATH, options.ca_path.c_str());
542 }
543#if SUPPORT_CURLOPT_SSL_CTX_FUNCTION
544#ifdef OPENSSL_BACKEND_USED
545 if (!options.ca_buffer.empty()) {
546 curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_FUNCTION, sslctx_function_load_ca_cert_from_buffer);
547 curl_easy_setopt(curl_->handle, CURLOPT_SSL_CTX_DATA, options.ca_buffer.c_str());
548 }
549#endif
550#endif
551 if (!options.crl_file.empty()) {
552 curl_easy_setopt(curl_->handle, CURLOPT_CRLFILE, options.crl_file.c_str());
553 }
554 if (!options.ciphers.empty()) {
555 curl_easy_setopt(curl_->handle, CURLOPT_SSL_CIPHER_LIST, options.ciphers.c_str());
556 }
557#if SUPPORT_TLSv13_CIPHERS
558 if (!options.tls13_ciphers.empty()) {
559 curl_easy_setopt(curl_->handle, CURLOPT_TLS13_CIPHERS, options.ciphers.c_str());
560 }
561#endif
562#if SUPPORT_SESSIONID_CACHE
563 curl_easy_setopt(curl_->handle, CURLOPT_SSL_SESSIONID_CACHE, options.session_id_cache ? ON : OFF);
564#endif
565}
566
567void Session::SetVerbose(const Verbose& verbose) {
568 curl_easy_setopt(curl_->handle, CURLOPT_VERBOSE, verbose.verbose ? ON : OFF);
569}
570
571void Session::SetInterface(const Interface& iface) {
572 if (iface.str().empty()) {
573 curl_easy_setopt(curl_->handle, CURLOPT_INTERFACE, nullptr);
574 } else {
575 curl_easy_setopt(curl_->handle, CURLOPT_INTERFACE, iface.c_str());
576 }
577}
578
579void Session::SetLocalPort(const LocalPort& local_port) {
580 curl_easy_setopt(curl_->handle, CURLOPT_LOCALPORT, local_port);
581}
582
583void Session::SetLocalPortRange(const LocalPortRange& local_port_range) {
584 curl_easy_setopt(curl_->handle, CURLOPT_LOCALPORTRANGE, local_port_range);
585}
586
587void Session::SetHttpVersion(const HttpVersion& version) {
588 switch (version.code) {
589 case HttpVersionCode::VERSION_NONE:
590 curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_NONE);
591 break;
592
593 case HttpVersionCode::VERSION_1_0:
594 curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
595 break;
596
597 case HttpVersionCode::VERSION_1_1:
598 curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
599 break;
600
601#if LIBCURL_VERSION_NUM >= 0x072100 // 7.33.0
602 case HttpVersionCode::VERSION_2_0:
603 curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
604 break;
605#endif
606
607#if LIBCURL_VERSION_NUM >= 0x072F00 // 7.47.0
608 case HttpVersionCode::VERSION_2_0_TLS:
609 curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
610 break;
611#endif
612
613#if LIBCURL_VERSION_NUM >= 0x073100 // 7.49.0
614 case HttpVersionCode::VERSION_2_0_PRIOR_KNOWLEDGE:
615 curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
616 break;
617#endif
618
619#if LIBCURL_VERSION_NUM >= 0x074200 // 7.66.0
620 case HttpVersionCode::VERSION_3_0:
621 curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
622 break;
623#endif
624
625 default: // Should not happen
626 throw std::invalid_argument("Invalid/Unknown HTTP version type.");
627 break;
628 }
629}
630
631void Session::SetRange(const Range& range) {
632 const std::string range_str = range.str();
633 curl_easy_setopt(curl_->handle, CURLOPT_RANGE, range_str.c_str());
634}
635
636void Session::SetMultiRange(const MultiRange& multi_range) {
637 const std::string multi_range_str = multi_range.str();
638 curl_easy_setopt(curl_->handle, CURLOPT_RANGE, multi_range_str.c_str());
639}
640
641void Session::SetReserveSize(const ReserveSize& reserve_size) {
642 ResponseStringReserve(size: reserve_size.size);
643}
644
645void Session::SetAcceptEncoding(const AcceptEncoding& accept_encoding) {
646 acceptEncoding_ = accept_encoding;
647}
648
649void Session::SetAcceptEncoding(AcceptEncoding&& accept_encoding) {
650 acceptEncoding_ = std::move(accept_encoding);
651}
652
653cpr_off_t Session::GetDownloadFileLength() {
654 cpr_off_t downloadFileLenth = -1;
655 curl_easy_setopt(curl_->handle, CURLOPT_URL, url_.c_str());
656
657 const std::string protocol = url_.str().substr(pos: 0, n: url_.str().find(c: ':'));
658 if (proxies_.has(protocol)) {
659 curl_easy_setopt(curl_->handle, CURLOPT_PROXY, proxies_[protocol].c_str());
660 if (proxyAuth_.has(protocol)) {
661 curl_easy_setopt(curl_->handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
662 curl_easy_setopt(curl_->handle, CURLOPT_PROXYUSERPWD, proxyAuth_[protocol]);
663 }
664 }
665
666 curl_easy_setopt(curl_->handle, CURLOPT_HTTPGET, 1);
667 curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 1);
668 if (DoEasyPerform() == CURLE_OK) {
669 // NOLINTNEXTLINE (google-runtime-int)
670 long status_code{};
671 curl_easy_getinfo(curl_->handle, CURLINFO_RESPONSE_CODE, &status_code);
672 if (200 == status_code) {
673 curl_easy_getinfo(curl_->handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &downloadFileLenth);
674 }
675 }
676 return downloadFileLenth;
677}
678
679void Session::ResponseStringReserve(size_t size) {
680 response_string_reserve_size_ = size;
681}
682
683Response Session::Delete() {
684 PrepareDelete();
685 return makeRequest();
686}
687
688Response Session::Download(const WriteCallback& write) {
689 PrepareDownload(write);
690 return makeDownloadRequest();
691}
692
693Response Session::Download(std::ofstream& file) {
694 PrepareDownload(file);
695 return makeDownloadRequest();
696}
697
698Response Session::Get() {
699 PrepareGet();
700 return makeRequest();
701}
702
703Response Session::Head() {
704 PrepareHead();
705 return makeRequest();
706}
707
708Response Session::Options() {
709 PrepareOptions();
710 return makeRequest();
711}
712
713Response Session::Patch() {
714 PreparePatch();
715 return makeRequest();
716}
717
718Response Session::Post() {
719 PreparePost();
720 return makeRequest();
721}
722
723Response Session::Put() {
724 PreparePut();
725 return makeRequest();
726}
727
728std::shared_ptr<Session> Session::GetSharedPtrFromThis() {
729 try {
730 return shared_from_this();
731 } catch (std::bad_weak_ptr&) {
732 throw std::runtime_error("Failed to get a shared pointer from this. The reason is probably that the session object is not managed by a shared pointer, which is required to use this functionality.");
733 }
734}
735
736AsyncResponse Session::GetAsync() {
737 auto shared_this = shared_from_this();
738 return async(fn: [shared_this]() { return shared_this->Get(); });
739}
740
741AsyncResponse Session::DeleteAsync() {
742 return async(fn: [shared_this = GetSharedPtrFromThis()]() { return shared_this->Delete(); });
743}
744
745AsyncResponse Session::DownloadAsync(const WriteCallback& write) {
746 return async(fn: [shared_this = GetSharedPtrFromThis(), write]() { return shared_this->Download(write); });
747}
748
749AsyncResponse Session::DownloadAsync(std::ofstream& file) {
750 return async(fn: [shared_this = GetSharedPtrFromThis(), &file]() { return shared_this->Download(file); });
751}
752
753AsyncResponse Session::HeadAsync() {
754 return async(fn: [shared_this = GetSharedPtrFromThis()]() { return shared_this->Head(); });
755}
756
757AsyncResponse Session::OptionsAsync() {
758 return async(fn: [shared_this = GetSharedPtrFromThis()]() { return shared_this->Options(); });
759}
760
761AsyncResponse Session::PatchAsync() {
762 return async(fn: [shared_this = GetSharedPtrFromThis()]() { return shared_this->Patch(); });
763}
764
765AsyncResponse Session::PostAsync() {
766 return async(fn: [shared_this = GetSharedPtrFromThis()]() { return shared_this->Post(); });
767}
768
769AsyncResponse Session::PutAsync() {
770 return async(fn: [shared_this = GetSharedPtrFromThis()]() { return shared_this->Put(); });
771}
772
773std::shared_ptr<CurlHolder> Session::GetCurlHolder() {
774 return curl_;
775}
776
777std::string Session::GetFullRequestUrl() {
778 const std::string parametersContent = parameters_.GetContent(*curl_);
779 return url_.str() + (parametersContent.empty() ? "" : "?") + parametersContent;
780}
781
782void Session::PrepareDelete() {
783 curl_easy_setopt(curl_->handle, CURLOPT_HTTPGET, 0L);
784 curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L);
785 curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, "DELETE");
786 prepareCommon();
787}
788
789void Session::PrepareGet() {
790 // In case there is a body or payload for this request, we create a custom GET-Request since a
791 // GET-Request with body is based on the HTTP RFC **not** a leagal request.
792 if (hasBodyOrPayload_) {
793 curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L);
794 curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, "GET");
795 } else {
796 curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L);
797 curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, nullptr);
798 curl_easy_setopt(curl_->handle, CURLOPT_HTTPGET, 1L);
799 }
800 prepareCommon();
801}
802
803void Session::PrepareHead() {
804 curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 1L);
805 curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, nullptr);
806 prepareCommon();
807}
808
809void Session::PrepareOptions() {
810 curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L);
811 curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, "OPTIONS");
812 prepareCommon();
813}
814
815void Session::PreparePatch() {
816 curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L);
817 curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, "PATCH");
818 prepareCommon();
819}
820
821void Session::PreparePost() {
822 curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L);
823
824 // In case there is no body or payload set it to an empty post:
825 if (hasBodyOrPayload_) {
826 curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, nullptr);
827 } else {
828 curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDS, readcb_.callback ? nullptr : "");
829 curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, "POST");
830 }
831 prepareCommon();
832}
833
834void Session::PreparePut() {
835 curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L);
836 if (!hasBodyOrPayload_ && readcb_.callback) {
837 /**
838 * Yes, this one has to be CURLOPT_POSTFIELDS even if we are performing a PUT request.
839 * In case we don't set this one, performing a POST-request with PUT won't work.
840 * It in theory this only enforces the usage of the readcallback for POST requests, but works here as well.
841 **/
842 curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDS, nullptr);
843 }
844 curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, "PUT");
845 curl_easy_setopt(curl_->handle, CURLOPT_RANGE, nullptr);
846 prepareCommon();
847}
848
849void Session::PrepareDownload(std::ofstream& file) {
850 curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L);
851 curl_easy_setopt(curl_->handle, CURLOPT_HTTPGET, 1);
852 curl_easy_setopt(curl_->handle, CURLOPT_WRITEFUNCTION, cpr::util::writeFileFunction);
853 curl_easy_setopt(curl_->handle, CURLOPT_WRITEDATA, &file);
854 curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, nullptr);
855
856 prepareCommonDownload();
857}
858
859void Session::PrepareDownload(const WriteCallback& write) {
860 curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L);
861 curl_easy_setopt(curl_->handle, CURLOPT_HTTPGET, 1);
862 curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, nullptr);
863
864 SetWriteCallback(write);
865
866 prepareCommonDownload();
867}
868
869Response Session::Complete(CURLcode curl_error) {
870 curl_slist* raw_cookies{nullptr};
871 curl_easy_getinfo(curl_->handle, CURLINFO_COOKIELIST, &raw_cookies);
872 Cookies cookies = util::parseCookies(raw_cookies);
873 curl_slist_free_all(list: raw_cookies);
874
875 // Reset the has no body property:
876 hasBodyOrPayload_ = false;
877
878 std::string errorMsg = curl_->error.data();
879 return Response(curl_, std::move(response_string_), std::move(header_string_), std::move(cookies), Error(curl_error, std::move(errorMsg)));
880}
881
882Response Session::CompleteDownload(CURLcode curl_error) {
883 if (!headercb_.callback) {
884 curl_easy_setopt(curl_->handle, CURLOPT_HEADERFUNCTION, nullptr);
885 curl_easy_setopt(curl_->handle, CURLOPT_HEADERDATA, 0);
886 }
887
888 curl_slist* raw_cookies{nullptr};
889 curl_easy_getinfo(curl_->handle, CURLINFO_COOKIELIST, &raw_cookies);
890 Cookies cookies = util::parseCookies(raw_cookies);
891 curl_slist_free_all(list: raw_cookies);
892 std::string errorMsg = curl_->error.data();
893
894 return Response(curl_, "", std::move(header_string_), std::move(cookies), Error(curl_error, std::move(errorMsg)));
895}
896
897void Session::AddInterceptor(const std::shared_ptr<Interceptor>& pinterceptor) {
898 interceptors_.push(x: pinterceptor);
899}
900
901Response Session::proceed() {
902 prepareCommon();
903 return makeRequest();
904}
905
906Response Session::intercept() {
907 // At least one interceptor exists -> Execute its intercept function
908 const std::shared_ptr<Interceptor> interceptor = interceptors_.front();
909 interceptors_.pop();
910 return interceptor->intercept(session&: *this);
911}
912
913// clang-format off
914void Session::SetOption(const Resolve& resolve) { SetResolve(resolve); }
915void Session::SetOption(const std::vector<Resolve>& resolves) { SetResolves(resolves); }
916void Session::SetOption(const ReadCallback& read) { SetReadCallback(read); }
917void Session::SetOption(const HeaderCallback& header) { SetHeaderCallback(header); }
918void Session::SetOption(const WriteCallback& write) { SetWriteCallback(write); }
919void Session::SetOption(const ProgressCallback& progress) { SetProgressCallback(progress); }
920void Session::SetOption(const DebugCallback& debug) { SetDebugCallback(debug); }
921void Session::SetOption(const Url& url) { SetUrl(url); }
922void Session::SetOption(const Parameters& parameters) { SetParameters(parameters); }
923void Session::SetOption(Parameters&& parameters) { SetParameters(std::move(parameters)); }
924void Session::SetOption(const Header& header) { SetHeader(header); }
925void Session::SetOption(const Timeout& timeout) { SetTimeout(timeout); }
926void Session::SetOption(const ConnectTimeout& timeout) { SetConnectTimeout(timeout); }
927void Session::SetOption(const Authentication& auth) { SetAuth(auth); }
928void Session::SetOption(const LimitRate& limit_rate) { SetLimitRate(limit_rate); }
929// Only supported with libcurl >= 7.61.0.
930// As an alternative use SetHeader and add the token manually.
931#if LIBCURL_VERSION_NUM >= 0x073D00
932void Session::SetOption(const Bearer& auth) { SetBearer(auth); }
933#endif
934void Session::SetOption(const UserAgent& ua) { SetUserAgent(ua); }
935void Session::SetOption(const Payload& payload) { SetPayload(payload); }
936void Session::SetOption(Payload&& payload) { SetPayload(std::move(payload)); }
937void Session::SetOption(const Proxies& proxies) { SetProxies(proxies); }
938void Session::SetOption(Proxies&& proxies) { SetProxies(std::move(proxies)); }
939void Session::SetOption(ProxyAuthentication&& proxy_auth) { SetProxyAuth(std::move(proxy_auth)); }
940void Session::SetOption(const ProxyAuthentication& proxy_auth) { SetProxyAuth(proxy_auth); }
941void Session::SetOption(const Multipart& multipart) { SetMultipart(multipart); }
942void Session::SetOption(Multipart&& multipart) { SetMultipart(std::move(multipart)); }
943void Session::SetOption(const Redirect& redirect) { SetRedirect(redirect); }
944void Session::SetOption(const Cookies& cookies) { SetCookies(cookies); }
945void Session::SetOption(const Body& body) { SetBody(body); }
946void Session::SetOption(Body&& body) { SetBody(std::move(body)); }
947void Session::SetOption(const LowSpeed& low_speed) { SetLowSpeed(low_speed); }
948void Session::SetOption(const VerifySsl& verify) { SetVerifySsl(verify); }
949void Session::SetOption(const Verbose& verbose) { SetVerbose(verbose); }
950void Session::SetOption(const UnixSocket& unix_socket) { SetUnixSocket(unix_socket); }
951void Session::SetOption(const SslOptions& options) { SetSslOptions(options); }
952void Session::SetOption(const Interface& iface) { SetInterface(iface); }
953void Session::SetOption(const LocalPort& local_port) { SetLocalPort(local_port); }
954void Session::SetOption(const LocalPortRange& local_port_range) { SetLocalPortRange(local_port_range); }
955void Session::SetOption(const HttpVersion& version) { SetHttpVersion(version); }
956void Session::SetOption(const Range& range) { SetRange(range); }
957void Session::SetOption(const MultiRange& multi_range) { SetMultiRange(multi_range); }
958void Session::SetOption(const ReserveSize& reserve_size) { SetReserveSize(reserve_size.size); }
959void Session::SetOption(const AcceptEncoding& accept_encoding) { SetAcceptEncoding(accept_encoding); }
960void Session::SetOption(AcceptEncoding&& accept_encoding) { SetAcceptEncoding(accept_encoding); }
961// clang-format on
962
963void Session::SetCancellationParam(std::shared_ptr<std::atomic_bool> param) {
964 cancellationcb_ = CancellationCallback{std::move(param)};
965 isCancellable = true;
966#if LIBCURL_VERSION_NUM < 0x072000
967 curl_easy_setopt(curl_->handle, CURLOPT_PROGRESSFUNCTION, cpr::util::progressUserFunction<CancellationCallback>);
968 curl_easy_setopt(curl_->handle, CURLOPT_PROGRESSDATA, &cancellationcb_);
969#else
970 curl_easy_setopt(curl_->handle, CURLOPT_XFERINFOFUNCTION, cpr::util::progressUserFunction<CancellationCallback>);
971 curl_easy_setopt(curl_->handle, CURLOPT_XFERINFODATA, &cancellationcb_);
972#endif
973 curl_easy_setopt(curl_->handle, CURLOPT_NOPROGRESS, 0L);
974}
975} // namespace cpr
976