This repository has been archived by the owner on Mar 4, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mkcurl.hpp
778 lines (708 loc) · 26.3 KB
/
mkcurl.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
// Part of Measurement Kit <https://measurement-kit.github.io/>.
// Measurement Kit is free software under the BSD license. See AUTHORS
// and LICENSE for more information on the copying conditions.
#ifndef MEASUREMENT_KIT_MKCURL_HPP
#define MEASUREMENT_KIT_MKCURL_HPP
#include <stdint.h>
#include <memory>
#include <string>
#include <vector>
/// MKCURL_INLINE_NAMESPACE controls the inline inner namespace in which
/// public symbols exported by this library are enclosed.
///
/// See <https://github.com/measurement-kit/measurement-kit/issues/1867#issuecomment-514562622>.
#define MKCURL_INLINE_NAMESPACE v0_11_2_or_greater
namespace mk {
namespace curl {
inline namespace MKCURL_INLINE_NAMESPACE {
/// Request is an HTTP request.
struct Request {
/// ca_path is the path to the CA bundle to use.
std::string ca_path;
/// enable_http2 indicates whether we should enable HTTP2.
bool enable_http2 = false;
/// method is the method we want to use.
std::string method = "GET";
/// url is the URL we want to use.
std::string url;
/// headers contains the request headers.
std::vector<std::string> headers;
/// body contains the request body (possibly a binary body).
std::string body;
/// timeout is the time after which the request is aborted (in seconds). A
/// value of zero means that no timeout is implemented.
int64_t timeout = 0;
/// proxy_url is the optional URL of the proxy to use.
std::string proxy_url;
/// enable_fastopen will enable TCP fastopen (if possible).
bool enable_fastopen = false;
/// follow_redir indicates whether we should follow redirects.
bool follow_redir = false;
/// connect_to is the string to pass to CURLOPT_CONNECT_TO. In the common
/// case, you want to set this string to `::<IP>:`.
std::string connect_to;
/// retries tells this library how many times it needs to retry if
/// a request fails because of a DNS error or a connect error. Note
/// that the number here is the number of times a request will be
/// _retried_, i.e., it does not count the initial request.
size_t retries = 2;
};
/// Log is a log entry.
struct Log {
/// msec is the number of milliseconds after which the specified event
/// was logged computed using C++'s steady clock.
int64_t msec = 0;
/// line is the specific log line.
std::string line;
};
/// Response is an HTTP response.
struct Response {
/// error is the CURL error that occurred. In CURL this is an enum hence it
/// is castable to int. Therefore using int64_t should always be okay. The
/// value of zero is equivalent to CURLE_OK.
int64_t error = 0;
/// redirect_url is the URL to which we were redirected, if any.
std::string redirect_url;
/// status_code is the HTTP status code. In CURL this is a long hence
/// using int64_t should always be wide enough.
int64_t status_code = 0;
/// body is the response body.
std::string body;
/// bytes_sent are the bytes sent when sending the request.
int64_t bytes_sent = 0;
/// bytes_recv are the bytes recv when receiving the response.
int64_t bytes_recv = 0;
// logs contains the (possibly non UTF-8) logs.
std::vector<Log> logs;
// request_headers contains the request line and the headers.
std::string request_headers;
// response_headers contains the response line and the headers.
std::string response_headers;
// certs contains a sequence of newline separated PEM certificates.
std::string certs;
// content_type is the response content type.
std::string content_type;
// http_version is the HTTP version.
std::string http_version;
};
/// Client is an HTTP client. This class is movable but not copyable because
/// at any give moment we want only a single client instance.
///
/// This is because a Client wraps a cURL handle and:
///
/// Handles. You must never share the same handle in multiple threads. You
/// can pass the handles around among threads, but you must never use a single
/// handle from more than one thread at any given time.
///
/// (see <https://curl.haxx.se/libcurl/c/threadsafe.html>) hence having single
/// ownership in place guarantees that we cannot make such mistakes.
class Client {
public:
/// Client creates a new client.
Client() noexcept;
/// Client is the deleted copy constructor.
Client(const Client &) noexcept = delete;
/// Client is the deleted copy assignment.
Client &operator=(const Client &) noexcept = delete;
/// Client is the move constructor.
Client(Client &&) noexcept;
/// Client is the move assignment.
Client &operator=(Client &&) noexcept;
/// ~Client is the destructor.
~Client() noexcept;
/// perform performs @p request and returns the Response.
Response perform(const Request &request) noexcept;
private:
// Impl is the implementation of a client.
class Impl;
// impl_ is a unique pointer to the opaque implementation.
std::unique_ptr<Impl> impl_;
};
/// perform performs @p request and returns the Response.
Response perform(const Request &request) noexcept;
} // inline namespace MKCURL_INLINE_NAMESPACE
} // namespace curl
} // namespace mk
// If you just want to know about the API, you can stop reading here. What
// follows is the inline implementation of the library. By default it is not
// included when you include the header, but you can change this.
#ifdef MKCURL_INLINE_IMPL
#include <assert.h>
#include <algorithm>
#include <chrono>
#include <sstream>
#include <curl/curl.h>
#include "mkmock.hpp"
// MKCURL_MOCK controls whether to enable mocking
#ifdef MKCURL_MOCK
#define MKCURL_HOOK MKMOCK_HOOK_ENABLED
#define MKCURL_HOOK_ALLOC MKMOCK_HOOK_ALLOC_ENABLED
#else
#define MKCURL_HOOK MKMOCK_HOOK_DISABLED
#define MKCURL_HOOK_ALLOC MKMOCK_HOOK_ALLOC_DISABLED
#endif
#ifndef MKCURL_ABORT
// MKCURL_ABORT allows to mock abort
#define MKCURL_ABORT abort
#endif
namespace mk {
namespace curl {
inline namespace MKCURL_INLINE_NAMESPACE {
// mkcurl_log appends @p line to @p logs. It adds information on the current
// time in millisecond. It also appends a newline to the end of the line.
static void mkcurl_log(std::vector<Log> &logs, std::string &&line) {
auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch());
Log log;
log.msec = now.count();
std::swap(line, log.line);
logs.push_back(std::move(log));
}
// mkcurl_deleter is a custom deleter for a CURL handle.
struct mkcurl_deleter {
void operator()(CURL *handle) { curl_easy_cleanup(handle); }
};
// mkcurl_uptr is a unique pointer to a CURL handle.
using mkcurl_uptr = std::unique_ptr<CURL, mkcurl_deleter>;
// Client::Impl contains the implementation of a client.
class Client::Impl {
public:
mkcurl_uptr handle;
Impl() noexcept = default;
Impl(const Impl &) noexcept = delete;
Impl &operator=(const Impl &) noexcept = delete;
Impl(Impl &&) noexcept = delete;
Impl &operator=(Impl &&) noexcept = delete;
~Impl() noexcept;
};
Client::Impl::~Impl() noexcept = default; // Avoid `-Wweak-vtables`
// mkcurl_slist is a curl_slist with RAII semantic.
struct mkcurl_slist {
// mkcurl_slist is the default constructor.
mkcurl_slist() = default;
// mkcurl_slist is the deleted copy constructor.
mkcurl_slist(const mkcurl_slist &) = delete;
// operator= is the deleted copy assignment.
mkcurl_slist &operator=(const mkcurl_slist &) = delete;
// mkcurl_slist is the deleted move constructor.
mkcurl_slist(mkcurl_slist &&) = delete;
// operator= is the deleted move assignment.
mkcurl_slist &operator=(mkcurl_slist &&) = delete;
// ~mkcurl_slist is the destructor.
~mkcurl_slist() { curl_slist_free_all(p); }
// p is the pointer to the wrapped slist.
curl_slist *p = nullptr;
};
} // inline namespace MKCURL_INLINE_NAMESPACE
} // namespace curl
} // namespace mk
extern "C" {
static size_t mkcurl_body_cb_(
char *ptr, size_t size, size_t nmemb, void *userdata) {
if (nmemb <= 0) {
return 0; // This means "no body"
}
if (size > SIZE_MAX / nmemb) {
// If size is zero we end up into this branch.
return 0;
}
if (ptr == nullptr || userdata == nullptr) {
MKCURL_ABORT();
}
auto realsiz = size * nmemb; // Overflow or zero not possible (see above)
auto res = static_cast<mk::curl::Response *>(userdata);
res->body += std::string{ptr, realsiz};
// From fwrite(3): "[the return value] equals the number of bytes
// written _only_ when `size` equals `1`". See also
// https://sourceware.org/git/?p=glibc.git;a=blob;f=libio/iofwrite.c;h=800341b7da546e5b7fd2005c5536f4c90037f50d;hb=HEAD#l29
return nmemb;
}
static int mkcurl_debug_cb_(CURL *handle,
curl_infotype type,
char *data,
size_t size,
void *userptr) {
(void)handle;
if (data == nullptr || userptr == nullptr) {
MKCURL_ABORT();
}
auto res = static_cast<mk::curl::Response *>(userptr);
auto log_many_lines = [&](std::string prefix, const std::string &str) {
std::stringstream ss;
ss << str;
std::string line;
while (std::getline(ss, line, '\n')) {
std::stringstream logline;
if (!prefix.empty()) {
logline << prefix << " ";
}
logline << line;
mkcurl_log(res->logs, logline.str());
}
};
switch (type) {
case CURLINFO_TEXT:
log_many_lines("", std::string{(const char *)data, size});
break;
case CURLINFO_HEADER_IN:
{
std::string s{(const char *)data, size};
log_many_lines("<", s);
res->response_headers += s;
}
break;
case CURLINFO_DATA_IN:
log_many_lines("<data:", std::to_string(size));
break;
case CURLINFO_SSL_DATA_IN:
log_many_lines("<tls_data:", std::to_string(size));
break;
case CURLINFO_HEADER_OUT:
{
std::string s{(const char *)data, size};
log_many_lines(">", s);
res->request_headers += s;
}
break;
case CURLINFO_DATA_OUT:
log_many_lines(">data:", std::to_string(size));
break;
case CURLINFO_SSL_DATA_OUT:
log_many_lines(">tls_data:", std::to_string(size));
break;
case CURLINFO_END:
/* NOTHING */
break;
}
// Note regarding counting TLS data
// ````````````````````````````````
//
// I am using the technique recommended by Stenberg on Stack Overflow [1]. It
// was initially not clear to me whether cURL using OpenSSL counted the data
// twice, once encrypted and once in clear text. However, using cURL using
// OpenSSL on Linux and reading the source code [2] helped me to clarify that
// it does indeed the right thing [3]. When using other TLS backends, it may
// be that TLS data is not counted, but that's okay since we tell to users
// that this is an estimate of the amount of used data.
//
// Notes
// `````
//
// .. [1] https://stackoverflow.com/a/26905099
//
// .. [2] https://github.com/curl/curl/blob/6684653b/lib/vtls/openssl.c#L2295
//
// .. [3] the SSL function used is SSL_CTX_set_msg_callback which "[is] never
// [called for] application_data(23) because the callback will only be
// called for protocol messages" [4].
//
// .. [4] https://www.openssl.org/docs/man1.1.0/ssl/SSL_CTX_set_msg_callback.html
switch (type) {
case CURLINFO_HEADER_IN:
case CURLINFO_DATA_IN:
case CURLINFO_SSL_DATA_IN:
// Implementation note: overflow is unlikely, so we don't care.
res->bytes_recv += size;
break;
case CURLINFO_HEADER_OUT:
case CURLINFO_DATA_OUT:
case CURLINFO_SSL_DATA_OUT:
// Implementation note: overflow is unlikely, so we don't care.
res->bytes_sent += size;
break;
case CURLINFO_TEXT:
case CURLINFO_END:
/* NOTHING */
break;
}
return 0;
}
} // extern "C"
namespace mk {
namespace curl {
inline namespace MKCURL_INLINE_NAMESPACE {
// HTTPVersionString returns a string representation of the cURL HTTP
// version string in @p httpv. If @p httpv has an unknown value, the
// return value is the empty string.
static const char *HTTPVersionString(long httpv) noexcept {
switch (httpv) {
case CURL_HTTP_VERSION_1_0:
return "HTTP/1.0";
case CURL_HTTP_VERSION_1_1:
return "HTTP/1.1";
case CURL_HTTP_VERSION_2_0:
return "HTTP/2";
}
return "";
}
// perform_and_retry performs the request implied by @p handle for
// @p retries times. A request is only retried if (a) it failed and (b)
// the reason for failure is either DNS or connect error.
static CURLcode perform_and_retry(
CURL *handlep, size_t retries, std::vector<Log> &logs) noexcept {
CURLcode rv{};
bool retriable{};
for (;;) {
rv = curl_easy_perform(handlep);
MKCURL_HOOK(curl_easy_perform, rv);
retriable = retries-- > 0 && (rv == CURLE_COULDNT_CONNECT ||
rv == CURLE_COULDNT_RESOLVE_HOST);
if (!retriable) {
break;
}
mkcurl_log(logs, "Transient failure; let's try one more time");
}
return rv;
}
// perform2 will use @p handle to perform @p req. If @p handle is not set
// we will initialise it. Otherwise the @p handle argument options are
// reset to allow constructing a fresh HTTP request. Still, in such case, we'll
// reuse existing connections etc. @return the response.
static Response perform2(mkcurl_uptr &handle, const Request &req) noexcept {
Response res;
if (!handle) {
CURL *handlep = curl_easy_init();
MKCURL_HOOK_ALLOC(curl_easy_init, handlep, curl_easy_cleanup);
handle.reset(handlep);
if (!handle) {
res.error = CURLE_OUT_OF_MEMORY;
mkcurl_log(res.logs, "curl_easy_init() failed");
return res;
}
// FALLTHROUGH
}
/*
* From <https://curl.haxx.se/libcurl/c/curl_easy_reset.html>:
*
* Re-initializes all options previously set on a specified CURL handle to
* the default values. This puts back the handle to the same state as it was
* in when it was just created with curl_easy_init.
*
* It does not change the following information kept in the handle: live
* connections, the Session ID cache, the DNS cache, the cookies and shares.
*
* So, this allows us to reuse the existing connections with a completely
* new request whose options can be set from scratch below.
*/
curl_easy_reset(handle.get());
mkcurl_slist headers; // This must have function scope
for (auto &s : req.headers) {
curl_slist *slistp = curl_slist_append(headers.p, s.c_str());
MKCURL_HOOK_ALLOC(curl_slist_append_headers, slistp, curl_slist_free_all);
if ((headers.p = slistp) == nullptr) {
res.error = CURLE_OUT_OF_MEMORY;
mkcurl_log(res.logs, "curl_slist_append() failed");
return res;
}
}
mkcurl_slist connect_to_settings; // This must have function scope
if (!req.connect_to.empty()) {
curl_slist *slistp = curl_slist_append(
connect_to_settings.p, req.connect_to.c_str());
MKCURL_HOOK_ALLOC(
curl_slist_append_connect_to, slistp, curl_slist_free_all);
if ((connect_to_settings.p = slistp) == nullptr) {
res.error = CURLE_OUT_OF_MEMORY;
mkcurl_log(res.logs, "curl_slist_append() failed");
return res;
}
res.error = curl_easy_setopt(handle.get(), CURLOPT_CONNECT_TO,
connect_to_settings.p);
MKCURL_HOOK(curl_easy_setopt_CURLOPT_CONNECT_TO, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_CONNECT_TO) failed");
return res;
}
}
if (req.enable_fastopen) {
res.error = curl_easy_setopt(handle.get(), CURLOPT_TCP_FASTOPEN, 1L);
MKCURL_HOOK(curl_easy_setopt_CURLOPT_TCP_FASTOPEN, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_TCP_FASTOPEN) failed");
return res;
}
}
if (!req.ca_path.empty()) {
res.error = curl_easy_setopt(handle.get(), CURLOPT_CAINFO,
req.ca_path.c_str());
MKCURL_HOOK(curl_easy_setopt_CURLOPT_CAINFO, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_CAINFO) failed");
return res;
}
}
if (req.enable_http2) {
res.error = curl_easy_setopt(handle.get(), CURLOPT_HTTP_VERSION,
CURL_HTTP_VERSION_2_0);
MKCURL_HOOK(curl_easy_setopt_CURLOPT_HTTP_VERSION, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_HTTP_VERSION) failed");
return res;
}
}
if (req.method == "POST" || req.method == "PUT") {
// Disable sending `Expect: 100 continue`. There are actually good
// arguments against NOT sending this specific HTTP header by default
// with P{OS,U}T <https://curl.haxx.se/mail/lib-2017-07/0013.html>.
{
curl_slist *slistp = curl_slist_append(headers.p, "Expect:");
MKCURL_HOOK_ALLOC(
curl_slist_append_Expect_header, slistp, curl_slist_free_all);
if ((headers.p = slistp) == nullptr) {
res.error = CURLE_OUT_OF_MEMORY;
mkcurl_log(res.logs, "curl_slist_append() failed");
return res;
}
}
{
res.error = curl_easy_setopt(handle.get(), CURLOPT_POST, 1L);
MKCURL_HOOK(curl_easy_setopt_CURLOPT_POST, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_POST) failed");
return res;
}
}
{
res.error = curl_easy_setopt(handle.get(), CURLOPT_POSTFIELDS,
req.body.c_str());
MKCURL_HOOK(curl_easy_setopt_CURLOPT_POSTFIELDS, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_POSTFIELDS) failed");
return res;
}
}
// The following is very important to allow us to upload any kind of
// binary file, otherwise CURL will use strlen(). We do not need to
// send more than 2 GiB of data, hence we can safely limit ourself to
// using CURLOPT_POSTFIELDSIZE that takes a `long` argument.
{
bool body_size_overflow = (req.body.size() > LONG_MAX);
MKCURL_HOOK(body_size_overflow_inject, body_size_overflow);
if (body_size_overflow) {
mkcurl_log(res.logs, "Body larger than LONG_MAX");
res.error = CURLE_FILESIZE_EXCEEDED;
return res;
}
res.error = curl_easy_setopt(handle.get(), CURLOPT_POSTFIELDSIZE,
(long)req.body.size());
MKCURL_HOOK(curl_easy_setopt_CURLOPT_POSTFIELDSIZE, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(MKCURLOPT_POSTFIELDSIZE) failed");
return res;
}
}
if (req.method == "PUT") {
res.error = curl_easy_setopt(handle.get(), CURLOPT_CUSTOMREQUEST, "PUT");
MKCURL_HOOK(curl_easy_setopt_CURLOPT_CUSTOMREQUEST, res.error);
if (res.error) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_CUSTOMREQUEST) failed");
return res;
}
}
} else if (req.method != "GET") {
res.error = CURLE_BAD_FUNCTION_ARGUMENT;
mkcurl_log(res.logs, "unsupported request method");
return res;
}
if (headers.p != nullptr) {
res.error = curl_easy_setopt(handle.get(), CURLOPT_HTTPHEADER, headers.p);
MKCURL_HOOK(curl_easy_setopt_CURLOPT_HTTPHEADER, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_HTTPHEADER) failed");
return res;
}
}
{
res.error = curl_easy_setopt(handle.get(), CURLOPT_URL, req.url.c_str());
MKCURL_HOOK(curl_easy_setopt_CURLOPT_URL, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_URL) failed");
return res;
}
}
{
res.error = curl_easy_setopt(handle.get(), CURLOPT_WRITEFUNCTION,
mkcurl_body_cb_);
MKCURL_HOOK(curl_easy_setopt_CURLOPT_WRITEFUNCTION, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_WRITEFUNCTION) failed");
return res;
}
}
{
res.error = curl_easy_setopt(handle.get(), CURLOPT_WRITEDATA, &res);
MKCURL_HOOK(curl_easy_setopt_CURLOPT_WRITEDATA, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_WRITEDATA) failed");
return res;
}
}
// CURL uses MSG_NOSIGNAL where available (i.e. Linux) and SO_NOSIGPIPE
// where available (i.e. BSD). This covers all the UNIX operating systems
// that we care about (Android, Linux, iOS, macOS). We additionally need
// to avoid signals because we are acting as a library that is integrated
// into several different languages, so stealing the signal handler from
// the language MAY have a negative impact. However, disabling signal
// handlers will also make the getaddrinfo() resolver in cURL block until
// getaddrinfo() returns, unless we're using the threaded or the c-ares
// DNS backend. Since the threaded resolver should now be used in most
// Unix distros, we need mainly to remember to enable it when we're cross
// compiling cURL in measurement-kit/script-build-unix.
{
res.error = curl_easy_setopt(handle.get(), CURLOPT_NOSIGNAL, 1L);
MKCURL_HOOK(curl_easy_setopt_CURLOPT_NOSIGNAL, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_NOSIGNAL) failed");
return res;
}
}
{
long t = 0L; // Note: `0L` means "infinite" for CURLOPT_TIMEOUT.
if (req.timeout >= 0 && req.timeout < LONG_MAX) t = (long)req.timeout;
res.error = curl_easy_setopt(handle.get(), CURLOPT_TIMEOUT, t);
MKCURL_HOOK(curl_easy_setopt_CURLOPT_TIMEOUT, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_TIMEOUT) failed");
return res;
}
}
{
res.error = curl_easy_setopt(handle.get(), CURLOPT_DEBUGFUNCTION,
mkcurl_debug_cb_);
MKCURL_HOOK(curl_easy_setopt_CURLOPT_DEBUGFUNCTION, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_DEBUGFUNCTION) failed");
return res;
}
}
{
res.error = curl_easy_setopt(handle.get(), CURLOPT_DEBUGDATA, &res);
MKCURL_HOOK(curl_easy_setopt_CURLOPT_DEBUGDATA, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_DEBUGDATA) failed");
return res;
}
}
{
res.error = curl_easy_setopt(handle.get(), CURLOPT_VERBOSE, 1L);
MKCURL_HOOK(curl_easy_setopt_CURLOPT_VERBOSE, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_VERBOSE) failed");
return res;
}
}
if (!req.proxy_url.empty()) {
res.error = curl_easy_setopt(handle.get(), CURLOPT_PROXY,
req.proxy_url.c_str());
MKCURL_HOOK(curl_easy_setopt_CURLOPT_PROXY, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_PROXY) failed");
return res;
}
}
if (req.follow_redir) {
res.error = curl_easy_setopt(handle.get(), CURLOPT_FOLLOWLOCATION, 1L);
MKCURL_HOOK(curl_easy_setopt_CURLOPT_FOLLOWLOCATION, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_FOLLOWLOCATION) failed");
return res;
}
}
{
res.error = curl_easy_setopt(handle.get(), CURLOPT_CERTINFO, 1L);
MKCURL_HOOK(curl_easy_setopt_CURLOPT_CERTINFO, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_setopt(CURLOPT_CERTINFO) failed");
return res;
}
}
{
res.error = perform_and_retry(handle.get(), req.retries, res.logs);
if (res.error != CURLE_OK) {
std::stringstream ss;
ss << "curl_easy_perform: " << curl_easy_strerror((CURLcode)res.error);
mkcurl_log(res.logs, ss.str());
return res;
}
}
{
long status_code = 0;
res.error = curl_easy_getinfo(
handle.get(), CURLINFO_RESPONSE_CODE, &status_code);
MKCURL_HOOK(curl_easy_getinfo_CURLINFO_RESPONSE_CODE, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_getinfo(CURLINFO_RESPONSE_CODE) failed");
return res;
}
res.status_code = (int64_t)status_code;
}
{
char *url = nullptr;
res.error = curl_easy_getinfo(handle.get(), CURLINFO_REDIRECT_URL, &url);
MKCURL_HOOK(curl_easy_getinfo_CURLINFO_REDIRECT_URL, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_getinfo(CURLINFO_REDIRECT_URL) failed");
return res;
}
if (url != nullptr) res.redirect_url = url;
}
{
curl_certinfo *certinfo = nullptr;
res.error = curl_easy_getinfo(handle.get(), CURLINFO_CERTINFO, &certinfo);
MKCURL_HOOK(curl_easy_getinfo_CURLINFO_CERTINFO, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_getinfo(CURLINFO_CERTINFO) failed");
return res;
}
if (certinfo != nullptr && certinfo->num_of_certs > 0) {
for (int i = 0; i < certinfo->num_of_certs; i++) {
for (auto slist = certinfo->certinfo[i]; slist; slist = slist->next) {
if (slist->data != nullptr) {
// Just pass in the certificates and ignore the rest.
std::string s = slist->data;
if (s.find("Cert:") == 0) {
res.certs += s.substr(5);
res.certs += "\n";
}
}
}
}
}
}
{
char *ct = nullptr;
res.error = curl_easy_getinfo(handle.get(), CURLINFO_CONTENT_TYPE, &ct);
MKCURL_HOOK(curl_easy_getinfo_CURLINFO_CONTENT_TYPE, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_getinfo(CURLINFO_CONTENT_TYPE) failed");
return res;
}
if (ct != nullptr) res.content_type = ct;
}
{
long httpv = 0L;
res.error = curl_easy_getinfo(handle.get(), CURLINFO_HTTP_VERSION, &httpv);
MKCURL_HOOK(curl_easy_getinfo_CURLINFO_HTTP_VERSION, res.error);
if (res.error != CURLE_OK) {
mkcurl_log(res.logs, "curl_easy_getinfo(CURLINFO_HTTP_VERSION) failed");
return res;
}
res.http_version = HTTPVersionString(httpv);
}
return res;
}
Client::Client() noexcept { impl_.reset(new Client::Impl); }
Client::Client(Client &&) noexcept = default;
Client &Client::operator=(Client &&) noexcept = default;
Client::~Client() noexcept = default;
Response Client::perform(const Request &req) noexcept {
return perform2(impl_->handle, req);
}
Response perform(const Request &req) noexcept {
return Client{}.perform(req);
}
} // inline namespace MKCURL_INLINE_NAMESPACE
} // namespace curl
} // namespace mk
#endif // MKCURL_INLINE_IMPL
#endif // MEASUREMENT_KIT_MKCURL_HPP