• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jbaldwin / liblifthttp / ca0ad7e25eeaf6668d268853de4d63b40c519697-PR-163

01 May 2025 09:36AM UTC coverage: 87.538%. First build
ca0ad7e25eeaf6668d268853de4d63b40c519697-PR-163

Pull #163

github

web-flow
Merge bf982a3af into ba15ed4fd
Pull Request #163: added network (CURLOPT_ERRORBUFFER) error message to response

18 of 24 new or added lines in 3 files covered. (75.0%)

1166 of 1332 relevant lines covered (87.54%)

10117.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.71
/src/executor.cpp
1
#include "lift/executor.hpp"
2
#include "lift/client.hpp"
3
#include "lift/init.hpp"
4

5
#include <cstring>
6

7
namespace lift
8
{
9
auto curl_write_header(char* buffer, size_t size, size_t nitems, void* user_ptr) -> size_t;
10

11
auto curl_write_data(void* buffer, size_t size, size_t nitems, void* user_ptr) -> size_t;
12

13
auto curl_xfer_info(
14
    void*      clientp,
15
    curl_off_t download_total_bytes,
16
    curl_off_t download_now_bytes,
17
    curl_off_t upload_total_bytes,
18
    curl_off_t upload_now_bytes) -> int;
19

20
auto curl_debug_info_callback(CURL* handle, curl_infotype type, char* data, size_t size, void* userptr) -> int;
21

22
executor::executor(request* request, share* share) : m_request_sync(request), m_request(m_request_sync), m_response()
25✔
23
{
24
    if (share != nullptr)
25✔
25
    {
26
        m_curl_share_handle = share->m_curl_share_ptr;
10✔
27
    }
28
}
25✔
29

30
executor::executor(client* c) : m_client(c)
7,488✔
31
{
32
}
7,488✔
33

34
executor::~executor()
7,515✔
35
{
36
    reset();
7,515✔
37
    curl_easy_cleanup(m_curl_handle);
7,515✔
38
}
7,515✔
39

40
auto executor::start_async(request_ptr req_ptr, share* share) -> void
20,205✔
41
{
42
    m_request_async = std::move(req_ptr);
20,205✔
43
    m_request       = m_request_async.get();
20,205✔
44
    if (share != nullptr)
20,210✔
45
    {
46
        m_curl_share_handle = share->m_curl_share_ptr;
19,998✔
47
    }
48
}
20,210✔
49

50
auto executor::perform() -> response
25✔
51
{
52
    global_init();
25✔
53

54
    prepare();
25✔
55

56
    auto curl_error_code = curl_easy_perform(m_curl_handle);
25✔
57
    copy_curl_to_response(curl_error_code);
25✔
58

59
    global_cleanup();
25✔
60

61
    return std::move(m_response);
25✔
62
}
63

64
auto executor::prepare() -> void
20,236✔
65
{
66
    curl_easy_setopt(m_curl_handle, CURLOPT_PRIVATE, this);
20,236✔
67
    curl_easy_setopt(m_curl_handle, CURLOPT_HEADERFUNCTION, curl_write_header);
20,236✔
68
    curl_easy_setopt(m_curl_handle, CURLOPT_HEADERDATA, this);
20,232✔
69
    curl_easy_setopt(m_curl_handle, CURLOPT_WRITEFUNCTION, curl_write_data);
20,234✔
70
    curl_easy_setopt(m_curl_handle, CURLOPT_WRITEDATA, this);
20,230✔
71
    curl_easy_setopt(m_curl_handle, CURLOPT_NOSIGNAL, 1L);
20,233✔
72

73
    curl_easy_setopt(m_curl_handle, CURLOPT_URL, m_request->url().c_str());
20,238✔
74

75
    switch (m_request->method())
20,238✔
76
    {
77
        default:
20,234✔
78
            /* INTENTIONAL FALLTHROUGH */
79
        case http::method::unknown: // default to GET on unknown/bad value.
80
            /* INTENTIONAL FALLTHROUGH */
81
        case http::method::get:
82
            curl_easy_setopt(m_curl_handle, CURLOPT_HTTPGET, 1L);
20,234✔
83
            break;
20,221✔
84
        case http::method::head:
1✔
85
            curl_easy_setopt(m_curl_handle, CURLOPT_NOBODY, 1L);
1✔
86
            break;
1✔
87
        case http::method::post:
4✔
88
            curl_easy_setopt(m_curl_handle, CURLOPT_POST, 1L);
4✔
89
            break;
4✔
90
        case http::method::put:
×
91
            curl_easy_setopt(m_curl_handle, CURLOPT_CUSTOMREQUEST, "PUT");
×
92
            break;
×
93
        case http::method::delete_t:
×
94
            curl_easy_setopt(m_curl_handle, CURLOPT_CUSTOMREQUEST, "DELETE");
×
95
            break;
×
96
        case http::method::connect:
×
97
            curl_easy_setopt(m_curl_handle, CURLOPT_CONNECT_ONLY, 1L);
×
98
            break;
×
99
        case http::method::options:
×
100
            curl_easy_setopt(m_curl_handle, CURLOPT_CUSTOMREQUEST, "OPTIONS");
×
101
            break;
×
102
        case http::method::patch:
×
103
            curl_easy_setopt(m_curl_handle, CURLOPT_CUSTOMREQUEST, "PATCH");
×
104
            break;
×
105
    }
106

107
    switch (m_request->version())
20,226✔
108
    {
109
        default:
20,217✔
110
            /* INTENTIONAL FALLTHROUGH */
111
        case http::version::unknown: // default to USE_BEST on unknown/bad value.
112
            /* INTENTIONAL FALLTHROUGH */
113
        case http::version::use_best:
114
            curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_NONE);
20,217✔
115
            break;
20,231✔
116
        case http::version::v1_0:
×
117
            curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
×
118
            break;
×
119
        case http::version::v1_1:
4✔
120
            curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
4✔
121
            break;
4✔
122
        case http::version::v2_0:
×
123
            curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
×
124
            break;
×
125
        case http::version::v2_0_tls:
×
126
            curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
×
127
            break;
×
128
        case http::version::v2_0_only:
2✔
129
            curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
2✔
130
            break;
×
131
    }
132

133
    // Synchronous requests get their timeout value set directly on the curl easy handle.
134
    // Asynchronous requests will handle timeouts on the event loop due to Connection Time.
135
    if (m_request_sync != nullptr)
20,235✔
136
    {
137
        if (m_request->connect_timeout().has_value())
25✔
138
        {
139
            curl_easy_setopt(
×
140
                m_curl_handle,
141
                CURLOPT_CONNECTTIMEOUT_MS,
142
                static_cast<long>(m_request->connect_timeout().value().count()));
143
        }
144

145
        if (m_request->timeout().has_value())
25✔
146
        {
147
            curl_easy_setopt(
10✔
148
                m_curl_handle, CURLOPT_TIMEOUT_MS, static_cast<long>(m_request->timeout().value().count()));
149
        }
150
    }
151

152
    // Connection timeout is handled when injecting into the CURLM* event loop for asynchronous requests.
153

154
    if (m_request->follow_redirects())
20,235✔
155
    {
156
        curl_easy_setopt(m_curl_handle, CURLOPT_FOLLOWLOCATION, 1L);
20,235✔
157
        curl_easy_setopt(m_curl_handle, CURLOPT_MAXREDIRS, static_cast<long>(m_request->max_redirects()));
20,239✔
158
    }
159
    else
160
    {
161
        curl_easy_setopt(m_curl_handle, CURLOPT_FOLLOWLOCATION, 0L);
×
162
    }
163

164
    // https://curl.se/libcurl/c/CURLOPT_CAINFO.html
165
    if (const auto& ca_info = m_request->ca_info(); ca_info.has_value())
20,230✔
166
    {
167
        curl_easy_setopt(m_curl_handle, CURLOPT_CAINFO, ca_info.value().c_str());
×
168
    }
169

170
    // https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html
171
    curl_easy_setopt(m_curl_handle, CURLOPT_SSL_VERIFYPEER, (m_request->verify_ssl_peer()) ? 1L : 0L);
20,224✔
172
    // Note that 1L is valid, but curl docs say its basically deprecated.
173
    // https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html
174
    curl_easy_setopt(m_curl_handle, CURLOPT_SSL_VERIFYHOST, (m_request->verify_ssl_host()) ? 2L : 0L);
20,236✔
175
    // https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYSTATUS.html
176
    curl_easy_setopt(m_curl_handle, CURLOPT_SSL_VERIFYSTATUS, (m_request->verify_ssl_status()) ? 1L : 0L);
20,233✔
177

178
    // https://curl.haxx.se/libcurl/c/CURLOPT_SSLCERT.html
179
    if (const auto& cert = m_request->ssl_cert(); cert.has_value())
20,237✔
180
    {
181
        curl_easy_setopt(m_curl_handle, CURLOPT_SSLCERT, cert.value().c_str());
×
182
    }
183
    // https://curl.haxx.se/libcurl/c/CURLOPT_SSLCERTTYPE.html
184
    if (const auto& cert_type = m_request->ssl_cert_type(); cert_type.has_value())
20,234✔
185
    {
186
        curl_easy_setopt(m_curl_handle, CURLOPT_SSLCERTTYPE, to_string(cert_type.value()).data());
×
187
    }
188
    // https://curl.haxx.se/libcurl/c/CURLOPT_SSLKEY.html
189
    if (const auto& key = m_request->ssl_key(); key.has_value())
20,229✔
190
    {
191
        curl_easy_setopt(m_curl_handle, CURLOPT_SSLKEY, key.value().c_str());
×
192
    }
193
    // https://curl.haxx.se/libcurl/c/CURLOPT_KEYPASSWD.html
194
    if (const auto& password = m_request->key_password(); password.has_value())
20,230✔
195
    {
196
        curl_easy_setopt(m_curl_handle, CURLOPT_KEYPASSWD, password.value().data());
×
197
    }
198

199
    // Set proxy information for the requst if provided.
200
    // https://curl.haxx.se/libcurl/c/CURLOPT_PROXY.html
201
    if (m_request->proxy().has_value())
20,224✔
202
    {
203
        auto& proxy_data = m_request->proxy().value();
3✔
204

205
        curl_easy_setopt(m_curl_handle, CURLOPT_PROXY, proxy_data.m_host.data());
3✔
206

207
        switch (proxy_data.m_type)
3✔
208
        {
209
            case proxy_type::https:
×
210
                curl_easy_setopt(m_curl_handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTPS);
×
211
                break;
×
212
            case proxy_type::http:
3✔
213
                /* intentional fallthrough */
214
            default:
215
                curl_easy_setopt(m_curl_handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
3✔
216
                break;
3✔
217
        }
218

219
        curl_easy_setopt(m_curl_handle, CURLOPT_PROXYPORT, proxy_data.m_port);
3✔
220

221
        if (proxy_data.m_username.has_value())
3✔
222
        {
223
            curl_easy_setopt(m_curl_handle, CURLOPT_PROXYUSERNAME, proxy_data.m_username.value().data());
2✔
224
        }
225
        if (proxy_data.m_password.has_value())
3✔
226
        {
227
            curl_easy_setopt(m_curl_handle, CURLOPT_PROXYPASSWORD, proxy_data.m_password.value().data());
2✔
228
        }
229

230
        if (proxy_data.m_auth_types.has_value())
3✔
231
        {
232
            int64_t auth_types{0};
2✔
233
            for (const auto& auth_type : proxy_data.m_auth_types.value())
4✔
234
            {
235
                switch (auth_type)
2✔
236
                {
237
                    default:
1✔
238
                        // CURLAUTH_BASIC is the default per docs https://curl.se/libcurl/c/CURLOPT_PROXYAUTH.html.
239
                        /* INTENTIONAL FALLTHROUGH */
240
                    case http_auth_type::basic:
241
                        auth_types |= CURLAUTH_BASIC;
1✔
242
                        break;
1✔
243
                    case http_auth_type::any:
×
244
                        auth_types |= CURLAUTH_ANY;
×
245
                        break;
×
246
                    case http_auth_type::any_safe:
1✔
247
                        auth_types |= CURLAUTH_ANYSAFE;
1✔
248
                        break;
1✔
249
                }
250
            }
251

252
            if (auth_types != 0)
2✔
253
            {
254
                curl_easy_setopt(m_curl_handle, CURLOPT_PROXYAUTH, auth_types);
2✔
255
            }
256
        }
257
    }
258

259
    const auto& encodings = m_request->accept_encodings();
20,232✔
260
    if (encodings.has_value())
20,230✔
261
    {
262
        if (!encodings.value().empty())
×
263
        {
264
            std::size_t length{0};
×
265
            for (const auto& e : encodings.value())
×
266
            {
267
                length += e.length() + 2; // for ", "
×
268
            }
269

270
            std::string joined{};
×
271
            joined.reserve(length);
×
272

273
            bool first{true};
×
274
            for (auto& e : encodings.value())
×
275
            {
276
                if (first)
×
277
                {
278
                    first = false;
×
279
                }
280
                else
281
                {
282
                    joined.append(", ");
×
283
                }
284
                joined.append(e);
×
285
            }
286

287
            // strings are copied into libcurl except for POSTFIELDS.
288
            curl_easy_setopt(m_curl_handle, CURLOPT_ACCEPT_ENCODING, joined.c_str());
×
289
        }
290
        else
291
        {
292
            // From the CURL docs (https://curl.haxx.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html):
293
            // 'To aid applications not having to bother about what specific algorithms this particular
294
            // libcurl build supports, libcurl allows a zero-length string to be set ("") to ask for an
295
            // Accept-Encoding: header to be used that contains all built-in supported encodings.'
296
            curl_easy_setopt(m_curl_handle, CURLOPT_ACCEPT_ENCODING, "");
×
297
        }
298
    }
299

300
    // Headers
301
    if (m_curl_request_headers != nullptr)
20,226✔
302
    {
303
        curl_slist_free_all(m_curl_request_headers);
×
304
        m_curl_request_headers = nullptr;
×
305
    }
306

307
    for (auto& header : m_request->m_request_headers)
20,233✔
308
    {
309
        m_curl_request_headers = curl_slist_append(m_curl_request_headers, header.data().data());
7✔
310
    }
311

312
    if (m_curl_request_headers != nullptr)
20,219✔
313
    {
314
        curl_easy_setopt(m_curl_handle, CURLOPT_HTTPHEADER, m_curl_request_headers);
3✔
315
    }
316
    else
317
    {
318
        curl_easy_setopt(m_curl_handle, CURLOPT_HTTPHEADER, nullptr);
20,216✔
319
    }
320

321
    // DNS resolve hosts
322
    if (!m_request->m_resolve_hosts.empty() || (m_client != nullptr && !m_client->m_resolve_hosts.empty()))
20,227✔
323
    {
324
        if (m_curl_resolve_hosts != nullptr)
3✔
325
        {
326
            curl_slist_free_all(m_curl_resolve_hosts);
×
327
            m_curl_resolve_hosts = nullptr;
×
328
        }
329

330
        for (const auto& resolve_host : m_request->m_resolve_hosts)
4✔
331
        {
332
            m_curl_resolve_hosts =
1✔
333
                curl_slist_append(m_curl_resolve_hosts, resolve_host.curl_formatted_resolve_host().data());
1✔
334
        }
335

336
        if (m_client != nullptr)
3✔
337
        {
338
            for (const auto& resolve_host : m_client->m_resolve_hosts)
6✔
339
            {
340
                m_curl_resolve_hosts =
4✔
341
                    curl_slist_append(m_curl_resolve_hosts, resolve_host.curl_formatted_resolve_host().data());
4✔
342
            }
343
        }
344

345
        curl_easy_setopt(m_curl_handle, CURLOPT_RESOLVE, m_curl_resolve_hosts);
3✔
346
    }
347

348
    // POST or MIME data
349
    if (m_request->m_request_data_set)
20,228✔
350
    {
351
        curl_easy_setopt(m_curl_handle, CURLOPT_POSTFIELDSIZE, static_cast<long>(m_request->data().size()));
4✔
352
        curl_easy_setopt(m_curl_handle, CURLOPT_POSTFIELDS, m_request->data().data());
4✔
353
    }
354
    else if (m_request->m_mime_fields_set)
20,224✔
355
    {
356
        m_mime_handle = curl_mime_init(m_curl_handle);
×
357

358
        for (const auto& mime_field : m_request->mime_fields())
×
359
        {
360
            auto* field = curl_mime_addpart(m_mime_handle);
×
361

362
            if (std::holds_alternative<std::string>(mime_field.value()))
×
363
            {
364
                curl_mime_name(field, mime_field.name().data());
×
365
                const auto& value = std::get<std::string>(mime_field.value());
×
366
                curl_mime_data(field, value.data(), value.length());
×
367
            }
368
            else
369
            {
370
                curl_mime_filename(field, mime_field.name().data());
×
371
                curl_mime_filedata(field, std::get<std::filesystem::path>(mime_field.value()).c_str());
×
372
            }
373
        }
374

375
        curl_easy_setopt(m_curl_handle, CURLOPT_MIMEPOST, m_mime_handle);
×
376
    }
377

378
    if (m_request->m_on_transfer_progress_handler != nullptr)
20,228✔
379
    {
380
        curl_easy_setopt(m_curl_handle, CURLOPT_XFERINFOFUNCTION, curl_xfer_info);
2✔
381
        curl_easy_setopt(m_curl_handle, CURLOPT_XFERINFODATA, this);
2✔
382
        curl_easy_setopt(m_curl_handle, CURLOPT_NOPROGRESS, 0L);
2✔
383
    }
384
    else
385
    {
386
        curl_easy_setopt(m_curl_handle, CURLOPT_NOPROGRESS, 1L);
20,229✔
387
    }
388

389
    if (const auto& timeout = m_request->happy_eyeballs_timeout(); timeout.has_value())
20,238✔
390
    {
391
        curl_easy_setopt(m_curl_handle, CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS, static_cast<long>(timeout.value().count()));
1✔
392
    }
393

394
    // Note that this will lock the mutexes in the share callbacks.
395
    if (m_curl_share_handle != nullptr)
20,216✔
396
    {
397
        curl_easy_setopt(m_curl_handle, CURLOPT_SHARE, m_curl_share_handle);
19,989✔
398
    }
399

400
    // Set debug info if the user added a debug info functor callback
401
    // https://curl.se/libcurl/c/CURLOPT_DEBUGFUNCTION.html
402
    if (m_request->m_debug_info_handler != nullptr)
20,239✔
403
    {
404
        curl_easy_setopt(m_curl_handle, CURLOPT_VERBOSE, 1L);
3✔
405
        curl_easy_setopt(m_curl_handle, CURLOPT_DEBUGFUNCTION, curl_debug_info_callback);
3✔
406
        curl_easy_setopt(m_curl_handle, CURLOPT_DEBUGDATA, this);
3✔
407
    }
408

409
    if (m_request->m_enable_error_message)
20,238✔
410
    {
411
        m_response.m_network_error_message.emplace();
×
412
        m_response.m_network_error_message->resize(CURL_ERROR_SIZE);
×
413
        static_assert(CURL_ERROR_SIZE > 0);
414
        m_response.m_network_error_message->at(0) = '\0';
×
NEW
415
        curl_easy_setopt(m_curl_handle, CURLOPT_ERRORBUFFER, m_response.m_network_error_message->data());
×
416
    }
417
}
20,238✔
418

419
auto executor::copy_curl_to_response(CURLcode curl_code) -> void
20,237✔
420
{
421
    m_response.m_lift_status = convert(curl_code);
20,237✔
422

423
    if (m_response.m_network_error_message)
20,235✔
424
    {
NEW
425
        if (curl_code != CURLE_OK)
×
426
        {
NEW
427
            m_response.m_network_error_message->resize(std::strlen(m_response.m_network_error_message->c_str()));
×
NEW
428
            if (m_response.m_network_error_message->empty())
×
429
            {
430
                // fallback to the more generic information from curl_easy_strerror if no detailed error information has
431
                // been written to the error buffer
NEW
432
                m_response.m_network_error_message = curl_easy_strerror(curl_code);
×
433
            }
434
        }
435
        else
436
        {
437
            m_response.m_network_error_message.reset();
×
438
        }
439
    }
440

441
    long http_response_code = 0;
20,233✔
442
    curl_easy_getinfo(m_curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code);
20,233✔
443
    m_response.m_status_code = http::to_enum(static_cast<uint16_t>(http_response_code));
20,238✔
444

445
    long http_version = 0;
20,236✔
446
    curl_easy_getinfo(m_curl_handle, CURLINFO_HTTP_VERSION, &http_version);
20,236✔
447
    m_response.m_version = static_cast<http::version>(http_version);
20,236✔
448

449
    double total_time = 0;
20,236✔
450
    curl_easy_getinfo(m_curl_handle, CURLINFO_TOTAL_TIME, &total_time);
20,236✔
451
    // std::duration defaults to seconds, so don't need to duration_cast total time to seconds.
452
    m_response.m_total_time = static_cast<uint32_t>(
20,233✔
453
        std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::duration<double>{total_time}).count());
20,236✔
454

455
    long connect_count = 0;
20,233✔
456
    curl_easy_getinfo(m_curl_handle, CURLINFO_NUM_CONNECTS, &connect_count);
20,233✔
457
    m_response.m_num_connects = (connect_count >= std::numeric_limits<uint8_t>::max())
20,237✔
458
                                    ? std::numeric_limits<uint8_t>::max()
20,237✔
459
                                    : static_cast<uint8_t>(connect_count);
460

461
    long redirect_count = 0;
20,237✔
462
    curl_easy_getinfo(m_curl_handle, CURLINFO_REDIRECT_COUNT, &redirect_count);
20,237✔
463
    m_response.m_num_redirects = (redirect_count >= std::numeric_limits<uint8_t>::max())
20,237✔
464
                                     ? std::numeric_limits<uint8_t>::max()
20,236✔
465
                                     : static_cast<uint8_t>(redirect_count);
466
}
20,237✔
467

468
auto executor::set_timesup_response(std::chrono::milliseconds total_time) -> void
1✔
469
{
470
    m_response.m_status_code   = lift::http::status_code::http_504_gateway_timeout;
1✔
471
    m_response.m_total_time    = static_cast<uint32_t>(total_time.count());
1✔
472
    m_response.m_num_connects  = 0;
1✔
473
    m_response.m_num_redirects = 0;
1✔
474
}
1✔
475

476
auto executor::reset() -> void
27,726✔
477
{
478
    if (m_mime_handle != nullptr)
27,726✔
479
    {
480
        curl_mime_free(m_mime_handle);
×
481
        m_mime_handle = nullptr;
×
482
    }
483

484
    if (m_curl_request_headers != nullptr)
27,726✔
485
    {
486
        curl_slist_free_all(m_curl_request_headers);
3✔
487
        m_curl_request_headers = nullptr;
3✔
488
    }
489

490
    if (m_curl_resolve_hosts != nullptr)
27,726✔
491
    {
492
        curl_slist_free_all(m_curl_resolve_hosts);
3✔
493
        m_curl_resolve_hosts = nullptr;
3✔
494
    }
495

496
    // Regardless of sync/async all three pointers get reset to nullptr.
497
    m_request_sync  = nullptr;
27,726✔
498
    m_request_async = nullptr;
27,726✔
499
    m_request       = nullptr;
27,726✔
500

501
    m_timeout_iterator.reset();
27,726✔
502
    m_on_complete_handler_processed = false;
27,726✔
503
    m_response                      = response{};
27,726✔
504

505
    curl_easy_setopt(m_curl_handle, CURLOPT_SHARE, nullptr);
27,725✔
506
    m_curl_share_handle = nullptr;
27,729✔
507

508
    curl_easy_reset(m_curl_handle);
27,729✔
509
}
27,729✔
510

511
auto executor::convert(CURLcode curl_code) -> lift_status
20,234✔
512
{
513
#pragma GCC diagnostic push
514
#pragma GCC diagnostic ignored "-Wswitch-enum"
515
    switch (curl_code)
20,234✔
516
    {
517
        case CURLcode::CURLE_OK:
15,694✔
518
            return lift_status::success;
15,694✔
519
        case CURLcode::CURLE_GOT_NOTHING:
×
520
            return lift_status::response_empty;
×
521
        case CURLcode::CURLE_OPERATION_TIMEDOUT:
4,540✔
522
            return lift_status::timeout;
4,540✔
523
        case CURLcode::CURLE_COULDNT_CONNECT:
×
524
            return lift_status::connect_error;
×
525
        case CURLcode::CURLE_COULDNT_RESOLVE_HOST:
×
526
            return lift_status::connect_dns_error;
×
527
        case CURLcode::CURLE_SSL_CONNECT_ERROR:
×
528
            return lift_status::connect_ssl_error;
×
529
        case CURLcode::CURLE_WRITE_ERROR:
×
530
            return lift_status::download_error;
×
531
        case CURLcode::CURLE_SEND_ERROR:
×
532
            return lift_status::error_failed_to_start;
×
533
        default:
×
534
            return lift_status::error;
×
535
    }
536
#pragma GCC diagnostic pop
537
}
538

539
auto curl_write_header(char* buffer, size_t size, size_t nitems, void* user_ptr) -> size_t
157,081✔
540
{
541
    auto*        executor_ptr = static_cast<executor*>(user_ptr);
157,081✔
542
    auto&        response     = executor_ptr->m_response;
157,081✔
543
    const size_t data_length  = size * nitems;
157,081✔
544

545
    std::string_view data_view{buffer, data_length};
157,081✔
546

547
    if (data_view.empty())
156,819✔
548
    {
549
        return data_length;
×
550
    }
551

552
    // Ignore empty header lines from curl.
553
    if (data_length == 2 && data_view == "\r\n")
156,898✔
554
    {
555
        return data_length;
15,699✔
556
    }
557
    // Ignore the HTTP/ 'header' line from curl.
558
    constexpr size_t HTTPSLASH_LEN = 5;
141,092✔
559
    if (data_length >= 4 && data_view.substr(0, HTTPSLASH_LEN) == "HTTP/")
141,092✔
560
    {
561
        return data_length;
15,658✔
562
    }
563

564
    // Drop the trailing \r\n from the header.
565
    if (data_length >= 2)
125,390✔
566
    {
567
        size_t rm_size = (data_view[data_length - 1] == '\n' && data_view[data_length - 2] == '\r') ? 2 : 0;
125,409✔
568
        data_view.remove_suffix(rm_size);
125,608✔
569
    }
570

571
    response.m_headers.emplace_back(std::string{data_view.data(), data_view.length()});
125,523✔
572

573
    return data_length; // return original size for curl to continue processing
125,430✔
574
}
575

576
auto curl_write_data(void* buffer, size_t size, size_t nitems, void* user_ptr) -> size_t
15,671✔
577
{
578
    auto*  executor_ptr = static_cast<executor*>(user_ptr);
15,671✔
579
    auto&  response     = executor_ptr->m_response;
15,671✔
580
    size_t data_length  = size * nitems;
15,671✔
581

582
    std::string_view from{static_cast<const char*>(buffer), data_length};
15,671✔
583

584
    std::copy(from.begin(), from.end(), std::back_inserter(response.m_data));
15,624✔
585

586
    return data_length;
15,695✔
587
}
588

589
auto curl_xfer_info(
29✔
590
    void*      clientp,
591
    curl_off_t download_total_bytes,
592
    curl_off_t download_now_bytes,
593
    curl_off_t upload_total_bytes,
594
    curl_off_t upload_now_bytes) -> int
595
{
596
    const auto* executor_ptr = static_cast<const executor*>(clientp);
29✔
597

598
    if (executor_ptr != nullptr && executor_ptr->m_request->m_on_transfer_progress_handler != nullptr)
29✔
599
    {
600
        if (executor_ptr->m_request->m_on_transfer_progress_handler(
29✔
601
                *executor_ptr->m_request,
29✔
602
                download_total_bytes,
603
                download_now_bytes,
604
                upload_total_bytes,
605
                upload_now_bytes))
606
        {
607
            return 0;
28✔
608
        }
609
        else
610
        {
611
            return 1;
1✔
612
        }
613
    }
614
    else
615
    {
616
        return 0; // continue the request.
×
617
    }
618
}
619

620
auto curl_debug_info_callback(CURL* /*handle*/, curl_infotype type, char* data, size_t size, void* userptr) -> int
51✔
621
{
622
    const auto* executor_ptr = static_cast<const executor*>(userptr);
51✔
623

624
    if (executor_ptr != nullptr && executor_ptr->m_request->m_debug_info_handler != nullptr)
51✔
625
    {
626
        executor_ptr->m_request->m_debug_info_handler(
102✔
627
            *executor_ptr->m_request, static_cast<debug_info_type>(type), std::string_view{data, size});
51✔
628
    }
629

630
    // "this function must return 0" according to libcurl docs.
631
    return 0;
51✔
632
}
633

634
} // namespace lift
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc