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

jbaldwin / liblifthttp / e470ad3c4a0de19144307a72fbe2a465a08a0d52-PR-163

29 Apr 2025 01:38PM UTC coverage: 88.337%. First build
e470ad3c4a0de19144307a72fbe2a465a08a0d52-PR-163

Pull #163

github

web-flow
Merge 9c0374631 into b9996bd10
Pull Request #163: added network (CURLOPT_ERRORBUFFER) error message to response

6 of 7 new or added lines in 1 file covered. (85.71%)

1174 of 1329 relevant lines covered (88.34%)

10098.91 hits per line

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

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

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

9
auto curl_write_data(void* buffer, size_t size, size_t nitems, void* user_ptr) -> size_t;
10

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

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

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

28
executor::executor(client* c) : m_client(c)
7,730✔
29
{
30
}
7,729✔
31

32
executor::~executor()
7,755✔
33
{
34
    reset();
7,737✔
35
    curl_easy_cleanup(m_curl_handle);
7,753✔
36
}
7,737✔
37

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

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

52
    prepare();
25✔
53

54
    m_curl_error_buffer.resize(CURL_ERROR_SIZE);
25✔
55
    m_curl_error_buffer.at(0) = '\0';
25✔
56
    curl_easy_setopt(m_curl_handle, CURLOPT_ERRORBUFFER, m_curl_error_buffer.data());
25✔
57

58
    auto curl_error_code     = curl_easy_perform(m_curl_handle);
25✔
59
    m_response.m_lift_status = convert(curl_error_code);
25✔
60

61
    if (curl_error_code != CURLE_OK)
25✔
62
    {
63
        if (m_curl_error_buffer.at(0) != '\0')
1✔
64
        {
65
            m_response.m_network_error_message = std::move(m_curl_error_buffer);
1✔
66
        }
67
        else
68
        {
NEW
69
            m_response.m_network_error_message = curl_easy_strerror(curl_error_code);
×
70
        }
71
    }
72

73
    copy_curl_to_response();
25✔
74

75
    global_cleanup();
25✔
76

77
    return std::move(m_response);
25✔
78
}
79

80
auto executor::prepare() -> void
20,233✔
81
{
82
    curl_easy_setopt(m_curl_handle, CURLOPT_PRIVATE, this);
20,233✔
83
    curl_easy_setopt(m_curl_handle, CURLOPT_HEADERFUNCTION, curl_write_header);
20,233✔
84
    curl_easy_setopt(m_curl_handle, CURLOPT_HEADERDATA, this);
20,237✔
85
    curl_easy_setopt(m_curl_handle, CURLOPT_WRITEFUNCTION, curl_write_data);
20,231✔
86
    curl_easy_setopt(m_curl_handle, CURLOPT_WRITEDATA, this);
20,238✔
87
    curl_easy_setopt(m_curl_handle, CURLOPT_NOSIGNAL, 1L);
20,238✔
88

89
    curl_easy_setopt(m_curl_handle, CURLOPT_URL, m_request->url().c_str());
20,239✔
90

91
    switch (m_request->method())
20,234✔
92
    {
93
        default:
20,232✔
94
            /* INTENTIONAL FALLTHROUGH */
95
        case http::method::unknown: // default to GET on unknown/bad value.
96
            /* INTENTIONAL FALLTHROUGH */
97
        case http::method::get:
98
            curl_easy_setopt(m_curl_handle, CURLOPT_HTTPGET, 1L);
20,232✔
99
            break;
20,229✔
100
        case http::method::head:
1✔
101
            curl_easy_setopt(m_curl_handle, CURLOPT_NOBODY, 1L);
1✔
102
            break;
1✔
103
        case http::method::post:
4✔
104
            curl_easy_setopt(m_curl_handle, CURLOPT_POST, 1L);
4✔
105
            break;
4✔
106
        case http::method::put:
×
107
            curl_easy_setopt(m_curl_handle, CURLOPT_UPLOAD, 1L);
×
108
            break;
×
109
        case http::method::delete_t:
×
110
            curl_easy_setopt(m_curl_handle, CURLOPT_CUSTOMREQUEST, "DELETE");
×
111
            break;
×
112
        case http::method::connect:
×
113
            curl_easy_setopt(m_curl_handle, CURLOPT_CONNECT_ONLY, 1L);
×
114
            break;
×
115
        case http::method::options:
×
116
            curl_easy_setopt(m_curl_handle, CURLOPT_CUSTOMREQUEST, "OPTIONS");
×
117
            break;
×
118
        case http::method::patch:
×
119
            curl_easy_setopt(m_curl_handle, CURLOPT_CUSTOMREQUEST, "PATCH");
×
120
            break;
×
121
    }
122

123
    switch (m_request->version())
20,234✔
124
    {
125
        default:
20,226✔
126
            /* INTENTIONAL FALLTHROUGH */
127
        case http::version::unknown: // default to USE_BEST on unknown/bad value.
128
            /* INTENTIONAL FALLTHROUGH */
129
        case http::version::use_best:
130
            curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_NONE);
20,226✔
131
            break;
20,234✔
132
        case http::version::v1_0:
×
133
            curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
×
134
            break;
×
135
        case http::version::v1_1:
4✔
136
            curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
4✔
137
            break;
4✔
138
        case http::version::v2_0:
×
139
            curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
×
140
            break;
×
141
        case http::version::v2_0_tls:
×
142
            curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
×
143
            break;
×
144
        case http::version::v2_0_only:
6✔
145
            curl_easy_setopt(m_curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
6✔
146
            break;
×
147
    }
148

149
    // Synchronous requests get their timeout value set directly on the curl easy handle.
150
    // Asynchronous requests will handle timeouts on the event loop due to Connection Time.
151
    if (m_request_sync != nullptr)
20,238✔
152
    {
153
        if (m_request->connect_timeout().has_value())
25✔
154
        {
155
            curl_easy_setopt(
×
156
                m_curl_handle,
157
                CURLOPT_CONNECTTIMEOUT_MS,
158
                static_cast<long>(m_request->connect_timeout().value().count()));
159
        }
160

161
        if (m_request->timeout().has_value())
25✔
162
        {
163
            curl_easy_setopt(
10✔
164
                m_curl_handle, CURLOPT_TIMEOUT_MS, static_cast<long>(m_request->timeout().value().count()));
165
        }
166
    }
167

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

170
    if (m_request->follow_redirects())
20,238✔
171
    {
172
        curl_easy_setopt(m_curl_handle, CURLOPT_FOLLOWLOCATION, 1L);
20,236✔
173
        curl_easy_setopt(m_curl_handle, CURLOPT_MAXREDIRS, static_cast<long>(m_request->max_redirects()));
20,239✔
174
    }
175
    else
176
    {
177
        curl_easy_setopt(m_curl_handle, CURLOPT_FOLLOWLOCATION, 0L);
×
178
    }
179

180
    // https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html
181
    curl_easy_setopt(m_curl_handle, CURLOPT_SSL_VERIFYPEER, (m_request->verify_ssl_peer()) ? 1L : 0L);
20,236✔
182
    // Note that 1L is valid, but curl docs say its basically deprecated.
183
    // https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html
184
    curl_easy_setopt(m_curl_handle, CURLOPT_SSL_VERIFYHOST, (m_request->verify_ssl_host()) ? 2L : 0L);
20,235✔
185
    // https://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYSTATUS.html
186
    curl_easy_setopt(m_curl_handle, CURLOPT_SSL_VERIFYSTATUS, (m_request->verify_ssl_status()) ? 1L : 0L);
20,232✔
187

188
    // https://curl.haxx.se/libcurl/c/CURLOPT_SSLCERT.html
189
    if (const auto& cert = m_request->ssl_cert(); cert.has_value())
20,238✔
190
    {
191
        curl_easy_setopt(m_curl_handle, CURLOPT_SSLCERT, cert.value().c_str());
×
192
    }
193
    // https://curl.haxx.se/libcurl/c/CURLOPT_SSLCERTTYPE.html
194
    if (const auto& cert_type = m_request->ssl_cert_type(); cert_type.has_value())
20,232✔
195
    {
196
        curl_easy_setopt(m_curl_handle, CURLOPT_SSLCERTTYPE, to_string(cert_type.value()).data());
×
197
    }
198
    // https://curl.haxx.se/libcurl/c/CURLOPT_SSLKEY.html
199
    if (const auto& key = m_request->ssl_key(); key.has_value())
20,225✔
200
    {
201
        curl_easy_setopt(m_curl_handle, CURLOPT_SSLKEY, key.value().c_str());
×
202
    }
203
    // https://curl.haxx.se/libcurl/c/CURLOPT_KEYPASSWD.html
204
    if (const auto& password = m_request->key_password(); password.has_value())
20,232✔
205
    {
206
        curl_easy_setopt(m_curl_handle, CURLOPT_KEYPASSWD, password.value().data());
×
207
    }
208

209
    // Set proxy information for the requst if provided.
210
    // https://curl.haxx.se/libcurl/c/CURLOPT_PROXY.html
211
    if (m_request->proxy().has_value())
20,227✔
212
    {
213
        auto& proxy_data = m_request->proxy().value();
3✔
214

215
        curl_easy_setopt(m_curl_handle, CURLOPT_PROXY, proxy_data.m_host.data());
3✔
216

217
        switch (proxy_data.m_type)
3✔
218
        {
219
            case proxy_type::https:
×
220
                curl_easy_setopt(m_curl_handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTPS);
×
221
                break;
×
222
            case proxy_type::http:
3✔
223
                /* intentional fallthrough */
224
            default:
225
                curl_easy_setopt(m_curl_handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
3✔
226
                break;
3✔
227
        }
228

229
        curl_easy_setopt(m_curl_handle, CURLOPT_PROXYPORT, proxy_data.m_port);
3✔
230

231
        if (proxy_data.m_username.has_value())
3✔
232
        {
233
            curl_easy_setopt(m_curl_handle, CURLOPT_PROXYUSERNAME, proxy_data.m_username.value().data());
2✔
234
        }
235
        if (proxy_data.m_password.has_value())
3✔
236
        {
237
            curl_easy_setopt(m_curl_handle, CURLOPT_PROXYPASSWORD, proxy_data.m_password.value().data());
2✔
238
        }
239

240
        if (proxy_data.m_auth_types.has_value())
3✔
241
        {
242
            int64_t auth_types{0};
2✔
243
            for (const auto& auth_type : proxy_data.m_auth_types.value())
4✔
244
            {
245
                switch (auth_type)
2✔
246
                {
247
                    default:
1✔
248
                        // CURLAUTH_BASIC is the default per docs https://curl.se/libcurl/c/CURLOPT_PROXYAUTH.html.
249
                        /* INTENTIONAL FALLTHROUGH */
250
                    case http_auth_type::basic:
251
                        auth_types |= CURLAUTH_BASIC;
1✔
252
                        break;
1✔
253
                    case http_auth_type::any:
×
254
                        auth_types |= CURLAUTH_ANY;
×
255
                        break;
×
256
                    case http_auth_type::any_safe:
1✔
257
                        auth_types |= CURLAUTH_ANYSAFE;
1✔
258
                        break;
1✔
259
                }
260
            }
261

262
            if (auth_types != 0)
2✔
263
            {
264
                curl_easy_setopt(m_curl_handle, CURLOPT_PROXYAUTH, auth_types);
2✔
265
            }
266
        }
267
    }
268

269
    const auto& encodings = m_request->accept_encodings();
20,225✔
270
    if (encodings.has_value())
20,226✔
271
    {
272
        if (!encodings.value().empty())
×
273
        {
274
            std::size_t length{0};
×
275
            for (const auto& e : encodings.value())
×
276
            {
277
                length += e.length() + 2; // for ", "
×
278
            }
279

280
            std::string joined{};
×
281
            joined.reserve(length);
×
282

283
            bool first{true};
×
284
            for (auto& e : encodings.value())
×
285
            {
286
                if (first)
×
287
                {
288
                    first = false;
×
289
                }
290
                else
291
                {
292
                    joined.append(", ");
×
293
                }
294
                joined.append(e);
×
295
            }
296

297
            // strings are copied into libcurl except for POSTFIELDS.
298
            curl_easy_setopt(m_curl_handle, CURLOPT_ACCEPT_ENCODING, joined.c_str());
×
299
        }
300
        else
301
        {
302
            // From the CURL docs (https://curl.haxx.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html):
303
            // 'To aid applications not having to bother about what specific algorithms this particular
304
            // libcurl build supports, libcurl allows a zero-length string to be set ("") to ask for an
305
            // Accept-Encoding: header to be used that contains all built-in supported encodings.'
306
            curl_easy_setopt(m_curl_handle, CURLOPT_ACCEPT_ENCODING, "");
×
307
        }
308
    }
309

310
    // Headers
311
    if (m_curl_request_headers != nullptr)
20,227✔
312
    {
313
        curl_slist_free_all(m_curl_request_headers);
×
314
        m_curl_request_headers = nullptr;
×
315
    }
316

317
    for (auto& header : m_request->m_request_headers)
20,234✔
318
    {
319
        m_curl_request_headers = curl_slist_append(m_curl_request_headers, header.data().data());
7✔
320
    }
321

322
    if (m_curl_request_headers != nullptr)
20,224✔
323
    {
324
        curl_easy_setopt(m_curl_handle, CURLOPT_HTTPHEADER, m_curl_request_headers);
3✔
325
    }
326
    else
327
    {
328
        curl_easy_setopt(m_curl_handle, CURLOPT_HTTPHEADER, nullptr);
20,221✔
329
    }
330

331
    // DNS resolve hosts
332
    if (!m_request->m_resolve_hosts.empty() || (m_client != nullptr && !m_client->m_resolve_hosts.empty()))
20,227✔
333
    {
334
        if (m_curl_resolve_hosts != nullptr)
3✔
335
        {
336
            curl_slist_free_all(m_curl_resolve_hosts);
×
337
            m_curl_resolve_hosts = nullptr;
×
338
        }
339

340
        for (const auto& resolve_host : m_request->m_resolve_hosts)
4✔
341
        {
342
            m_curl_resolve_hosts =
1✔
343
                curl_slist_append(m_curl_resolve_hosts, resolve_host.curl_formatted_resolve_host().data());
1✔
344
        }
345

346
        if (m_client != nullptr)
3✔
347
        {
348
            for (const auto& resolve_host : m_client->m_resolve_hosts)
6✔
349
            {
350
                m_curl_resolve_hosts =
4✔
351
                    curl_slist_append(m_curl_resolve_hosts, resolve_host.curl_formatted_resolve_host().data());
4✔
352
            }
353
        }
354

355
        curl_easy_setopt(m_curl_handle, CURLOPT_RESOLVE, m_curl_resolve_hosts);
3✔
356
    }
357

358
    // POST or MIME data
359
    if (m_request->m_request_data_set)
20,225✔
360
    {
361
        curl_easy_setopt(m_curl_handle, CURLOPT_POSTFIELDSIZE, static_cast<long>(m_request->data().size()));
4✔
362
        curl_easy_setopt(m_curl_handle, CURLOPT_POSTFIELDS, m_request->data().data());
4✔
363
    }
364
    else if (m_request->m_mime_fields_set)
20,221✔
365
    {
366
        m_mime_handle = curl_mime_init(m_curl_handle);
×
367

368
        for (const auto& mime_field : m_request->mime_fields())
×
369
        {
370
            auto* field = curl_mime_addpart(m_mime_handle);
×
371

372
            if (std::holds_alternative<std::string>(mime_field.value()))
×
373
            {
374
                curl_mime_name(field, mime_field.name().data());
×
375
                const auto& value = std::get<std::string>(mime_field.value());
×
376
                curl_mime_data(field, value.data(), value.length());
×
377
            }
378
            else
379
            {
380
                curl_mime_filename(field, mime_field.name().data());
×
381
                curl_mime_filedata(field, std::get<std::filesystem::path>(mime_field.value()).c_str());
×
382
            }
383
        }
384

385
        curl_easy_setopt(m_curl_handle, CURLOPT_MIMEPOST, m_mime_handle);
×
386
    }
387

388
    if (m_request->m_on_transfer_progress_handler != nullptr)
20,225✔
389
    {
390
        curl_easy_setopt(m_curl_handle, CURLOPT_XFERINFOFUNCTION, curl_xfer_info);
2✔
391
        curl_easy_setopt(m_curl_handle, CURLOPT_XFERINFODATA, this);
2✔
392
        curl_easy_setopt(m_curl_handle, CURLOPT_NOPROGRESS, 0L);
2✔
393
    }
394
    else
395
    {
396
        curl_easy_setopt(m_curl_handle, CURLOPT_NOPROGRESS, 1L);
20,229✔
397
    }
398

399
    if (const auto& timeout = m_request->happy_eyeballs_timeout(); timeout.has_value())
20,228✔
400
    {
401
        curl_easy_setopt(m_curl_handle, CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS, static_cast<long>(timeout.value().count()));
1✔
402
    }
403

404
    // Note that this will lock the mutexes in the share callbacks.
405
    if (m_curl_share_handle != nullptr)
20,217✔
406
    {
407
        curl_easy_setopt(m_curl_handle, CURLOPT_SHARE, m_curl_share_handle);
19,990✔
408
    }
409

410
    // Set debug info if the user added a debug info functor callback
411
    // https://curl.se/libcurl/c/CURLOPT_DEBUGFUNCTION.html
412
    if (m_request->m_debug_info_handler != nullptr)
20,239✔
413
    {
414
        curl_easy_setopt(m_curl_handle, CURLOPT_VERBOSE, 1L);
3✔
415
        curl_easy_setopt(m_curl_handle, CURLOPT_DEBUGFUNCTION, curl_debug_info_callback);
3✔
416
        curl_easy_setopt(m_curl_handle, CURLOPT_DEBUGDATA, this);
3✔
417
    }
418
}
20,239✔
419

420
auto executor::copy_curl_to_response() -> void
20,231✔
421
{
422
    long http_response_code = 0;
20,231✔
423
    curl_easy_getinfo(m_curl_handle, CURLINFO_RESPONSE_CODE, &http_response_code);
20,231✔
424
    m_response.m_status_code = http::to_enum(static_cast<uint16_t>(http_response_code));
20,236✔
425

426
    long http_version = 0;
20,235✔
427
    curl_easy_getinfo(m_curl_handle, CURLINFO_HTTP_VERSION, &http_version);
20,235✔
428
    m_response.m_version = static_cast<http::version>(http_version);
20,233✔
429

430
    double total_time = 0;
20,233✔
431
    curl_easy_getinfo(m_curl_handle, CURLINFO_TOTAL_TIME, &total_time);
20,233✔
432
    // std::duration defaults to seconds, so don't need to duration_cast total time to seconds.
433
    m_response.m_total_time = static_cast<uint32_t>(
20,230✔
434
        std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::duration<double>{total_time}).count());
20,230✔
435

436
    long connect_count = 0;
20,230✔
437
    curl_easy_getinfo(m_curl_handle, CURLINFO_NUM_CONNECTS, &connect_count);
20,230✔
438
    m_response.m_num_connects = (connect_count >= std::numeric_limits<uint8_t>::max())
20,234✔
439
                                    ? std::numeric_limits<uint8_t>::max()
20,231✔
440
                                    : static_cast<uint8_t>(connect_count);
441

442
    long redirect_count = 0;
20,232✔
443
    curl_easy_getinfo(m_curl_handle, CURLINFO_REDIRECT_COUNT, &redirect_count);
20,232✔
444
    m_response.m_num_redirects = (redirect_count >= std::numeric_limits<uint8_t>::max())
20,236✔
445
                                     ? std::numeric_limits<uint8_t>::max()
20,238✔
446
                                     : static_cast<uint8_t>(redirect_count);
447
}
20,238✔
448

449
auto executor::set_timesup_response(std::chrono::milliseconds total_time) -> void
1✔
450
{
451
    m_response.m_status_code   = lift::http::status_code::http_504_gateway_timeout;
1✔
452
    m_response.m_total_time    = static_cast<uint32_t>(total_time.count());
1✔
453
    m_response.m_num_connects  = 0;
1✔
454
    m_response.m_num_redirects = 0;
1✔
455
}
1✔
456

457
auto executor::reset() -> void
27,943✔
458
{
459
    if (m_mime_handle != nullptr)
27,943✔
460
    {
461
        curl_mime_free(m_mime_handle);
×
462
        m_mime_handle = nullptr;
×
463
    }
464

465
    if (m_curl_request_headers != nullptr)
27,943✔
466
    {
467
        curl_slist_free_all(m_curl_request_headers);
3✔
468
        m_curl_request_headers = nullptr;
3✔
469
    }
470

471
    if (m_curl_resolve_hosts != nullptr)
27,943✔
472
    {
473
        curl_slist_free_all(m_curl_resolve_hosts);
3✔
474
        m_curl_resolve_hosts = nullptr;
3✔
475
    }
476

477
    // Regardless of sync/async all three pointers get reset to nullptr.
478
    m_request_sync  = nullptr;
27,943✔
479
    m_request_async = nullptr;
27,943✔
480
    m_request       = nullptr;
27,953✔
481

482
    m_timeout_iterator.reset();
27,953✔
483
    m_on_complete_handler_processed = false;
27,942✔
484
    m_response                      = response{};
27,942✔
485

486
    curl_easy_setopt(m_curl_handle, CURLOPT_SHARE, nullptr);
27,937✔
487
    m_curl_share_handle = nullptr;
27,959✔
488

489
    curl_easy_reset(m_curl_handle);
27,959✔
490
}
27,968✔
491

492
auto executor::convert(CURLcode curl_code) -> lift_status
20,238✔
493
{
494
#pragma GCC diagnostic push
495
#pragma GCC diagnostic ignored "-Wswitch-enum"
496
    switch (curl_code)
20,238✔
497
    {
498
        case CURLcode::CURLE_OK:
15,499✔
499
            return lift_status::success;
15,499✔
500
        case CURLcode::CURLE_GOT_NOTHING:
×
501
            return lift_status::response_empty;
×
502
        case CURLcode::CURLE_OPERATION_TIMEDOUT:
4,737✔
503
            return lift_status::timeout;
4,737✔
504
        case CURLcode::CURLE_COULDNT_CONNECT:
×
505
            return lift_status::connect_error;
×
506
        case CURLcode::CURLE_COULDNT_RESOLVE_HOST:
×
507
            return lift_status::connect_dns_error;
×
508
        case CURLcode::CURLE_SSL_CONNECT_ERROR:
×
509
            return lift_status::connect_ssl_error;
×
510
        case CURLcode::CURLE_WRITE_ERROR:
×
511
            return lift_status::download_error;
×
512
        case CURLcode::CURLE_SEND_ERROR:
×
513
            return lift_status::error_failed_to_start;
×
514
        default:
2✔
515
            return lift_status::error;
2✔
516
    }
517
#pragma GCC diagnostic pop
518
}
519

520
auto curl_write_header(char* buffer, size_t size, size_t nitems, void* user_ptr) -> size_t
155,391✔
521
{
522
    auto*        executor_ptr = static_cast<executor*>(user_ptr);
155,391✔
523
    auto&        response     = executor_ptr->m_response;
155,391✔
524
    const size_t data_length  = size * nitems;
155,391✔
525

526
    std::string_view data_view{buffer, data_length};
155,391✔
527

528
    if (data_view.empty())
155,281✔
529
    {
530
        return data_length;
×
531
    }
532

533
    // Ignore empty header lines from curl.
534
    if (data_length == 2 && data_view == "\r\n")
155,313✔
535
    {
536
        return data_length;
15,545✔
537
    }
538
    // Ignore the HTTP/ 'header' line from curl.
539
    constexpr size_t HTTPSLASH_LEN = 5;
139,749✔
540
    if (data_length >= 4 && data_view.substr(0, HTTPSLASH_LEN) == "HTTP/")
139,749✔
541
    {
542
        return data_length;
15,506✔
543
    }
544

545
    // Drop the trailing \r\n from the header.
546
    if (data_length >= 2)
124,181✔
547
    {
548
        size_t rm_size = (data_view[data_length - 1] == '\n' && data_view[data_length - 2] == '\r') ? 2 : 0;
124,219✔
549
        data_view.remove_suffix(rm_size);
124,284✔
550
    }
551

552
    response.m_headers.emplace_back(std::string{data_view.data(), data_view.length()});
124,208✔
553

554
    return data_length; // return original size for curl to continue processing
124,137✔
555
}
556

557
auto curl_write_data(void* buffer, size_t size, size_t nitems, void* user_ptr) -> size_t
15,496✔
558
{
559
    auto*  executor_ptr = static_cast<executor*>(user_ptr);
15,496✔
560
    auto&  response     = executor_ptr->m_response;
15,496✔
561
    size_t data_length  = size * nitems;
15,496✔
562

563
    std::string_view from{static_cast<const char*>(buffer), data_length};
15,496✔
564

565
    std::copy(from.begin(), from.end(), std::back_inserter(response.m_data));
15,469✔
566

567
    return data_length;
15,499✔
568
}
569

570
auto curl_xfer_info(
29✔
571
    void*      clientp,
572
    curl_off_t download_total_bytes,
573
    curl_off_t download_now_bytes,
574
    curl_off_t upload_total_bytes,
575
    curl_off_t upload_now_bytes) -> int
576
{
577
    const auto* executor_ptr = static_cast<const executor*>(clientp);
29✔
578

579
    if (executor_ptr != nullptr && executor_ptr->m_request->m_on_transfer_progress_handler != nullptr)
29✔
580
    {
581
        if (executor_ptr->m_request->m_on_transfer_progress_handler(
29✔
582
                *executor_ptr->m_request,
29✔
583
                download_total_bytes,
584
                download_now_bytes,
585
                upload_total_bytes,
586
                upload_now_bytes))
587
        {
588
            return 0;
28✔
589
        }
590
        else
591
        {
592
            return 1;
1✔
593
        }
594
    }
595
    else
596
    {
597
        return 0; // continue the request.
×
598
    }
599
}
600

601
auto curl_debug_info_callback(CURL* /*handle*/, curl_infotype type, char* data, size_t size, void* userptr) -> int
51✔
602
{
603
    const auto* executor_ptr = static_cast<const executor*>(userptr);
51✔
604

605
    if (executor_ptr != nullptr && executor_ptr->m_request->m_debug_info_handler != nullptr)
51✔
606
    {
607
        executor_ptr->m_request->m_debug_info_handler(
102✔
608
            *executor_ptr->m_request, static_cast<debug_info_type>(type), std::string_view{data, size});
51✔
609
    }
610

611
    // "this function must return 0" according to libcurl docs.
612
    return 0;
51✔
613
}
614

615
} // 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