Coverage Report

Created: 2026-07-16 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cpp-httplib/httplib.h
Line
Count
Source
1
//
2
//  httplib.h
3
//
4
//  Copyright (c) 2026 Yuji Hirose. All rights reserved.
5
//  MIT License
6
//
7
8
#ifndef CPPHTTPLIB_HTTPLIB_H
9
#define CPPHTTPLIB_HTTPLIB_H
10
11
#define CPPHTTPLIB_VERSION "0.50.1"
12
#define CPPHTTPLIB_VERSION_NUM "0x003201"
13
14
#ifdef _WIN32
15
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00
16
#error                                                                         \
17
    "cpp-httplib doesn't support Windows 8 or lower. Please use Windows 10 or later."
18
#endif
19
#endif
20
21
/*
22
 * Configuration
23
 */
24
25
#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND
26
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
27
#endif
28
29
#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND
30
0
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND 10000
31
#endif
32
33
#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
34
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100
35
#endif
36
37
#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
38
#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300
39
#endif
40
41
#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND
42
#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0
43
#endif
44
45
#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND
46
#define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND 5
47
#endif
48
49
#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND
50
#define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND 0
51
#endif
52
53
#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND
54
#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND 5
55
#endif
56
57
#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND
58
#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND 0
59
#endif
60
61
#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND
62
#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND 300
63
#endif
64
65
#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND
66
#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND 0
67
#endif
68
69
#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND
70
#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND 5
71
#endif
72
73
#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND
74
#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0
75
#endif
76
77
#ifndef CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND
78
#define CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND 0
79
#endif
80
81
#ifndef CPPHTTPLIB_EXPECT_100_THRESHOLD
82
#define CPPHTTPLIB_EXPECT_100_THRESHOLD 1024
83
#endif
84
85
#ifndef CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND
86
#define CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND 1000
87
#endif
88
89
#ifndef CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD
90
#define CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD (1024 * 1024)
91
#endif
92
93
#ifndef CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND
94
#define CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND 50
95
#endif
96
97
#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND
98
#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0
99
#endif
100
101
#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND
102
#ifdef _WIN32
103
#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 1000
104
#else
105
#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0
106
#endif
107
#endif
108
109
#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH
110
5.11k
#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
111
#endif
112
113
#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH
114
41.8k
#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192
115
#endif
116
117
#ifndef CPPHTTPLIB_HEADER_MAX_COUNT
118
15.8k
#define CPPHTTPLIB_HEADER_MAX_COUNT 100
119
#endif
120
121
#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT
122
#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
123
#endif
124
125
#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT
126
25.0k
#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024
127
#endif
128
129
#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH
130
#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (100 * 1024 * 1024) // 100MB
131
#endif
132
133
#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH
134
54
#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192
135
#endif
136
137
#ifndef CPPHTTPLIB_RANGE_MAX_COUNT
138
566
#define CPPHTTPLIB_RANGE_MAX_COUNT 1024
139
#endif
140
141
#ifndef CPPHTTPLIB_TCP_NODELAY
142
#define CPPHTTPLIB_TCP_NODELAY false
143
#endif
144
145
#ifndef CPPHTTPLIB_IPV6_V6ONLY
146
#define CPPHTTPLIB_IPV6_V6ONLY false
147
#endif
148
149
#ifndef CPPHTTPLIB_RECV_BUFSIZ
150
5.63k
#define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u)
151
#endif
152
153
#ifndef CPPHTTPLIB_SEND_BUFSIZ
154
#define CPPHTTPLIB_SEND_BUFSIZ size_t(16384u)
155
#endif
156
157
#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ
158
#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u)
159
#endif
160
161
#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
162
#define CPPHTTPLIB_THREAD_POOL_COUNT                                           \
163
0
  ((std::max)(8u, std::thread::hardware_concurrency() > 0                      \
164
0
                      ? std::thread::hardware_concurrency() - 1                \
165
0
                      : 0))
166
#endif
167
168
#ifndef CPPHTTPLIB_THREAD_POOL_MAX_COUNT
169
0
#define CPPHTTPLIB_THREAD_POOL_MAX_COUNT (CPPHTTPLIB_THREAD_POOL_COUNT * 4)
170
#endif
171
172
#ifndef CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT
173
#define CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT 3 // seconds
174
#endif
175
176
#ifndef CPPHTTPLIB_RECV_FLAGS
177
0
#define CPPHTTPLIB_RECV_FLAGS 0
178
#endif
179
180
#ifndef CPPHTTPLIB_SEND_FLAGS
181
0
#define CPPHTTPLIB_SEND_FLAGS 0
182
#endif
183
184
#ifndef CPPHTTPLIB_LISTEN_BACKLOG
185
#define CPPHTTPLIB_LISTEN_BACKLOG 5
186
#endif
187
188
#ifndef CPPHTTPLIB_MAX_LINE_LENGTH
189
4.42M
#define CPPHTTPLIB_MAX_LINE_LENGTH 32768
190
#endif
191
192
#ifndef CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH
193
#define CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH 16777216
194
#endif
195
196
#ifndef CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND
197
0
#define CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND 300
198
#endif
199
200
#ifndef CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND
201
0
#define CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND 5
202
#endif
203
204
#ifndef CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND
205
#define CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND 30
206
#endif
207
208
#ifndef CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS
209
#define CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS 0
210
#endif
211
212
/*
213
 * Headers
214
 */
215
216
#ifdef _WIN32
217
#ifndef _CRT_SECURE_NO_WARNINGS
218
#define _CRT_SECURE_NO_WARNINGS
219
#endif //_CRT_SECURE_NO_WARNINGS
220
221
#ifndef _CRT_NONSTDC_NO_DEPRECATE
222
#define _CRT_NONSTDC_NO_DEPRECATE
223
#endif //_CRT_NONSTDC_NO_DEPRECATE
224
225
#if defined(_MSC_VER)
226
#if _MSC_VER < 1900
227
#error Sorry, Visual Studio versions prior to 2015 are not supported
228
#endif
229
230
#pragma comment(lib, "ws2_32.lib")
231
232
#ifndef _SSIZE_T_DEFINED
233
using ssize_t = __int64;
234
#define _SSIZE_T_DEFINED
235
#endif
236
#endif // _MSC_VER
237
238
#ifndef S_ISREG
239
#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG)
240
#endif // S_ISREG
241
242
#ifndef S_ISDIR
243
#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR)
244
#endif // S_ISDIR
245
246
#ifndef NOMINMAX
247
#define NOMINMAX
248
#endif // NOMINMAX
249
250
#include <io.h>
251
#include <winsock2.h>
252
#include <ws2tcpip.h>
253
254
#if defined(__has_include)
255
#if __has_include(<afunix.h>)
256
// afunix.h uses types declared in winsock2.h, so has to be included after it.
257
#include <afunix.h>
258
#define CPPHTTPLIB_HAVE_AFUNIX_H 1
259
#endif
260
#endif
261
262
#ifndef WSA_FLAG_NO_HANDLE_INHERIT
263
#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
264
#endif
265
266
using nfds_t = unsigned long;
267
using socket_t = SOCKET;
268
using socklen_t = int;
269
270
#else // not _WIN32
271
272
#include <arpa/inet.h>
273
#if !defined(_AIX) && !defined(__MVS__)
274
#include <ifaddrs.h>
275
#endif
276
#ifdef __MVS__
277
#include <strings.h>
278
#ifndef NI_MAXHOST
279
#define NI_MAXHOST 1025
280
#endif
281
#endif
282
#include <net/if.h>
283
#include <netdb.h>
284
#include <netinet/in.h>
285
#ifdef __linux__
286
#include <resolv.h>
287
#undef _res // Undefine _res macro to avoid conflicts with user code (#2278)
288
#endif
289
#include <csignal>
290
#include <netinet/tcp.h>
291
#include <poll.h>
292
#include <pthread.h>
293
#include <sys/mman.h>
294
#include <sys/socket.h>
295
#include <sys/un.h>
296
#include <unistd.h>
297
298
using socket_t = int;
299
#ifndef INVALID_SOCKET
300
0
#define INVALID_SOCKET (-1)
301
#endif
302
#endif //_WIN32
303
304
#if defined(__APPLE__)
305
#include <TargetConditionals.h>
306
#endif
307
308
#include <algorithm>
309
#include <array>
310
#include <atomic>
311
#include <cassert>
312
#include <chrono>
313
#include <climits>
314
#include <condition_variable>
315
#include <cstdlib>
316
#include <cstring>
317
#include <errno.h>
318
#include <exception>
319
#include <fcntl.h>
320
#include <fstream>
321
#include <functional>
322
#include <iomanip>
323
#include <iostream>
324
#include <list>
325
#include <map>
326
#include <memory>
327
#include <mutex>
328
#include <random>
329
#include <regex>
330
#include <set>
331
#include <sstream>
332
#include <string>
333
#include <sys/stat.h>
334
#include <system_error>
335
#include <thread>
336
#include <unordered_map>
337
#include <unordered_set>
338
#include <utility>
339
340
// On macOS with a TLS backend, enable Keychain root certificates by default
341
// unless the user explicitly opts out. Not enabled on iOS/tvOS/watchOS since
342
// the SecTrustSettings APIs used to enumerate anchor certificates are macOS
343
// only; on those platforms the user must provide a CA bundle explicitly.
344
#if defined(__APPLE__) && defined(__clang__) &&                                \
345
    !defined(CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES) &&         \
346
    (defined(CPPHTTPLIB_OPENSSL_SUPPORT) ||                                    \
347
     defined(CPPHTTPLIB_MBEDTLS_SUPPORT) ||                                    \
348
     defined(CPPHTTPLIB_WOLFSSL_SUPPORT))
349
#if TARGET_OS_OSX
350
#ifndef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
351
#define CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
352
#endif
353
#endif
354
#endif
355
356
#if defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) &&                      \
357
    defined(__APPLE__) && !TARGET_OS_OSX
358
#error                                                                         \
359
    "CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN is only supported on macOS. On iOS/tvOS/watchOS, supply a CA bundle via set_ca_cert_path()."
360
#endif
361
362
// On Windows, enable Schannel certificate verification by default
363
// unless the user explicitly opts out.
364
#if defined(_WIN32) &&                                                         \
365
    !defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
366
#define CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
367
#endif
368
369
#if defined(CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO) ||                        \
370
    defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
371
#if TARGET_OS_MAC && defined(__clang__)
372
#include <CFNetwork/CFHost.h>
373
#include <CoreFoundation/CoreFoundation.h>
374
#endif
375
#endif
376
377
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
378
#ifdef _WIN32
379
#include <wincrypt.h>
380
381
// these are defined in wincrypt.h and it breaks compilation if BoringSSL is
382
// used
383
#undef X509_NAME
384
#undef X509_CERT_PAIR
385
#undef X509_EXTENSIONS
386
#undef PKCS7_SIGNER_INFO
387
388
#ifdef _MSC_VER
389
#pragma comment(lib, "crypt32.lib")
390
#endif
391
#endif // _WIN32
392
393
#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
394
#if TARGET_OS_OSX
395
#include <Security/Security.h>
396
#endif
397
#endif
398
399
#include <openssl/err.h>
400
#include <openssl/evp.h>
401
#include <openssl/ssl.h>
402
#include <openssl/x509v3.h>
403
404
#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK)
405
#include <openssl/applink.c>
406
#endif
407
408
#include <iostream>
409
#include <sstream>
410
411
#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER)
412
#if OPENSSL_VERSION_NUMBER < 0x1010107f
413
#error Please use OpenSSL or a current version of BoringSSL
414
#endif
415
#define SSL_get1_peer_certificate SSL_get_peer_certificate
416
#elif OPENSSL_VERSION_NUMBER < 0x30000000L
417
#error Sorry, OpenSSL versions prior to 3.0.0 are not supported
418
#endif
419
420
#endif // CPPHTTPLIB_OPENSSL_SUPPORT
421
422
#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
423
#include <mbedtls/ctr_drbg.h>
424
#include <mbedtls/entropy.h>
425
#include <mbedtls/error.h>
426
#include <mbedtls/md5.h>
427
#include <mbedtls/net_sockets.h>
428
#include <mbedtls/oid.h>
429
#include <mbedtls/pk.h>
430
#include <mbedtls/sha1.h>
431
#include <mbedtls/sha256.h>
432
#include <mbedtls/sha512.h>
433
#include <mbedtls/ssl.h>
434
#include <mbedtls/x509_crt.h>
435
#ifdef _WIN32
436
#include <wincrypt.h>
437
#ifdef _MSC_VER
438
#pragma comment(lib, "crypt32.lib")
439
#endif
440
#endif // _WIN32
441
#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
442
#if TARGET_OS_OSX
443
#include <Security/Security.h>
444
#endif
445
#endif
446
447
// Mbed TLS 3.x API compatibility
448
#if MBEDTLS_VERSION_MAJOR >= 3
449
#define CPPHTTPLIB_MBEDTLS_V3
450
#endif
451
452
#endif // CPPHTTPLIB_MBEDTLS_SUPPORT
453
454
#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT
455
#include <wolfssl/options.h>
456
457
#include <wolfssl/openssl/x509v3.h>
458
459
// Fallback definitions for older wolfSSL versions (e.g., 5.6.6)
460
#ifndef WOLFSSL_GEN_EMAIL
461
#define WOLFSSL_GEN_EMAIL 1
462
#endif
463
#ifndef WOLFSSL_GEN_DNS
464
#define WOLFSSL_GEN_DNS 2
465
#endif
466
#ifndef WOLFSSL_GEN_URI
467
#define WOLFSSL_GEN_URI 6
468
#endif
469
#ifndef WOLFSSL_GEN_IPADD
470
#define WOLFSSL_GEN_IPADD 7
471
#endif
472
473
#include <wolfssl/ssl.h>
474
#include <wolfssl/wolfcrypt/hash.h>
475
#include <wolfssl/wolfcrypt/md5.h>
476
#include <wolfssl/wolfcrypt/sha256.h>
477
#include <wolfssl/wolfcrypt/sha512.h>
478
#ifdef _WIN32
479
#include <wincrypt.h>
480
#ifdef _MSC_VER
481
#pragma comment(lib, "crypt32.lib")
482
#endif
483
#endif // _WIN32
484
#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
485
#if TARGET_OS_OSX
486
#include <Security/Security.h>
487
#endif
488
#endif
489
#endif // CPPHTTPLIB_WOLFSSL_SUPPORT
490
491
// Define CPPHTTPLIB_SSL_ENABLED if any SSL backend is available
492
#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) ||                                     \
493
    defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
494
#define CPPHTTPLIB_SSL_ENABLED
495
#endif
496
497
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
498
#include <zlib.h>
499
#endif
500
501
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
502
#include <brotli/decode.h>
503
#include <brotli/encode.h>
504
#endif
505
506
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
507
#include <zstd.h>
508
#endif
509
510
/*
511
 * Declaration
512
 */
513
namespace httplib {
514
515
namespace ws {
516
class WebSocket;
517
} // namespace ws
518
519
namespace detail {
520
521
/*
522
 * Backport std::make_unique from C++14.
523
 *
524
 * NOTE: This code came up with the following stackoverflow post:
525
 * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique
526
 *
527
 */
528
529
template <class T, class... Args>
530
typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
531
569
make_unique(Args &&...args) {
532
569
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
533
569
}
Unexecuted instantiation: _ZN7httplib6detail11make_uniqueINS0_17PathParamsMatcherEJRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEEEENS3_9enable_ifIXntsr3std8is_arrayIT_EE5valueENS3_10unique_ptrISD_NS3_14default_deleteISD_EEEEE4typeEDpOT0_
_ZN7httplib6detail11make_uniqueINS0_12RegexMatcherEJRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEEEENS3_9enable_ifIXntsr3std8is_arrayIT_EE5valueENS3_10unique_ptrISD_NS3_14default_deleteISD_EEEEE4typeEDpOT0_
Line
Count
Source
531
12
make_unique(Args &&...args) {
532
12
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
533
12
}
_ZN7httplib6detail11make_uniqueINS0_15gzip_compressorEJEEENSt3__19enable_ifIXntsr3std8is_arrayIT_EE5valueENS3_10unique_ptrIS5_NS3_14default_deleteIS5_EEEEE4typeEDpOT0_
Line
Count
Source
531
37
make_unique(Args &&...args) {
532
37
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
533
37
}
Unexecuted instantiation: _ZN7httplib6detail11make_uniqueINS0_12nocompressorEJEEENSt3__19enable_ifIXntsr3std8is_arrayIT_EE5valueENS3_10unique_ptrIS5_NS3_14default_deleteIS5_EEEEE4typeEDpOT0_
_ZN7httplib6detail11make_uniqueINS0_17gzip_decompressorEJEEENSt3__19enable_ifIXntsr3std8is_arrayIT_EE5valueENS3_10unique_ptrIS5_NS3_14default_deleteIS5_EEEEE4typeEDpOT0_
Line
Count
Source
531
520
make_unique(Args &&...args) {
532
520
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
533
520
}
Unexecuted instantiation: _ZN7httplib6detail11make_uniqueINS_8ResponseEJEEENSt3__19enable_ifIXntsr3std8is_arrayIT_EE5valueENS3_10unique_ptrIS5_NS3_14default_deleteIS5_EEEEE4typeEDpOT0_
Unexecuted instantiation: _ZN7httplib6detail11make_uniqueINS_16ClientConnectionEJEEENSt3__19enable_ifIXntsr3std8is_arrayIT_EE5valueENS3_10unique_ptrIS5_NS3_14default_deleteIS5_EEEEE4typeEDpOT0_
Unexecuted instantiation: _ZN7httplib6detail11make_uniqueINS0_12SocketStreamEJRiRlS4_S4_S4_EEENSt3__19enable_ifIXntsr3std8is_arrayIT_EE5valueENS5_10unique_ptrIS7_NS5_14default_deleteIS7_EEEEE4typeEDpOT0_
Unexecuted instantiation: _ZN7httplib6detail11make_uniqueINS_10ClientImplEJRNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEERiRKS9_SD_EEENS3_9enable_ifIXntsr3std8is_arrayIT_EE5valueENS3_10unique_ptrISF_NS3_14default_deleteISF_EEEEE4typeEDpOT0_
Unexecuted instantiation: _ZN7httplib6detail11make_uniqueINS_10ClientImplEJRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEiSB_SB_EEENS3_9enable_ifIXntsr3std8is_arrayIT_EE5valueENS3_10unique_ptrISD_NS3_14default_deleteISD_EEEEE4typeEDpOT0_
Unexecuted instantiation: _ZN7httplib6detail11make_uniqueINS_10ClientImplEJRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEERiSB_SB_EEENS3_9enable_ifIXntsr3std8is_arrayIT_EE5valueENS3_10unique_ptrISE_NS3_14default_deleteISE_EEEEE4typeEDpOT0_
534
535
template <class T>
536
typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type
537
make_unique(std::size_t n) {
538
  typedef typename std::remove_extent<T>::type RT;
539
  return std::unique_ptr<T>(new RT[n]);
540
}
541
542
// Locale-independent ASCII character classification. The <cctype>
543
// counterparts (std::isalnum, std::isdigit, ...) consult the global C locale,
544
// so e.g. std::isalnum(0xC5) can return true once an embedder calls
545
// setlocale(). HTTP grammars are defined over ASCII, so raw bytes must be
546
// classified without regard to the locale.
547
1.91M
inline bool is_ascii_digit(char c) { return '0' <= c && c <= '9'; }
548
549
1.59M
inline bool is_ascii_alpha(char c) {
550
1.59M
  return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
551
1.59M
}
552
553
1.60M
inline bool is_ascii_alnum(char c) {
554
1.60M
  return is_ascii_digit(c) || is_ascii_alpha(c);
555
1.60M
}
556
557
namespace case_ignore {
558
559
73.2M
inline unsigned char to_lower(int c) {
560
73.2M
  const static unsigned char table[256] = {
561
73.2M
      0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,
562
73.2M
      15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,
563
73.2M
      30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,
564
73.2M
      45,  46,  47,  48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,
565
73.2M
      60,  61,  62,  63,  64,  97,  98,  99,  100, 101, 102, 103, 104, 105, 106,
566
73.2M
      107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
567
73.2M
      122, 91,  92,  93,  94,  95,  96,  97,  98,  99,  100, 101, 102, 103, 104,
568
73.2M
      105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
569
73.2M
      120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
570
73.2M
      135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
571
73.2M
      150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
572
73.2M
      165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
573
73.2M
      180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 224, 225, 226,
574
73.2M
      227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241,
575
73.2M
      242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, 224,
576
73.2M
      225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
577
73.2M
      240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
578
73.2M
      255,
579
73.2M
  };
580
73.2M
  return table[(unsigned char)(char)c];
581
73.2M
}
582
583
24
inline std::string to_lower(const std::string &s) {
584
24
  std::string result = s;
585
24
  std::transform(
586
24
      result.begin(), result.end(), result.begin(),
587
569
      [](unsigned char c) { return static_cast<char>(to_lower(c)); });
588
24
  return result;
589
24
}
590
591
10.6M
inline bool equal(const std::string &a, const std::string &b) {
592
10.6M
  return a.size() == b.size() &&
593
34.2M
         std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) {
594
34.2M
           return to_lower(ca) == to_lower(cb);
595
34.2M
         });
596
10.6M
}
597
598
struct equal_to {
599
10.6M
  bool operator()(const std::string &a, const std::string &b) const {
600
10.6M
    return equal(a, b);
601
10.6M
  }
602
};
603
604
struct hash {
605
392k
  size_t operator()(const std::string &key) const {
606
392k
    return hash_core(key.data(), key.size(), 0);
607
392k
  }
608
609
4.17M
  size_t hash_core(const char *s, size_t l, size_t h) const {
610
4.17M
    return (l == 0) ? h
611
4.17M
                    : hash_core(s + 1, l - 1,
612
                                // Unsets the 6 high bits of h, therefore no
613
                                // overflow happens
614
3.78M
                                (((std::numeric_limits<size_t>::max)() >> 6) &
615
3.78M
                                 h * 33) ^
616
3.78M
                                    static_cast<unsigned char>(to_lower(*s)));
617
4.17M
  }
618
};
619
620
template <typename T>
621
using unordered_set = std::unordered_set<T, detail::case_ignore::hash,
622
                                         detail::case_ignore::equal_to>;
623
624
} // namespace case_ignore
625
626
// This is based on
627
// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189".
628
629
struct scope_exit {
630
  explicit scope_exit(std::function<void(void)> &&f)
631
0
      : exit_function(std::move(f)), execute_on_destruction{true} {}
632
633
  scope_exit(scope_exit &&rhs) noexcept
634
      : exit_function(std::move(rhs.exit_function)),
635
0
        execute_on_destruction{rhs.execute_on_destruction} {
636
0
    rhs.release();
637
0
  }
638
639
0
  ~scope_exit() {
640
0
    if (execute_on_destruction) { this->exit_function(); }
641
0
  }
642
643
0
  void release() { this->execute_on_destruction = false; }
644
645
private:
646
  scope_exit(const scope_exit &) = delete;
647
  void operator=(const scope_exit &) = delete;
648
  scope_exit &operator=(scope_exit &&) = delete;
649
650
  std::function<void(void)> exit_function;
651
  bool execute_on_destruction;
652
};
653
654
// Simple from_chars implementation for integer and double types (C++17
655
// substitute)
656
template <typename T> struct from_chars_result {
657
  const char *ptr;
658
  std::errc ec;
659
};
660
661
template <typename T>
662
inline from_chars_result<T> from_chars(const char *first, const char *last,
663
9.87k
                                       T &value, int base = 10) {
664
9.87k
  value = 0;
665
9.87k
  const char *p = first;
666
9.87k
  bool negative = false;
667
668
9.87k
  if (p != last && *p == '-') {
669
0
    negative = true;
670
0
    ++p;
671
0
  }
672
9.87k
  if (p == last) { return {first, std::errc::invalid_argument}; }
673
674
9.87k
  T result = 0;
675
32.7k
  for (; p != last; ++p) {
676
23.2k
    char c = *p;
677
23.2k
    int digit = -1;
678
23.2k
    if (is_ascii_digit(c)) {
679
23.2k
      digit = c - '0';
680
23.2k
    } else if ('a' <= c && c <= 'z') {
681
0
      digit = c - 'a' + 10;
682
0
    } else if ('A' <= c && c <= 'Z') {
683
0
      digit = c - 'A' + 10;
684
0
    } else {
685
0
      break;
686
0
    }
687
688
23.2k
    if (digit < 0 || digit >= base) { break; }
689
23.2k
    if (result > ((std::numeric_limits<T>::max)() - digit) / base) {
690
457
      return {p, std::errc::result_out_of_range};
691
457
    }
692
22.8k
    result = result * base + digit;
693
22.8k
  }
694
695
9.41k
  if (p == first || (negative && p == first + 1)) {
696
0
    return {first, std::errc::invalid_argument};
697
0
  }
698
699
9.41k
  value = negative ? -result : result;
700
9.41k
  return {p, std::errc{}};
701
9.41k
}
httplib::detail::from_chars_result<long> httplib::detail::from_chars<long>(char const*, char const*, long&, int)
Line
Count
Source
663
9.87k
                                       T &value, int base = 10) {
664
9.87k
  value = 0;
665
9.87k
  const char *p = first;
666
9.87k
  bool negative = false;
667
668
9.87k
  if (p != last && *p == '-') {
669
0
    negative = true;
670
0
    ++p;
671
0
  }
672
9.87k
  if (p == last) { return {first, std::errc::invalid_argument}; }
673
674
9.87k
  T result = 0;
675
32.7k
  for (; p != last; ++p) {
676
23.2k
    char c = *p;
677
23.2k
    int digit = -1;
678
23.2k
    if (is_ascii_digit(c)) {
679
23.2k
      digit = c - '0';
680
23.2k
    } else if ('a' <= c && c <= 'z') {
681
0
      digit = c - 'a' + 10;
682
0
    } else if ('A' <= c && c <= 'Z') {
683
0
      digit = c - 'A' + 10;
684
0
    } else {
685
0
      break;
686
0
    }
687
688
23.2k
    if (digit < 0 || digit >= base) { break; }
689
23.2k
    if (result > ((std::numeric_limits<T>::max)() - digit) / base) {
690
457
      return {p, std::errc::result_out_of_range};
691
457
    }
692
22.8k
    result = result * base + digit;
693
22.8k
  }
694
695
9.41k
  if (p == first || (negative && p == first + 1)) {
696
0
    return {first, std::errc::invalid_argument};
697
0
  }
698
699
9.41k
  value = negative ? -result : result;
700
9.41k
  return {p, std::errc{}};
701
9.41k
}
Unexecuted instantiation: httplib::detail::from_chars_result<int> httplib::detail::from_chars<int>(char const*, char const*, int&, int)
702
703
// from_chars for double (hand-written, locale-independent)
704
//
705
// The only double consumed by this library is the HTTP quality value, whose
706
// grammar is (RFC 9110 12.4.2):
707
//   qvalue = ( "0" [ "." 0*3DIGIT ] ) / ( "1" [ "." 0*3("0") ] )
708
// i.e. a non-negative decimal with no sign, exponent, "inf"/"nan", or wide
709
// magnitude. So this parser recognizes exactly  1*DIGIT [ "." *DIGIT ]  with
710
// '.' always the decimal separator (std::strtod would instead read it from the
711
// global C locale, mis-parsing q-values once an embedder calls
712
// setlocale(LC_ALL, "") into a comma-decimal locale). The caller range-checks
713
// the result to [0, 1], so inputs outside that range need not be distinguished
714
// here. Allocation-free, single pass, and free of the overflow/rounding edge
715
// cases that exponent and wide-range handling would introduce.
716
inline from_chars_result<double> from_chars(const char *first, const char *last,
717
5.71k
                                            double &value) {
718
5.71k
  value = 0.0;
719
5.71k
  const char *p = first;
720
721
  // Each 1eN is exactly representable, so a single final division by the
722
  // matching entry yields a correctly-rounded result.
723
5.71k
  static const double powers_of_ten[] = {
724
5.71k
      1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,  1e8, 1e9,
725
5.71k
      1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18};
726
5.71k
  const int max_frac_digits =
727
5.71k
      static_cast<int>(sizeof(powers_of_ten) / sizeof(powers_of_ten[0])) - 1;
728
729
  // Accumulate digits into a 64-bit integer and remember how many were
730
  // fractional. Two independent caps keep this bounded and safe:
731
  //   * accumulation saturates before mantissa could overflow uint64_t, and
732
  //   * frac_digits is capped at max_frac_digits so it is always a valid index
733
  //     into powers_of_ten (without this an input like "0.000...0" would never
734
  //     grow mantissa, so the saturation cap alone would not bound it).
735
  // Both caps only drop digits far beyond the precision a q-value needs; any
736
  // value they would change is well outside [0, 1] and rejected by the caller.
737
5.71k
  uint64_t mantissa = 0;
738
5.71k
  int frac_digits = 0;
739
5.71k
  bool seen_digit = false;
740
741
5.71k
  const uint64_t limit = ((std::numeric_limits<uint64_t>::max)() - 9) / 10;
742
9.51k
  auto accumulate = [&](char c) {
743
9.51k
    if (mantissa <= limit) {
744
8.87k
      mantissa = mantissa * 10 + static_cast<uint64_t>(c - '0');
745
8.87k
      return true;
746
8.87k
    }
747
640
    return false;
748
9.51k
  };
749
750
13.7k
  for (; p != last && is_ascii_digit(*p); ++p) {
751
8.03k
    seen_digit = true;
752
8.03k
    accumulate(*p);
753
8.03k
  }
754
755
5.71k
  if (p != last && *p == '.') {
756
458
    ++p;
757
2.25k
    for (; p != last && is_ascii_digit(*p); ++p) {
758
1.79k
      seen_digit = true;
759
1.79k
      if (frac_digits < max_frac_digits && accumulate(*p)) { ++frac_digits; }
760
1.79k
    }
761
458
  }
762
763
5.71k
  if (!seen_digit) { return {first, std::errc::invalid_argument}; }
764
765
5.38k
  value = static_cast<double>(mantissa) / powers_of_ten[frac_digits];
766
5.38k
  return {p, std::errc{}};
767
5.71k
}
768
769
0
inline bool parse_port(const char *s, size_t len, int &port) {
770
0
  int val = 0;
771
0
  auto r = from_chars(s, s + len, val);
772
0
  if (r.ec != std::errc{} || val < 1 || val > 65535) { return false; }
773
0
  port = val;
774
0
  return true;
775
0
}
776
777
0
inline bool parse_port(const std::string &s, int &port) {
778
0
  return parse_port(s.data(), s.size(), port);
779
0
}
780
781
struct UrlComponents {
782
  std::string scheme;
783
  std::string host;
784
  std::string port;
785
  std::string path;
786
  std::string query;
787
};
788
789
0
inline bool parse_url(const std::string &url, UrlComponents &uc) {
790
0
  uc = {};
791
0
  size_t pos = 0;
792
0
793
0
  auto sep = url.find("://");
794
0
  if (sep != std::string::npos) {
795
0
    uc.scheme = url.substr(0, sep);
796
0
797
0
    // Scheme must be [a-z]+ only
798
0
    if (uc.scheme.empty()) { return false; }
799
0
    for (auto c : uc.scheme) {
800
0
      if (c < 'a' || c > 'z') { return false; }
801
0
    }
802
0
803
0
    pos = sep + 3;
804
0
  } else if (url.compare(0, 2, "//") == 0) {
805
0
    pos = 2;
806
0
  }
807
0
808
0
  auto has_authority_prefix = pos > 0;
809
0
  auto has_authority = has_authority_prefix || (!url.empty() && url[0] != '/' &&
810
0
                                                url[0] != '?' && url[0] != '#');
811
0
  if (has_authority) {
812
0
    if (pos < url.size() && url[pos] == '[') {
813
0
      auto close = url.find(']', pos);
814
0
      if (close == std::string::npos) { return false; }
815
0
      uc.host = url.substr(pos + 1, close - pos - 1);
816
0
817
0
      // IPv6 host must be [a-fA-F0-9:]+ only
818
0
      if (uc.host.empty()) { return false; }
819
0
      for (auto c : uc.host) {
820
0
        if (!(is_ascii_digit(c) || (c >= 'a' && c <= 'f') ||
821
0
              (c >= 'A' && c <= 'F') || c == ':')) {
822
0
          return false;
823
0
        }
824
0
      }
825
0
826
0
      pos = close + 1;
827
0
    } else {
828
0
      auto end = url.find_first_of(":/?#", pos);
829
0
      if (end == std::string::npos) { end = url.size(); }
830
0
      uc.host = url.substr(pos, end - pos);
831
0
      pos = end;
832
0
    }
833
0
834
0
    if (pos < url.size() && url[pos] == ':') {
835
0
      ++pos;
836
0
      auto end = url.find_first_of("/?#", pos);
837
0
      if (end == std::string::npos) { end = url.size(); }
838
0
      uc.port = url.substr(pos, end - pos);
839
0
      pos = end;
840
0
    }
841
0
842
0
    // Without :// or //, the entire input must be consumed as host[:port].
843
0
    // If there is leftover (path, query, etc.), this is not a valid
844
0
    // host[:port] string — clear and reparse as a plain path.
845
0
    if (!has_authority_prefix && pos < url.size()) {
846
0
      uc.host.clear();
847
0
      uc.port.clear();
848
0
      pos = 0;
849
0
    }
850
0
  }
851
0
852
0
  if (pos < url.size() && url[pos] != '?' && url[pos] != '#') {
853
0
    auto end = url.find_first_of("?#", pos);
854
0
    if (end == std::string::npos) { end = url.size(); }
855
0
    uc.path = url.substr(pos, end - pos);
856
0
    pos = end;
857
0
  }
858
0
859
0
  if (pos < url.size() && url[pos] == '?') {
860
0
    auto end = url.find('#', pos);
861
0
    if (end == std::string::npos) { end = url.size(); }
862
0
    uc.query = url.substr(pos, end - pos);
863
0
  }
864
0
865
0
  return true;
866
0
}
867
868
} // namespace detail
869
870
enum class SSLVerifierResponse {
871
  // no decision has been made, use the built-in certificate verifier
872
  NoDecisionMade,
873
  // connection certificate is verified and accepted
874
  CertificateAccepted,
875
  // connection certificate was processed but is rejected
876
  CertificateRejected
877
};
878
879
// System CA loading policy for SSL clients. Auto (the default) loads system
880
// CA certs only when no custom CA is configured; enable_system_ca() switches
881
// to an explicit policy.
882
enum class SystemCAMode { Auto, Enabled, Disabled };
883
884
enum StatusCode {
885
  // Information responses
886
  Continue_100 = 100,
887
  SwitchingProtocol_101 = 101,
888
  Processing_102 = 102,
889
  EarlyHints_103 = 103,
890
891
  // Successful responses
892
  OK_200 = 200,
893
  Created_201 = 201,
894
  Accepted_202 = 202,
895
  NonAuthoritativeInformation_203 = 203,
896
  NoContent_204 = 204,
897
  ResetContent_205 = 205,
898
  PartialContent_206 = 206,
899
  MultiStatus_207 = 207,
900
  AlreadyReported_208 = 208,
901
  IMUsed_226 = 226,
902
903
  // Redirection messages
904
  MultipleChoices_300 = 300,
905
  MovedPermanently_301 = 301,
906
  Found_302 = 302,
907
  SeeOther_303 = 303,
908
  NotModified_304 = 304,
909
  UseProxy_305 = 305,
910
  unused_306 = 306,
911
  TemporaryRedirect_307 = 307,
912
  PermanentRedirect_308 = 308,
913
914
  // Client error responses
915
  BadRequest_400 = 400,
916
  Unauthorized_401 = 401,
917
  PaymentRequired_402 = 402,
918
  Forbidden_403 = 403,
919
  NotFound_404 = 404,
920
  MethodNotAllowed_405 = 405,
921
  NotAcceptable_406 = 406,
922
  ProxyAuthenticationRequired_407 = 407,
923
  RequestTimeout_408 = 408,
924
  Conflict_409 = 409,
925
  Gone_410 = 410,
926
  LengthRequired_411 = 411,
927
  PreconditionFailed_412 = 412,
928
  PayloadTooLarge_413 = 413,
929
  UriTooLong_414 = 414,
930
  UnsupportedMediaType_415 = 415,
931
  RangeNotSatisfiable_416 = 416,
932
  ExpectationFailed_417 = 417,
933
  ImATeapot_418 = 418,
934
  MisdirectedRequest_421 = 421,
935
  UnprocessableContent_422 = 422,
936
  Locked_423 = 423,
937
  FailedDependency_424 = 424,
938
  TooEarly_425 = 425,
939
  UpgradeRequired_426 = 426,
940
  PreconditionRequired_428 = 428,
941
  TooManyRequests_429 = 429,
942
  RequestHeaderFieldsTooLarge_431 = 431,
943
  UnavailableForLegalReasons_451 = 451,
944
945
  // Server error responses
946
  InternalServerError_500 = 500,
947
  NotImplemented_501 = 501,
948
  BadGateway_502 = 502,
949
  ServiceUnavailable_503 = 503,
950
  GatewayTimeout_504 = 504,
951
  HttpVersionNotSupported_505 = 505,
952
  VariantAlsoNegotiates_506 = 506,
953
  InsufficientStorage_507 = 507,
954
  LoopDetected_508 = 508,
955
  NotExtended_510 = 510,
956
  NetworkAuthenticationRequired_511 = 511,
957
};
958
959
using Headers =
960
    std::unordered_multimap<std::string, std::string, detail::case_ignore::hash,
961
                            detail::case_ignore::equal_to>;
962
963
using Params = std::multimap<std::string, std::string>;
964
using Match = std::smatch;
965
966
using DownloadProgress = std::function<bool(size_t current, size_t total)>;
967
using UploadProgress = std::function<bool(size_t current, size_t total)>;
968
969
/*
970
 * detail: type-erased storage used by UserData.
971
 * ABI-stable regardless of C++ standard — always uses this custom
972
 * implementation instead of std::any.
973
 */
974
namespace detail {
975
976
using any_type_id = const void *;
977
978
template <typename T> any_type_id any_typeid() noexcept {
979
  static const char id = 0;
980
  return &id;
981
}
982
983
struct any_storage {
984
  virtual ~any_storage() = default;
985
  virtual std::unique_ptr<any_storage> clone() const = 0;
986
  virtual any_type_id type_id() const noexcept = 0;
987
};
988
989
template <typename T> struct any_value final : any_storage {
990
  T value;
991
  template <typename U> explicit any_value(U &&v) : value(std::forward<U>(v)) {}
992
  std::unique_ptr<any_storage> clone() const override {
993
    return std::unique_ptr<any_storage>(new any_value<T>(value));
994
  }
995
  any_type_id type_id() const noexcept override { return any_typeid<T>(); }
996
};
997
998
} // namespace detail
999
1000
class UserData {
1001
public:
1002
6.77k
  UserData() = default;
1003
  UserData(UserData &&) noexcept = default;
1004
  UserData &operator=(UserData &&) noexcept = default;
1005
1006
0
  UserData(const UserData &o) {
1007
0
    for (const auto &e : o.entries_) {
1008
0
      if (e.second) { entries_[e.first] = e.second->clone(); }
1009
0
    }
1010
0
  }
1011
1012
0
  UserData &operator=(const UserData &o) {
1013
0
    if (this != &o) {
1014
0
      entries_.clear();
1015
0
      for (const auto &e : o.entries_) {
1016
0
        if (e.second) { entries_[e.first] = e.second->clone(); }
1017
0
      }
1018
0
    }
1019
0
    return *this;
1020
0
  }
1021
1022
  template <typename T> void set(const std::string &key, T &&value) {
1023
    using D = typename std::decay<T>::type;
1024
    entries_[key].reset(new detail::any_value<D>(std::forward<T>(value)));
1025
  }
1026
1027
  template <typename T> T *get(const std::string &key) noexcept {
1028
    auto it = entries_.find(key);
1029
    if (it == entries_.end() || !it->second) { return nullptr; }
1030
    if (it->second->type_id() != detail::any_typeid<T>()) { return nullptr; }
1031
    return &static_cast<detail::any_value<T> *>(it->second.get())->value;
1032
  }
1033
1034
  template <typename T> const T *get(const std::string &key) const noexcept {
1035
    auto it = entries_.find(key);
1036
    if (it == entries_.end() || !it->second) { return nullptr; }
1037
    if (it->second->type_id() != detail::any_typeid<T>()) { return nullptr; }
1038
    return &static_cast<const detail::any_value<T> *>(it->second.get())->value;
1039
  }
1040
1041
0
  bool has(const std::string &key) const noexcept {
1042
0
    return entries_.find(key) != entries_.end();
1043
0
  }
1044
1045
0
  void erase(const std::string &key) { entries_.erase(key); }
1046
1047
0
  void clear() noexcept { entries_.clear(); }
1048
1049
private:
1050
  std::unordered_map<std::string, std::unique_ptr<detail::any_storage>>
1051
      entries_;
1052
};
1053
1054
struct Response;
1055
using ResponseHandler = std::function<bool(const Response &response)>;
1056
1057
struct FormData {
1058
  std::string name;
1059
  std::string content;
1060
  std::string filename;
1061
  std::string content_type;
1062
  Headers headers;
1063
};
1064
1065
struct FormField {
1066
  std::string name;
1067
  std::string content;
1068
  Headers headers;
1069
};
1070
using FormFields = std::multimap<std::string, FormField>;
1071
1072
using FormFiles = std::multimap<std::string, FormData>;
1073
1074
struct MultipartFormData {
1075
  FormFields fields; // Text fields from multipart
1076
  FormFiles files;   // Files from multipart
1077
1078
  // Text field access
1079
  std::string get_field(const std::string &key, size_t id = 0) const;
1080
  std::vector<std::string> get_fields(const std::string &key) const;
1081
  bool has_field(const std::string &key) const;
1082
  size_t get_field_count(const std::string &key) const;
1083
1084
  // File access
1085
  FormData get_file(const std::string &key, size_t id = 0) const;
1086
  std::vector<FormData> get_files(const std::string &key) const;
1087
  bool has_file(const std::string &key) const;
1088
  size_t get_file_count(const std::string &key) const;
1089
};
1090
1091
struct UploadFormData {
1092
  std::string name;
1093
  std::string content;
1094
  std::string filename;
1095
  std::string content_type;
1096
};
1097
using UploadFormDataItems = std::vector<UploadFormData>;
1098
1099
class DataSink {
1100
public:
1101
0
  DataSink() : os(&sb_), sb_(*this) {}
1102
1103
  DataSink(const DataSink &) = delete;
1104
  DataSink &operator=(const DataSink &) = delete;
1105
  DataSink(DataSink &&) = delete;
1106
  DataSink &operator=(DataSink &&) = delete;
1107
1108
  std::function<bool(const char *data, size_t data_len)> write;
1109
  std::function<bool()> is_writable;
1110
  std::function<void()> done;
1111
  std::function<void(const Headers &trailer)> done_with_trailer;
1112
  std::ostream os;
1113
1114
private:
1115
  class data_sink_streambuf final : public std::streambuf {
1116
  public:
1117
0
    explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {}
1118
1119
  protected:
1120
0
    std::streamsize xsputn(const char *s, std::streamsize n) override {
1121
0
      if (sink_.write(s, static_cast<size_t>(n))) { return n; }
1122
0
      return 0;
1123
0
    }
1124
1125
  private:
1126
    DataSink &sink_;
1127
  };
1128
1129
  data_sink_streambuf sb_;
1130
};
1131
1132
using ContentProvider =
1133
    std::function<bool(size_t offset, size_t length, DataSink &sink)>;
1134
1135
using ContentProviderWithoutLength =
1136
    std::function<bool(size_t offset, DataSink &sink)>;
1137
1138
using ContentProviderResourceReleaser = std::function<void(bool success)>;
1139
1140
struct FormDataProvider {
1141
  std::string name;
1142
  ContentProviderWithoutLength provider;
1143
  std::string filename;
1144
  std::string content_type;
1145
};
1146
using FormDataProviderItems = std::vector<FormDataProvider>;
1147
1148
inline FormDataProvider
1149
make_file_provider(const std::string &name, const std::string &filepath,
1150
                   const std::string &filename = std::string(),
1151
0
                   const std::string &content_type = std::string()) {
1152
0
  FormDataProvider fdp;
1153
0
  fdp.name = name;
1154
0
  fdp.filename = filename.empty() ? filepath : filename;
1155
0
  fdp.content_type = content_type;
1156
0
  fdp.provider = [filepath](size_t offset, DataSink &sink) -> bool {
1157
0
    std::ifstream f(filepath, std::ios::binary);
1158
0
    if (!f) { return false; }
1159
0
    if (offset > 0) {
1160
0
      f.seekg(static_cast<std::streamoff>(offset));
1161
0
      if (!f.good()) {
1162
0
        sink.done();
1163
0
        return true;
1164
0
      }
1165
0
    }
1166
0
    char buf[8192];
1167
0
    f.read(buf, sizeof(buf));
1168
0
    auto n = static_cast<size_t>(f.gcount());
1169
0
    if (n > 0) { return sink.write(buf, n); }
1170
0
    sink.done(); // EOF
1171
0
    return true;
1172
0
  };
1173
0
  return fdp;
1174
0
}
1175
1176
inline std::pair<size_t, ContentProvider>
1177
0
make_file_body(const std::string &filepath) {
1178
0
  size_t size = 0;
1179
0
  {
1180
0
    std::ifstream f(filepath, std::ios::binary | std::ios::ate);
1181
0
    if (!f) { return {0, ContentProvider{}}; }
1182
0
    size = static_cast<size_t>(f.tellg());
1183
0
  }
1184
0
1185
0
  ContentProvider provider = [filepath](size_t offset, size_t length,
1186
0
                                        DataSink &sink) -> bool {
1187
0
    std::ifstream f(filepath, std::ios::binary);
1188
0
    if (!f) { return false; }
1189
0
    f.seekg(static_cast<std::streamoff>(offset));
1190
0
    if (!f.good()) { return false; }
1191
0
    char buf[8192];
1192
0
    while (length > 0) {
1193
0
      auto to_read = (std::min)(sizeof(buf), length);
1194
0
      f.read(buf, static_cast<std::streamsize>(to_read));
1195
0
      auto n = static_cast<size_t>(f.gcount());
1196
0
      if (n == 0) { break; }
1197
0
      if (!sink.write(buf, n)) { return false; }
1198
0
      length -= n;
1199
0
    }
1200
0
    return true;
1201
0
  };
1202
0
  return {size, std::move(provider)};
1203
0
}
1204
1205
using ContentReceiverWithProgress = std::function<bool(
1206
    const char *data, size_t data_length, size_t offset, size_t total_length)>;
1207
1208
using ContentReceiver =
1209
    std::function<bool(const char *data, size_t data_length)>;
1210
1211
using FormDataHeader = std::function<bool(const FormData &file)>;
1212
1213
class ContentReader {
1214
public:
1215
  using Reader = std::function<bool(ContentReceiver receiver)>;
1216
  using FormDataReader =
1217
      std::function<bool(FormDataHeader header, ContentReceiver receiver)>;
1218
1219
  ContentReader(Reader reader, FormDataReader multipart_reader)
1220
3.57k
      : reader_(std::move(reader)),
1221
3.57k
        formdata_reader_(std::move(multipart_reader)) {}
1222
1223
0
  bool operator()(FormDataHeader header, ContentReceiver receiver) const {
1224
0
    return formdata_reader_(std::move(header), std::move(receiver));
1225
0
  }
1226
1227
0
  bool operator()(ContentReceiver receiver) const {
1228
0
    return reader_(std::move(receiver));
1229
0
  }
1230
1231
  Reader reader_;
1232
  FormDataReader formdata_reader_;
1233
};
1234
1235
using Range = std::pair<ssize_t, ssize_t>;
1236
using Ranges = std::vector<Range>;
1237
1238
#ifdef CPPHTTPLIB_SSL_ENABLED
1239
// TLS abstraction layer - public type definitions and API
1240
namespace tls {
1241
1242
// Opaque handles (defined as void* for abstraction)
1243
using ctx_t = void *;
1244
using session_t = void *;
1245
using const_session_t = const void *; // For read-only session access
1246
using cert_t = void *;
1247
using ca_store_t = void *;
1248
1249
// TLS versions
1250
enum class Version {
1251
  TLS1_2 = 0x0303,
1252
  TLS1_3 = 0x0304,
1253
};
1254
1255
// Subject Alternative Names (SAN) entry types
1256
enum class SanType { DNS, IP, EMAIL, URI, OTHER };
1257
1258
// SAN entry structure
1259
struct SanEntry {
1260
  SanType type;
1261
  std::string value;
1262
};
1263
1264
// Verification context for certificate verification callback
1265
struct VerifyContext {
1266
  session_t session;        // TLS session handle
1267
  cert_t cert;              // Current certificate being verified
1268
  int depth;                // Certificate chain depth (0 = leaf)
1269
  bool preverify_ok;        // OpenSSL/Mbed TLS pre-verification result
1270
  long error_code;          // Backend-specific error code (0 = no error)
1271
  const char *error_string; // Human-readable error description
1272
1273
  // Certificate introspection methods
1274
  std::string subject_cn() const;
1275
  std::string issuer_name() const;
1276
  bool check_hostname(const char *hostname) const;
1277
  std::vector<SanEntry> sans() const;
1278
  bool validity(time_t &not_before, time_t &not_after) const;
1279
  std::string serial() const;
1280
};
1281
1282
using VerifyCallback = std::function<bool(const VerifyContext &ctx)>;
1283
1284
// TlsError codes for TLS operations (backend-independent)
1285
enum class ErrorCode : int {
1286
  Success = 0,
1287
  WantRead,         // Non-blocking: need to wait for read
1288
  WantWrite,        // Non-blocking: need to wait for write
1289
  PeerClosed,       // Peer closed the connection
1290
  Fatal,            // Unrecoverable error
1291
  SyscallError,     // System call error (check sys_errno)
1292
  CertVerifyFailed, // Certificate verification failed
1293
  HostnameMismatch, // Hostname verification failed
1294
};
1295
1296
// TLS error information
1297
struct TlsError {
1298
  ErrorCode code = ErrorCode::Fatal;
1299
  uint64_t backend_code = 0; // OpenSSL: ERR_get_error(), mbedTLS: return value
1300
  int sys_errno = 0;         // errno when SyscallError
1301
1302
  // Convert verification error code to human-readable string
1303
  static std::string verify_error_to_string(long error_code);
1304
};
1305
1306
// RAII wrapper for peer certificate
1307
class PeerCert {
1308
public:
1309
  PeerCert();
1310
  PeerCert(PeerCert &&other) noexcept;
1311
  PeerCert &operator=(PeerCert &&other) noexcept;
1312
  ~PeerCert();
1313
1314
  PeerCert(const PeerCert &) = delete;
1315
  PeerCert &operator=(const PeerCert &) = delete;
1316
1317
  explicit operator bool() const;
1318
  std::string subject_cn() const;
1319
  std::string issuer_name() const;
1320
  bool check_hostname(const char *hostname) const;
1321
  std::vector<SanEntry> sans() const;
1322
  bool validity(time_t &not_before, time_t &not_after) const;
1323
  std::string serial() const;
1324
1325
private:
1326
  explicit PeerCert(cert_t cert);
1327
  cert_t cert_ = nullptr;
1328
  friend PeerCert get_peer_cert_from_session(const_session_t session);
1329
};
1330
1331
// Callback for TLS context setup (used by SSLServer constructor)
1332
using ContextSetupCallback = std::function<bool(ctx_t ctx)>;
1333
1334
} // namespace tls
1335
#endif
1336
1337
struct Request {
1338
  std::string method;
1339
  std::string path;
1340
  std::string matched_route;
1341
  Params params;
1342
  Headers headers;
1343
  Headers trailers;
1344
  std::string body;
1345
1346
  std::string remote_addr;
1347
  int remote_port = -1;
1348
  std::string local_addr;
1349
  int local_port = -1;
1350
1351
  // for server
1352
  std::string version;
1353
  std::string target;
1354
  MultipartFormData form;
1355
  Ranges ranges;
1356
  Match matches;
1357
  std::unordered_map<std::string, std::string> path_params;
1358
0
  std::function<bool()> is_connection_closed = []() { return true; };
1359
1360
  // for client
1361
  std::vector<std::string> accept_content_types;
1362
  ResponseHandler response_handler;
1363
  ContentReceiverWithProgress content_receiver;
1364
  DownloadProgress download_progress;
1365
  UploadProgress upload_progress;
1366
1367
  bool has_header(const std::string &key) const;
1368
  std::string get_header_value(const std::string &key, const char *def = "",
1369
                               size_t id = 0) const;
1370
  size_t get_header_value_u64(const std::string &key, size_t def = 0,
1371
                              size_t id = 0) const;
1372
  size_t get_header_value_count(const std::string &key) const;
1373
  void set_header(const std::string &key, const std::string &val);
1374
1375
  bool has_trailer(const std::string &key) const;
1376
  std::string get_trailer_value(const std::string &key, size_t id = 0) const;
1377
  size_t get_trailer_value_count(const std::string &key) const;
1378
1379
  bool has_param(const std::string &key) const;
1380
  std::string get_param_value(const std::string &key, size_t id = 0) const;
1381
  std::vector<std::string> get_param_values(const std::string &key) const;
1382
  size_t get_param_value_count(const std::string &key) const;
1383
1384
  bool is_multipart_form_data() const;
1385
1386
  // private members...
1387
  bool body_consumed_ = false;
1388
  size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT;
1389
  size_t content_length_ = 0;
1390
  ContentProvider content_provider_;
1391
  bool is_chunked_content_provider_ = false;
1392
  size_t authorization_count_ = 0;
1393
  std::chrono::time_point<std::chrono::steady_clock> start_time_ =
1394
      (std::chrono::steady_clock::time_point::min)();
1395
1396
#ifdef CPPHTTPLIB_SSL_ENABLED
1397
  tls::const_session_t ssl = nullptr;
1398
  tls::PeerCert peer_cert() const;
1399
  std::string sni() const;
1400
#endif
1401
};
1402
1403
struct Response {
1404
  std::string version;
1405
  int status = -1;
1406
  std::string reason;
1407
  Headers headers;
1408
  Headers trailers;
1409
  std::string body;
1410
  std::string location; // Redirect location
1411
1412
  // User-defined context — set by pre-routing/pre-request handlers and read
1413
  // by route handlers to pass arbitrary data (e.g. decoded auth tokens).
1414
  UserData user_data;
1415
1416
  bool has_header(const std::string &key) const;
1417
  std::string get_header_value(const std::string &key, const char *def = "",
1418
                               size_t id = 0) const;
1419
  size_t get_header_value_u64(const std::string &key, size_t def = 0,
1420
                              size_t id = 0) const;
1421
  size_t get_header_value_count(const std::string &key) const;
1422
  void set_header(const std::string &key, const std::string &val);
1423
1424
  bool has_trailer(const std::string &key) const;
1425
  std::string get_trailer_value(const std::string &key, size_t id = 0) const;
1426
  size_t get_trailer_value_count(const std::string &key) const;
1427
1428
  void set_redirect(const std::string &url, int status = StatusCode::Found_302);
1429
  void set_content(const char *s, size_t n, const std::string &content_type);
1430
  void set_content(const std::string &s, const std::string &content_type);
1431
  void set_content(std::string &&s, const std::string &content_type);
1432
1433
  void set_content_provider(
1434
      size_t length, const std::string &content_type, ContentProvider provider,
1435
      ContentProviderResourceReleaser resource_releaser = nullptr);
1436
1437
  void set_content_provider(
1438
      const std::string &content_type, ContentProviderWithoutLength provider,
1439
      ContentProviderResourceReleaser resource_releaser = nullptr);
1440
1441
  void set_chunked_content_provider(
1442
      const std::string &content_type, ContentProviderWithoutLength provider,
1443
      ContentProviderResourceReleaser resource_releaser = nullptr);
1444
1445
  void set_file_content(const std::string &path,
1446
                        const std::string &content_type);
1447
  void set_file_content(const std::string &path);
1448
1449
6.77k
  Response() = default;
1450
  Response(const Response &) = default;
1451
  Response &operator=(const Response &) = default;
1452
  Response(Response &&) = default;
1453
  Response &operator=(Response &&) = default;
1454
6.77k
  ~Response() {
1455
6.77k
    if (content_provider_resource_releaser_) {
1456
0
      content_provider_resource_releaser_(content_provider_success_);
1457
0
    }
1458
6.77k
  }
1459
1460
  // private members...
1461
  size_t content_length_ = 0;
1462
  ContentProvider content_provider_;
1463
  ContentProviderResourceReleaser content_provider_resource_releaser_;
1464
  bool is_chunked_content_provider_ = false;
1465
  bool content_provider_success_ = false;
1466
  std::string file_content_path_;
1467
  std::string file_content_content_type_;
1468
};
1469
1470
enum class Error {
1471
  Success = 0,
1472
  Unknown,
1473
  Connection,
1474
  BindIPAddress,
1475
  Read,
1476
  Write,
1477
  ExceedRedirectCount,
1478
  Canceled,
1479
  SSLConnection,
1480
  SSLLoadingCerts,
1481
  SSLServerVerification,
1482
  SSLServerHostnameVerification,
1483
  UnsupportedMultipartBoundaryChars,
1484
  Compression,
1485
  ConnectionTimeout,
1486
  ProxyConnection,
1487
  ConnectionClosed,
1488
  Timeout,
1489
  ResourceExhaustion,
1490
  TooManyFormDataFiles,
1491
  ExceedMaxPayloadSize,
1492
  ExceedUriMaxLength,
1493
  ExceedMaxSocketDescriptorCount,
1494
  InvalidRequestLine,
1495
  InvalidHTTPMethod,
1496
  InvalidHTTPVersion,
1497
  InvalidHeaders,
1498
  MultipartParsing,
1499
  OpenFile,
1500
  Listen,
1501
  GetSockName,
1502
  UnsupportedAddressFamily,
1503
  HTTPParsing,
1504
  InvalidRangeHeader,
1505
1506
  // For internal use only
1507
  SSLPeerCouldBeClosed_,
1508
};
1509
1510
std::string to_string(Error error);
1511
1512
std::ostream &operator<<(std::ostream &os, const Error &obj);
1513
1514
class Stream {
1515
public:
1516
13.5k
  virtual ~Stream() = default;
1517
1518
  virtual bool is_readable() const = 0;
1519
  virtual bool wait_readable() const = 0;
1520
  virtual bool wait_writable() const = 0;
1521
0
  virtual bool is_peer_alive() const { return wait_writable(); }
1522
1523
  virtual ssize_t read(char *ptr, size_t size) = 0;
1524
  virtual ssize_t write(const char *ptr, size_t size) = 0;
1525
  virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
1526
  virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0;
1527
  virtual socket_t socket() const = 0;
1528
1529
  virtual time_t duration() const = 0;
1530
1531
0
  virtual void set_read_timeout(time_t sec, time_t usec = 0) {
1532
0
    (void)sec;
1533
0
    (void)usec;
1534
0
  }
1535
1536
  ssize_t write(const char *ptr);
1537
  ssize_t write(const std::string &s);
1538
1539
1.35k
  Error get_error() const { return error_; }
1540
1541
protected:
1542
  Error error_ = Error::Success;
1543
};
1544
1545
class TaskQueue {
1546
public:
1547
0
  TaskQueue() = default;
1548
0
  virtual ~TaskQueue() = default;
1549
1550
  virtual bool enqueue(std::function<void()> fn) = 0;
1551
  virtual void shutdown() = 0;
1552
1553
0
  virtual void on_idle() {}
1554
};
1555
1556
class ThreadPool final : public TaskQueue {
1557
public:
1558
  explicit ThreadPool(
1559
      size_t n, size_t max_n = 0, size_t mqr = 0,
1560
      time_t idle_timeout_sec = CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT);
1561
  ThreadPool(const ThreadPool &) = delete;
1562
0
  ~ThreadPool() override = default;
1563
1564
  bool enqueue(std::function<void()> fn) override;
1565
  void shutdown() override;
1566
1567
private:
1568
  void worker(bool is_dynamic);
1569
  void move_to_finished(std::thread::id id);
1570
  void cleanup_finished_threads();
1571
1572
  size_t base_thread_count_;
1573
  size_t max_thread_count_;
1574
  size_t max_queued_requests_;
1575
  time_t idle_timeout_sec_;
1576
  size_t idle_thread_count_;
1577
1578
  bool shutdown_;
1579
1580
  std::list<std::function<void()>> jobs_;
1581
  std::vector<std::thread> threads_;       // base threads
1582
  std::list<std::thread> dynamic_threads_; // dynamic threads
1583
  std::vector<std::thread>
1584
      finished_threads_; // exited dynamic threads awaiting join
1585
1586
  std::condition_variable cond_;
1587
  std::mutex mutex_;
1588
};
1589
1590
using Logger = std::function<void(const Request &, const Response &)>;
1591
1592
// Forward declaration for Error type
1593
enum class Error;
1594
using ErrorLogger = std::function<void(const Error &, const Request *)>;
1595
1596
using SocketOptions = std::function<void(socket_t sock)>;
1597
1598
void default_socket_options(socket_t sock);
1599
1600
bool set_socket_opt(socket_t sock, int level, int optname, int optval);
1601
1602
const char *status_message(int status);
1603
1604
std::string to_string(Error error);
1605
1606
std::ostream &operator<<(std::ostream &os, const Error &obj);
1607
1608
std::string get_bearer_token_auth(const Request &req);
1609
1610
namespace detail {
1611
1612
class MatcherBase {
1613
public:
1614
12
  MatcherBase(std::string pattern) : pattern_(std::move(pattern)) {}
1615
0
  virtual ~MatcherBase() = default;
1616
1617
4.19k
  const std::string &pattern() const { return pattern_; }
1618
1619
  // Match request path and populate its matches and
1620
  virtual bool match(Request &request) const = 0;
1621
1622
private:
1623
  std::string pattern_;
1624
};
1625
1626
/**
1627
 * Captures parameters in request path and stores them in Request::path_params
1628
 *
1629
 * Capture name is a substring of a pattern from : to /.
1630
 * The rest of the pattern is matched against the request path directly
1631
 * Parameters are captured starting from the next character after
1632
 * the end of the last matched static pattern fragment until the next /.
1633
 *
1634
 * Example pattern:
1635
 * "/path/fragments/:capture/more/fragments/:second_capture"
1636
 * Static fragments:
1637
 * "/path/fragments/", "more/fragments/"
1638
 *
1639
 * Given the following request path:
1640
 * "/path/fragments/:1/more/fragments/:2"
1641
 * the resulting capture will be
1642
 * {{"capture", "1"}, {"second_capture", "2"}}
1643
 */
1644
class PathParamsMatcher final : public MatcherBase {
1645
public:
1646
  PathParamsMatcher(const std::string &pattern);
1647
1648
  bool match(Request &request) const override;
1649
1650
private:
1651
  // Treat segment separators as the end of path parameter capture
1652
  // Does not need to handle query parameters as they are parsed before path
1653
  // matching
1654
  static constexpr char separator = '/';
1655
1656
  // Contains static path fragments to match against, excluding the '/' after
1657
  // path params
1658
  // Fragments are separated by path params
1659
  std::vector<std::string> static_fragments_;
1660
  // Stores the names of the path parameters to be used as keys in the
1661
  // Request::path_params map
1662
  std::vector<std::string> param_names_;
1663
};
1664
1665
/**
1666
 * Performs std::regex_match on request path
1667
 * and stores the result in Request::matches
1668
 *
1669
 * Note that regex match is performed directly on the whole request.
1670
 * This means that wildcard patterns may match multiple path segments with /:
1671
 * "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end".
1672
 */
1673
class RegexMatcher final : public MatcherBase {
1674
public:
1675
  RegexMatcher(const std::string &pattern)
1676
12
      : MatcherBase(pattern), regex_(pattern) {}
1677
1678
  bool match(Request &request) const override;
1679
1680
private:
1681
  std::regex regex_;
1682
};
1683
1684
int close_socket(socket_t sock) noexcept;
1685
1686
ssize_t write_headers(Stream &strm, const Headers &headers);
1687
1688
bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec,
1689
                         time_t usec);
1690
1691
size_t get_multipart_content_length(const UploadFormDataItems &items,
1692
                                    const std::string &boundary);
1693
1694
ContentProvider
1695
make_multipart_content_provider(const UploadFormDataItems &items,
1696
                                const std::string &boundary);
1697
1698
} // namespace detail
1699
1700
bool is_valid_multipart_boundary(const std::string &boundary);
1701
1702
// Serializer for multipart/form-data request bodies. The boundary is owned
1703
// by the writer so that per-part framing and the final terminator always
1704
// agree. Field names and filenames are escaped following the WHATWG HTML
1705
// standard ('"' -> %22, CR -> %0D, LF -> %0A); CR and LF are also escaped
1706
// in content types.
1707
class MultipartFormDataWriter {
1708
public:
1709
  MultipartFormDataWriter();
1710
  // precondition: is_valid_multipart_boundary(boundary)
1711
  explicit MultipartFormDataWriter(std::string boundary);
1712
1713
  const std::string &boundary() const;
1714
  std::string content_type() const;
1715
1716
  // In-memory items -> whole body (known length)
1717
  std::string serialize(const UploadFormDataItems &items) const;
1718
  size_t content_length(const UploadFormDataItems &items) const;
1719
1720
  // Per-part framing for streaming via a content provider
1721
  std::string item_begin(const UploadFormData &item) const;
1722
  static std::string item_end();
1723
  std::string finish() const;
1724
1725
private:
1726
  std::string boundary_;
1727
};
1728
1729
class Server {
1730
public:
1731
  using Handler = std::function<void(const Request &, Response &)>;
1732
1733
  using ExceptionHandler =
1734
      std::function<void(const Request &, Response &, std::exception_ptr ep)>;
1735
1736
  enum class HandlerResponse {
1737
    Handled,
1738
    Unhandled,
1739
  };
1740
  using HandlerWithResponse =
1741
      std::function<HandlerResponse(const Request &, Response &)>;
1742
1743
  using HandlerWithContentReader = std::function<void(
1744
      const Request &, Response &, const ContentReader &content_reader)>;
1745
1746
  using Expect100ContinueHandler =
1747
      std::function<int(const Request &, Response &)>;
1748
1749
  using StartHandler = std::function<void()>;
1750
1751
  using WebSocketHandler =
1752
      std::function<void(const Request &, ws::WebSocket &)>;
1753
  using SubProtocolSelector =
1754
      std::function<std::string(const std::vector<std::string> &protocols)>;
1755
1756
  Server();
1757
1758
  virtual ~Server();
1759
1760
  virtual bool is_valid() const;
1761
1762
  Server &Get(const std::string &pattern, Handler handler);
1763
  Server &Post(const std::string &pattern, Handler handler);
1764
  Server &Post(const std::string &pattern, HandlerWithContentReader handler);
1765
  Server &Put(const std::string &pattern, Handler handler);
1766
  Server &Put(const std::string &pattern, HandlerWithContentReader handler);
1767
  Server &Patch(const std::string &pattern, Handler handler);
1768
  Server &Patch(const std::string &pattern, HandlerWithContentReader handler);
1769
  Server &Delete(const std::string &pattern, Handler handler);
1770
  Server &Delete(const std::string &pattern, HandlerWithContentReader handler);
1771
  Server &Options(const std::string &pattern, Handler handler);
1772
1773
  Server &WebSocket(const std::string &pattern, WebSocketHandler handler);
1774
  Server &WebSocket(const std::string &pattern, WebSocketHandler handler,
1775
                    SubProtocolSelector sub_protocol_selector);
1776
1777
  bool set_base_dir(const std::string &dir,
1778
                    const std::string &mount_point = std::string());
1779
  bool set_mount_point(const std::string &mount_point, const std::string &dir,
1780
                       Headers headers = Headers());
1781
  bool remove_mount_point(const std::string &mount_point);
1782
  Server &set_file_extension_and_mimetype_mapping(const std::string &ext,
1783
                                                  const std::string &mime);
1784
  Server &set_default_file_mimetype(const std::string &mime);
1785
  Server &set_file_request_handler(Handler handler);
1786
1787
  template <class ErrorHandlerFunc>
1788
  Server &set_error_handler(ErrorHandlerFunc &&handler) {
1789
    return set_error_handler_core(
1790
        std::forward<ErrorHandlerFunc>(handler),
1791
        std::is_convertible<ErrorHandlerFunc, HandlerWithResponse>{});
1792
  }
1793
1794
  Server &set_exception_handler(ExceptionHandler handler);
1795
1796
  Server &set_pre_routing_handler(HandlerWithResponse handler);
1797
  Server &set_post_routing_handler(Handler handler);
1798
1799
  Server &set_pre_request_handler(HandlerWithResponse handler);
1800
1801
  Server &set_expect_100_continue_handler(Expect100ContinueHandler handler);
1802
1803
  Server &set_start_handler(StartHandler handler);
1804
1805
  Server &set_logger(Logger logger);
1806
  Server &set_pre_compression_logger(Logger logger);
1807
  Server &set_error_logger(ErrorLogger error_logger);
1808
1809
  Server &set_address_family(int family);
1810
  Server &set_tcp_nodelay(bool on);
1811
  Server &set_ipv6_v6only(bool on);
1812
  Server &set_socket_options(SocketOptions socket_options);
1813
1814
  Server &set_default_headers(Headers headers);
1815
  Server &
1816
  set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
1817
1818
  Server &set_trusted_proxies(const std::vector<std::string> &proxies);
1819
1820
  Server &set_keep_alive_max_count(size_t count);
1821
  Server &set_keep_alive_timeout(time_t sec);
1822
  template <class Rep, class Period>
1823
  Server &
1824
  set_keep_alive_timeout(const std::chrono::duration<Rep, Period> &duration);
1825
1826
  Server &set_read_timeout(time_t sec, time_t usec = 0);
1827
  template <class Rep, class Period>
1828
  Server &set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
1829
1830
  Server &set_write_timeout(time_t sec, time_t usec = 0);
1831
  template <class Rep, class Period>
1832
  Server &set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
1833
1834
  Server &set_idle_interval(time_t sec, time_t usec = 0);
1835
  template <class Rep, class Period>
1836
  Server &set_idle_interval(const std::chrono::duration<Rep, Period> &duration);
1837
1838
  Server &set_payload_max_length(size_t length);
1839
1840
  Server &set_websocket_ping_interval(time_t sec);
1841
  template <class Rep, class Period>
1842
  Server &set_websocket_ping_interval(
1843
      const std::chrono::duration<Rep, Period> &duration);
1844
1845
  Server &set_websocket_max_missed_pongs(int count);
1846
1847
  bool bind_to_port(const std::string &host, int port, int socket_flags = 0);
1848
  int bind_to_any_port(const std::string &host, int socket_flags = 0);
1849
  bool listen_after_bind();
1850
1851
  bool listen(const std::string &host, int port, int socket_flags = 0);
1852
1853
  bool is_running() const;
1854
  void wait_until_ready() const;
1855
  void stop() noexcept;
1856
  void decommission();
1857
1858
  std::function<TaskQueue *(void)> new_task_queue;
1859
1860
protected:
1861
  bool process_request(Stream &strm, const std::string &remote_addr,
1862
                       int remote_port, const std::string &local_addr,
1863
                       int local_port, bool close_connection,
1864
                       bool &connection_closed,
1865
                       const std::function<void(Request &)> &setup_request,
1866
                       bool *websocket_upgraded = nullptr);
1867
1868
  std::atomic<socket_t> svr_sock_{INVALID_SOCKET};
1869
1870
  std::vector<std::string> trusted_proxies_;
1871
1872
  size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
1873
  time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
1874
  time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND;
1875
  time_t read_timeout_usec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND;
1876
  time_t write_timeout_sec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND;
1877
  time_t write_timeout_usec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND;
1878
  time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND;
1879
  time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND;
1880
  size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
1881
  time_t websocket_ping_interval_sec_ =
1882
      CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND;
1883
  int websocket_max_missed_pongs_ = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS;
1884
1885
private:
1886
  using Handlers =
1887
      std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, Handler>>;
1888
  using HandlersForContentReader =
1889
      std::vector<std::pair<std::unique_ptr<detail::MatcherBase>,
1890
                            HandlerWithContentReader>>;
1891
1892
  static std::unique_ptr<detail::MatcherBase>
1893
  make_matcher(const std::string &pattern);
1894
1895
  template <typename H>
1896
  Server &add_handler(
1897
      std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, H>> &handlers,
1898
12
      const std::string &pattern, H handler) {
1899
12
    handlers.emplace_back(make_matcher(pattern), std::move(handler));
1900
12
    return *this;
1901
12
  }
httplib::Server& httplib::Server::add_handler<std::__1::function<void (httplib::Request const&, httplib::Response&)> >(std::__1::vector<std::__1::pair<std::__1::unique_ptr<httplib::detail::MatcherBase, std::__1::default_delete<httplib::detail::MatcherBase> >, std::__1::function<void (httplib::Request const&, httplib::Response&)> >, std::__1::allocator<std::__1::pair<std::__1::unique_ptr<httplib::detail::MatcherBase, std::__1::default_delete<httplib::detail::MatcherBase> >, std::__1::function<void (httplib::Request const&, httplib::Response&)> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::function<void (httplib::Request const&, httplib::Response&)>)
Line
Count
Source
1898
12
      const std::string &pattern, H handler) {
1899
12
    handlers.emplace_back(make_matcher(pattern), std::move(handler));
1900
12
    return *this;
1901
12
  }
Unexecuted instantiation: httplib::Server& httplib::Server::add_handler<std::__1::function<void (httplib::Request const&, httplib::Response&, httplib::ContentReader const&)> >(std::__1::vector<std::__1::pair<std::__1::unique_ptr<httplib::detail::MatcherBase, std::__1::default_delete<httplib::detail::MatcherBase> >, std::__1::function<void (httplib::Request const&, httplib::Response&, httplib::ContentReader const&)> >, std::__1::allocator<std::__1::pair<std::__1::unique_ptr<httplib::detail::MatcherBase, std::__1::default_delete<httplib::detail::MatcherBase> >, std::__1::function<void (httplib::Request const&, httplib::Response&, httplib::ContentReader const&)> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::function<void (httplib::Request const&, httplib::Response&, httplib::ContentReader const&)>)
1902
1903
  Server &set_error_handler_core(HandlerWithResponse handler, std::true_type);
1904
  Server &set_error_handler_core(Handler handler, std::false_type);
1905
1906
  socket_t create_server_socket(const std::string &host, int port,
1907
                                int socket_flags,
1908
                                SocketOptions socket_options) const;
1909
  int bind_internal(const std::string &host, int port, int socket_flags);
1910
  bool listen_internal();
1911
1912
  bool routing(Request &req, Response &res, Stream &strm);
1913
  bool handle_file_request(Request &req, Response &res);
1914
  bool check_if_not_modified(const Request &req, Response &res,
1915
                             const std::string &etag, time_t mtime) const;
1916
  bool check_if_range(Request &req, const std::string &etag,
1917
                      time_t mtime) const;
1918
  bool dispatch_request(Request &req, Response &res, const Handlers &handlers,
1919
                        Stream &strm);
1920
  bool dispatch_request_for_content_reader(
1921
      Request &req, Response &res, ContentReader content_reader,
1922
      const HandlersForContentReader &handlers) const;
1923
1924
  bool parse_request_line(const char *s, Request &req) const;
1925
  void apply_ranges(const Request &req, Response &res,
1926
                    std::string &content_type, std::string &boundary) const;
1927
  bool write_response(Stream &strm, bool close_connection, Request &req,
1928
                      Response &res);
1929
  bool write_response_with_content(Stream &strm, bool close_connection,
1930
                                   const Request &req, Response &res);
1931
  bool write_response_core(Stream &strm, bool close_connection,
1932
                           const Request &req, Response &res,
1933
                           bool need_apply_ranges);
1934
  bool write_content_with_provider(Stream &strm, const Request &req,
1935
                                   Response &res, const std::string &boundary,
1936
                                   const std::string &content_type);
1937
  bool read_content(Stream &strm, Request &req, Response &res);
1938
  bool read_content_with_content_receiver(Stream &strm, Request &req,
1939
                                          Response &res,
1940
                                          ContentReceiver receiver,
1941
                                          FormDataHeader multipart_header,
1942
                                          ContentReceiver multipart_receiver);
1943
  bool read_content_core(Stream &strm, Request &req, Response &res,
1944
                         ContentReceiver receiver,
1945
                         FormDataHeader multipart_header,
1946
                         ContentReceiver multipart_receiver) const;
1947
1948
  virtual bool process_and_close_socket(socket_t sock);
1949
1950
  void output_log(const Request &req, const Response &res) const;
1951
  void output_pre_compression_log(const Request &req,
1952
                                  const Response &res) const;
1953
  void output_error_log(const Error &err, const Request *req) const;
1954
1955
  std::atomic<bool> is_running_{false};
1956
  std::atomic<bool> is_decommissioned{false};
1957
1958
  struct MountPointEntry {
1959
    std::string mount_point;
1960
    std::string base_dir;
1961
    std::string resolved_base_dir;
1962
    Headers headers;
1963
  };
1964
  std::vector<MountPointEntry> base_dirs_;
1965
  std::map<std::string, std::string> file_extension_and_mimetype_map_;
1966
  std::string default_file_mimetype_ = "application/octet-stream";
1967
  Handler file_request_handler_;
1968
1969
  Handlers get_handlers_;
1970
  Handlers post_handlers_;
1971
  HandlersForContentReader post_handlers_for_content_reader_;
1972
  Handlers put_handlers_;
1973
  HandlersForContentReader put_handlers_for_content_reader_;
1974
  Handlers patch_handlers_;
1975
  HandlersForContentReader patch_handlers_for_content_reader_;
1976
  Handlers delete_handlers_;
1977
  HandlersForContentReader delete_handlers_for_content_reader_;
1978
  Handlers options_handlers_;
1979
1980
  struct WebSocketHandlerEntry {
1981
    std::unique_ptr<detail::MatcherBase> matcher;
1982
    WebSocketHandler handler;
1983
    SubProtocolSelector sub_protocol_selector;
1984
  };
1985
  using WebSocketHandlers = std::vector<WebSocketHandlerEntry>;
1986
  WebSocketHandlers websocket_handlers_;
1987
1988
  HandlerWithResponse error_handler_;
1989
  ExceptionHandler exception_handler_;
1990
  HandlerWithResponse pre_routing_handler_;
1991
  Handler post_routing_handler_;
1992
  HandlerWithResponse pre_request_handler_;
1993
  Expect100ContinueHandler expect_100_continue_handler_;
1994
  StartHandler start_handler_;
1995
1996
  mutable std::mutex logger_mutex_;
1997
  Logger logger_;
1998
  Logger pre_compression_logger_;
1999
  ErrorLogger error_logger_;
2000
2001
  int address_family_ = AF_UNSPEC;
2002
  bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
2003
  bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
2004
  SocketOptions socket_options_ = default_socket_options;
2005
2006
  Headers default_headers_;
2007
  std::function<ssize_t(Stream &, Headers &)> header_writer_ =
2008
      detail::write_headers;
2009
};
2010
2011
class Result {
2012
public:
2013
  Result() = default;
2014
  Result(std::unique_ptr<Response> &&res, Error err,
2015
         Headers &&request_headers = Headers{})
2016
      : res_(std::move(res)), err_(err),
2017
0
        request_headers_(std::move(request_headers)) {}
2018
  // Response
2019
0
  operator bool() const { return res_ != nullptr; }
2020
0
  bool operator==(std::nullptr_t) const { return res_ == nullptr; }
2021
0
  bool operator!=(std::nullptr_t) const { return res_ != nullptr; }
2022
0
  const Response &value() const { return *res_; }
2023
0
  Response &value() { return *res_; }
2024
0
  const Response &operator*() const { return *res_; }
2025
0
  Response &operator*() { return *res_; }
2026
0
  const Response *operator->() const { return res_.get(); }
2027
0
  Response *operator->() { return res_.get(); }
2028
2029
  // Error
2030
0
  Error error() const { return err_; }
2031
2032
  // Request Headers
2033
  bool has_request_header(const std::string &key) const;
2034
  std::string get_request_header_value(const std::string &key,
2035
                                       const char *def = "",
2036
                                       size_t id = 0) const;
2037
  size_t get_request_header_value_u64(const std::string &key, size_t def = 0,
2038
                                      size_t id = 0) const;
2039
  size_t get_request_header_value_count(const std::string &key) const;
2040
2041
private:
2042
  std::unique_ptr<Response> res_;
2043
  Error err_ = Error::Unknown;
2044
  Headers request_headers_;
2045
2046
#ifdef CPPHTTPLIB_SSL_ENABLED
2047
public:
2048
  Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
2049
         int ssl_error)
2050
      : res_(std::move(res)), err_(err),
2051
        request_headers_(std::move(request_headers)), ssl_error_(ssl_error) {}
2052
  Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
2053
         int ssl_error, uint64_t ssl_backend_error)
2054
      : res_(std::move(res)), err_(err),
2055
        request_headers_(std::move(request_headers)), ssl_error_(ssl_error),
2056
        ssl_backend_error_(ssl_backend_error) {}
2057
2058
  int ssl_error() const { return ssl_error_; }
2059
  uint64_t ssl_backend_error() const { return ssl_backend_error_; }
2060
2061
private:
2062
  int ssl_error_ = 0;
2063
  uint64_t ssl_backend_error_ = 0;
2064
#endif
2065
};
2066
2067
struct ClientConnection {
2068
  socket_t sock = INVALID_SOCKET;
2069
2070
0
  bool is_open() const { return sock != INVALID_SOCKET; }
2071
2072
  ClientConnection() = default;
2073
2074
  ~ClientConnection();
2075
2076
  ClientConnection(const ClientConnection &) = delete;
2077
  ClientConnection &operator=(const ClientConnection &) = delete;
2078
2079
  ClientConnection(ClientConnection &&other) noexcept
2080
      : sock(other.sock)
2081
#ifdef CPPHTTPLIB_SSL_ENABLED
2082
        ,
2083
        session(other.session)
2084
#endif
2085
0
  {
2086
0
    other.sock = INVALID_SOCKET;
2087
0
#ifdef CPPHTTPLIB_SSL_ENABLED
2088
0
    other.session = nullptr;
2089
0
#endif
2090
0
  }
2091
2092
0
  ClientConnection &operator=(ClientConnection &&other) noexcept {
2093
0
    if (this != &other) {
2094
0
      sock = other.sock;
2095
0
      other.sock = INVALID_SOCKET;
2096
0
#ifdef CPPHTTPLIB_SSL_ENABLED
2097
0
      session = other.session;
2098
0
      other.session = nullptr;
2099
0
#endif
2100
0
    }
2101
0
    return *this;
2102
0
  }
2103
2104
#ifdef CPPHTTPLIB_SSL_ENABLED
2105
  tls::session_t session = nullptr;
2106
#endif
2107
};
2108
2109
namespace detail {
2110
2111
struct ChunkedDecoder;
2112
2113
struct BodyReader {
2114
  Stream *stream = nullptr;
2115
  bool has_content_length = false;
2116
  size_t content_length = 0;
2117
  size_t payload_max_length = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
2118
  size_t bytes_read = 0;
2119
  bool chunked = false;
2120
  bool eof = false;
2121
  std::unique_ptr<ChunkedDecoder> chunked_decoder;
2122
  Error last_error = Error::Success;
2123
2124
  ssize_t read(char *buf, size_t len);
2125
0
  bool has_error() const { return last_error != Error::Success; }
2126
};
2127
2128
inline ssize_t read_body_content(Stream *stream, BodyReader &br, char *buf,
2129
3.64k
                                 size_t len) {
2130
3.64k
  (void)stream;
2131
3.64k
  return br.read(buf, len);
2132
3.64k
}
2133
2134
class decompressor;
2135
2136
enum class NoProxyKind {
2137
  Wildcard,       // "*"
2138
  HostnameSuffix, // "example.com" or ".example.com"
2139
  IPv4Cidr,       // "10.0.0.0/8" (or single IP, treated as /32)
2140
  IPv6Cidr,       // "fe80::/10" (or single IP, treated as /128)
2141
};
2142
2143
// Unified 16-byte buffer holding either a v4 (first 4 bytes) or v6 address.
2144
// Lets one CIDR matcher cover both families.
2145
using IPBytes = std::array<uint8_t, 16>;
2146
2147
struct NoProxyEntry {
2148
  NoProxyKind kind = NoProxyKind::Wildcard;
2149
  std::string hostname_pattern; // lowercased, leading/trailing dot stripped
2150
  IPBytes net{};
2151
  int prefix_bits = 0;
2152
};
2153
2154
struct NormalizedTarget {
2155
  std::string hostname; // lowercase; brackets and trailing dot removed
2156
  bool is_ipv4 = false;
2157
  bool is_ipv6 = false;
2158
  IPBytes ip{};
2159
};
2160
2161
} // namespace detail
2162
2163
class ClientImpl {
2164
public:
2165
  explicit ClientImpl(const std::string &host);
2166
2167
  explicit ClientImpl(const std::string &host, int port);
2168
2169
  explicit ClientImpl(const std::string &host, int port,
2170
                      const std::string &client_cert_path,
2171
                      const std::string &client_key_path);
2172
2173
  virtual ~ClientImpl();
2174
2175
  virtual bool is_valid() const;
2176
2177
  struct StreamHandle {
2178
    std::unique_ptr<Response> response;
2179
    Error error = Error::Success;
2180
2181
    StreamHandle() = default;
2182
    StreamHandle(const StreamHandle &) = delete;
2183
    StreamHandle &operator=(const StreamHandle &) = delete;
2184
    StreamHandle(StreamHandle &&) = default;
2185
    StreamHandle &operator=(StreamHandle &&) = default;
2186
    ~StreamHandle() = default;
2187
2188
0
    bool is_valid() const {
2189
0
      return response != nullptr && error == Error::Success;
2190
0
    }
2191
2192
    ssize_t read(char *buf, size_t len);
2193
    void parse_trailers_if_needed();
2194
0
    Error get_read_error() const { return body_reader_.last_error; }
2195
0
    bool has_read_error() const { return body_reader_.has_error(); }
2196
2197
    bool trailers_parsed_ = false;
2198
2199
  private:
2200
    friend class ClientImpl;
2201
2202
    ssize_t read_with_decompression(char *buf, size_t len);
2203
2204
    std::unique_ptr<ClientConnection> connection_;
2205
    std::unique_ptr<Stream> socket_stream_;
2206
    Stream *stream_ = nullptr;
2207
    detail::BodyReader body_reader_;
2208
2209
    std::unique_ptr<detail::decompressor> decompressor_;
2210
    std::string decompress_buffer_;
2211
    size_t decompress_offset_ = 0;
2212
    size_t decompressed_bytes_read_ = 0;
2213
  };
2214
2215
  // clang-format off
2216
  Result Get(const std::string &path, DownloadProgress progress = nullptr);
2217
  Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2218
  Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2219
  Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2220
  Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2221
  Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2222
  Result Get(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
2223
  Result Get(const std::string &path, const Params &params, const Headers &headers, DownloadProgress progress = nullptr);
2224
  Result Get(const std::string &path, const Params &params, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2225
  Result Get(const std::string &path, const Params &params, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2226
2227
  Result Head(const std::string &path);
2228
  Result Head(const std::string &path, const Headers &headers);
2229
2230
  Result Post(const std::string &path);
2231
  Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2232
  Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2233
  Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2234
  Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2235
  Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2236
  Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2237
  Result Post(const std::string &path, const Params &params);
2238
  Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2239
  Result Post(const std::string &path, const Headers &headers);
2240
  Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2241
  Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2242
  Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2243
  Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2244
  Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2245
  Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2246
  Result Post(const std::string &path, const Headers &headers, const Params &params);
2247
  Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2248
  Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2249
  Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2250
  Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2251
2252
  Result Put(const std::string &path);
2253
  Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2254
  Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2255
  Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2256
  Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2257
  Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2258
  Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2259
  Result Put(const std::string &path, const Params &params);
2260
  Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2261
  Result Put(const std::string &path, const Headers &headers);
2262
  Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2263
  Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2264
  Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2265
  Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2266
  Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2267
  Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2268
  Result Put(const std::string &path, const Headers &headers, const Params &params);
2269
  Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2270
  Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2271
  Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2272
  Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2273
2274
  Result Patch(const std::string &path);
2275
  Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2276
  Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2277
  Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2278
  Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2279
  Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2280
  Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2281
  Result Patch(const std::string &path, const Params &params);
2282
  Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2283
  Result Patch(const std::string &path, const Headers &headers, UploadProgress progress = nullptr);
2284
  Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2285
  Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2286
  Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2287
  Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2288
  Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2289
  Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2290
  Result Patch(const std::string &path, const Headers &headers, const Params &params);
2291
  Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2292
  Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2293
  Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2294
  Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2295
2296
  Result Delete(const std::string &path, DownloadProgress progress = nullptr);
2297
  Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2298
  Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2299
  Result Delete(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
2300
  Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2301
  Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2302
  Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2303
  Result Delete(const std::string &path, const Headers &headers, const Params &params, DownloadProgress progress = nullptr);
2304
2305
  Result Options(const std::string &path);
2306
  Result Options(const std::string &path, const Headers &headers);
2307
  // clang-format on
2308
2309
  // Streaming API: Open a stream for reading response body incrementally
2310
  // Socket ownership is transferred to StreamHandle for true streaming
2311
  // Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.)
2312
  StreamHandle open_stream(const std::string &method, const std::string &path,
2313
                           const Params &params = {},
2314
                           const Headers &headers = {},
2315
                           const std::string &body = {},
2316
                           const std::string &content_type = {});
2317
2318
  bool send(Request &req, Response &res, Error &error);
2319
  Result send(const Request &req);
2320
2321
  void stop();
2322
2323
  std::string host() const;
2324
  int port() const;
2325
2326
  size_t is_socket_open() const;
2327
  socket_t socket() const;
2328
2329
  void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
2330
2331
  void set_default_headers(Headers headers);
2332
2333
  void
2334
  set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
2335
2336
  void set_address_family(int family);
2337
  void set_tcp_nodelay(bool on);
2338
  void set_ipv6_v6only(bool on);
2339
  void set_socket_options(SocketOptions socket_options);
2340
2341
  void set_connection_timeout(time_t sec, time_t usec = 0);
2342
  template <class Rep, class Period>
2343
  void
2344
  set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
2345
2346
  void set_read_timeout(time_t sec, time_t usec = 0);
2347
  template <class Rep, class Period>
2348
  void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
2349
2350
  void set_write_timeout(time_t sec, time_t usec = 0);
2351
  template <class Rep, class Period>
2352
  void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
2353
2354
  void set_max_timeout(time_t msec);
2355
  template <class Rep, class Period>
2356
  void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);
2357
2358
  void set_basic_auth(const std::string &username, const std::string &password);
2359
  void set_bearer_token_auth(const std::string &token);
2360
2361
  void set_keep_alive(bool on);
2362
  void set_follow_location(bool on);
2363
2364
  void set_path_encode(bool on);
2365
2366
  void set_compress(bool on);
2367
2368
  void set_decompress(bool on);
2369
2370
  void set_payload_max_length(size_t length);
2371
2372
  void set_interface(const std::string &intf);
2373
2374
  void set_proxy(const std::string &host, int port);
2375
  void set_proxy_basic_auth(const std::string &username,
2376
                            const std::string &password);
2377
  void set_proxy_bearer_token_auth(const std::string &token);
2378
  void set_no_proxy(const std::vector<std::string> &patterns);
2379
2380
  void set_logger(Logger logger);
2381
  void set_error_logger(ErrorLogger error_logger);
2382
2383
protected:
2384
  struct Socket {
2385
    socket_t sock = INVALID_SOCKET;
2386
2387
    // For Mbed TLS compatibility: start_time for request timeout tracking
2388
    std::chrono::time_point<std::chrono::steady_clock> start_time_;
2389
2390
0
    bool is_open() const { return sock != INVALID_SOCKET; }
2391
2392
#ifdef CPPHTTPLIB_SSL_ENABLED
2393
    tls::session_t ssl = nullptr;
2394
#endif
2395
  };
2396
2397
  virtual bool create_and_connect_socket(Socket &socket, Error &error);
2398
  virtual bool ensure_socket_connection(Socket &socket, Error &error);
2399
  virtual bool setup_proxy_connection(
2400
      Socket &socket,
2401
      std::chrono::time_point<std::chrono::steady_clock> start_time,
2402
      Response &res, bool &success, Error &error);
2403
2404
  bool is_proxy_enabled_for_host(const std::string &host) const;
2405
2406
  // All of:
2407
  //   shutdown_ssl
2408
  //   shutdown_socket
2409
  //   close_socket
2410
  //   disconnect
2411
  // should ONLY be called when socket_mutex_ is locked, and only when
2412
  // no other thread is using the socket.
2413
  virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully);
2414
  void shutdown_socket(Socket &socket) const;
2415
  void close_socket(Socket &socket);
2416
  void disconnect(bool gracefully);
2417
2418
  bool process_request(Stream &strm, Request &req, Response &res,
2419
                       bool close_connection, Error &error);
2420
2421
  bool write_content_with_provider(Stream &strm, const Request &req,
2422
                                   Error &error) const;
2423
2424
  void copy_settings(const ClientImpl &rhs);
2425
2426
  void output_log(const Request &req, const Response &res) const;
2427
  void output_error_log(const Error &err, const Request *req) const;
2428
2429
  // Socket endpoint information
2430
  const std::string host_;
2431
  const int port_;
2432
2433
  // Current open socket
2434
  Socket socket_;
2435
  mutable std::mutex socket_mutex_;
2436
  std::recursive_mutex request_mutex_;
2437
2438
  // These are all protected under socket_mutex
2439
  size_t socket_requests_in_flight_ = 0;
2440
  std::thread::id socket_requests_are_from_thread_ = std::thread::id();
2441
  bool socket_should_be_closed_when_request_is_done_ = false;
2442
2443
  // Hostname-IP map
2444
  std::map<std::string, std::string> addr_map_;
2445
2446
  // Default headers
2447
  Headers default_headers_;
2448
2449
  // Header writer
2450
  std::function<ssize_t(Stream &, Headers &)> header_writer_ =
2451
      detail::write_headers;
2452
2453
  // Settings
2454
  std::string client_cert_path_;
2455
  std::string client_key_path_;
2456
2457
  time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
2458
  time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
2459
  time_t read_timeout_sec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND;
2460
  time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND;
2461
  time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND;
2462
  time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND;
2463
  time_t max_timeout_msec_ = CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND;
2464
2465
  std::string basic_auth_username_;
2466
  std::string basic_auth_password_;
2467
  std::string bearer_token_auth_token_;
2468
2469
  bool keep_alive_ = false;
2470
  bool follow_location_ = false;
2471
2472
  bool path_encode_ = true;
2473
2474
  int address_family_ = AF_UNSPEC;
2475
  bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
2476
  bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
2477
  SocketOptions socket_options_ = nullptr;
2478
2479
  bool compress_ = false;
2480
  bool decompress_ = true;
2481
2482
  size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
2483
  bool has_payload_max_length_ = false;
2484
2485
  std::string interface_;
2486
2487
  std::string proxy_host_;
2488
  int proxy_port_ = -1;
2489
2490
  std::string proxy_basic_auth_username_;
2491
  std::string proxy_basic_auth_password_;
2492
  std::string proxy_bearer_token_auth_token_;
2493
2494
  std::vector<detail::NoProxyEntry> no_proxy_entries_;
2495
2496
  mutable detail::NormalizedTarget host_normalized_;
2497
  mutable bool host_normalized_valid_ = false;
2498
2499
  mutable std::mutex logger_mutex_;
2500
  Logger logger_;
2501
  ErrorLogger error_logger_;
2502
2503
private:
2504
  bool send_(Request &req, Response &res, Error &error);
2505
  Result send_(Request &&req);
2506
2507
  socket_t create_client_socket(Error &error) const;
2508
  bool read_response_line(Stream &strm, const Request &req, Response &res,
2509
                          bool skip_100_continue = true) const;
2510
  bool write_request(Stream &strm, Request &req, bool close_connection,
2511
                     Error &error, bool skip_body = false);
2512
  bool write_request_body(Stream &strm, Request &req, Error &error);
2513
  void prepare_default_headers(Request &r, bool for_stream,
2514
                               const std::string &ct);
2515
  bool redirect(Request &req, Response &res, Error &error);
2516
  bool create_redirect_client(const std::string &scheme,
2517
                              const std::string &host, int port, Request &req,
2518
                              Response &res, const std::string &path,
2519
                              const std::string &location, Error &error);
2520
  template <typename ClientType> void setup_redirect_client(ClientType &client);
2521
  bool handle_request(Stream &strm, Request &req, Response &res,
2522
                      bool close_connection, Error &error);
2523
  std::unique_ptr<Response> send_with_content_provider_and_receiver(
2524
      Request &req, const char *body, size_t content_length,
2525
      ContentProvider content_provider,
2526
      ContentProviderWithoutLength content_provider_without_length,
2527
      const std::string &content_type, ContentReceiver content_receiver,
2528
      Error &error);
2529
  Result send_with_content_provider_and_receiver(
2530
      const std::string &method, const std::string &path,
2531
      const Headers &headers, const char *body, size_t content_length,
2532
      ContentProvider content_provider,
2533
      ContentProviderWithoutLength content_provider_without_length,
2534
      const std::string &content_type, ContentReceiver content_receiver,
2535
      UploadProgress progress);
2536
  ContentProviderWithoutLength get_multipart_content_provider(
2537
      const std::string &boundary, const UploadFormDataItems &items,
2538
      const FormDataProviderItems &provider_items) const;
2539
2540
  virtual bool
2541
  process_socket(const Socket &socket,
2542
                 std::chrono::time_point<std::chrono::steady_clock> start_time,
2543
                 std::function<bool(Stream &strm)> callback);
2544
  virtual bool is_ssl() const;
2545
2546
  void transfer_socket_ownership_to_handle(StreamHandle &handle);
2547
2548
#ifdef CPPHTTPLIB_SSL_ENABLED
2549
public:
2550
  void set_digest_auth(const std::string &username,
2551
                       const std::string &password);
2552
  void set_proxy_digest_auth(const std::string &username,
2553
                             const std::string &password);
2554
  void set_ca_cert_path(const std::string &ca_cert_file_path,
2555
                        const std::string &ca_cert_dir_path = std::string());
2556
  void enable_server_certificate_verification(bool enabled);
2557
  void enable_server_hostname_verification(bool enabled);
2558
  void enable_system_ca(bool enabled);
2559
2560
protected:
2561
  std::string digest_auth_username_;
2562
  std::string digest_auth_password_;
2563
  std::string proxy_digest_auth_username_;
2564
  std::string proxy_digest_auth_password_;
2565
  std::string ca_cert_file_path_;
2566
  std::string ca_cert_dir_path_;
2567
  bool server_certificate_verification_ = true;
2568
  bool server_hostname_verification_ = true;
2569
  SystemCAMode system_ca_mode_ = SystemCAMode::Auto;
2570
  std::string ca_cert_pem_; // Store CA cert PEM for redirect transfer
2571
  int last_ssl_error_ = 0;
2572
  uint64_t last_backend_error_ = 0;
2573
#endif
2574
};
2575
2576
class Client {
2577
public:
2578
  // Universal interface
2579
  explicit Client(const std::string &scheme_host_port);
2580
2581
  explicit Client(const std::string &scheme_host_port,
2582
                  const std::string &client_cert_path,
2583
                  const std::string &client_key_path);
2584
2585
  // HTTP only interface
2586
  explicit Client(const std::string &host, int port);
2587
2588
  explicit Client(const std::string &host, int port,
2589
                  const std::string &client_cert_path,
2590
                  const std::string &client_key_path);
2591
2592
  Client(Client &&) = default;
2593
  Client &operator=(Client &&) = default;
2594
2595
  ~Client();
2596
2597
  bool is_valid() const;
2598
2599
  // clang-format off
2600
  Result Get(const std::string &path, DownloadProgress progress = nullptr);
2601
  Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2602
  Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2603
  Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2604
  Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2605
  Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2606
  Result Get(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
2607
  Result Get(const std::string &path, const Params &params, const Headers &headers, DownloadProgress progress = nullptr);
2608
  Result Get(const std::string &path, const Params &params, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2609
  Result Get(const std::string &path, const Params &params, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2610
2611
  Result Head(const std::string &path);
2612
  Result Head(const std::string &path, const Headers &headers);
2613
2614
  Result Post(const std::string &path);
2615
  Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2616
  Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2617
  Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2618
  Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2619
  Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2620
  Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2621
  Result Post(const std::string &path, const Params &params);
2622
  Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2623
  Result Post(const std::string &path, const Headers &headers);
2624
  Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2625
  Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2626
  Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2627
  Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2628
  Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2629
  Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2630
  Result Post(const std::string &path, const Headers &headers, const Params &params);
2631
  Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2632
  Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2633
  Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2634
  Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2635
2636
  Result Put(const std::string &path);
2637
  Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2638
  Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2639
  Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2640
  Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2641
  Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2642
  Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2643
  Result Put(const std::string &path, const Params &params);
2644
  Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2645
  Result Put(const std::string &path, const Headers &headers);
2646
  Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2647
  Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2648
  Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2649
  Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2650
  Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2651
  Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2652
  Result Put(const std::string &path, const Headers &headers, const Params &params);
2653
  Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2654
  Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2655
  Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2656
  Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2657
2658
  Result Patch(const std::string &path);
2659
  Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2660
  Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2661
  Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2662
  Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2663
  Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2664
  Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2665
  Result Patch(const std::string &path, const Params &params);
2666
  Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2667
  Result Patch(const std::string &path, const Headers &headers);
2668
  Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
2669
  Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
2670
  Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2671
  Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2672
  Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
2673
  Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
2674
  Result Patch(const std::string &path, const Headers &headers, const Params &params);
2675
  Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
2676
  Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
2677
  Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
2678
  Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
2679
2680
  Result Delete(const std::string &path, DownloadProgress progress = nullptr);
2681
  Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2682
  Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2683
  Result Delete(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
2684
  Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
2685
  Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
2686
  Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
2687
  Result Delete(const std::string &path, const Headers &headers, const Params &params, DownloadProgress progress = nullptr);
2688
2689
  Result Options(const std::string &path);
2690
  Result Options(const std::string &path, const Headers &headers);
2691
  // clang-format on
2692
2693
  // Streaming API: Open a stream for reading response body incrementally
2694
  // Socket ownership is transferred to StreamHandle for true streaming
2695
  // Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.)
2696
  ClientImpl::StreamHandle open_stream(const std::string &method,
2697
                                       const std::string &path,
2698
                                       const Params &params = {},
2699
                                       const Headers &headers = {},
2700
                                       const std::string &body = {},
2701
                                       const std::string &content_type = {});
2702
2703
  bool send(Request &req, Response &res, Error &error);
2704
  Result send(const Request &req);
2705
2706
  void stop();
2707
2708
  std::string host() const;
2709
  int port() const;
2710
2711
  size_t is_socket_open() const;
2712
  socket_t socket() const;
2713
2714
  void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
2715
2716
  void set_default_headers(Headers headers);
2717
2718
  void
2719
  set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);
2720
2721
  void set_address_family(int family);
2722
  void set_tcp_nodelay(bool on);
2723
  void set_socket_options(SocketOptions socket_options);
2724
2725
  void set_connection_timeout(time_t sec, time_t usec = 0);
2726
  template <class Rep, class Period>
2727
  void
2728
  set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);
2729
2730
  void set_read_timeout(time_t sec, time_t usec = 0);
2731
  template <class Rep, class Period>
2732
  void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);
2733
2734
  void set_write_timeout(time_t sec, time_t usec = 0);
2735
  template <class Rep, class Period>
2736
  void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);
2737
2738
  void set_max_timeout(time_t msec);
2739
  template <class Rep, class Period>
2740
  void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);
2741
2742
  void set_basic_auth(const std::string &username, const std::string &password);
2743
  void set_bearer_token_auth(const std::string &token);
2744
2745
  void set_keep_alive(bool on);
2746
  void set_follow_location(bool on);
2747
2748
  void set_path_encode(bool on);
2749
2750
  void set_compress(bool on);
2751
2752
  void set_decompress(bool on);
2753
2754
  void set_payload_max_length(size_t length);
2755
2756
  void set_interface(const std::string &intf);
2757
2758
  void set_proxy(const std::string &host, int port);
2759
  void set_proxy_basic_auth(const std::string &username,
2760
                            const std::string &password);
2761
  void set_proxy_bearer_token_auth(const std::string &token);
2762
  void set_no_proxy(const std::vector<std::string> &patterns);
2763
  void set_logger(Logger logger);
2764
  void set_error_logger(ErrorLogger error_logger);
2765
2766
private:
2767
  std::unique_ptr<ClientImpl> cli_;
2768
2769
#ifdef CPPHTTPLIB_SSL_ENABLED
2770
public:
2771
  void set_digest_auth(const std::string &username,
2772
                       const std::string &password);
2773
  void set_proxy_digest_auth(const std::string &username,
2774
                             const std::string &password);
2775
  void enable_server_certificate_verification(bool enabled);
2776
  void enable_server_hostname_verification(bool enabled);
2777
  void enable_system_ca(bool enabled);
2778
  void set_ca_cert_path(const std::string &ca_cert_file_path,
2779
                        const std::string &ca_cert_dir_path = std::string());
2780
2781
  void set_ca_cert_store(tls::ca_store_t ca_cert_store);
2782
  void load_ca_cert_store(const char *ca_cert, std::size_t size);
2783
2784
  void set_server_certificate_verifier(tls::VerifyCallback verifier);
2785
2786
  void set_session_verifier(
2787
      std::function<SSLVerifierResponse(tls::session_t)> verifier);
2788
2789
  tls::ctx_t tls_context() const;
2790
2791
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
2792
  void enable_windows_certificate_verification(bool enabled);
2793
#endif
2794
2795
private:
2796
  bool is_ssl_ = false;
2797
#endif
2798
};
2799
2800
#ifdef CPPHTTPLIB_SSL_ENABLED
2801
class SSLServer : public Server {
2802
public:
2803
  SSLServer(const char *cert_path, const char *private_key_path,
2804
            const char *client_ca_cert_file_path = nullptr,
2805
            const char *client_ca_cert_dir_path = nullptr,
2806
            const char *private_key_password = nullptr);
2807
2808
  struct PemMemory {
2809
    const char *cert_pem;
2810
    size_t cert_pem_len;
2811
    const char *key_pem;
2812
    size_t key_pem_len;
2813
    const char *client_ca_pem;
2814
    size_t client_ca_pem_len;
2815
    const char *private_key_password;
2816
  };
2817
  explicit SSLServer(const PemMemory &pem);
2818
2819
  // The callback receives the ctx_t handle which can be cast to the
2820
  // appropriate backend type (SSL_CTX* for OpenSSL,
2821
  // tls::impl::MbedTlsContext* for Mbed TLS)
2822
  explicit SSLServer(const tls::ContextSetupCallback &setup_callback);
2823
2824
  ~SSLServer() override;
2825
2826
  bool is_valid() const override;
2827
2828
  bool update_certs_pem(const char *cert_pem, const char *key_pem,
2829
                        const char *client_ca_pem = nullptr,
2830
                        const char *password = nullptr);
2831
2832
  tls::ctx_t tls_context() const { return ctx_; }
2833
2834
  int ssl_last_error() const { return last_ssl_error_; }
2835
2836
private:
2837
  bool process_and_close_socket(socket_t sock) override;
2838
2839
  tls::ctx_t ctx_ = nullptr;
2840
  std::mutex ctx_mutex_;
2841
2842
  int last_ssl_error_ = 0;
2843
};
2844
2845
class SSLClient final : public ClientImpl {
2846
public:
2847
  explicit SSLClient(const std::string &host);
2848
2849
  explicit SSLClient(const std::string &host, int port);
2850
2851
  explicit SSLClient(const std::string &host, int port,
2852
                     const std::string &client_cert_path,
2853
                     const std::string &client_key_path,
2854
                     const std::string &private_key_password = std::string());
2855
2856
  struct PemMemory {
2857
    const char *cert_pem;
2858
    size_t cert_pem_len;
2859
    const char *key_pem;
2860
    size_t key_pem_len;
2861
    const char *private_key_password;
2862
  };
2863
  explicit SSLClient(const std::string &host, int port, const PemMemory &pem);
2864
2865
  ~SSLClient() override;
2866
2867
  bool is_valid() const override;
2868
2869
  void set_ca_cert_store(tls::ca_store_t ca_cert_store);
2870
  void load_ca_cert_store(const char *ca_cert, std::size_t size);
2871
2872
  void set_server_certificate_verifier(tls::VerifyCallback verifier);
2873
2874
  // Post-handshake session verifier (backend-independent)
2875
  void set_session_verifier(
2876
      std::function<SSLVerifierResponse(tls::session_t)> verifier);
2877
2878
  tls::ctx_t tls_context() const { return ctx_; }
2879
2880
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
2881
  void enable_windows_certificate_verification(bool enabled);
2882
#endif
2883
2884
private:
2885
  bool create_and_connect_socket(Socket &socket, Error &error) override;
2886
  bool ensure_socket_connection(Socket &socket, Error &error) override;
2887
  void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
2888
  void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully);
2889
2890
  bool
2891
  process_socket(const Socket &socket,
2892
                 std::chrono::time_point<std::chrono::steady_clock> start_time,
2893
                 std::function<bool(Stream &strm)> callback) override;
2894
  bool is_ssl() const override;
2895
2896
  bool setup_proxy_connection(
2897
      Socket &socket,
2898
      std::chrono::time_point<std::chrono::steady_clock> start_time,
2899
      Response &res, bool &success, Error &error) override;
2900
  bool connect_with_proxy(
2901
      Socket &sock,
2902
      std::chrono::time_point<std::chrono::steady_clock> start_time,
2903
      Response &res, bool &success, Error &error);
2904
  bool initialize_ssl(Socket &socket, Error &error);
2905
2906
  void init_ctx();
2907
  void reset_ctx_on_error();
2908
2909
  bool load_certs();
2910
2911
  tls::ctx_t ctx_ = nullptr;
2912
  std::mutex ctx_mutex_;
2913
  std::once_flag initialize_cert_;
2914
2915
  // Tracks whether a custom CA store was applied via set_ca_cert_store(),
2916
  // since the store handle itself is owned by ctx_ and leaves no other trace.
2917
  // Used to keep custom CA configuration exclusive with system CA loading.
2918
  bool ca_cert_store_set_ = false;
2919
2920
  long verify_result_ = 0;
2921
2922
  std::function<SSLVerifierResponse(tls::session_t)> session_verifier_;
2923
2924
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
2925
  bool enable_windows_cert_verification_ = true;
2926
#endif
2927
2928
  friend class ClientImpl;
2929
};
2930
#endif // CPPHTTPLIB_SSL_ENABLED
2931
2932
namespace detail {
2933
2934
template <typename T, typename U>
2935
inline void duration_to_sec_and_usec(const T &duration, U callback) {
2936
  auto sec = std::chrono::duration_cast<std::chrono::seconds>(duration).count();
2937
  auto usec = std::chrono::duration_cast<std::chrono::microseconds>(
2938
                  duration - std::chrono::seconds(sec))
2939
                  .count();
2940
  callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
2941
}
2942
2943
1.00k
template <size_t N> inline constexpr size_t str_len(const char (&)[N]) {
2944
1.00k
  return N - 1;
2945
1.00k
}
Unexecuted instantiation: unsigned long httplib::detail::str_len<3ul>(char const (&) [3ul])
Unexecuted instantiation: unsigned long httplib::detail::str_len<4ul>(char const (&) [4ul])
unsigned long httplib::detail::str_len<14ul>(char const (&) [14ul])
Line
Count
Source
2943
1.00k
template <size_t N> inline constexpr size_t str_len(const char (&)[N]) {
2944
1.00k
  return N - 1;
2945
1.00k
}
Unexecuted instantiation: unsigned long httplib::detail::str_len<8ul>(char const (&) [8ul])
Unexecuted instantiation: unsigned long httplib::detail::str_len<21ul>(char const (&) [21ul])
Unexecuted instantiation: unsigned long httplib::detail::str_len<11ul>(char const (&) [11ul])
2946
2947
4.01k
inline bool is_numeric(const std::string &str) {
2948
4.01k
  return !str.empty() && std::all_of(str.cbegin(), str.cend(), is_ascii_digit);
2949
4.01k
}
2950
2951
inline size_t get_header_value_u64(const Headers &headers,
2952
                                   const std::string &key, size_t def,
2953
12.6k
                                   size_t id, bool &is_invalid_value) {
2954
12.6k
  is_invalid_value = false;
2955
12.6k
  auto rng = headers.equal_range(key);
2956
12.6k
  auto it = rng.first;
2957
12.6k
  std::advance(it, static_cast<ssize_t>(id));
2958
12.6k
  if (it != rng.second) {
2959
4.01k
    if (is_numeric(it->second)) {
2960
3.88k
      return static_cast<size_t>(std::strtoull(it->second.data(), nullptr, 10));
2961
3.88k
    } else {
2962
124
      is_invalid_value = true;
2963
124
    }
2964
4.01k
  }
2965
8.73k
  return def;
2966
12.6k
}
2967
2968
inline size_t get_header_value_u64(const Headers &headers,
2969
                                   const std::string &key, size_t def,
2970
11.1k
                                   size_t id) {
2971
11.1k
  auto dummy = false;
2972
11.1k
  return get_header_value_u64(headers, key, def, id, dummy);
2973
11.1k
}
2974
2975
} // namespace detail
2976
2977
template <class Rep, class Period>
2978
inline Server &
2979
Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
2980
  detail::duration_to_sec_and_usec(
2981
      duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
2982
  return *this;
2983
}
2984
2985
template <class Rep, class Period>
2986
inline Server &
2987
Server::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
2988
  detail::duration_to_sec_and_usec(
2989
      duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
2990
  return *this;
2991
}
2992
2993
template <class Rep, class Period>
2994
inline Server &
2995
Server::set_idle_interval(const std::chrono::duration<Rep, Period> &duration) {
2996
  detail::duration_to_sec_and_usec(
2997
      duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); });
2998
  return *this;
2999
}
3000
3001
template <class Rep, class Period>
3002
inline void ClientImpl::set_connection_timeout(
3003
    const std::chrono::duration<Rep, Period> &duration) {
3004
  detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) {
3005
    set_connection_timeout(sec, usec);
3006
  });
3007
}
3008
3009
template <class Rep, class Period>
3010
inline void ClientImpl::set_read_timeout(
3011
    const std::chrono::duration<Rep, Period> &duration) {
3012
  detail::duration_to_sec_and_usec(
3013
      duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
3014
}
3015
3016
template <class Rep, class Period>
3017
inline void ClientImpl::set_write_timeout(
3018
    const std::chrono::duration<Rep, Period> &duration) {
3019
  detail::duration_to_sec_and_usec(
3020
      duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
3021
}
3022
3023
template <class Rep, class Period>
3024
inline void ClientImpl::set_max_timeout(
3025
    const std::chrono::duration<Rep, Period> &duration) {
3026
  auto msec =
3027
      std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
3028
  set_max_timeout(msec);
3029
}
3030
3031
template <class Rep, class Period>
3032
inline void Client::set_connection_timeout(
3033
    const std::chrono::duration<Rep, Period> &duration) {
3034
  cli_->set_connection_timeout(duration);
3035
}
3036
3037
template <class Rep, class Period>
3038
inline void
3039
Client::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
3040
  cli_->set_read_timeout(duration);
3041
}
3042
3043
template <class Rep, class Period>
3044
inline void
3045
Client::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
3046
  cli_->set_write_timeout(duration);
3047
}
3048
3049
0
inline void Client::set_max_timeout(time_t msec) {
3050
0
  cli_->set_max_timeout(msec);
3051
0
}
3052
3053
template <class Rep, class Period>
3054
inline void
3055
Client::set_max_timeout(const std::chrono::duration<Rep, Period> &duration) {
3056
  cli_->set_max_timeout(duration);
3057
}
3058
3059
/*
3060
 * Forward declarations and types that will be part of the .h file if split into
3061
 * .h + .cc.
3062
 */
3063
3064
std::string hosted_at(const std::string &hostname);
3065
3066
void hosted_at(const std::string &hostname, std::vector<std::string> &addrs);
3067
3068
// JavaScript-style URL encoding/decoding functions
3069
std::string encode_uri_component(const std::string &value);
3070
std::string encode_uri(const std::string &value);
3071
std::string decode_uri_component(const std::string &value);
3072
std::string decode_uri(const std::string &value);
3073
3074
// RFC 3986 compliant URL component encoding/decoding functions
3075
std::string encode_path_component(const std::string &component);
3076
std::string decode_path_component(const std::string &component);
3077
std::string encode_query_component(const std::string &component,
3078
                                   bool space_as_plus = true);
3079
std::string decode_query_component(const std::string &component,
3080
                                   bool plus_as_space = true);
3081
3082
std::string sanitize_filename(const std::string &filename);
3083
3084
std::string append_query_params(const std::string &path, const Params &params);
3085
3086
std::pair<std::string, std::string> make_range_header(const Ranges &ranges);
3087
3088
std::pair<std::string, std::string>
3089
make_basic_authentication_header(const std::string &username,
3090
                                 const std::string &password,
3091
                                 bool is_proxy = false);
3092
3093
namespace detail {
3094
3095
#if defined(_WIN32)
3096
inline std::wstring u8string_to_wstring(const char *s) {
3097
  if (!s) { return std::wstring(); }
3098
3099
  auto len = static_cast<int>(strlen(s));
3100
  if (!len) { return std::wstring(); }
3101
3102
  auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0);
3103
  if (!wlen) { return std::wstring(); }
3104
3105
  std::wstring ws;
3106
  ws.resize(wlen);
3107
  wlen = ::MultiByteToWideChar(
3108
      CP_UTF8, 0, s, len,
3109
      const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(ws.data())), wlen);
3110
  if (wlen != static_cast<int>(ws.size())) { ws.clear(); }
3111
  return ws;
3112
}
3113
#endif
3114
3115
struct FileStat {
3116
  FileStat(const std::string &path);
3117
  bool is_file() const;
3118
  bool is_dir() const;
3119
  time_t mtime() const;
3120
  size_t size() const;
3121
3122
private:
3123
#if defined(_WIN32)
3124
  struct _stat st_;
3125
#else
3126
  struct stat st_;
3127
#endif
3128
  int ret_ = -1;
3129
};
3130
3131
std::string make_host_and_port_string(const std::string &host, int port,
3132
                                      bool is_ssl);
3133
3134
std::string trim_copy(const std::string &s);
3135
3136
void divide(
3137
    const char *data, std::size_t size, char d,
3138
    std::function<void(const char *, std::size_t, const char *, std::size_t)>
3139
        fn);
3140
3141
void divide(
3142
    const std::string &str, char d,
3143
    std::function<void(const char *, std::size_t, const char *, std::size_t)>
3144
        fn);
3145
3146
void split(const char *b, const char *e, char d,
3147
           std::function<void(const char *, const char *)> fn);
3148
3149
void split(const char *b, const char *e, char d, size_t m,
3150
           std::function<void(const char *, const char *)> fn);
3151
3152
bool process_client_socket(
3153
    socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
3154
    time_t write_timeout_sec, time_t write_timeout_usec,
3155
    time_t max_timeout_msec,
3156
    std::chrono::time_point<std::chrono::steady_clock> start_time,
3157
    std::function<bool(Stream &)> callback);
3158
3159
socket_t create_client_socket(const std::string &host, const std::string &ip,
3160
                              int port, int address_family, bool tcp_nodelay,
3161
                              bool ipv6_v6only, SocketOptions socket_options,
3162
                              time_t connection_timeout_sec,
3163
                              time_t connection_timeout_usec,
3164
                              time_t read_timeout_sec, time_t read_timeout_usec,
3165
                              time_t write_timeout_sec,
3166
                              time_t write_timeout_usec,
3167
                              const std::string &intf, Error &error);
3168
3169
const char *get_header_value(const Headers &headers, const std::string &key,
3170
                             const char *def, size_t id);
3171
3172
std::string params_to_query_str(const Params &params);
3173
3174
void parse_query_text(const char *data, std::size_t size, Params &params);
3175
3176
void parse_query_text(const std::string &s, Params &params);
3177
3178
bool parse_multipart_boundary(const std::string &content_type,
3179
                              std::string &boundary);
3180
3181
bool parse_range_header(const std::string &s, Ranges &ranges);
3182
3183
bool parse_accept_header(const std::string &s,
3184
                         std::vector<std::string> &content_types);
3185
3186
ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags);
3187
3188
ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags);
3189
3190
enum class EncodingType { None = 0, Gzip, Brotli, Zstd };
3191
3192
EncodingType encoding_type(const Request &req, const Response &res);
3193
3194
class BufferStream final : public Stream {
3195
public:
3196
6.77k
  BufferStream() = default;
3197
6.77k
  ~BufferStream() override = default;
3198
3199
  bool is_readable() const override;
3200
  bool wait_readable() const override;
3201
  bool wait_writable() const override;
3202
  ssize_t read(char *ptr, size_t size) override;
3203
  ssize_t write(const char *ptr, size_t size) override;
3204
  void get_remote_ip_and_port(std::string &ip, int &port) const override;
3205
  void get_local_ip_and_port(std::string &ip, int &port) const override;
3206
  socket_t socket() const override;
3207
  time_t duration() const override;
3208
3209
  const std::string &get_buffer() const;
3210
3211
private:
3212
  std::string buffer;
3213
  size_t position = 0;
3214
};
3215
3216
class compressor {
3217
public:
3218
37
  virtual ~compressor() = default;
3219
3220
  typedef std::function<bool(const char *data, size_t data_len)> Callback;
3221
  virtual bool compress(const char *data, size_t data_length, bool last,
3222
                        Callback callback) = 0;
3223
};
3224
3225
class decompressor {
3226
public:
3227
520
  virtual ~decompressor() = default;
3228
3229
  virtual bool is_valid() const = 0;
3230
3231
  typedef std::function<bool(const char *data, size_t data_len)> Callback;
3232
  virtual bool decompress(const char *data, size_t data_length,
3233
                          Callback callback) = 0;
3234
};
3235
3236
class nocompressor final : public compressor {
3237
public:
3238
0
  ~nocompressor() override = default;
3239
3240
  bool compress(const char *data, size_t data_length, bool /*last*/,
3241
                Callback callback) override;
3242
};
3243
3244
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
3245
class gzip_compressor final : public compressor {
3246
public:
3247
  gzip_compressor();
3248
  ~gzip_compressor() override;
3249
3250
  bool compress(const char *data, size_t data_length, bool last,
3251
                Callback callback) override;
3252
3253
private:
3254
  bool is_valid_ = false;
3255
  z_stream strm_;
3256
};
3257
3258
class gzip_decompressor final : public decompressor {
3259
public:
3260
  gzip_decompressor();
3261
  ~gzip_decompressor() override;
3262
3263
  bool is_valid() const override;
3264
3265
  bool decompress(const char *data, size_t data_length,
3266
                  Callback callback) override;
3267
3268
private:
3269
  bool is_valid_ = false;
3270
  z_stream strm_;
3271
};
3272
#endif
3273
3274
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
3275
class brotli_compressor final : public compressor {
3276
public:
3277
  brotli_compressor();
3278
  ~brotli_compressor();
3279
3280
  bool compress(const char *data, size_t data_length, bool last,
3281
                Callback callback) override;
3282
3283
private:
3284
  BrotliEncoderState *state_ = nullptr;
3285
};
3286
3287
class brotli_decompressor final : public decompressor {
3288
public:
3289
  brotli_decompressor();
3290
  ~brotli_decompressor();
3291
3292
  bool is_valid() const override;
3293
3294
  bool decompress(const char *data, size_t data_length,
3295
                  Callback callback) override;
3296
3297
private:
3298
  BrotliDecoderResult decoder_r;
3299
  BrotliDecoderState *decoder_s = nullptr;
3300
};
3301
#endif
3302
3303
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
3304
class zstd_compressor : public compressor {
3305
public:
3306
  zstd_compressor();
3307
  ~zstd_compressor();
3308
3309
  bool compress(const char *data, size_t data_length, bool last,
3310
                Callback callback) override;
3311
3312
private:
3313
  ZSTD_CCtx *ctx_ = nullptr;
3314
};
3315
3316
class zstd_decompressor : public decompressor {
3317
public:
3318
  zstd_decompressor();
3319
  ~zstd_decompressor();
3320
3321
  bool is_valid() const override;
3322
3323
  bool decompress(const char *data, size_t data_length,
3324
                  Callback callback) override;
3325
3326
private:
3327
  ZSTD_DCtx *ctx_ = nullptr;
3328
};
3329
#endif
3330
3331
// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
3332
// to store data. The call can set memory on stack for performance.
3333
class stream_line_reader {
3334
public:
3335
  stream_line_reader(Stream &strm, char *fixed_buffer,
3336
                     size_t fixed_buffer_size);
3337
  const char *ptr() const;
3338
  size_t size() const;
3339
  bool end_with_crlf() const;
3340
  bool getline();
3341
3342
private:
3343
  void append(char c);
3344
3345
  Stream &strm_;
3346
  char *fixed_buffer_;
3347
  const size_t fixed_buffer_size_;
3348
  size_t fixed_buffer_used_size_ = 0;
3349
  std::string growable_buffer_;
3350
};
3351
3352
bool parse_trailers(stream_line_reader &line_reader, Headers &dest,
3353
                    const Headers &src_headers);
3354
3355
struct ChunkedDecoder {
3356
  Stream &strm;
3357
  size_t chunk_remaining = 0;
3358
  bool finished = false;
3359
  char line_buf[64];
3360
  size_t last_chunk_total = 0;
3361
  size_t last_chunk_offset = 0;
3362
3363
  explicit ChunkedDecoder(Stream &s);
3364
3365
  ssize_t read_payload(char *buf, size_t len, size_t &out_chunk_offset,
3366
                       size_t &out_chunk_total);
3367
3368
  bool parse_trailers_into(Headers &dest, const Headers &src_headers);
3369
};
3370
3371
class mmap {
3372
public:
3373
  mmap(const char *path);
3374
  ~mmap();
3375
3376
  bool open(const char *path);
3377
  void close();
3378
3379
  bool is_open() const;
3380
  size_t size() const;
3381
  const char *data() const;
3382
3383
private:
3384
#if defined(_WIN32)
3385
  HANDLE hFile_ = NULL;
3386
  HANDLE hMapping_ = NULL;
3387
#else
3388
  int fd_ = -1;
3389
#endif
3390
  size_t size_ = 0;
3391
  void *addr_ = nullptr;
3392
  bool is_open_empty_file = false;
3393
};
3394
3395
// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
3396
namespace fields {
3397
3398
bool is_token_char(char c);
3399
bool is_token(const std::string &s);
3400
bool is_field_name(const std::string &s);
3401
bool is_vchar(char c);
3402
bool is_obs_text(char c);
3403
bool is_field_vchar(char c);
3404
bool is_field_content(const std::string &s);
3405
bool is_field_value(const std::string &s);
3406
3407
} // namespace fields
3408
} // namespace detail
3409
3410
/*
3411
 * TLS Abstraction Layer Declarations
3412
 */
3413
3414
#ifdef CPPHTTPLIB_SSL_ENABLED
3415
// TLS abstraction layer - backend-specific type declarations
3416
#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
3417
namespace tls {
3418
namespace impl {
3419
3420
// Mbed TLS context wrapper (holds config, entropy, DRBG, CA chain, own
3421
// cert/key). This struct is accessible via tls::impl for use in SSL context
3422
// setup callbacks (cast ctx_t to tls::impl::MbedTlsContext*).
3423
struct MbedTlsContext {
3424
  mbedtls_ssl_config conf;
3425
  mbedtls_entropy_context entropy;
3426
  mbedtls_ctr_drbg_context ctr_drbg;
3427
  mbedtls_x509_crt ca_chain;
3428
  mbedtls_x509_crt own_cert;
3429
  mbedtls_pk_context own_key;
3430
  bool is_server = false;
3431
  bool verify_client = false;
3432
  bool has_verify_callback = false;
3433
3434
  MbedTlsContext();
3435
  ~MbedTlsContext();
3436
3437
  MbedTlsContext(const MbedTlsContext &) = delete;
3438
  MbedTlsContext &operator=(const MbedTlsContext &) = delete;
3439
};
3440
3441
} // namespace impl
3442
} // namespace tls
3443
#endif
3444
3445
#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT
3446
namespace tls {
3447
namespace impl {
3448
3449
// wolfSSL context wrapper (holds WOLFSSL_CTX and related state).
3450
// This struct is accessible via tls::impl for use in SSL context
3451
// setup callbacks (cast ctx_t to tls::impl::WolfSSLContext*).
3452
struct WolfSSLContext {
3453
  WOLFSSL_CTX *ctx = nullptr;
3454
  bool is_server = false;
3455
  bool verify_client = false;
3456
  bool has_verify_callback = false;
3457
  std::string ca_pem_data_; // accumulated PEM for get_ca_names/get_ca_certs
3458
3459
  WolfSSLContext();
3460
  ~WolfSSLContext();
3461
3462
  WolfSSLContext(const WolfSSLContext &) = delete;
3463
  WolfSSLContext &operator=(const WolfSSLContext &) = delete;
3464
};
3465
3466
// CA store for wolfSSL: holds raw PEM bytes to allow reloading into any ctx
3467
struct WolfSSLCAStore {
3468
  std::string pem_data;
3469
};
3470
3471
} // namespace impl
3472
} // namespace tls
3473
#endif
3474
3475
#endif // CPPHTTPLIB_SSL_ENABLED
3476
3477
namespace stream {
3478
3479
class Result {
3480
public:
3481
  Result();
3482
  explicit Result(ClientImpl::StreamHandle &&handle, size_t chunk_size = 8192);
3483
  Result(Result &&other) noexcept;
3484
  Result &operator=(Result &&other) noexcept;
3485
  Result(const Result &) = delete;
3486
  Result &operator=(const Result &) = delete;
3487
3488
  // Response info
3489
  bool is_valid() const;
3490
  explicit operator bool() const;
3491
  int status() const;
3492
  const Headers &headers() const;
3493
  std::string get_header_value(const std::string &key,
3494
                               const char *def = "") const;
3495
  bool has_header(const std::string &key) const;
3496
  Error error() const;
3497
  Error read_error() const;
3498
  bool has_read_error() const;
3499
3500
  // Stream reading
3501
  bool next();
3502
  const char *data() const;
3503
  size_t size() const;
3504
  std::string read_all();
3505
3506
private:
3507
  ClientImpl::StreamHandle handle_;
3508
  std::string buffer_;
3509
  size_t current_size_ = 0;
3510
  size_t chunk_size_;
3511
  bool finished_ = false;
3512
};
3513
3514
// GET
3515
template <typename ClientType>
3516
inline Result Get(ClientType &cli, const std::string &path,
3517
                  size_t chunk_size = 8192) {
3518
  return Result{cli.open_stream("GET", path), chunk_size};
3519
}
3520
3521
template <typename ClientType>
3522
inline Result Get(ClientType &cli, const std::string &path,
3523
0
                  const Headers &headers, size_t chunk_size = 8192) {
3524
0
  return Result{cli.open_stream("GET", path, {}, headers), chunk_size};
3525
0
}
3526
3527
template <typename ClientType>
3528
inline Result Get(ClientType &cli, const std::string &path,
3529
                  const Params &params, size_t chunk_size = 8192) {
3530
  return Result{cli.open_stream("GET", path, params), chunk_size};
3531
}
3532
3533
template <typename ClientType>
3534
inline Result Get(ClientType &cli, const std::string &path,
3535
                  const Params &params, const Headers &headers,
3536
                  size_t chunk_size = 8192) {
3537
  return Result{cli.open_stream("GET", path, params, headers), chunk_size};
3538
}
3539
3540
// POST
3541
template <typename ClientType>
3542
inline Result Post(ClientType &cli, const std::string &path,
3543
                   const std::string &body, const std::string &content_type,
3544
                   size_t chunk_size = 8192) {
3545
  return Result{cli.open_stream("POST", path, {}, {}, body, content_type),
3546
                chunk_size};
3547
}
3548
3549
template <typename ClientType>
3550
inline Result Post(ClientType &cli, const std::string &path,
3551
                   const Headers &headers, const std::string &body,
3552
                   const std::string &content_type, size_t chunk_size = 8192) {
3553
  return Result{cli.open_stream("POST", path, {}, headers, body, content_type),
3554
                chunk_size};
3555
}
3556
3557
template <typename ClientType>
3558
inline Result Post(ClientType &cli, const std::string &path,
3559
                   const Params &params, const std::string &body,
3560
                   const std::string &content_type, size_t chunk_size = 8192) {
3561
  return Result{cli.open_stream("POST", path, params, {}, body, content_type),
3562
                chunk_size};
3563
}
3564
3565
template <typename ClientType>
3566
inline Result Post(ClientType &cli, const std::string &path,
3567
                   const Params &params, const Headers &headers,
3568
                   const std::string &body, const std::string &content_type,
3569
                   size_t chunk_size = 8192) {
3570
  return Result{
3571
      cli.open_stream("POST", path, params, headers, body, content_type),
3572
      chunk_size};
3573
}
3574
3575
// PUT
3576
template <typename ClientType>
3577
inline Result Put(ClientType &cli, const std::string &path,
3578
                  const std::string &body, const std::string &content_type,
3579
                  size_t chunk_size = 8192) {
3580
  return Result{cli.open_stream("PUT", path, {}, {}, body, content_type),
3581
                chunk_size};
3582
}
3583
3584
template <typename ClientType>
3585
inline Result Put(ClientType &cli, const std::string &path,
3586
                  const Headers &headers, const std::string &body,
3587
                  const std::string &content_type, size_t chunk_size = 8192) {
3588
  return Result{cli.open_stream("PUT", path, {}, headers, body, content_type),
3589
                chunk_size};
3590
}
3591
3592
template <typename ClientType>
3593
inline Result Put(ClientType &cli, const std::string &path,
3594
                  const Params &params, const std::string &body,
3595
                  const std::string &content_type, size_t chunk_size = 8192) {
3596
  return Result{cli.open_stream("PUT", path, params, {}, body, content_type),
3597
                chunk_size};
3598
}
3599
3600
template <typename ClientType>
3601
inline Result Put(ClientType &cli, const std::string &path,
3602
                  const Params &params, const Headers &headers,
3603
                  const std::string &body, const std::string &content_type,
3604
                  size_t chunk_size = 8192) {
3605
  return Result{
3606
      cli.open_stream("PUT", path, params, headers, body, content_type),
3607
      chunk_size};
3608
}
3609
3610
// PATCH
3611
template <typename ClientType>
3612
inline Result Patch(ClientType &cli, const std::string &path,
3613
                    const std::string &body, const std::string &content_type,
3614
                    size_t chunk_size = 8192) {
3615
  return Result{cli.open_stream("PATCH", path, {}, {}, body, content_type),
3616
                chunk_size};
3617
}
3618
3619
template <typename ClientType>
3620
inline Result Patch(ClientType &cli, const std::string &path,
3621
                    const Headers &headers, const std::string &body,
3622
                    const std::string &content_type, size_t chunk_size = 8192) {
3623
  return Result{cli.open_stream("PATCH", path, {}, headers, body, content_type),
3624
                chunk_size};
3625
}
3626
3627
template <typename ClientType>
3628
inline Result Patch(ClientType &cli, const std::string &path,
3629
                    const Params &params, const std::string &body,
3630
                    const std::string &content_type, size_t chunk_size = 8192) {
3631
  return Result{cli.open_stream("PATCH", path, params, {}, body, content_type),
3632
                chunk_size};
3633
}
3634
3635
template <typename ClientType>
3636
inline Result Patch(ClientType &cli, const std::string &path,
3637
                    const Params &params, const Headers &headers,
3638
                    const std::string &body, const std::string &content_type,
3639
                    size_t chunk_size = 8192) {
3640
  return Result{
3641
      cli.open_stream("PATCH", path, params, headers, body, content_type),
3642
      chunk_size};
3643
}
3644
3645
// DELETE
3646
template <typename ClientType>
3647
inline Result Delete(ClientType &cli, const std::string &path,
3648
                     size_t chunk_size = 8192) {
3649
  return Result{cli.open_stream("DELETE", path), chunk_size};
3650
}
3651
3652
template <typename ClientType>
3653
inline Result Delete(ClientType &cli, const std::string &path,
3654
                     const Headers &headers, size_t chunk_size = 8192) {
3655
  return Result{cli.open_stream("DELETE", path, {}, headers), chunk_size};
3656
}
3657
3658
template <typename ClientType>
3659
inline Result Delete(ClientType &cli, const std::string &path,
3660
                     const std::string &body, const std::string &content_type,
3661
                     size_t chunk_size = 8192) {
3662
  return Result{cli.open_stream("DELETE", path, {}, {}, body, content_type),
3663
                chunk_size};
3664
}
3665
3666
template <typename ClientType>
3667
inline Result Delete(ClientType &cli, const std::string &path,
3668
                     const Headers &headers, const std::string &body,
3669
                     const std::string &content_type,
3670
                     size_t chunk_size = 8192) {
3671
  return Result{
3672
      cli.open_stream("DELETE", path, {}, headers, body, content_type),
3673
      chunk_size};
3674
}
3675
3676
template <typename ClientType>
3677
inline Result Delete(ClientType &cli, const std::string &path,
3678
                     const Params &params, size_t chunk_size = 8192) {
3679
  return Result{cli.open_stream("DELETE", path, params), chunk_size};
3680
}
3681
3682
template <typename ClientType>
3683
inline Result Delete(ClientType &cli, const std::string &path,
3684
                     const Params &params, const Headers &headers,
3685
                     size_t chunk_size = 8192) {
3686
  return Result{cli.open_stream("DELETE", path, params, headers), chunk_size};
3687
}
3688
3689
template <typename ClientType>
3690
inline Result Delete(ClientType &cli, const std::string &path,
3691
                     const Params &params, const std::string &body,
3692
                     const std::string &content_type,
3693
                     size_t chunk_size = 8192) {
3694
  return Result{cli.open_stream("DELETE", path, params, {}, body, content_type),
3695
                chunk_size};
3696
}
3697
3698
template <typename ClientType>
3699
inline Result Delete(ClientType &cli, const std::string &path,
3700
                     const Params &params, const Headers &headers,
3701
                     const std::string &body, const std::string &content_type,
3702
                     size_t chunk_size = 8192) {
3703
  return Result{
3704
      cli.open_stream("DELETE", path, params, headers, body, content_type),
3705
      chunk_size};
3706
}
3707
3708
// HEAD
3709
template <typename ClientType>
3710
inline Result Head(ClientType &cli, const std::string &path,
3711
                   size_t chunk_size = 8192) {
3712
  return Result{cli.open_stream("HEAD", path), chunk_size};
3713
}
3714
3715
template <typename ClientType>
3716
inline Result Head(ClientType &cli, const std::string &path,
3717
                   const Headers &headers, size_t chunk_size = 8192) {
3718
  return Result{cli.open_stream("HEAD", path, {}, headers), chunk_size};
3719
}
3720
3721
template <typename ClientType>
3722
inline Result Head(ClientType &cli, const std::string &path,
3723
                   const Params &params, size_t chunk_size = 8192) {
3724
  return Result{cli.open_stream("HEAD", path, params), chunk_size};
3725
}
3726
3727
template <typename ClientType>
3728
inline Result Head(ClientType &cli, const std::string &path,
3729
                   const Params &params, const Headers &headers,
3730
                   size_t chunk_size = 8192) {
3731
  return Result{cli.open_stream("HEAD", path, params, headers), chunk_size};
3732
}
3733
3734
// OPTIONS
3735
template <typename ClientType>
3736
inline Result Options(ClientType &cli, const std::string &path,
3737
                      size_t chunk_size = 8192) {
3738
  return Result{cli.open_stream("OPTIONS", path), chunk_size};
3739
}
3740
3741
template <typename ClientType>
3742
inline Result Options(ClientType &cli, const std::string &path,
3743
                      const Headers &headers, size_t chunk_size = 8192) {
3744
  return Result{cli.open_stream("OPTIONS", path, {}, headers), chunk_size};
3745
}
3746
3747
template <typename ClientType>
3748
inline Result Options(ClientType &cli, const std::string &path,
3749
                      const Params &params, size_t chunk_size = 8192) {
3750
  return Result{cli.open_stream("OPTIONS", path, params), chunk_size};
3751
}
3752
3753
template <typename ClientType>
3754
inline Result Options(ClientType &cli, const std::string &path,
3755
                      const Params &params, const Headers &headers,
3756
                      size_t chunk_size = 8192) {
3757
  return Result{cli.open_stream("OPTIONS", path, params, headers), chunk_size};
3758
}
3759
3760
} // namespace stream
3761
3762
namespace sse {
3763
3764
struct SSEMessage {
3765
  std::string event; // Event type (default: "message")
3766
  std::string data;  // Event payload
3767
  std::string id;    // Event ID for Last-Event-ID header
3768
3769
  SSEMessage();
3770
  void clear();
3771
};
3772
3773
class SSEClient {
3774
public:
3775
  using MessageHandler = std::function<void(const SSEMessage &)>;
3776
  using ErrorHandler = std::function<void(Error)>;
3777
  using OpenHandler = std::function<void()>;
3778
3779
  SSEClient(Client &client, const std::string &path);
3780
  SSEClient(Client &client, const std::string &path, const Headers &headers);
3781
  ~SSEClient();
3782
3783
  SSEClient(const SSEClient &) = delete;
3784
  SSEClient &operator=(const SSEClient &) = delete;
3785
3786
  // Event handlers
3787
  SSEClient &on_message(MessageHandler handler);
3788
  SSEClient &on_event(const std::string &type, MessageHandler handler);
3789
  SSEClient &on_open(OpenHandler handler);
3790
  SSEClient &on_error(ErrorHandler handler);
3791
  SSEClient &set_reconnect_interval(int ms);
3792
  SSEClient &set_max_reconnect_attempts(int n);
3793
3794
  // Update headers (thread-safe)
3795
  SSEClient &set_headers(const Headers &headers);
3796
3797
  // State accessors
3798
  bool is_connected() const;
3799
  const std::string &last_event_id() const;
3800
3801
  // Blocking start - runs event loop with auto-reconnect
3802
  void start();
3803
3804
  // Non-blocking start - runs in background thread
3805
  void start_async();
3806
3807
  // Stop the client (thread-safe)
3808
  void stop();
3809
3810
private:
3811
  bool parse_sse_line(const std::string &line, SSEMessage &msg, int &retry_ms);
3812
  void run_event_loop();
3813
  void dispatch_event(const SSEMessage &msg);
3814
  bool should_reconnect(int count) const;
3815
  void wait_for_reconnect();
3816
3817
  // Client and path
3818
  Client &client_;
3819
  std::string path_;
3820
  Headers headers_;
3821
  mutable std::mutex headers_mutex_;
3822
3823
  // Callbacks
3824
  MessageHandler on_message_;
3825
  std::map<std::string, MessageHandler> event_handlers_;
3826
  OpenHandler on_open_;
3827
  ErrorHandler on_error_;
3828
3829
  // Configuration
3830
  int reconnect_interval_ms_ = 3000;
3831
  int max_reconnect_attempts_ = 0; // 0 = unlimited
3832
3833
  // State
3834
  std::atomic<bool> running_{false};
3835
  std::atomic<bool> connected_{false};
3836
  std::string last_event_id_;
3837
3838
  // Async support
3839
  std::thread async_thread_;
3840
};
3841
3842
} // namespace sse
3843
3844
namespace ws {
3845
3846
enum class Opcode : uint8_t {
3847
  Continuation = 0x0,
3848
  Text = 0x1,
3849
  Binary = 0x2,
3850
  Close = 0x8,
3851
  Ping = 0x9,
3852
  Pong = 0xA,
3853
};
3854
3855
enum class CloseStatus : uint16_t {
3856
  Normal = 1000,
3857
  GoingAway = 1001,
3858
  ProtocolError = 1002,
3859
  UnsupportedData = 1003,
3860
  NoStatus = 1005,
3861
  Abnormal = 1006,
3862
  InvalidPayload = 1007,
3863
  PolicyViolation = 1008,
3864
  MessageTooBig = 1009,
3865
  MandatoryExtension = 1010,
3866
  InternalError = 1011,
3867
};
3868
3869
enum ReadResult : int { Fail = 0, Text = 1, Binary = 2 };
3870
3871
class WebSocket {
3872
public:
3873
  WebSocket(const WebSocket &) = delete;
3874
  WebSocket &operator=(const WebSocket &) = delete;
3875
  ~WebSocket();
3876
3877
  ReadResult read(std::string &msg);
3878
  bool send(const std::string &data);
3879
  bool send(const char *data, size_t len);
3880
  void close(CloseStatus status = CloseStatus::Normal,
3881
             const std::string &reason = "");
3882
  const Request &request() const;
3883
  bool is_open() const;
3884
3885
private:
3886
  friend class httplib::Server;
3887
  friend class WebSocketClient;
3888
3889
  WebSocket(
3890
      Stream &strm, const Request &req, bool is_server,
3891
      time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND,
3892
      int max_missed_pongs = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS)
3893
0
      : strm_(strm), req_(req), is_server_(is_server),
3894
0
        ping_interval_sec_(ping_interval_sec),
3895
0
        max_missed_pongs_(max_missed_pongs) {
3896
0
    start_heartbeat();
3897
0
  }
3898
3899
  WebSocket(
3900
      std::unique_ptr<Stream> &&owned_strm, const Request &req, bool is_server,
3901
      time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND,
3902
      int max_missed_pongs = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS)
3903
      : strm_(*owned_strm), owned_strm_(std::move(owned_strm)), req_(req),
3904
        is_server_(is_server), ping_interval_sec_(ping_interval_sec),
3905
0
        max_missed_pongs_(max_missed_pongs) {
3906
0
    start_heartbeat();
3907
0
  }
3908
3909
  void start_heartbeat();
3910
  bool send_frame(Opcode op, const char *data, size_t len, bool fin = true);
3911
3912
  Stream &strm_;
3913
  std::unique_ptr<Stream> owned_strm_;
3914
  Request req_;
3915
  bool is_server_;
3916
  time_t ping_interval_sec_;
3917
  int max_missed_pongs_;
3918
  int unacked_pings_ = 0;
3919
  std::atomic<bool> closed_{false};
3920
  std::mutex write_mutex_;
3921
  std::thread ping_thread_;
3922
  std::mutex ping_mutex_;
3923
  std::condition_variable ping_cv_;
3924
};
3925
3926
class WebSocketClient {
3927
public:
3928
  explicit WebSocketClient(const std::string &scheme_host_port_path,
3929
                           const Headers &headers = {});
3930
3931
  ~WebSocketClient();
3932
  WebSocketClient(const WebSocketClient &) = delete;
3933
  WebSocketClient &operator=(const WebSocketClient &) = delete;
3934
3935
  bool is_valid() const;
3936
3937
  bool connect();
3938
  ReadResult read(std::string &msg);
3939
  bool send(const std::string &data);
3940
  bool send(const char *data, size_t len);
3941
  void close(CloseStatus status = CloseStatus::Normal,
3942
             const std::string &reason = "");
3943
  bool is_open() const;
3944
  const std::string &subprotocol() const;
3945
  void set_read_timeout(time_t sec, time_t usec = 0);
3946
  void set_write_timeout(time_t sec, time_t usec = 0);
3947
  void set_websocket_ping_interval(time_t sec);
3948
  void set_websocket_max_missed_pongs(int count);
3949
  void set_tcp_nodelay(bool on);
3950
  void set_address_family(int family);
3951
  void set_ipv6_v6only(bool on);
3952
  void set_socket_options(SocketOptions socket_options);
3953
  void set_connection_timeout(time_t sec, time_t usec = 0);
3954
  void set_interface(const std::string &intf);
3955
  void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
3956
3957
#ifdef CPPHTTPLIB_SSL_ENABLED
3958
  void set_ca_cert_path(const std::string &path);
3959
  void set_ca_cert_store(tls::ca_store_t store);
3960
  void load_ca_cert_store(const char *ca_cert, std::size_t size);
3961
  void enable_server_certificate_verification(bool enabled);
3962
  void enable_system_ca(bool enabled);
3963
#endif
3964
3965
private:
3966
  void shutdown_and_close();
3967
  bool create_stream(std::unique_ptr<Stream> &strm);
3968
3969
  std::string host_;
3970
  int port_;
3971
  std::string path_;
3972
  Headers headers_;
3973
  std::string subprotocol_;
3974
  bool is_valid_ = false;
3975
  socket_t sock_ = INVALID_SOCKET;
3976
  std::unique_ptr<WebSocket> ws_;
3977
  time_t read_timeout_sec_ = CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND;
3978
  time_t read_timeout_usec_ = 0;
3979
  time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND;
3980
  time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND;
3981
  time_t websocket_ping_interval_sec_ =
3982
      CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND;
3983
  int websocket_max_missed_pongs_ = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS;
3984
  int address_family_ = AF_UNSPEC;
3985
  bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
3986
  bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
3987
  SocketOptions socket_options_ = nullptr;
3988
  time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
3989
  time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
3990
  std::string interface_;
3991
3992
  // Hostname-IP map
3993
  std::map<std::string, std::string> addr_map_;
3994
3995
#ifdef CPPHTTPLIB_SSL_ENABLED
3996
  bool is_ssl_ = false;
3997
  tls::ctx_t tls_ctx_ = nullptr;
3998
  tls::session_t tls_session_ = nullptr;
3999
  std::string ca_cert_file_path_;
4000
  bool custom_ca_loaded_ = false;
4001
  bool certs_loaded_ = false;
4002
  SystemCAMode system_ca_mode_ = SystemCAMode::Auto;
4003
  bool server_certificate_verification_ = true;
4004
#endif
4005
};
4006
4007
namespace impl {
4008
4009
bool is_valid_utf8(const std::string &s);
4010
4011
bool read_websocket_frame(Stream &strm, Opcode &opcode, std::string &payload,
4012
                          bool &fin, bool expect_masked, size_t max_len);
4013
4014
} // namespace impl
4015
4016
} // namespace ws
4017
4018
// ----------------------------------------------------------------------------
4019
4020
/*
4021
 * Implementation that will be part of the .cc file if split into .h + .cc.
4022
 */
4023
4024
namespace stream {
4025
4026
// stream::Result implementations
4027
inline Result::Result() : chunk_size_(8192) {}
4028
4029
inline Result::Result(ClientImpl::StreamHandle &&handle, size_t chunk_size)
4030
    : handle_(std::move(handle)), chunk_size_(chunk_size) {}
4031
4032
inline Result::Result(Result &&other) noexcept
4033
    : handle_(std::move(other.handle_)), buffer_(std::move(other.buffer_)),
4034
      current_size_(other.current_size_), chunk_size_(other.chunk_size_),
4035
      finished_(other.finished_) {
4036
  other.current_size_ = 0;
4037
  other.finished_ = true;
4038
}
4039
4040
0
inline Result &Result::operator=(Result &&other) noexcept {
4041
0
  if (this != &other) {
4042
0
    handle_ = std::move(other.handle_);
4043
0
    buffer_ = std::move(other.buffer_);
4044
0
    current_size_ = other.current_size_;
4045
0
    chunk_size_ = other.chunk_size_;
4046
0
    finished_ = other.finished_;
4047
0
    other.current_size_ = 0;
4048
0
    other.finished_ = true;
4049
0
  }
4050
0
  return *this;
4051
0
}
4052
4053
0
inline bool Result::is_valid() const { return handle_.is_valid(); }
4054
0
inline Result::operator bool() const { return is_valid(); }
4055
4056
0
inline int Result::status() const {
4057
0
  return handle_.response ? handle_.response->status : -1;
4058
0
}
4059
4060
0
inline const Headers &Result::headers() const {
4061
0
  static const Headers empty_headers;
4062
0
  return handle_.response ? handle_.response->headers : empty_headers;
4063
0
}
4064
4065
inline std::string Result::get_header_value(const std::string &key,
4066
0
                                            const char *def) const {
4067
0
  return handle_.response ? handle_.response->get_header_value(key, def) : def;
4068
0
}
4069
4070
0
inline bool Result::has_header(const std::string &key) const {
4071
0
  return handle_.response ? handle_.response->has_header(key) : false;
4072
0
}
4073
4074
0
inline Error Result::error() const { return handle_.error; }
4075
0
inline Error Result::read_error() const { return handle_.get_read_error(); }
4076
0
inline bool Result::has_read_error() const { return handle_.has_read_error(); }
4077
4078
0
inline bool Result::next() {
4079
0
  if (!handle_.is_valid() || finished_) { return false; }
4080
0
4081
0
  if (buffer_.size() < chunk_size_) { buffer_.resize(chunk_size_); }
4082
0
4083
0
  ssize_t n = handle_.read(&buffer_[0], chunk_size_);
4084
0
  if (n > 0) {
4085
0
    current_size_ = static_cast<size_t>(n);
4086
0
    return true;
4087
0
  }
4088
0
4089
0
  current_size_ = 0;
4090
0
  finished_ = true;
4091
0
  return false;
4092
0
}
4093
4094
0
inline const char *Result::data() const { return buffer_.data(); }
4095
0
inline size_t Result::size() const { return current_size_; }
4096
4097
0
inline std::string Result::read_all() {
4098
0
  std::string result;
4099
0
  while (next()) {
4100
0
    result.append(data(), size());
4101
0
  }
4102
0
  return result;
4103
0
}
4104
4105
} // namespace stream
4106
4107
namespace sse {
4108
4109
// SSEMessage implementations
4110
inline SSEMessage::SSEMessage() : event("message") {}
4111
4112
0
inline void SSEMessage::clear() {
4113
0
  event = "message";
4114
0
  data.clear();
4115
0
  id.clear();
4116
0
}
4117
4118
// SSEClient implementations
4119
inline SSEClient::SSEClient(Client &client, const std::string &path)
4120
    : client_(client), path_(path) {}
4121
4122
inline SSEClient::SSEClient(Client &client, const std::string &path,
4123
                            const Headers &headers)
4124
    : client_(client), path_(path), headers_(headers) {}
4125
4126
inline SSEClient::~SSEClient() { stop(); }
4127
4128
0
inline SSEClient &SSEClient::on_message(MessageHandler handler) {
4129
0
  on_message_ = std::move(handler);
4130
0
  return *this;
4131
0
}
4132
4133
inline SSEClient &SSEClient::on_event(const std::string &type,
4134
0
                                      MessageHandler handler) {
4135
0
  event_handlers_[type] = std::move(handler);
4136
0
  return *this;
4137
0
}
4138
4139
0
inline SSEClient &SSEClient::on_open(OpenHandler handler) {
4140
0
  on_open_ = std::move(handler);
4141
0
  return *this;
4142
0
}
4143
4144
0
inline SSEClient &SSEClient::on_error(ErrorHandler handler) {
4145
0
  on_error_ = std::move(handler);
4146
0
  return *this;
4147
0
}
4148
4149
0
inline SSEClient &SSEClient::set_reconnect_interval(int ms) {
4150
0
  reconnect_interval_ms_ = ms;
4151
0
  return *this;
4152
0
}
4153
4154
0
inline SSEClient &SSEClient::set_max_reconnect_attempts(int n) {
4155
0
  max_reconnect_attempts_ = n;
4156
0
  return *this;
4157
0
}
4158
4159
0
inline SSEClient &SSEClient::set_headers(const Headers &headers) {
4160
0
  std::lock_guard<std::mutex> lock(headers_mutex_);
4161
0
  headers_ = headers;
4162
0
  return *this;
4163
0
}
4164
4165
0
inline bool SSEClient::is_connected() const { return connected_.load(); }
4166
4167
0
inline const std::string &SSEClient::last_event_id() const {
4168
0
  return last_event_id_;
4169
0
}
4170
4171
0
inline void SSEClient::start() {
4172
0
  running_.store(true);
4173
0
  run_event_loop();
4174
0
}
4175
4176
0
inline void SSEClient::start_async() {
4177
0
  running_.store(true);
4178
0
  async_thread_ = std::thread([this]() { run_event_loop(); });
4179
0
}
4180
4181
0
inline void SSEClient::stop() {
4182
0
  running_.store(false);
4183
0
  client_.stop(); // Cancel any pending operations
4184
0
  if (async_thread_.joinable()) { async_thread_.join(); }
4185
0
}
4186
4187
inline bool SSEClient::parse_sse_line(const std::string &line, SSEMessage &msg,
4188
0
                                      int &retry_ms) {
4189
0
  // Blank line signals end of event
4190
0
  if (line.empty() || line == "\r") { return true; }
4191
0
4192
0
  // Lines starting with ':' are comments (ignored)
4193
0
  if (!line.empty() && line[0] == ':') { return false; }
4194
0
4195
0
  // Find the colon separator
4196
0
  auto colon_pos = line.find(':');
4197
0
  if (colon_pos == std::string::npos) {
4198
0
    // Line with no colon is treated as field name with empty value
4199
0
    return false;
4200
0
  }
4201
0
4202
0
  auto field = line.substr(0, colon_pos);
4203
0
  std::string value;
4204
0
4205
0
  // Value starts after colon, skip optional single space
4206
0
  if (colon_pos + 1 < line.size()) {
4207
0
    auto value_start = colon_pos + 1;
4208
0
    if (line[value_start] == ' ') { value_start++; }
4209
0
    value = line.substr(value_start);
4210
0
    // Remove trailing \r if present
4211
0
    if (!value.empty() && value.back() == '\r') { value.pop_back(); }
4212
0
  }
4213
0
4214
0
  // Handle known fields
4215
0
  if (field == "event") {
4216
0
    msg.event = value;
4217
0
  } else if (field == "data") {
4218
0
    // Multiple data lines are concatenated with newlines
4219
0
    if (!msg.data.empty()) { msg.data += "\n"; }
4220
0
    msg.data += value;
4221
0
  } else if (field == "id") {
4222
0
    // Empty id is valid (clears the last event ID)
4223
0
    msg.id = value;
4224
0
  } else if (field == "retry") {
4225
0
    // Parse retry interval in milliseconds
4226
0
    {
4227
0
      int v = 0;
4228
0
      auto res =
4229
0
          detail::from_chars(value.data(), value.data() + value.size(), v);
4230
0
      if (res.ec == std::errc{}) { retry_ms = v; }
4231
0
    }
4232
0
  }
4233
0
  // Unknown fields are ignored per SSE spec
4234
0
4235
0
  return false;
4236
0
}
4237
4238
0
inline void SSEClient::run_event_loop() {
4239
0
  auto reconnect_count = 0;
4240
0
4241
0
  while (running_.load()) {
4242
0
    // Build headers, including Last-Event-ID if we have one
4243
0
    Headers request_headers;
4244
0
    {
4245
0
      std::lock_guard<std::mutex> lock(headers_mutex_);
4246
0
      request_headers = headers_;
4247
0
    }
4248
0
    if (!last_event_id_.empty()) {
4249
0
      request_headers.emplace("Last-Event-ID", last_event_id_);
4250
0
    }
4251
0
4252
0
    // Open streaming connection
4253
0
    auto result = stream::Get(client_, path_, request_headers);
4254
0
4255
0
    // Connection error handling
4256
0
    if (!result) {
4257
0
      connected_.store(false);
4258
0
      if (on_error_) { on_error_(result.error()); }
4259
0
4260
0
      if (!should_reconnect(reconnect_count)) { break; }
4261
0
      wait_for_reconnect();
4262
0
      reconnect_count++;
4263
0
      continue;
4264
0
    }
4265
0
4266
0
    if (result.status() != StatusCode::OK_200) {
4267
0
      connected_.store(false);
4268
0
      if (on_error_) { on_error_(Error::Connection); }
4269
0
4270
0
      // For certain errors, don't reconnect.
4271
0
      // Note: 401 is intentionally absent so that handlers can refresh
4272
0
      // credentials via set_headers() and let the client reconnect.
4273
0
      if (result.status() == StatusCode::NoContent_204 ||
4274
0
          result.status() == StatusCode::NotFound_404 ||
4275
0
          result.status() == StatusCode::Forbidden_403) {
4276
0
        break;
4277
0
      }
4278
0
4279
0
      if (!should_reconnect(reconnect_count)) { break; }
4280
0
      wait_for_reconnect();
4281
0
      reconnect_count++;
4282
0
      continue;
4283
0
    }
4284
0
4285
0
    // Connection successful
4286
0
    connected_.store(true);
4287
0
    reconnect_count = 0;
4288
0
    if (on_open_) { on_open_(); }
4289
0
4290
0
    // Event receiving loop
4291
0
    std::string buffer;
4292
0
    SSEMessage current_msg;
4293
0
4294
0
    while (running_.load() && result.next()) {
4295
0
      buffer.append(result.data(), result.size());
4296
0
4297
0
      // Process complete lines in the buffer
4298
0
      size_t line_start = 0;
4299
0
      size_t newline_pos;
4300
0
4301
0
      while ((newline_pos = buffer.find('\n', line_start)) !=
4302
0
             std::string::npos) {
4303
0
        auto line = buffer.substr(line_start, newline_pos - line_start);
4304
0
        line_start = newline_pos + 1;
4305
0
4306
0
        // Parse the line and check if event is complete
4307
0
        auto event_complete =
4308
0
            parse_sse_line(line, current_msg, reconnect_interval_ms_);
4309
0
4310
0
        if (event_complete && !current_msg.data.empty()) {
4311
0
          // Update last_event_id for reconnection
4312
0
          if (!current_msg.id.empty()) { last_event_id_ = current_msg.id; }
4313
0
4314
0
          // Dispatch event to appropriate handler
4315
0
          dispatch_event(current_msg);
4316
0
4317
0
          current_msg.clear();
4318
0
        }
4319
0
      }
4320
0
4321
0
      // Keep unprocessed data in buffer
4322
0
      buffer.erase(0, line_start);
4323
0
    }
4324
0
4325
0
    // Connection ended
4326
0
    connected_.store(false);
4327
0
4328
0
    if (!running_.load()) { break; }
4329
0
4330
0
    // Check for read errors
4331
0
    if (result.has_read_error()) {
4332
0
      if (on_error_) { on_error_(result.read_error()); }
4333
0
    }
4334
0
4335
0
    if (!should_reconnect(reconnect_count)) { break; }
4336
0
    wait_for_reconnect();
4337
0
    reconnect_count++;
4338
0
  }
4339
0
4340
0
  connected_.store(false);
4341
0
}
4342
4343
0
inline void SSEClient::dispatch_event(const SSEMessage &msg) {
4344
0
  // Check for specific event type handler first
4345
0
  auto it = event_handlers_.find(msg.event);
4346
0
  if (it != event_handlers_.end()) {
4347
0
    it->second(msg);
4348
0
    return;
4349
0
  }
4350
0
4351
0
  // Fall back to generic message handler
4352
0
  if (on_message_) { on_message_(msg); }
4353
0
}
4354
4355
0
inline bool SSEClient::should_reconnect(int count) const {
4356
0
  if (!running_.load()) { return false; }
4357
0
  if (max_reconnect_attempts_ == 0) { return true; } // unlimited
4358
0
  return count < max_reconnect_attempts_;
4359
0
}
4360
4361
0
inline void SSEClient::wait_for_reconnect() {
4362
0
  // Use small increments to check running_ flag frequently
4363
0
  auto waited = 0;
4364
0
  while (running_.load() && waited < reconnect_interval_ms_) {
4365
0
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
4366
0
    waited += 100;
4367
0
  }
4368
0
}
4369
4370
} // namespace sse
4371
4372
#ifdef CPPHTTPLIB_SSL_ENABLED
4373
/*
4374
 * TLS abstraction layer - internal function declarations
4375
 * These are implementation details and not part of the public API.
4376
 */
4377
namespace tls {
4378
4379
// Client context
4380
ctx_t create_client_context();
4381
void free_context(ctx_t ctx);
4382
bool set_min_version(ctx_t ctx, Version version);
4383
bool load_ca_pem(ctx_t ctx, const char *pem, size_t len);
4384
bool load_ca_file(ctx_t ctx, const char *file_path);
4385
bool load_ca_dir(ctx_t ctx, const char *dir_path);
4386
bool load_system_certs(ctx_t ctx);
4387
bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
4388
                         const char *password);
4389
bool set_client_cert_file(ctx_t ctx, const char *cert_path,
4390
                          const char *key_path, const char *password);
4391
4392
// Server context
4393
ctx_t create_server_context();
4394
bool set_server_cert_pem(ctx_t ctx, const char *cert, const char *key,
4395
                         const char *password);
4396
bool set_server_cert_file(ctx_t ctx, const char *cert_path,
4397
                          const char *key_path, const char *password);
4398
bool set_client_ca_file(ctx_t ctx, const char *ca_file, const char *ca_dir);
4399
void set_verify_client(ctx_t ctx, bool require);
4400
4401
// Session management
4402
session_t create_session(ctx_t ctx, socket_t sock);
4403
void free_session(session_t session);
4404
bool set_sni(session_t session, const char *hostname);
4405
bool set_hostname(session_t session, const char *hostname);
4406
4407
// Handshake (non-blocking capable)
4408
TlsError connect(session_t session);
4409
TlsError accept(session_t session);
4410
4411
// Handshake with timeout (blocking until timeout)
4412
bool connect_nonblocking(session_t session, socket_t sock, time_t timeout_sec,
4413
                         time_t timeout_usec, TlsError *err);
4414
bool accept_nonblocking(session_t session, socket_t sock, time_t timeout_sec,
4415
                        time_t timeout_usec, TlsError *err);
4416
4417
// I/O (non-blocking capable)
4418
ssize_t read(session_t session, void *buf, size_t len, TlsError &err);
4419
ssize_t write(session_t session, const void *buf, size_t len, TlsError &err);
4420
int pending(const_session_t session);
4421
void shutdown(session_t session, bool graceful);
4422
4423
// Connection state
4424
bool is_peer_closed(session_t session, socket_t sock);
4425
4426
// Certificate verification
4427
cert_t get_peer_cert(const_session_t session);
4428
void free_cert(cert_t cert);
4429
bool verify_hostname(cert_t cert, const char *hostname);
4430
uint64_t hostname_mismatch_code();
4431
long get_verify_result(const_session_t session);
4432
4433
// Certificate introspection
4434
std::string get_cert_subject_cn(cert_t cert);
4435
std::string get_cert_issuer_name(cert_t cert);
4436
bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans);
4437
bool get_cert_validity(cert_t cert, time_t &not_before, time_t &not_after);
4438
std::string get_cert_serial(cert_t cert);
4439
bool get_cert_der(cert_t cert, std::vector<unsigned char> &der);
4440
const char *get_sni(const_session_t session);
4441
4442
// CA store management
4443
ca_store_t create_ca_store(const char *pem, size_t len);
4444
void free_ca_store(ca_store_t store);
4445
bool set_ca_store(ctx_t ctx, ca_store_t store);
4446
size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs);
4447
std::vector<std::string> get_ca_names(ctx_t ctx);
4448
4449
// Dynamic certificate update (for servers)
4450
bool update_server_cert(ctx_t ctx, const char *cert_pem, const char *key_pem,
4451
                        const char *password);
4452
bool update_server_client_ca(ctx_t ctx, const char *ca_pem);
4453
4454
// Certificate verification callback
4455
bool set_verify_callback(ctx_t ctx, VerifyCallback callback);
4456
long get_verify_error(const_session_t session);
4457
std::string verify_error_string(long error_code);
4458
4459
// TlsError information
4460
uint64_t peek_error();
4461
uint64_t get_error();
4462
std::string error_string(uint64_t code);
4463
4464
} // namespace tls
4465
#endif // CPPHTTPLIB_SSL_ENABLED
4466
4467
/*
4468
 * Group 1: detail namespace - Non-SSL utilities
4469
 */
4470
4471
namespace detail {
4472
4473
inline bool set_socket_opt_impl(socket_t sock, int level, int optname,
4474
0
                                const void *optval, socklen_t optlen) {
4475
0
  return setsockopt(sock, level, optname,
4476
#ifdef _WIN32
4477
                    reinterpret_cast<const char *>(optval),
4478
#else
4479
0
                    optval,
4480
0
#endif
4481
0
                    optlen) == 0;
4482
0
}
4483
4484
inline bool set_socket_opt_time(socket_t sock, int level, int optname,
4485
0
                                time_t sec, time_t usec) {
4486
#ifdef _WIN32
4487
  auto timeout = static_cast<uint32_t>(sec * 1000 + usec / 1000);
4488
#else
4489
0
  timeval timeout;
4490
0
  timeout.tv_sec = static_cast<long>(sec);
4491
0
  timeout.tv_usec = static_cast<decltype(timeout.tv_usec)>(usec);
4492
0
#endif
4493
0
  return set_socket_opt_impl(sock, level, optname, &timeout, sizeof(timeout));
4494
0
}
4495
4496
208k
inline bool is_hex(char c, int &v) {
4497
208k
  if (is_ascii_digit(c)) {
4498
107k
    v = c - '0';
4499
107k
    return true;
4500
107k
  } else if ('A' <= c && c <= 'F') {
4501
11.3k
    v = c - 'A' + 10;
4502
11.3k
    return true;
4503
89.7k
  } else if ('a' <= c && c <= 'f') {
4504
12.4k
    v = c - 'a' + 10;
4505
12.4k
    return true;
4506
12.4k
  }
4507
77.3k
  return false;
4508
208k
}
4509
4510
inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt,
4511
30.6k
                          int &val) {
4512
30.6k
  if (i >= s.size()) { return false; }
4513
4514
30.4k
  val = 0;
4515
52.4k
  for (; cnt; i++, cnt--) {
4516
47.0k
    if (!s[i]) { return false; }
4517
46.7k
    auto v = 0;
4518
46.7k
    if (is_hex(s[i], v)) {
4519
22.0k
      val = val * 16 + v;
4520
24.7k
    } else {
4521
24.7k
      return false;
4522
24.7k
    }
4523
46.7k
  }
4524
5.39k
  return true;
4525
30.4k
}
4526
4527
0
inline std::string from_i_to_hex(size_t n) {
4528
0
  static const auto charset = "0123456789abcdef";
4529
0
  std::string ret;
4530
0
  do {
4531
0
    ret = charset[n & 15] + ret;
4532
0
    n >>= 4;
4533
0
  } while (n > 0);
4534
0
  return ret;
4535
0
}
4536
4537
0
inline std::string compute_etag(const FileStat &fs) {
4538
0
  if (!fs.is_file()) { return std::string(); }
4539
4540
  // If mtime cannot be determined (negative value indicates an error
4541
  // or sentinel), do not generate an ETag. Returning a neutral / fixed
4542
  // value like 0 could collide with a real file that legitimately has
4543
  // mtime == 0 (epoch) and lead to misleading validators.
4544
0
  auto mtime_raw = fs.mtime();
4545
0
  if (mtime_raw < 0) { return std::string(); }
4546
4547
0
  auto mtime = static_cast<size_t>(mtime_raw);
4548
0
  auto size = fs.size();
4549
4550
0
  return std::string("W/\"") + from_i_to_hex(mtime) + "-" +
4551
0
         from_i_to_hex(size) + "\"";
4552
0
}
4553
4554
// Format time_t as HTTP-date (RFC 9110 Section 5.6.7): "Sun, 06 Nov 1994
4555
// 08:49:37 GMT" This implementation is defensive: it validates `mtime`, checks
4556
// return values from `gmtime_r`/`gmtime_s`, and ensures `strftime` succeeds.
4557
0
inline std::string file_mtime_to_http_date(time_t mtime) {
4558
0
  if (mtime < 0) { return std::string(); }
4559
4560
0
  struct tm tm_buf;
4561
#ifdef _WIN32
4562
  if (gmtime_s(&tm_buf, &mtime) != 0) { return std::string(); }
4563
#else
4564
0
  if (gmtime_r(&mtime, &tm_buf) == nullptr) { return std::string(); }
4565
0
#endif
4566
0
  char buf[64];
4567
0
  if (strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", &tm_buf) == 0) {
4568
0
    return std::string();
4569
0
  }
4570
4571
0
  return std::string(buf);
4572
0
}
4573
4574
// Parse HTTP-date (RFC 9110 Section 5.6.7) to time_t. Returns -1 on failure.
4575
0
inline time_t parse_http_date(const std::string &date_str) {
4576
0
  struct tm tm_buf;
4577
4578
  // Create a classic locale object once for all parsing attempts
4579
0
  const std::locale classic_locale = std::locale::classic();
4580
4581
  // Try to parse using std::get_time (C++11, cross-platform)
4582
0
  auto try_parse = [&](const char *fmt) -> bool {
4583
0
    std::istringstream ss(date_str);
4584
0
    ss.imbue(classic_locale);
4585
4586
0
    memset(&tm_buf, 0, sizeof(tm_buf));
4587
0
    ss >> std::get_time(&tm_buf, fmt);
4588
4589
0
    return !ss.fail();
4590
0
  };
4591
4592
  // RFC 9110 preferred format (HTTP-date): "Sun, 06 Nov 1994 08:49:37 GMT"
4593
0
  if (!try_parse("%a, %d %b %Y %H:%M:%S")) {
4594
    // RFC 850 format: "Sunday, 06-Nov-94 08:49:37 GMT"
4595
0
    if (!try_parse("%A, %d-%b-%y %H:%M:%S")) {
4596
      // asctime format: "Sun Nov  6 08:49:37 1994"
4597
0
      if (!try_parse("%a %b %d %H:%M:%S %Y")) {
4598
0
        return static_cast<time_t>(-1);
4599
0
      }
4600
0
    }
4601
0
  }
4602
4603
#ifdef _WIN32
4604
  return _mkgmtime(&tm_buf);
4605
#elif defined _AIX
4606
  return mktime(&tm_buf);
4607
#else
4608
0
  return timegm(&tm_buf);
4609
0
#endif
4610
0
}
4611
4612
0
inline bool is_weak_etag(const std::string &s) {
4613
  // Check if the string is a weak ETag (starts with 'W/"')
4614
0
  return s.size() > 3 && s[0] == 'W' && s[1] == '/' && s[2] == '"';
4615
0
}
4616
4617
0
inline bool is_strong_etag(const std::string &s) {
4618
  // Check if the string is a strong ETag (starts and ends with '"', at least 2
4619
  // chars)
4620
0
  return s.size() >= 2 && s[0] == '"' && s.back() == '"';
4621
0
}
4622
4623
3.73k
inline size_t to_utf8(int code, char *buff) {
4624
3.73k
  if (code < 0x0080) {
4625
237
    buff[0] = static_cast<char>(code & 0x7F);
4626
237
    return 1;
4627
3.49k
  } else if (code < 0x0800) {
4628
362
    buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F));
4629
362
    buff[1] = static_cast<char>(0x80 | (code & 0x3F));
4630
362
    return 2;
4631
3.13k
  } else if (code < 0xD800) {
4632
2.33k
    buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
4633
2.33k
    buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
4634
2.33k
    buff[2] = static_cast<char>(0x80 | (code & 0x3F));
4635
2.33k
    return 3;
4636
2.33k
  } else if (code < 0xE000) { // D800 - DFFF is invalid...
4637
360
    return 0;
4638
439
  } else if (code < 0x10000) {
4639
439
    buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
4640
439
    buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
4641
439
    buff[2] = static_cast<char>(0x80 | (code & 0x3F));
4642
439
    return 3;
4643
439
  } else if (code < 0x110000) {
4644
0
    buff[0] = static_cast<char>(0xF0 | ((code >> 18) & 0x7));
4645
0
    buff[1] = static_cast<char>(0x80 | ((code >> 12) & 0x3F));
4646
0
    buff[2] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
4647
0
    buff[3] = static_cast<char>(0x80 | (code & 0x3F));
4648
0
    return 4;
4649
0
  }
4650
4651
  // NOTREACHED
4652
0
  return 0;
4653
3.73k
}
4654
4655
} // namespace detail
4656
4657
namespace ws {
4658
namespace impl {
4659
4660
0
inline bool is_valid_utf8(const std::string &s) {
4661
0
  size_t i = 0;
4662
0
  auto n = s.size();
4663
0
  while (i < n) {
4664
0
    auto c = static_cast<unsigned char>(s[i]);
4665
0
    size_t len;
4666
0
    uint32_t cp;
4667
0
    if (c < 0x80) {
4668
0
      i++;
4669
0
      continue;
4670
0
    } else if ((c & 0xE0) == 0xC0) {
4671
0
      len = 2;
4672
0
      cp = c & 0x1F;
4673
0
    } else if ((c & 0xF0) == 0xE0) {
4674
0
      len = 3;
4675
0
      cp = c & 0x0F;
4676
0
    } else if ((c & 0xF8) == 0xF0) {
4677
0
      len = 4;
4678
0
      cp = c & 0x07;
4679
0
    } else {
4680
0
      return false;
4681
0
    }
4682
0
    if (i + len > n) { return false; }
4683
0
    for (size_t j = 1; j < len; j++) {
4684
0
      auto b = static_cast<unsigned char>(s[i + j]);
4685
0
      if ((b & 0xC0) != 0x80) { return false; }
4686
0
      cp = (cp << 6) | (b & 0x3F);
4687
0
    }
4688
0
    // Overlong encoding check
4689
0
    if (len == 2 && cp < 0x80) { return false; }
4690
0
    if (len == 3 && cp < 0x800) { return false; }
4691
0
    if (len == 4 && cp < 0x10000) { return false; }
4692
0
    // Surrogate halves (U+D800..U+DFFF) and beyond U+10FFFF are invalid
4693
0
    if (cp >= 0xD800 && cp <= 0xDFFF) { return false; }
4694
0
    if (cp > 0x10FFFF) { return false; }
4695
0
    i += len;
4696
0
  }
4697
0
  return true;
4698
0
}
4699
4700
} // namespace impl
4701
} // namespace ws
4702
4703
namespace detail {
4704
4705
// NOTE: This code came up with the following stackoverflow post:
4706
// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c
4707
0
inline std::string base64_encode(const std::string &in) {
4708
0
  static const auto lookup =
4709
0
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4710
4711
0
  std::string out;
4712
0
  out.reserve(in.size());
4713
4714
  // Unsigned: the accumulator is never masked, so with a signed int the
4715
  // `val << 8` below overflows once enough bytes are folded in (undefined
4716
  // behaviour before C++20). Only the low bits are ever emitted, so the
4717
  // wrap-around of an unsigned accumulator does not affect the output.
4718
0
  uint32_t val = 0;
4719
0
  auto valb = -6;
4720
4721
0
  for (auto c : in) {
4722
0
    val = (val << 8) + static_cast<uint8_t>(c);
4723
0
    valb += 8;
4724
0
    while (valb >= 0) {
4725
0
      out.push_back(lookup[(val >> valb) & 0x3F]);
4726
0
      valb -= 6;
4727
0
    }
4728
0
  }
4729
4730
0
  if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); }
4731
4732
0
  while (out.size() % 4) {
4733
0
    out.push_back('=');
4734
0
  }
4735
4736
0
  return out;
4737
0
}
4738
4739
0
inline std::string sha1(const std::string &input) {
4740
  // RFC 3174 SHA-1 implementation
4741
0
  auto left_rotate = [](uint32_t x, uint32_t n) -> uint32_t {
4742
0
    return (x << n) | (x >> (32 - n));
4743
0
  };
4744
4745
0
  uint32_t h0 = 0x67452301;
4746
0
  uint32_t h1 = 0xEFCDAB89;
4747
0
  uint32_t h2 = 0x98BADCFE;
4748
0
  uint32_t h3 = 0x10325476;
4749
0
  uint32_t h4 = 0xC3D2E1F0;
4750
4751
  // Pre-processing: adding padding bits
4752
0
  std::string msg = input;
4753
0
  uint64_t original_bit_len = static_cast<uint64_t>(msg.size()) * 8;
4754
0
  msg.push_back(static_cast<char>(0x80u));
4755
0
  while (msg.size() % 64 != 56) {
4756
0
    msg.push_back(0);
4757
0
  }
4758
4759
  // Append original length in bits as 64-bit big-endian
4760
0
  for (int i = 56; i >= 0; i -= 8) {
4761
0
    msg.push_back(static_cast<char>((original_bit_len >> i) & 0xFF));
4762
0
  }
4763
4764
  // Process each 512-bit chunk
4765
0
  for (size_t offset = 0; offset < msg.size(); offset += 64) {
4766
0
    uint32_t w[80];
4767
4768
0
    for (size_t i = 0; i < 16; i++) {
4769
0
      w[i] =
4770
0
          (static_cast<uint32_t>(static_cast<uint8_t>(msg[offset + i * 4]))
4771
0
           << 24) |
4772
0
          (static_cast<uint32_t>(static_cast<uint8_t>(msg[offset + i * 4 + 1]))
4773
0
           << 16) |
4774
0
          (static_cast<uint32_t>(static_cast<uint8_t>(msg[offset + i * 4 + 2]))
4775
0
           << 8) |
4776
0
          (static_cast<uint32_t>(
4777
0
              static_cast<uint8_t>(msg[offset + i * 4 + 3])));
4778
0
    }
4779
4780
0
    for (int i = 16; i < 80; i++) {
4781
0
      w[i] = left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
4782
0
    }
4783
4784
0
    uint32_t a = h0, b = h1, c = h2, d = h3, e = h4;
4785
4786
0
    for (int i = 0; i < 80; i++) {
4787
0
      uint32_t f, k;
4788
0
      if (i < 20) {
4789
0
        f = (b & c) | ((~b) & d);
4790
0
        k = 0x5A827999;
4791
0
      } else if (i < 40) {
4792
0
        f = b ^ c ^ d;
4793
0
        k = 0x6ED9EBA1;
4794
0
      } else if (i < 60) {
4795
0
        f = (b & c) | (b & d) | (c & d);
4796
0
        k = 0x8F1BBCDC;
4797
0
      } else {
4798
0
        f = b ^ c ^ d;
4799
0
        k = 0xCA62C1D6;
4800
0
      }
4801
4802
0
      uint32_t temp = left_rotate(a, 5) + f + e + k + w[i];
4803
0
      e = d;
4804
0
      d = c;
4805
0
      c = left_rotate(b, 30);
4806
0
      b = a;
4807
0
      a = temp;
4808
0
    }
4809
4810
0
    h0 += a;
4811
0
    h1 += b;
4812
0
    h2 += c;
4813
0
    h3 += d;
4814
0
    h4 += e;
4815
0
  }
4816
4817
  // Produce the final hash as a 20-byte binary string
4818
0
  std::string hash(20, '\0');
4819
0
  for (size_t i = 0; i < 4; i++) {
4820
0
    hash[i] = static_cast<char>((h0 >> (24 - i * 8)) & 0xFF);
4821
0
    hash[4 + i] = static_cast<char>((h1 >> (24 - i * 8)) & 0xFF);
4822
0
    hash[8 + i] = static_cast<char>((h2 >> (24 - i * 8)) & 0xFF);
4823
0
    hash[12 + i] = static_cast<char>((h3 >> (24 - i * 8)) & 0xFF);
4824
0
    hash[16 + i] = static_cast<char>((h4 >> (24 - i * 8)) & 0xFF);
4825
0
  }
4826
0
  return hash;
4827
0
}
4828
4829
0
inline std::string websocket_accept_key(const std::string &client_key) {
4830
0
  const std::string magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
4831
0
  return base64_encode(sha1(client_key + magic));
4832
0
}
4833
4834
4.53k
inline bool is_websocket_upgrade(const Request &req) {
4835
4.53k
  if (req.method != "GET") { return false; }
4836
4837
  // Check Upgrade: websocket (case-insensitive)
4838
1.48k
  auto upgrade_it = req.headers.find("Upgrade");
4839
1.48k
  if (upgrade_it == req.headers.end()) { return false; }
4840
20
  auto upgrade_val = case_ignore::to_lower(upgrade_it->second);
4841
20
  if (upgrade_val != "websocket") { return false; }
4842
4843
  // Check Connection header contains "Upgrade"
4844
5
  auto connection_it = req.headers.find("Connection");
4845
5
  if (connection_it == req.headers.end()) { return false; }
4846
4
  auto connection_val = case_ignore::to_lower(connection_it->second);
4847
4
  if (connection_val.find("upgrade") == std::string::npos) { return false; }
4848
4849
  // Check Sec-WebSocket-Key is a valid base64-encoded 16-byte value (24 chars)
4850
  // RFC 6455 Section 4.2.1
4851
2
  auto ws_key = req.get_header_value("Sec-WebSocket-Key");
4852
2
  if (ws_key.size() != 24 || ws_key[22] != '=' || ws_key[23] != '=') {
4853
2
    return false;
4854
2
  }
4855
0
  static const std::string b64chars =
4856
0
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4857
0
  for (size_t i = 0; i < 22; i++) {
4858
0
    if (b64chars.find(ws_key[i]) == std::string::npos) { return false; }
4859
0
  }
4860
4861
  // Check Sec-WebSocket-Version: 13
4862
0
  auto version = req.get_header_value("Sec-WebSocket-Version");
4863
0
  if (version != "13") { return false; }
4864
4865
0
  return true;
4866
0
}
4867
4868
inline bool write_websocket_frame(Stream &strm, ws::Opcode opcode,
4869
                                  const char *data, size_t len, bool fin,
4870
0
                                  bool mask) {
4871
  // First byte: FIN + opcode
4872
0
  uint8_t header[2];
4873
0
  header[0] = static_cast<uint8_t>((fin ? 0x80 : 0x00) |
4874
0
                                   (static_cast<uint8_t>(opcode) & 0x0F));
4875
4876
  // Second byte: MASK + payload length
4877
0
  if (len < 126) {
4878
0
    header[1] = static_cast<uint8_t>(len);
4879
0
    if (mask) { header[1] |= 0x80; }
4880
0
    if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
4881
0
  } else if (len <= 0xFFFF) {
4882
0
    header[1] = 126;
4883
0
    if (mask) { header[1] |= 0x80; }
4884
0
    if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
4885
0
    uint8_t ext[2];
4886
0
    ext[0] = static_cast<uint8_t>((len >> 8) & 0xFF);
4887
0
    ext[1] = static_cast<uint8_t>(len & 0xFF);
4888
0
    if (strm.write(reinterpret_cast<char *>(ext), 2) < 0) { return false; }
4889
0
  } else {
4890
0
    header[1] = 127;
4891
0
    if (mask) { header[1] |= 0x80; }
4892
0
    if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
4893
0
    uint8_t ext[8];
4894
0
    for (int i = 7; i >= 0; i--) {
4895
0
      ext[7 - i] =
4896
0
          static_cast<uint8_t>((static_cast<uint64_t>(len) >> (i * 8)) & 0xFF);
4897
0
    }
4898
0
    if (strm.write(reinterpret_cast<char *>(ext), 8) < 0) { return false; }
4899
0
  }
4900
4901
0
  if (mask) {
4902
    // Generate random mask key
4903
0
    thread_local std::mt19937 rng(std::random_device{}());
4904
0
    uint8_t mask_key[4];
4905
0
    auto r = rng();
4906
0
    std::memcpy(mask_key, &r, 4);
4907
0
    if (strm.write(reinterpret_cast<char *>(mask_key), 4) < 0) { return false; }
4908
4909
    // Write masked payload in chunks
4910
0
    const size_t chunk_size = 4096;
4911
0
    std::vector<char> buf((std::min)(len, chunk_size));
4912
0
    for (size_t offset = 0; offset < len; offset += chunk_size) {
4913
0
      size_t n = (std::min)(chunk_size, len - offset);
4914
0
      for (size_t i = 0; i < n; i++) {
4915
0
        buf[i] =
4916
0
            data[offset + i] ^ static_cast<char>(mask_key[(offset + i) % 4]);
4917
0
      }
4918
0
      if (strm.write(buf.data(), n) < 0) { return false; }
4919
0
    }
4920
0
  } else {
4921
0
    if (len > 0) {
4922
0
      if (strm.write(data, len) < 0) { return false; }
4923
0
    }
4924
0
  }
4925
4926
0
  return true;
4927
0
}
4928
4929
} // namespace detail
4930
4931
namespace ws {
4932
namespace impl {
4933
4934
inline bool read_websocket_frame(Stream &strm, Opcode &opcode,
4935
                                 std::string &payload, bool &fin,
4936
0
                                 bool expect_masked, size_t max_len) {
4937
  // Read first 2 bytes
4938
0
  uint8_t header[2];
4939
0
  if (strm.read(reinterpret_cast<char *>(header), 2) != 2) { return false; }
4940
4941
0
  fin = (header[0] & 0x80) != 0;
4942
4943
  // RSV1, RSV2, RSV3 must be 0 when no extension is negotiated
4944
0
  if (header[0] & 0x70) { return false; }
4945
4946
0
  opcode = static_cast<Opcode>(header[0] & 0x0F);
4947
0
  bool masked = (header[1] & 0x80) != 0;
4948
0
  uint64_t payload_len = header[1] & 0x7F;
4949
4950
  // RFC 6455 Section 5.5: control frames MUST NOT be fragmented and
4951
  // MUST have a payload length of 125 bytes or less
4952
0
  bool is_control = (static_cast<uint8_t>(opcode) & 0x08) != 0;
4953
0
  if (is_control) {
4954
0
    if (!fin) { return false; }
4955
0
    if (payload_len > 125) { return false; }
4956
0
  }
4957
4958
0
  if (masked != expect_masked) { return false; }
4959
4960
  // Extended payload length
4961
0
  if (payload_len == 126) {
4962
0
    uint8_t ext[2];
4963
0
    if (strm.read(reinterpret_cast<char *>(ext), 2) != 2) { return false; }
4964
0
    payload_len = (static_cast<uint64_t>(ext[0]) << 8) | ext[1];
4965
0
  } else if (payload_len == 127) {
4966
0
    uint8_t ext[8];
4967
0
    if (strm.read(reinterpret_cast<char *>(ext), 8) != 8) { return false; }
4968
    // RFC 6455 Section 5.2: the most significant bit MUST be 0
4969
0
    if (ext[0] & 0x80) { return false; }
4970
0
    payload_len = 0;
4971
0
    for (int i = 0; i < 8; i++) {
4972
0
      payload_len = (payload_len << 8) | ext[i];
4973
0
    }
4974
0
  }
4975
4976
0
  if (payload_len > max_len) { return false; }
4977
4978
  // Read mask key if present
4979
0
  uint8_t mask_key[4] = {0};
4980
0
  if (masked) {
4981
0
    if (strm.read(reinterpret_cast<char *>(mask_key), 4) != 4) { return false; }
4982
0
  }
4983
4984
  // Read payload
4985
0
  payload.resize(static_cast<size_t>(payload_len));
4986
0
  if (payload_len > 0) {
4987
0
    size_t total_read = 0;
4988
0
    while (total_read < payload_len) {
4989
0
      auto n = strm.read(&payload[total_read],
4990
0
                         static_cast<size_t>(payload_len - total_read));
4991
0
      if (n <= 0) { return false; }
4992
0
      total_read += static_cast<size_t>(n);
4993
0
    }
4994
0
  }
4995
4996
  // Unmask if needed
4997
0
  if (masked) {
4998
0
    for (size_t i = 0; i < payload.size(); i++) {
4999
0
      payload[i] ^= static_cast<char>(mask_key[i % 4]);
5000
0
    }
5001
0
  }
5002
5003
0
  return true;
5004
0
}
5005
5006
} // namespace impl
5007
} // namespace ws
5008
5009
namespace detail {
5010
5011
0
inline bool is_valid_path(const std::string &path) {
5012
0
  size_t level = 0;
5013
0
  size_t i = 0;
5014
5015
  // Skip slash
5016
0
  while (i < path.size() && path[i] == '/') {
5017
0
    i++;
5018
0
  }
5019
5020
0
  while (i < path.size()) {
5021
    // Read component
5022
0
    auto beg = i;
5023
0
    while (i < path.size() && path[i] != '/') {
5024
0
      if (path[i] == '\0') {
5025
0
        return false;
5026
0
      } else if (path[i] == '\\') {
5027
0
        return false;
5028
0
      }
5029
0
      i++;
5030
0
    }
5031
5032
0
    auto len = i - beg;
5033
0
    assert(len > 0);
5034
5035
0
    if (!path.compare(beg, len, ".")) {
5036
0
      ;
5037
0
    } else if (!path.compare(beg, len, "..")) {
5038
0
      if (level == 0) { return false; }
5039
0
      level--;
5040
0
    } else {
5041
0
      level++;
5042
0
    }
5043
5044
    // Skip slash
5045
0
    while (i < path.size() && path[i] == '/') {
5046
0
      i++;
5047
0
    }
5048
0
  }
5049
5050
0
  return true;
5051
0
}
5052
5053
0
inline bool canonicalize_path(const char *path, std::string &resolved) {
5054
#if defined(_WIN32)
5055
  char buf[_MAX_PATH];
5056
  if (_fullpath(buf, path, _MAX_PATH) == nullptr) { return false; }
5057
  resolved = buf;
5058
#elif defined(PATH_MAX)
5059
  char buf[PATH_MAX];
5060
0
  if (realpath(path, buf) == nullptr) { return false; }
5061
0
  resolved = buf;
5062
#else
5063
  auto buf = realpath(path, nullptr);
5064
  auto guard = scope_exit([&]() { std::free(buf); });
5065
  if (buf == nullptr) { return false; }
5066
  resolved = buf;
5067
#endif
5068
0
  return true;
5069
0
}
5070
5071
inline bool is_path_within_base(const std::string &resolved_path,
5072
0
                                const std::string &resolved_base) {
5073
#if defined(_WIN32)
5074
  return _strnicmp(resolved_path.c_str(), resolved_base.c_str(),
5075
                   resolved_base.size()) == 0;
5076
#else
5077
0
  return strncmp(resolved_path.c_str(), resolved_base.c_str(),
5078
0
                 resolved_base.size()) == 0;
5079
0
#endif
5080
0
}
5081
5082
0
inline FileStat::FileStat(const std::string &path) {
5083
#if defined(_WIN32)
5084
  auto wpath = u8string_to_wstring(path.c_str());
5085
  ret_ = _wstat(wpath.c_str(), &st_);
5086
#else
5087
0
  ret_ = stat(path.c_str(), &st_);
5088
0
#endif
5089
0
}
5090
0
inline bool FileStat::is_file() const {
5091
0
  return ret_ >= 0 && S_ISREG(st_.st_mode);
5092
0
}
5093
0
inline bool FileStat::is_dir() const {
5094
0
  return ret_ >= 0 && S_ISDIR(st_.st_mode);
5095
0
}
5096
5097
0
inline time_t FileStat::mtime() const {
5098
0
  return ret_ >= 0 ? static_cast<time_t>(st_.st_mtime)
5099
0
                   : static_cast<time_t>(-1);
5100
0
}
5101
5102
0
inline size_t FileStat::size() const {
5103
0
  return ret_ >= 0 ? static_cast<size_t>(st_.st_size) : 0;
5104
0
}
5105
5106
0
inline std::string encode_path(const std::string &s) {
5107
0
  std::string result;
5108
0
  result.reserve(s.size());
5109
0
5110
0
  for (size_t i = 0; s[i]; i++) {
5111
0
    switch (s[i]) {
5112
0
    case ' ': result += "%20"; break;
5113
0
    case '+': result += "%2B"; break;
5114
0
    case '\r': result += "%0D"; break;
5115
0
    case '\n': result += "%0A"; break;
5116
0
    case '\'': result += "%27"; break;
5117
0
    case ',': result += "%2C"; break;
5118
0
    // case ':': result += "%3A"; break; // ok? probably...
5119
0
    case ';': result += "%3B"; break;
5120
0
    default:
5121
0
      auto c = static_cast<uint8_t>(s[i]);
5122
0
      if (c >= 0x80) {
5123
0
        result += '%';
5124
0
        char hex[4];
5125
0
        auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
5126
0
        assert(len == 2);
5127
0
        result.append(hex, static_cast<size_t>(len));
5128
0
      } else {
5129
0
        result += s[i];
5130
0
      }
5131
0
      break;
5132
0
    }
5133
0
  }
5134
0
5135
0
  return result;
5136
0
}
5137
5138
0
inline std::string file_extension(const std::string &path) {
5139
0
  std::smatch m;
5140
0
  thread_local auto re = std::regex("\\.([a-zA-Z0-9]+)$");
5141
0
  if (std::regex_search(path, m, re)) { return m[1].str(); }
5142
0
  return std::string();
5143
0
}
5144
5145
696M
inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; }
5146
5147
template <typename T>
5148
inline bool parse_header(const char *beg, const char *end, T fn);
5149
5150
template <typename T>
5151
155k
inline bool parse_header(const char *beg, const char *end, T fn) {
5152
  // Skip trailing spaces and tabs.
5153
158k
  while (beg < end && is_space_or_tab(end[-1])) {
5154
2.47k
    end--;
5155
2.47k
  }
5156
5157
155k
  auto p = beg;
5158
1.66M
  while (p < end && *p != ':') {
5159
1.50M
    p++;
5160
1.50M
  }
5161
5162
155k
  auto name = std::string(beg, p);
5163
155k
  if (!detail::fields::is_field_name(name)) { return false; }
5164
5165
155k
  if (p == end) { return false; }
5166
5167
155k
  auto key_end = p;
5168
5169
155k
  if (*p++ != ':') { return false; }
5170
5171
157k
  while (p < end && is_space_or_tab(*p)) {
5172
2.45k
    p++;
5173
2.45k
  }
5174
5175
155k
  if (p <= end) {
5176
155k
    auto key_len = key_end - beg;
5177
155k
    if (!key_len) { return false; }
5178
5179
155k
    auto key = std::string(beg, key_end);
5180
155k
    auto val = std::string(p, end);
5181
5182
155k
    if (!detail::fields::is_field_value(val)) { return false; }
5183
5184
    // RFC 9110 §5.5: header field values are opaque octets and MUST NOT be
5185
    // percent-decoded by the recipient. Applications that need to interpret a
5186
    // value as a URI component should call httplib::decode_uri_component()
5187
    // (or decode_path_component()) explicitly.
5188
155k
    fn(key, val);
5189
5190
155k
    return true;
5191
155k
  }
5192
5193
0
  return false;
5194
155k
}
bool httplib::detail::parse_header<httplib::detail::read_headers(httplib::Stream&, std::__1::unordered_multimap<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, httplib::detail::case_ignore::hash, httplib::detail::case_ignore::equal_to, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > >&)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#1}>(char const*, char const, httplib::detail::read_headers(httplib::Stream&, std::__1::unordered_multimap<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, httplib::detail::case_ignore::hash, httplib::detail::case_ignore::equal_to, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > >&)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#1})
Line
Count
Source
5151
13.9k
inline bool parse_header(const char *beg, const char *end, T fn) {
5152
  // Skip trailing spaces and tabs.
5153
14.1k
  while (beg < end && is_space_or_tab(end[-1])) {
5154
213
    end--;
5155
213
  }
5156
5157
13.9k
  auto p = beg;
5158
313k
  while (p < end && *p != ':') {
5159
299k
    p++;
5160
299k
  }
5161
5162
13.9k
  auto name = std::string(beg, p);
5163
13.9k
  if (!detail::fields::is_field_name(name)) { return false; }
5164
5165
13.8k
  if (p == end) { return false; }
5166
5167
13.7k
  auto key_end = p;
5168
5169
13.7k
  if (*p++ != ':') { return false; }
5170
5171
14.1k
  while (p < end && is_space_or_tab(*p)) {
5172
377
    p++;
5173
377
  }
5174
5175
13.7k
  if (p <= end) {
5176
13.7k
    auto key_len = key_end - beg;
5177
13.7k
    if (!key_len) { return false; }
5178
5179
13.7k
    auto key = std::string(beg, key_end);
5180
13.7k
    auto val = std::string(p, end);
5181
5182
13.7k
    if (!detail::fields::is_field_value(val)) { return false; }
5183
5184
    // RFC 9110 §5.5: header field values are opaque octets and MUST NOT be
5185
    // percent-decoded by the recipient. Applications that need to interpret a
5186
    // value as a URI component should call httplib::decode_uri_component()
5187
    // (or decode_path_component()) explicitly.
5188
13.7k
    fn(key, val);
5189
5190
13.7k
    return true;
5191
13.7k
  }
5192
5193
0
  return false;
5194
13.7k
}
bool httplib::detail::parse_header<httplib::detail::FormDataParser::parse(char const*, unsigned long, std::__1::function<bool (httplib::FormData const&)> const&, std::__1::function<bool (char const*, unsigned long)> const&)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#1}>(char const*, char const*, httplib::detail::FormDataParser::parse(char const*, unsigned long, std::__1::function<bool (httplib::FormData const&)> const&, std::__1::function<bool (char const*, unsigned long)> const&)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#1})
Line
Count
Source
5151
70.1k
inline bool parse_header(const char *beg, const char *end, T fn) {
5152
  // Skip trailing spaces and tabs.
5153
71.1k
  while (beg < end && is_space_or_tab(end[-1])) {
5154
1.01k
    end--;
5155
1.01k
  }
5156
5157
70.1k
  auto p = beg;
5158
637k
  while (p < end && *p != ':') {
5159
567k
    p++;
5160
567k
  }
5161
5162
70.1k
  auto name = std::string(beg, p);
5163
70.1k
  if (!detail::fields::is_field_name(name)) { return false; }
5164
5165
70.0k
  if (p == end) { return false; }
5166
5167
70.0k
  auto key_end = p;
5168
5169
70.0k
  if (*p++ != ':') { return false; }
5170
5171
70.9k
  while (p < end && is_space_or_tab(*p)) {
5172
903
    p++;
5173
903
  }
5174
5175
70.0k
  if (p <= end) {
5176
70.0k
    auto key_len = key_end - beg;
5177
70.0k
    if (!key_len) { return false; }
5178
5179
70.0k
    auto key = std::string(beg, key_end);
5180
70.0k
    auto val = std::string(p, end);
5181
5182
70.0k
    if (!detail::fields::is_field_value(val)) { return false; }
5183
5184
    // RFC 9110 §5.5: header field values are opaque octets and MUST NOT be
5185
    // percent-decoded by the recipient. Applications that need to interpret a
5186
    // value as a URI component should call httplib::decode_uri_component()
5187
    // (or decode_path_component()) explicitly.
5188
70.0k
    fn(key, val);
5189
5190
70.0k
    return true;
5191
70.0k
  }
5192
5193
0
  return false;
5194
70.0k
}
bool httplib::detail::parse_header<httplib::detail::FormDataParser::parse(char const*, unsigned long, std::__1::function<bool (httplib::FormData const&)> const&, std::__1::function<bool (char const*, unsigned long)> const&)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#2}>(char const*, char const*, httplib::detail::FormDataParser::parse(char const*, unsigned long, std::__1::function<bool (httplib::FormData const&)> const&, std::__1::function<bool (char const*, unsigned long)> const&)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#2})
Line
Count
Source
5151
70.0k
inline bool parse_header(const char *beg, const char *end, T fn) {
5152
  // Skip trailing spaces and tabs.
5153
71.0k
  while (beg < end && is_space_or_tab(end[-1])) {
5154
1.00k
    end--;
5155
1.00k
  }
5156
5157
70.0k
  auto p = beg;
5158
636k
  while (p < end && *p != ':') {
5159
566k
    p++;
5160
566k
  }
5161
5162
70.0k
  auto name = std::string(beg, p);
5163
70.0k
  if (!detail::fields::is_field_name(name)) { return false; }
5164
5165
70.0k
  if (p == end) { return false; }
5166
5167
70.0k
  auto key_end = p;
5168
5169
70.0k
  if (*p++ != ':') { return false; }
5170
5171
70.9k
  while (p < end && is_space_or_tab(*p)) {
5172
903
    p++;
5173
903
  }
5174
5175
70.0k
  if (p <= end) {
5176
70.0k
    auto key_len = key_end - beg;
5177
70.0k
    if (!key_len) { return false; }
5178
5179
70.0k
    auto key = std::string(beg, key_end);
5180
70.0k
    auto val = std::string(p, end);
5181
5182
70.0k
    if (!detail::fields::is_field_value(val)) { return false; }
5183
5184
    // RFC 9110 §5.5: header field values are opaque octets and MUST NOT be
5185
    // percent-decoded by the recipient. Applications that need to interpret a
5186
    // value as a URI component should call httplib::decode_uri_component()
5187
    // (or decode_path_component()) explicitly.
5188
70.0k
    fn(key, val);
5189
5190
70.0k
    return true;
5191
70.0k
  }
5192
5193
0
  return false;
5194
70.0k
}
bool httplib::detail::parse_header<httplib::detail::parse_trailers(httplib::detail::stream_line_reader&, std::__1::unordered_multimap<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, httplib::detail::case_ignore::hash, httplib::detail::case_ignore::equal_to, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > >&, std::__1::unordered_multimap<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, httplib::detail::case_ignore::hash, httplib::detail::case_ignore::equal_to, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > > const&)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#1}>(char const*, char const, httplib::detail::parse_trailers(httplib::detail::stream_line_reader&, std::__1::unordered_multimap<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, httplib::detail::case_ignore::hash, httplib::detail::case_ignore::equal_to, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > >&, std::__1::unordered_multimap<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, httplib::detail::case_ignore::hash, httplib::detail::case_ignore::equal_to, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > > const&)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#1})
Line
Count
Source
5151
1.88k
inline bool parse_header(const char *beg, const char *end, T fn) {
5152
  // Skip trailing spaces and tabs.
5153
2.11k
  while (beg < end && is_space_or_tab(end[-1])) {
5154
238
    end--;
5155
238
  }
5156
5157
1.88k
  auto p = beg;
5158
74.2k
  while (p < end && *p != ':') {
5159
72.4k
    p++;
5160
72.4k
  }
5161
5162
1.88k
  auto name = std::string(beg, p);
5163
1.88k
  if (!detail::fields::is_field_name(name)) { return false; }
5164
5165
1.48k
  if (p == end) { return false; }
5166
5167
1.43k
  auto key_end = p;
5168
5169
1.43k
  if (*p++ != ':') { return false; }
5170
5171
1.71k
  while (p < end && is_space_or_tab(*p)) {
5172
276
    p++;
5173
276
  }
5174
5175
1.43k
  if (p <= end) {
5176
1.43k
    auto key_len = key_end - beg;
5177
1.43k
    if (!key_len) { return false; }
5178
5179
1.43k
    auto key = std::string(beg, key_end);
5180
1.43k
    auto val = std::string(p, end);
5181
5182
1.43k
    if (!detail::fields::is_field_value(val)) { return false; }
5183
5184
    // RFC 9110 §5.5: header field values are opaque octets and MUST NOT be
5185
    // percent-decoded by the recipient. Applications that need to interpret a
5186
    // value as a URI component should call httplib::decode_uri_component()
5187
    // (or decode_path_component()) explicitly.
5188
1.41k
    fn(key, val);
5189
5190
1.41k
    return true;
5191
1.43k
  }
5192
5193
0
  return false;
5194
1.43k
}
5195
5196
inline bool parse_trailers(stream_line_reader &line_reader, Headers &dest,
5197
543
                           const Headers &src_headers) {
5198
  // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentions "The chunked
5199
  // transfer coding is complete when a chunk with a chunk-size of zero is
5200
  // received, possibly followed by a trailer section, and finally terminated by
5201
  // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1
5202
  //
5203
  // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section
5204
  // doesn't care for the existence of the final CRLF. In other words, it seems
5205
  // to be ok whether the final CRLF exists or not in the chunked data.
5206
  // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3
5207
  //
5208
  // According to the reference code in RFC 9112, cpp-httplib now allows
5209
  // chunked transfer coding data without the final CRLF.
5210
5211
  // RFC 7230 Section 4.1.2 - Headers prohibited in trailers
5212
543
  thread_local case_ignore::unordered_set<std::string> prohibited_trailers = {
5213
543
      "transfer-encoding",
5214
543
      "content-length",
5215
543
      "host",
5216
543
      "authorization",
5217
543
      "www-authenticate",
5218
543
      "proxy-authenticate",
5219
543
      "proxy-authorization",
5220
543
      "cookie",
5221
543
      "set-cookie",
5222
543
      "cache-control",
5223
543
      "expect",
5224
543
      "max-forwards",
5225
543
      "pragma",
5226
543
      "range",
5227
543
      "te",
5228
543
      "age",
5229
543
      "expires",
5230
543
      "date",
5231
543
      "location",
5232
543
      "retry-after",
5233
543
      "vary",
5234
543
      "warning",
5235
543
      "content-encoding",
5236
543
      "content-type",
5237
543
      "content-range",
5238
543
      "trailer"};
5239
5240
543
  case_ignore::unordered_set<std::string> declared_trailers;
5241
543
  auto trailer_header = get_header_value(src_headers, "Trailer", "", 0);
5242
543
  if (trailer_header && std::strlen(trailer_header)) {
5243
415
    auto len = std::strlen(trailer_header);
5244
415
    split(trailer_header, trailer_header + len, ',',
5245
11.1k
          [&](const char *b, const char *e) {
5246
11.1k
            const char *kbeg = b;
5247
11.1k
            const char *kend = e;
5248
11.1k
            while (kbeg < kend && (*kbeg == ' ' || *kbeg == '\t')) {
5249
0
              ++kbeg;
5250
0
            }
5251
11.1k
            while (kend > kbeg && (kend[-1] == ' ' || kend[-1] == '\t')) {
5252
0
              --kend;
5253
0
            }
5254
11.1k
            std::string key(kbeg, static_cast<size_t>(kend - kbeg));
5255
11.1k
            if (!key.empty() &&
5256
11.1k
                prohibited_trailers.find(key) == prohibited_trailers.end()) {
5257
10.5k
              declared_trailers.insert(key);
5258
10.5k
            }
5259
11.1k
          });
5260
415
  }
5261
5262
543
  size_t trailer_header_count = 0;
5263
1.89k
  while (strcmp(line_reader.ptr(), "\r\n") != 0) {
5264
1.88k
    if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
5265
1.88k
    if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
5266
5267
1.88k
    constexpr auto line_terminator_len = 2;
5268
1.88k
    auto line_beg = line_reader.ptr();
5269
1.88k
    auto line_end =
5270
1.88k
        line_reader.ptr() + line_reader.size() - line_terminator_len;
5271
5272
1.88k
    if (!parse_header(line_beg, line_end,
5273
1.88k
                      [&](const std::string &key, const std::string &val) {
5274
1.41k
                        if (declared_trailers.find(key) !=
5275
1.41k
                            declared_trailers.end()) {
5276
845
                          dest.emplace(key, val);
5277
845
                          trailer_header_count++;
5278
845
                        }
5279
1.41k
                      })) {
5280
466
      return false;
5281
466
    }
5282
5283
1.41k
    if (!line_reader.getline()) { return false; }
5284
1.41k
  }
5285
5286
7
  return true;
5287
543
}
5288
5289
inline std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left,
5290
1.19M
                                      size_t right) {
5291
347M
  while (b + left < e && is_space_or_tab(b[left])) {
5292
346M
    left++;
5293
346M
  }
5294
348M
  while (right > 0 && is_space_or_tab(b[right - 1])) {
5295
346M
    right--;
5296
346M
  }
5297
1.19M
  return std::make_pair(left, right);
5298
1.19M
}
5299
5300
452k
inline std::string trim_copy(const std::string &s) {
5301
452k
  auto r = trim(s.data(), s.data() + s.size(), 0, s.size());
5302
452k
  return s.substr(r.first, r.second - r.first);
5303
452k
}
5304
5305
65.7k
inline std::string trim_double_quotes_copy(const std::string &s) {
5306
65.7k
  if (s.length() >= 2 && s.front() == '"' && s.back() == '"') {
5307
2.75k
    return s.substr(1, s.size() - 2);
5308
2.75k
  }
5309
63.0k
  return s;
5310
65.7k
}
5311
5312
inline void
5313
divide(const char *data, std::size_t size, char d,
5314
       std::function<void(const char *, std::size_t, const char *, std::size_t)>
5315
235k
           fn) {
5316
235k
  const auto it = std::find(data, data + size, d);
5317
235k
  const auto found = static_cast<std::size_t>(it != data + size);
5318
235k
  const auto lhs_data = data;
5319
235k
  const auto lhs_size = static_cast<std::size_t>(it - data);
5320
235k
  const auto rhs_data = it + found;
5321
235k
  const auto rhs_size = size - lhs_size - found;
5322
5323
235k
  fn(lhs_data, lhs_size, rhs_data, rhs_size);
5324
235k
}
5325
5326
inline void
5327
divide(const std::string &str, char d,
5328
       std::function<void(const char *, std::size_t, const char *, std::size_t)>
5329
6.44k
           fn) {
5330
6.44k
  divide(str.data(), str.size(), d, std::move(fn));
5331
6.44k
}
5332
5333
inline void split(const char *b, const char *e, char d,
5334
60.7k
                  std::function<void(const char *, const char *)> fn) {
5335
60.7k
  return split(b, e, d, (std::numeric_limits<size_t>::max)(), std::move(fn));
5336
60.7k
}
5337
5338
inline void split(const char *b, const char *e, char d, size_t m,
5339
60.7k
                  std::function<void(const char *, const char *)> fn) {
5340
60.7k
  size_t i = 0;
5341
60.7k
  size_t beg = 0;
5342
60.7k
  size_t count = 1;
5343
5344
4.55M
  while (e ? (b + i < e) : (b[i] != '\0')) {
5345
4.49M
    if (b[i] == d && count < m) {
5346
435k
      auto r = trim(b, e, beg, i);
5347
435k
      if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
5348
435k
      beg = i + 1;
5349
435k
      count++;
5350
435k
    }
5351
4.49M
    i++;
5352
4.49M
  }
5353
5354
60.7k
  if (i) {
5355
54.6k
    auto r = trim(b, e, beg, i);
5356
54.6k
    if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
5357
54.6k
  }
5358
60.7k
}
5359
5360
inline bool split_find(const char *b, const char *e, char d, size_t m,
5361
6.98k
                       std::function<bool(const char *, const char *)> fn) {
5362
6.98k
  size_t i = 0;
5363
6.98k
  size_t beg = 0;
5364
6.98k
  size_t count = 1;
5365
5366
80.8k
  while (e ? (b + i < e) : (b[i] != '\0')) {
5367
74.1k
    if (b[i] == d && count < m) {
5368
13.8k
      auto r = trim(b, e, beg, i);
5369
13.8k
      if (r.first < r.second) {
5370
3.77k
        auto found = fn(&b[r.first], &b[r.second]);
5371
3.77k
        if (found) { return true; }
5372
3.77k
      }
5373
13.5k
      beg = i + 1;
5374
13.5k
      count++;
5375
13.5k
    }
5376
73.8k
    i++;
5377
73.8k
  }
5378
5379
6.68k
  if (i) {
5380
6.68k
    auto r = trim(b, e, beg, i);
5381
6.68k
    if (r.first < r.second) {
5382
6.43k
      auto found = fn(&b[r.first], &b[r.second]);
5383
6.43k
      if (found) { return true; }
5384
6.43k
    }
5385
6.68k
  }
5386
5387
992
  return false;
5388
6.68k
}
5389
5390
inline bool split_find(const char *b, const char *e, char d,
5391
0
                       std::function<bool(const char *, const char *)> fn) {
5392
0
  return split_find(b, e, d, (std::numeric_limits<size_t>::max)(),
5393
0
                    std::move(fn));
5394
0
}
5395
5396
inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer,
5397
                                              size_t fixed_buffer_size)
5398
118k
    : strm_(strm), fixed_buffer_(fixed_buffer),
5399
118k
      fixed_buffer_size_(fixed_buffer_size) {}
5400
5401
163k
inline const char *stream_line_reader::ptr() const {
5402
163k
  if (growable_buffer_.empty()) {
5403
158k
    return fixed_buffer_;
5404
158k
  } else {
5405
5.23k
    return growable_buffer_.data();
5406
5.23k
  }
5407
163k
}
5408
5409
4.51M
inline size_t stream_line_reader::size() const {
5410
4.51M
  if (growable_buffer_.empty()) {
5411
2.14M
    return fixed_buffer_used_size_;
5412
2.37M
  } else {
5413
2.37M
    return growable_buffer_.size();
5414
2.37M
  }
5415
4.51M
}
5416
5417
19.2k
inline bool stream_line_reader::end_with_crlf() const {
5418
19.2k
  auto end = ptr() + size();
5419
19.2k
  return size() >= 2 && end[-2] == '\r' && end[-1] == '\n';
5420
19.2k
}
5421
5422
134k
inline bool stream_line_reader::getline() {
5423
134k
  fixed_buffer_used_size_ = 0;
5424
134k
  growable_buffer_.clear();
5425
5426
134k
#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
5427
134k
  char prev_byte = 0;
5428
134k
#endif
5429
5430
4.42M
  for (size_t i = 0;; i++) {
5431
4.42M
    if (size() >= CPPHTTPLIB_MAX_LINE_LENGTH) {
5432
      // Treat exceptionally long lines as an error to
5433
      // prevent infinite loops/memory exhaustion
5434
4
      return false;
5435
4
    }
5436
4.42M
    char byte;
5437
4.42M
    auto n = strm_.read(&byte, 1);
5438
5439
4.42M
    if (n < 0) {
5440
0
      return false;
5441
4.42M
    } else if (n == 0) {
5442
2.89k
      if (i == 0) {
5443
1.99k
        return false;
5444
1.99k
      } else {
5445
900
        break;
5446
900
      }
5447
2.89k
    }
5448
5449
4.42M
    append(byte);
5450
5451
#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
5452
    if (byte == '\n') { break; }
5453
#else
5454
4.42M
    if (prev_byte == '\r' && byte == '\n') { break; }
5455
4.29M
    prev_byte = byte;
5456
4.29M
#endif
5457
4.29M
  }
5458
5459
132k
  return true;
5460
134k
}
5461
5462
4.42M
inline void stream_line_reader::append(char c) {
5463
4.42M
  if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) {
5464
2.04M
    fixed_buffer_[fixed_buffer_used_size_++] = c;
5465
2.04M
    fixed_buffer_[fixed_buffer_used_size_] = '\0';
5466
2.38M
  } else {
5467
2.38M
    if (growable_buffer_.empty()) {
5468
4.73k
      assert(fixed_buffer_[fixed_buffer_used_size_] == '\0');
5469
4.73k
      growable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_);
5470
4.73k
    }
5471
2.38M
    growable_buffer_ += c;
5472
2.38M
  }
5473
4.42M
}
5474
5475
0
inline mmap::mmap(const char *path) { open(path); }
5476
5477
0
inline mmap::~mmap() { close(); }
5478
5479
0
inline bool mmap::open(const char *path) {
5480
0
  close();
5481
5482
#if defined(_WIN32)
5483
  auto wpath = u8string_to_wstring(path);
5484
  if (wpath.empty()) { return false; }
5485
5486
  hFile_ =
5487
      ::CreateFile2(wpath.c_str(), GENERIC_READ,
5488
                    FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, NULL);
5489
5490
  if (hFile_ == INVALID_HANDLE_VALUE) { return false; }
5491
5492
  LARGE_INTEGER size{};
5493
  if (!::GetFileSizeEx(hFile_, &size)) { return false; }
5494
  // If the following line doesn't compile due to QuadPart, update Windows SDK.
5495
  // See:
5496
  // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721
5497
  if (static_cast<ULONGLONG>(size.QuadPart) >
5498
      (std::numeric_limits<decltype(size_)>::max)()) {
5499
    // `size_t` might be 32-bits, on 32-bits Windows.
5500
    return false;
5501
  }
5502
  size_ = static_cast<size_t>(size.QuadPart);
5503
5504
  hMapping_ =
5505
      ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL);
5506
5507
  // Special treatment for an empty file...
5508
  if (hMapping_ == NULL && size_ == 0) {
5509
    close();
5510
    is_open_empty_file = true;
5511
    return true;
5512
  }
5513
5514
  if (hMapping_ == NULL) {
5515
    close();
5516
    return false;
5517
  }
5518
5519
  addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0);
5520
5521
  if (addr_ == nullptr) {
5522
    close();
5523
    return false;
5524
  }
5525
#else
5526
0
  fd_ = ::open(path, O_RDONLY);
5527
0
  if (fd_ == -1) { return false; }
5528
5529
0
  struct stat sb;
5530
0
  if (fstat(fd_, &sb) == -1) {
5531
0
    close();
5532
0
    return false;
5533
0
  }
5534
0
  size_ = static_cast<size_t>(sb.st_size);
5535
5536
0
  addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0);
5537
5538
  // Special treatment for an empty file...
5539
0
  if (addr_ == MAP_FAILED && size_ == 0) {
5540
0
    close();
5541
0
    is_open_empty_file = true;
5542
0
    return false;
5543
0
  }
5544
0
#endif
5545
5546
0
  return true;
5547
0
}
5548
5549
0
inline bool mmap::is_open() const {
5550
0
  return is_open_empty_file ? true : addr_ != nullptr;
5551
0
}
5552
5553
0
inline size_t mmap::size() const { return size_; }
5554
5555
0
inline const char *mmap::data() const {
5556
0
  return is_open_empty_file ? "" : static_cast<const char *>(addr_);
5557
0
}
5558
5559
0
inline void mmap::close() {
5560
#if defined(_WIN32)
5561
  if (addr_) {
5562
    ::UnmapViewOfFile(addr_);
5563
    addr_ = nullptr;
5564
  }
5565
5566
  if (hMapping_) {
5567
    ::CloseHandle(hMapping_);
5568
    hMapping_ = NULL;
5569
  }
5570
5571
  if (hFile_ != INVALID_HANDLE_VALUE) {
5572
    ::CloseHandle(hFile_);
5573
    hFile_ = INVALID_HANDLE_VALUE;
5574
  }
5575
5576
  is_open_empty_file = false;
5577
#else
5578
0
  if (addr_ != nullptr) {
5579
0
    munmap(addr_, size_);
5580
0
    addr_ = nullptr;
5581
0
  }
5582
5583
0
  if (fd_ != -1) {
5584
0
    ::close(fd_);
5585
0
    fd_ = -1;
5586
0
  }
5587
0
#endif
5588
0
  size_ = 0;
5589
0
}
5590
0
inline int close_socket(socket_t sock) noexcept {
5591
#ifdef _WIN32
5592
  return closesocket(sock);
5593
#else
5594
0
  return close(sock);
5595
0
#endif
5596
0
}
5597
5598
0
template <typename T> inline ssize_t handle_EINTR(T fn) {
5599
0
  ssize_t res = 0;
5600
0
  while (true) {
5601
0
    res = fn();
5602
0
    if (res < 0 && errno == EINTR) {
5603
0
      std::this_thread::sleep_for(std::chrono::microseconds{1});
5604
0
      continue;
5605
0
    }
5606
0
    break;
5607
0
  }
5608
0
  return res;
5609
0
}
Unexecuted instantiation: long httplib::detail::handle_EINTR<httplib::detail::select_impl(int, short, long, long)::{lambda()#1}>(httplib::detail::select_impl(int, short, long, long)::{lambda()#1})
Unexecuted instantiation: long httplib::detail::handle_EINTR<httplib::detail::read_socket(int, void*, unsigned long, int)::{lambda()#1}>(httplib::detail::read_socket(int, void*, unsigned long, int)::{lambda()#1})
Unexecuted instantiation: long httplib::detail::handle_EINTR<httplib::detail::send_socket(int, void const*, unsigned long, int)::{lambda()#1}>(httplib::detail::send_socket(int, void const*, unsigned long, int)::{lambda()#1})
Unexecuted instantiation: long httplib::detail::handle_EINTR<httplib::detail::wait_until_socket_is_ready(int, long, long)::{lambda()#1}>(httplib::detail::wait_until_socket_is_ready(int, long, long)::{lambda()#1})
5610
5611
0
inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) {
5612
0
  return handle_EINTR([&]() {
5613
0
    return recv(sock,
5614
#ifdef _WIN32
5615
                static_cast<char *>(ptr), static_cast<int>(size),
5616
#else
5617
0
                ptr, size,
5618
0
#endif
5619
0
                flags);
5620
0
  });
5621
0
}
5622
5623
inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size,
5624
0
                           int flags) {
5625
0
  return handle_EINTR([&]() {
5626
0
    return send(sock,
5627
#ifdef _WIN32
5628
                static_cast<const char *>(ptr), static_cast<int>(size),
5629
#else
5630
0
                ptr, size,
5631
0
#endif
5632
0
                flags);
5633
0
  });
5634
0
}
5635
5636
0
inline int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout) {
5637
#ifdef _WIN32
5638
  return ::WSAPoll(fds, nfds, timeout);
5639
#else
5640
0
  return ::poll(fds, nfds, timeout);
5641
0
#endif
5642
0
}
5643
5644
inline ssize_t select_impl(socket_t sock, short events, time_t sec,
5645
0
                           time_t usec) {
5646
0
  struct pollfd pfd;
5647
0
  pfd.fd = sock;
5648
0
  pfd.events = events;
5649
0
  pfd.revents = 0;
5650
5651
0
  auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
5652
5653
0
  return handle_EINTR([&]() { return poll_wrapper(&pfd, 1, timeout); });
5654
0
}
5655
5656
0
inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
5657
0
  return select_impl(sock, POLLIN, sec, usec);
5658
0
}
5659
5660
0
inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
5661
0
  return select_impl(sock, POLLOUT, sec, usec);
5662
0
}
5663
5664
inline Error wait_until_socket_is_ready(socket_t sock, time_t sec,
5665
0
                                        time_t usec) {
5666
0
  struct pollfd pfd_read;
5667
0
  pfd_read.fd = sock;
5668
0
  pfd_read.events = POLLIN | POLLOUT;
5669
0
  pfd_read.revents = 0;
5670
5671
0
  auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
5672
5673
0
  auto poll_res =
5674
0
      handle_EINTR([&]() { return poll_wrapper(&pfd_read, 1, timeout); });
5675
5676
0
  if (poll_res == 0) { return Error::ConnectionTimeout; }
5677
5678
0
  if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
5679
0
    auto error = 0;
5680
0
    socklen_t len = sizeof(error);
5681
0
    auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
5682
0
                          reinterpret_cast<char *>(&error), &len);
5683
0
    auto successful = res >= 0 && !error;
5684
0
    return successful ? Error::Success : Error::Connection;
5685
0
  }
5686
5687
0
  return Error::Connection;
5688
0
}
5689
5690
0
inline bool is_socket_alive(socket_t sock) {
5691
0
  const auto val = detail::select_read(sock, 0, 0);
5692
0
  if (val == 0) {
5693
0
    return true;
5694
0
  } else if (val < 0 && errno == EBADF) {
5695
0
    return false;
5696
0
  }
5697
0
  char buf[1];
5698
0
  return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0;
5699
0
}
5700
5701
class SocketStream final : public Stream {
5702
public:
5703
  SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
5704
               time_t write_timeout_sec, time_t write_timeout_usec,
5705
               time_t max_timeout_msec = 0,
5706
               std::chrono::time_point<std::chrono::steady_clock> start_time =
5707
                   (std::chrono::steady_clock::time_point::min)());
5708
  ~SocketStream() override;
5709
5710
  bool is_readable() const override;
5711
  bool wait_readable() const override;
5712
  bool wait_writable() const override;
5713
  bool is_peer_alive() const override;
5714
  ssize_t read(char *ptr, size_t size) override;
5715
  ssize_t write(const char *ptr, size_t size) override;
5716
  void get_remote_ip_and_port(std::string &ip, int &port) const override;
5717
  void get_local_ip_and_port(std::string &ip, int &port) const override;
5718
  socket_t socket() const override;
5719
  time_t duration() const override;
5720
  void set_read_timeout(time_t sec, time_t usec = 0) override;
5721
5722
private:
5723
  socket_t sock_;
5724
  time_t read_timeout_sec_;
5725
  time_t read_timeout_usec_;
5726
  time_t write_timeout_sec_;
5727
  time_t write_timeout_usec_;
5728
  time_t max_timeout_msec_;
5729
  const std::chrono::time_point<std::chrono::steady_clock> start_time_;
5730
5731
  std::vector<char> read_buff_;
5732
  size_t read_buff_off_ = 0;
5733
  size_t read_buff_content_size_ = 0;
5734
5735
  static const size_t read_buff_size_ = 1024l * 4;
5736
};
5737
5738
inline bool keep_alive(const std::atomic<socket_t> &svr_sock, socket_t sock,
5739
0
                       time_t keep_alive_timeout_sec) {
5740
0
  using namespace std::chrono;
5741
5742
0
  const auto interval_usec =
5743
0
      CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND;
5744
5745
  // Avoid expensive `steady_clock::now()` call for the first time
5746
0
  if (select_read(sock, 0, interval_usec) > 0) { return true; }
5747
5748
0
  const auto start = steady_clock::now() - microseconds{interval_usec};
5749
0
  const auto timeout = seconds{keep_alive_timeout_sec};
5750
5751
0
  while (true) {
5752
0
    if (svr_sock == INVALID_SOCKET) {
5753
0
      break; // Server socket is closed
5754
0
    }
5755
5756
0
    auto val = select_read(sock, 0, interval_usec);
5757
0
    if (val < 0) {
5758
0
      break; // Ssocket error
5759
0
    } else if (val == 0) {
5760
0
      if (steady_clock::now() - start > timeout) {
5761
0
        break; // Timeout
5762
0
      }
5763
0
    } else {
5764
0
      return true; // Ready for read
5765
0
    }
5766
0
  }
5767
5768
0
  return false;
5769
0
}
5770
5771
template <typename T>
5772
inline bool
5773
process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
5774
                           size_t keep_alive_max_count,
5775
0
                           time_t keep_alive_timeout_sec, T callback) {
5776
0
  assert(keep_alive_max_count > 0);
5777
0
  auto ret = false;
5778
0
  auto count = keep_alive_max_count;
5779
0
  while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) {
5780
0
    auto close_connection = count == 1;
5781
0
    auto connection_closed = false;
5782
0
    ret = callback(close_connection, connection_closed);
5783
0
    if (!ret || connection_closed) { break; }
5784
0
    count--;
5785
0
  }
5786
0
  return ret;
5787
0
}
5788
5789
template <typename T>
5790
inline bool
5791
process_server_socket(const std::atomic<socket_t> &svr_sock, socket_t sock,
5792
                      size_t keep_alive_max_count,
5793
                      time_t keep_alive_timeout_sec, time_t read_timeout_sec,
5794
                      time_t read_timeout_usec, time_t write_timeout_sec,
5795
0
                      time_t write_timeout_usec, T callback) {
5796
0
  return process_server_socket_core(
5797
0
      svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
5798
0
      [&](bool close_connection, bool &connection_closed) {
5799
0
        SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
5800
0
                          write_timeout_sec, write_timeout_usec);
5801
0
        return callback(strm, close_connection, connection_closed);
5802
0
      });
5803
0
}
5804
5805
inline bool process_client_socket(
5806
    socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
5807
    time_t write_timeout_sec, time_t write_timeout_usec,
5808
    time_t max_timeout_msec,
5809
    std::chrono::time_point<std::chrono::steady_clock> start_time,
5810
0
    std::function<bool(Stream &)> callback) {
5811
0
  SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
5812
0
                    write_timeout_sec, write_timeout_usec, max_timeout_msec,
5813
0
                    start_time);
5814
0
  return callback(strm);
5815
0
}
5816
5817
0
inline int shutdown_socket(socket_t sock) noexcept {
5818
#ifdef _WIN32
5819
  return shutdown(sock, SD_BOTH);
5820
#else
5821
0
  return shutdown(sock, SHUT_RDWR);
5822
0
#endif
5823
0
}
5824
5825
0
inline std::string escape_abstract_namespace_unix_domain(const std::string &s) {
5826
0
  if (s.size() > 1 && s[0] == '\0') {
5827
0
    auto ret = s;
5828
0
    ret[0] = '@';
5829
0
    return ret;
5830
0
  }
5831
0
  return s;
5832
0
}
5833
5834
inline std::string
5835
0
unescape_abstract_namespace_unix_domain(const std::string &s) {
5836
0
  if (s.size() > 1 && s[0] == '@') {
5837
0
    auto ret = s;
5838
0
    ret[0] = '\0';
5839
0
    return ret;
5840
0
  }
5841
0
  return s;
5842
0
}
5843
5844
inline int getaddrinfo_with_timeout(const char *node, const char *service,
5845
                                    const struct addrinfo *hints,
5846
0
                                    struct addrinfo **res, time_t timeout_sec) {
5847
#ifdef CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO
5848
  if (timeout_sec <= 0) {
5849
    // No timeout specified, use standard getaddrinfo
5850
    return getaddrinfo(node, service, hints, res);
5851
  }
5852
5853
#ifdef _WIN32
5854
  // Windows-specific implementation using GetAddrInfoEx with overlapped I/O
5855
  OVERLAPPED overlapped = {};
5856
  HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
5857
  if (!event) { return EAI_FAIL; }
5858
5859
  overlapped.hEvent = event;
5860
5861
  PADDRINFOEXW result_addrinfo = nullptr;
5862
  HANDLE cancel_handle = nullptr;
5863
5864
  ADDRINFOEXW hints_ex = {};
5865
  if (hints) {
5866
    hints_ex.ai_flags = hints->ai_flags;
5867
    hints_ex.ai_family = hints->ai_family;
5868
    hints_ex.ai_socktype = hints->ai_socktype;
5869
    hints_ex.ai_protocol = hints->ai_protocol;
5870
  }
5871
5872
  auto wnode = u8string_to_wstring(node);
5873
  auto wservice = u8string_to_wstring(service);
5874
5875
  auto ret = ::GetAddrInfoExW(wnode.data(), wservice.data(), NS_DNS, nullptr,
5876
                              hints ? &hints_ex : nullptr, &result_addrinfo,
5877
                              nullptr, &overlapped, nullptr, &cancel_handle);
5878
5879
  if (ret == WSA_IO_PENDING) {
5880
    auto wait_result =
5881
        ::WaitForSingleObject(event, static_cast<DWORD>(timeout_sec * 1000));
5882
    if (wait_result == WAIT_TIMEOUT) {
5883
      if (cancel_handle) { ::GetAddrInfoExCancel(&cancel_handle); }
5884
      ::CloseHandle(event);
5885
      return EAI_AGAIN;
5886
    }
5887
5888
    DWORD bytes_returned;
5889
    if (!::GetOverlappedResult((HANDLE)INVALID_SOCKET, &overlapped,
5890
                               &bytes_returned, FALSE)) {
5891
      ::CloseHandle(event);
5892
      return ::WSAGetLastError();
5893
    }
5894
  }
5895
5896
  ::CloseHandle(event);
5897
5898
  if (ret == NO_ERROR || ret == WSA_IO_PENDING) {
5899
    *res = reinterpret_cast<struct addrinfo *>(result_addrinfo);
5900
    return 0;
5901
  }
5902
5903
  return ret;
5904
#elif TARGET_OS_MAC && defined(__clang__)
5905
  if (!node) { return EAI_NONAME; }
5906
  // macOS implementation using CFHost API for asynchronous DNS resolution
5907
  CFStringRef hostname_ref = CFStringCreateWithCString(
5908
      kCFAllocatorDefault, node, kCFStringEncodingUTF8);
5909
  if (!hostname_ref) { return EAI_MEMORY; }
5910
5911
  CFHostRef host_ref = CFHostCreateWithName(kCFAllocatorDefault, hostname_ref);
5912
  CFRelease(hostname_ref);
5913
  if (!host_ref) { return EAI_MEMORY; }
5914
5915
  // Set up context for callback
5916
  struct CFHostContext {
5917
    bool completed = false;
5918
    bool success = false;
5919
    CFArrayRef addresses = nullptr;
5920
    std::mutex mutex;
5921
    std::condition_variable cv;
5922
  } context;
5923
5924
  CFHostClientContext client_context;
5925
  memset(&client_context, 0, sizeof(client_context));
5926
  client_context.info = &context;
5927
5928
  // Set callback
5929
  auto callback = [](CFHostRef theHost, CFHostInfoType /*typeInfo*/,
5930
                     const CFStreamError *error, void *info) {
5931
    auto ctx = static_cast<CFHostContext *>(info);
5932
    std::lock_guard<std::mutex> lock(ctx->mutex);
5933
5934
    if (error && error->error != 0) {
5935
      ctx->success = false;
5936
    } else {
5937
      Boolean hasBeenResolved;
5938
      ctx->addresses = CFHostGetAddressing(theHost, &hasBeenResolved);
5939
      if (ctx->addresses && hasBeenResolved) {
5940
        CFRetain(ctx->addresses);
5941
        ctx->success = true;
5942
      } else {
5943
        ctx->success = false;
5944
      }
5945
    }
5946
    ctx->completed = true;
5947
    ctx->cv.notify_one();
5948
  };
5949
5950
  if (!CFHostSetClient(host_ref, callback, &client_context)) {
5951
    CFRelease(host_ref);
5952
    return EAI_SYSTEM;
5953
  }
5954
5955
  // Schedule on run loop
5956
  CFRunLoopRef run_loop = CFRunLoopGetCurrent();
5957
  CFHostScheduleWithRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
5958
5959
  // Start resolution
5960
  CFStreamError stream_error;
5961
  if (!CFHostStartInfoResolution(host_ref, kCFHostAddresses, &stream_error)) {
5962
    CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
5963
    CFRelease(host_ref);
5964
    return EAI_FAIL;
5965
  }
5966
5967
  // Wait for completion with timeout
5968
  auto timeout_time =
5969
      std::chrono::steady_clock::now() + std::chrono::seconds(timeout_sec);
5970
  bool timed_out = false;
5971
5972
  {
5973
    std::unique_lock<std::mutex> lock(context.mutex);
5974
5975
    while (!context.completed) {
5976
      auto now = std::chrono::steady_clock::now();
5977
      if (now >= timeout_time) {
5978
        timed_out = true;
5979
        break;
5980
      }
5981
5982
      // Run the runloop for a short time
5983
      lock.unlock();
5984
      CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true);
5985
      lock.lock();
5986
    }
5987
  }
5988
5989
  // Clean up
5990
  CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
5991
  CFHostSetClient(host_ref, nullptr, nullptr);
5992
5993
  if (timed_out || !context.completed) {
5994
    CFHostCancelInfoResolution(host_ref, kCFHostAddresses);
5995
    CFRelease(host_ref);
5996
    return EAI_AGAIN;
5997
  }
5998
5999
  if (!context.success || !context.addresses) {
6000
    CFRelease(host_ref);
6001
    return EAI_NODATA;
6002
  }
6003
6004
  // Convert CFArray to addrinfo
6005
  CFIndex count = CFArrayGetCount(context.addresses);
6006
  if (count == 0) {
6007
    CFRelease(context.addresses);
6008
    CFRelease(host_ref);
6009
    return EAI_NODATA;
6010
  }
6011
6012
  struct addrinfo *result_addrinfo = nullptr;
6013
  struct addrinfo **current = &result_addrinfo;
6014
6015
  for (CFIndex i = 0; i < count; i++) {
6016
    CFDataRef addr_data =
6017
        static_cast<CFDataRef>(CFArrayGetValueAtIndex(context.addresses, i));
6018
    if (!addr_data) continue;
6019
6020
    const struct sockaddr *sockaddr_ptr =
6021
        reinterpret_cast<const struct sockaddr *>(CFDataGetBytePtr(addr_data));
6022
    socklen_t sockaddr_len = static_cast<socklen_t>(CFDataGetLength(addr_data));
6023
6024
    // Allocate addrinfo structure
6025
    *current = static_cast<struct addrinfo *>(malloc(sizeof(struct addrinfo)));
6026
    if (!*current) {
6027
      freeaddrinfo(result_addrinfo);
6028
      CFRelease(context.addresses);
6029
      CFRelease(host_ref);
6030
      return EAI_MEMORY;
6031
    }
6032
6033
    memset(*current, 0, sizeof(struct addrinfo));
6034
6035
    // Set up addrinfo fields
6036
    (*current)->ai_family = sockaddr_ptr->sa_family;
6037
    (*current)->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM;
6038
    (*current)->ai_protocol = hints ? hints->ai_protocol : IPPROTO_TCP;
6039
    (*current)->ai_addrlen = sockaddr_len;
6040
6041
    // Copy sockaddr
6042
    (*current)->ai_addr = static_cast<struct sockaddr *>(malloc(sockaddr_len));
6043
    if (!(*current)->ai_addr) {
6044
      freeaddrinfo(result_addrinfo);
6045
      CFRelease(context.addresses);
6046
      CFRelease(host_ref);
6047
      return EAI_MEMORY;
6048
    }
6049
    memcpy((*current)->ai_addr, sockaddr_ptr, sockaddr_len);
6050
6051
    // Set port if service is specified
6052
    if (service && *service) {
6053
      int port = 0;
6054
      if (parse_port(service, strlen(service), port)) {
6055
        if (sockaddr_ptr->sa_family == AF_INET) {
6056
          reinterpret_cast<struct sockaddr_in *>((*current)->ai_addr)
6057
              ->sin_port = htons(static_cast<uint16_t>(port));
6058
        } else if (sockaddr_ptr->sa_family == AF_INET6) {
6059
          reinterpret_cast<struct sockaddr_in6 *>((*current)->ai_addr)
6060
              ->sin6_port = htons(static_cast<uint16_t>(port));
6061
        }
6062
      }
6063
    }
6064
6065
    current = &((*current)->ai_next);
6066
  }
6067
6068
  CFRelease(context.addresses);
6069
  CFRelease(host_ref);
6070
6071
  *res = result_addrinfo;
6072
  return 0;
6073
#elif defined(_GNU_SOURCE) && defined(__GLIBC__) &&                            \
6074
    (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2))
6075
  // #2431: gai_cancel() is non-blocking and may return EAI_NOTCANCELED while
6076
  // the resolver worker still references the stack-local gaicb. The cancel
6077
  // path therefore waits (gai_suspend with no timeout) for the worker to
6078
  // actually finish before letting the stack frame go. The trade-off is that
6079
  // a wedged DNS server can hold this thread for the system resolver timeout
6080
  // (~30s by default) past the caller's connection timeout.
6081
  struct gaicb request {};
6082
  struct gaicb *requests[1] = {&request};
6083
  struct sigevent sevp {};
6084
  struct timespec timeout {
6085
    timeout_sec, 0
6086
  };
6087
6088
  request.ar_name = node;
6089
  request.ar_service = service;
6090
  request.ar_request = hints;
6091
  sevp.sigev_notify = SIGEV_NONE;
6092
6093
  int rc = getaddrinfo_a(GAI_NOWAIT, requests, 1, &sevp);
6094
  if (rc != 0) { return rc; }
6095
6096
  auto cleanup = scope_exit([&] {
6097
    if (request.ar_result) { freeaddrinfo(request.ar_result); }
6098
  });
6099
6100
  int wait_result = gai_suspend(requests, 1, &timeout);
6101
6102
  if (wait_result == 0 || wait_result == EAI_ALLDONE) {
6103
    int gai_result = gai_error(&request);
6104
    if (gai_result == 0) {
6105
      *res = request.ar_result;
6106
      request.ar_result = nullptr;
6107
      return 0;
6108
    }
6109
    return gai_result;
6110
  }
6111
6112
  gai_cancel(&request);
6113
  while (gai_error(&request) == EAI_INPROGRESS) {
6114
    gai_suspend(requests, 1, nullptr);
6115
  }
6116
  return wait_result;
6117
#else
6118
  // Fallback implementation using thread-based timeout for other Unix systems.
6119
6120
  struct GetAddrInfoState {
6121
    ~GetAddrInfoState() {
6122
      if (info) { freeaddrinfo(info); }
6123
    }
6124
6125
    std::mutex mutex;
6126
    std::condition_variable result_cv;
6127
    bool completed = false;
6128
    int result = EAI_SYSTEM;
6129
    std::string node;
6130
    std::string service;
6131
    struct addrinfo hints;
6132
    struct addrinfo *info = nullptr;
6133
  };
6134
6135
  // Allocate on the heap, so the resolver thread can keep using the data.
6136
  auto state = std::make_shared<GetAddrInfoState>();
6137
  if (node) { state->node = node; }
6138
  state->service = service;
6139
  state->hints = *hints;
6140
6141
  std::thread resolve_thread([state]() {
6142
    auto thread_result =
6143
        getaddrinfo(state->node.c_str(), state->service.c_str(), &state->hints,
6144
                    &state->info);
6145
6146
    std::lock_guard<std::mutex> lock(state->mutex);
6147
    state->result = thread_result;
6148
    state->completed = true;
6149
    state->result_cv.notify_one();
6150
  });
6151
6152
  // Wait for completion or timeout
6153
  std::unique_lock<std::mutex> lock(state->mutex);
6154
  auto finished =
6155
      state->result_cv.wait_for(lock, std::chrono::seconds(timeout_sec),
6156
                                [&] { return state->completed; });
6157
6158
  if (finished) {
6159
    // Operation completed within timeout
6160
    resolve_thread.join();
6161
    *res = state->info;
6162
    state->info = nullptr; // Pass ownership to caller
6163
    return state->result;
6164
  } else {
6165
    // Timeout occurred
6166
    resolve_thread.detach(); // Let the thread finish in background
6167
    return EAI_AGAIN;        // Return timeout error
6168
  }
6169
#endif
6170
#else
6171
0
  (void)(timeout_sec); // Unused parameter for non-blocking getaddrinfo
6172
0
  return getaddrinfo(node, service, hints, res);
6173
0
#endif
6174
0
}
6175
6176
template <typename BindOrConnect>
6177
socket_t create_socket(const std::string &host, const std::string &ip, int port,
6178
                       int address_family, int socket_flags, bool tcp_nodelay,
6179
                       bool ipv6_v6only, SocketOptions socket_options,
6180
0
                       BindOrConnect bind_or_connect, time_t timeout_sec = 0) {
6181
  // Get address info
6182
0
  const char *node = nullptr;
6183
0
  struct addrinfo hints;
6184
0
  struct addrinfo *result;
6185
6186
0
  memset(&hints, 0, sizeof(struct addrinfo));
6187
0
  hints.ai_socktype = SOCK_STREAM;
6188
0
  hints.ai_protocol = IPPROTO_IP;
6189
6190
0
  if (!ip.empty()) {
6191
0
    node = ip.c_str();
6192
    // Ask getaddrinfo to convert IP in c-string to address
6193
0
    hints.ai_family = AF_UNSPEC;
6194
0
    hints.ai_flags = AI_NUMERICHOST;
6195
0
  } else {
6196
0
    if (!host.empty()) { node = host.c_str(); }
6197
0
    hints.ai_family = address_family;
6198
0
    hints.ai_flags = socket_flags;
6199
0
  }
6200
6201
0
#if !defined(_WIN32) || defined(CPPHTTPLIB_HAVE_AFUNIX_H)
6202
0
  if (hints.ai_family == AF_UNIX) {
6203
0
    const auto addrlen = host.length();
6204
0
    if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; }
6205
6206
0
#ifdef SOCK_CLOEXEC
6207
0
    auto sock = socket(hints.ai_family, hints.ai_socktype | SOCK_CLOEXEC,
6208
0
                       hints.ai_protocol);
6209
#else
6210
    auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
6211
#endif
6212
6213
0
    if (sock != INVALID_SOCKET) {
6214
0
      sockaddr_un addr{};
6215
0
      addr.sun_family = AF_UNIX;
6216
6217
0
      auto unescaped_host = unescape_abstract_namespace_unix_domain(host);
6218
0
      std::copy(unescaped_host.begin(), unescaped_host.end(), addr.sun_path);
6219
6220
0
      hints.ai_addr = reinterpret_cast<sockaddr *>(&addr);
6221
0
      hints.ai_addrlen = static_cast<socklen_t>(
6222
0
          sizeof(addr) - sizeof(addr.sun_path) + addrlen);
6223
6224
#ifndef SOCK_CLOEXEC
6225
#ifndef _WIN32
6226
      fcntl(sock, F_SETFD, FD_CLOEXEC);
6227
#endif
6228
#endif
6229
6230
0
      if (socket_options) { socket_options(sock); }
6231
6232
#ifdef _WIN32
6233
      // Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so
6234
      // remove the option.
6235
      set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0);
6236
#endif
6237
6238
0
      bool dummy;
6239
0
      if (!bind_or_connect(sock, hints, dummy)) {
6240
0
        close_socket(sock);
6241
0
        sock = INVALID_SOCKET;
6242
0
      }
6243
0
    }
6244
0
    return sock;
6245
0
  }
6246
0
#endif
6247
6248
0
  auto service = std::to_string(port);
6249
6250
0
  if (getaddrinfo_with_timeout(node, service.c_str(), &hints, &result,
6251
0
                               timeout_sec)) {
6252
0
#if defined __linux__ && !defined __ANDROID__
6253
0
    res_init();
6254
0
#endif
6255
0
    return INVALID_SOCKET;
6256
0
  }
6257
0
  auto se = detail::scope_exit([&] { freeaddrinfo(result); });
6258
6259
0
  for (auto rp = result; rp; rp = rp->ai_next) {
6260
    // Create a socket
6261
#ifdef _WIN32
6262
    auto sock =
6263
        WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0,
6264
                   WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED);
6265
    /**
6266
     * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1
6267
     * and above the socket creation fails on older Windows Systems.
6268
     *
6269
     * Let's try to create a socket the old way in this case.
6270
     *
6271
     * Reference:
6272
     * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa
6273
     *
6274
     * WSA_FLAG_NO_HANDLE_INHERIT:
6275
     * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with
6276
     * SP1, and later
6277
     *
6278
     */
6279
    if (sock == INVALID_SOCKET) {
6280
      sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
6281
    }
6282
#else
6283
6284
0
#ifdef SOCK_CLOEXEC
6285
0
    auto sock =
6286
0
        socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC, rp->ai_protocol);
6287
#else
6288
    auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
6289
#endif
6290
6291
0
#endif
6292
0
    if (sock == INVALID_SOCKET) { continue; }
6293
6294
#if !defined _WIN32 && !defined SOCK_CLOEXEC
6295
    if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) {
6296
      close_socket(sock);
6297
      continue;
6298
    }
6299
#endif
6300
6301
0
    if (tcp_nodelay) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); }
6302
6303
0
    if (rp->ai_family == AF_INET6) {
6304
0
      set_socket_opt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ipv6_v6only ? 1 : 0);
6305
0
    }
6306
6307
0
    if (socket_options) { socket_options(sock); }
6308
6309
    // bind or connect
6310
0
    auto quit = false;
6311
0
    if (bind_or_connect(sock, *rp, quit)) { return sock; }
6312
6313
0
    close_socket(sock);
6314
6315
0
    if (quit) { break; }
6316
0
  }
6317
6318
0
  return INVALID_SOCKET;
6319
0
}
Unexecuted instantiation: int httplib::detail::create_socket<httplib::detail::create_client_socket(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, int, bool, bool, std::__1::function<void (int)>, long, long, long, long, long, long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, httplib::Error&)::{lambda(int, addrinfo&, bool&)#1}>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, int, int, bool, bool, std::__1::function<void (int)>, httplib::detail::create_client_socket(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, int, bool, bool, std::__1::function<void (int)>, long, long, long, long, long, long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, httplib::Error&)::{lambda(int, addrinfo&, bool&)#1}, long)
Unexecuted instantiation: int httplib::detail::create_socket<httplib::Server::create_server_socket(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, int, std::__1::function<void (int)>) const::{lambda(int, addrinfo&, bool&)#1}>(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, int, int, bool, bool, std::__1::function<void (int)>, httplib::Server::create_server_socket(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int, int, std::__1::function<void (int)>) const::{lambda(int, addrinfo&, bool&)#1}, long)
6320
6321
0
inline void set_nonblocking(socket_t sock, bool nonblocking) {
6322
#ifdef _WIN32
6323
  auto flags = nonblocking ? 1UL : 0UL;
6324
  ioctlsocket(sock, FIONBIO, &flags);
6325
#else
6326
0
  auto flags = fcntl(sock, F_GETFL, 0);
6327
0
  fcntl(sock, F_SETFL,
6328
0
        nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK)));
6329
0
#endif
6330
0
}
6331
6332
0
inline bool is_connection_error() {
6333
#ifdef _WIN32
6334
  return WSAGetLastError() != WSAEWOULDBLOCK;
6335
#else
6336
0
  return errno != EINPROGRESS;
6337
0
#endif
6338
0
}
6339
6340
0
inline bool bind_ip_address(socket_t sock, const std::string &host) {
6341
0
  struct addrinfo hints;
6342
0
  struct addrinfo *result;
6343
6344
0
  memset(&hints, 0, sizeof(struct addrinfo));
6345
0
  hints.ai_family = AF_UNSPEC;
6346
0
  hints.ai_socktype = SOCK_STREAM;
6347
0
  hints.ai_protocol = 0;
6348
6349
0
  if (getaddrinfo_with_timeout(host.c_str(), "0", &hints, &result, 0)) {
6350
0
    return false;
6351
0
  }
6352
6353
0
  auto se = detail::scope_exit([&] { freeaddrinfo(result); });
6354
6355
0
  auto ret = false;
6356
0
  for (auto rp = result; rp; rp = rp->ai_next) {
6357
0
    const auto &ai = *rp;
6358
0
    if (!::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
6359
0
      ret = true;
6360
0
      break;
6361
0
    }
6362
0
  }
6363
6364
0
  return ret;
6365
0
}
6366
6367
#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__
6368
#define USE_IF2IP
6369
#endif
6370
6371
#ifdef USE_IF2IP
6372
0
inline std::string if2ip(int address_family, const std::string &ifn) {
6373
0
  struct ifaddrs *ifap;
6374
0
  getifaddrs(&ifap);
6375
0
  auto se = detail::scope_exit([&] { freeifaddrs(ifap); });
6376
6377
0
  std::string addr_candidate;
6378
0
  for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
6379
0
    if (ifa->ifa_addr && ifn == ifa->ifa_name &&
6380
0
        (AF_UNSPEC == address_family ||
6381
0
         ifa->ifa_addr->sa_family == address_family)) {
6382
0
      if (ifa->ifa_addr->sa_family == AF_INET) {
6383
0
        auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
6384
0
        char buf[INET_ADDRSTRLEN];
6385
0
        if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
6386
0
          return std::string(buf, INET_ADDRSTRLEN);
6387
0
        }
6388
0
      } else if (ifa->ifa_addr->sa_family == AF_INET6) {
6389
0
        auto sa = reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_addr);
6390
0
        if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) {
6391
0
          char buf[INET6_ADDRSTRLEN] = {};
6392
0
          if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) {
6393
            // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL
6394
0
            auto s6_addr_head = sa->sin6_addr.s6_addr[0];
6395
0
            if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) {
6396
0
              addr_candidate = std::string(buf, INET6_ADDRSTRLEN);
6397
0
            } else {
6398
0
              return std::string(buf, INET6_ADDRSTRLEN);
6399
0
            }
6400
0
          }
6401
0
        }
6402
0
      }
6403
0
    }
6404
0
  }
6405
0
  return addr_candidate;
6406
0
}
6407
#endif
6408
6409
inline socket_t create_client_socket(
6410
    const std::string &host, const std::string &ip, int port,
6411
    int address_family, bool tcp_nodelay, bool ipv6_v6only,
6412
    SocketOptions socket_options, time_t connection_timeout_sec,
6413
    time_t connection_timeout_usec, time_t read_timeout_sec,
6414
    time_t read_timeout_usec, time_t write_timeout_sec,
6415
0
    time_t write_timeout_usec, const std::string &intf, Error &error) {
6416
0
  auto sock = create_socket(
6417
0
      host, ip, port, address_family, 0, tcp_nodelay, ipv6_v6only,
6418
0
      std::move(socket_options),
6419
0
      [&](socket_t sock2, struct addrinfo &ai, bool &quit) -> bool {
6420
0
        if (!intf.empty()) {
6421
0
#ifdef USE_IF2IP
6422
0
          auto ip_from_if = if2ip(address_family, intf);
6423
0
          if (ip_from_if.empty()) { ip_from_if = intf; }
6424
0
          if (!bind_ip_address(sock2, ip_from_if)) {
6425
0
            error = Error::BindIPAddress;
6426
0
            return false;
6427
0
          }
6428
0
#endif
6429
0
        }
6430
6431
0
        set_nonblocking(sock2, true);
6432
6433
0
        auto ret =
6434
0
            ::connect(sock2, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
6435
6436
0
        if (ret < 0) {
6437
0
          if (is_connection_error()) {
6438
0
            error = Error::Connection;
6439
0
            return false;
6440
0
          }
6441
0
          error = wait_until_socket_is_ready(sock2, connection_timeout_sec,
6442
0
                                             connection_timeout_usec);
6443
0
          if (error != Error::Success) {
6444
0
            if (error == Error::ConnectionTimeout) { quit = true; }
6445
0
            return false;
6446
0
          }
6447
0
        }
6448
6449
0
        set_nonblocking(sock2, false);
6450
0
        set_socket_opt_time(sock2, SOL_SOCKET, SO_RCVTIMEO, read_timeout_sec,
6451
0
                            read_timeout_usec);
6452
0
        set_socket_opt_time(sock2, SOL_SOCKET, SO_SNDTIMEO, write_timeout_sec,
6453
0
                            write_timeout_usec);
6454
6455
0
        error = Error::Success;
6456
0
        return true;
6457
0
      },
6458
0
      connection_timeout_sec); // Pass DNS timeout
6459
6460
0
  if (sock != INVALID_SOCKET) {
6461
0
    error = Error::Success;
6462
0
  } else {
6463
0
    if (error == Error::Success) { error = Error::Connection; }
6464
0
  }
6465
6466
0
  return sock;
6467
0
}
6468
6469
inline bool get_ip_and_port(const struct sockaddr_storage &addr,
6470
0
                            socklen_t addr_len, std::string &ip, int &port) {
6471
0
  if (addr.ss_family == AF_INET) {
6472
0
    port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
6473
0
  } else if (addr.ss_family == AF_INET6) {
6474
0
    port =
6475
0
        ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
6476
0
  } else {
6477
0
    return false;
6478
0
  }
6479
6480
0
  std::array<char, NI_MAXHOST> ipstr{};
6481
0
  if (getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len,
6482
0
                  ipstr.data(), static_cast<socklen_t>(ipstr.size()), nullptr,
6483
0
                  0, NI_NUMERICHOST)) {
6484
0
    return false;
6485
0
  }
6486
6487
0
  ip = ipstr.data();
6488
0
  return true;
6489
0
}
6490
6491
0
inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) {
6492
0
  struct sockaddr_storage addr;
6493
0
  socklen_t addr_len = sizeof(addr);
6494
0
  if (!getsockname(sock, reinterpret_cast<struct sockaddr *>(&addr),
6495
0
                   &addr_len)) {
6496
0
    get_ip_and_port(addr, addr_len, ip, port);
6497
0
  }
6498
0
}
6499
6500
0
inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
6501
0
  struct sockaddr_storage addr;
6502
0
  socklen_t addr_len = sizeof(addr);
6503
6504
0
  if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
6505
0
                   &addr_len)) {
6506
0
#ifndef _WIN32
6507
0
    if (addr.ss_family == AF_UNIX) {
6508
0
#if defined(__linux__)
6509
0
      struct ucred ucred;
6510
0
      socklen_t len = sizeof(ucred);
6511
0
      if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) {
6512
0
        port = ucred.pid;
6513
0
      }
6514
#elif defined(SOL_LOCAL) && defined(SO_PEERPID)
6515
      pid_t pid;
6516
      socklen_t len = sizeof(pid);
6517
      if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) {
6518
        port = pid;
6519
      }
6520
#endif
6521
0
      return;
6522
0
    }
6523
0
#endif
6524
0
    get_ip_and_port(addr, addr_len, ip, port);
6525
0
  }
6526
0
}
6527
6528
// Recursive form retained so operator""_t below can compute hashes for
6529
// switch-case labels at compile time (C++11 constexpr forbids loops). Do not
6530
// call from runtime paths with arbitrary-length inputs — use str2tag()
6531
// instead, which is iterative and stack-safe.
6532
inline constexpr unsigned int str2tag_core(const char *s, size_t l,
6533
0
                                           unsigned int h) {
6534
0
  return (l == 0)
6535
0
             ? h
6536
0
             : str2tag_core(
6537
0
                   s + 1, l - 1,
6538
0
                   // Unsets the 6 high bits of h, therefore no overflow happens
6539
0
                   (((std::numeric_limits<unsigned int>::max)() >> 6) &
6540
0
                    h * 33) ^
6541
0
                       static_cast<unsigned char>(*s));
6542
0
}
6543
6544
66.4k
inline unsigned int str2tag(const std::string &s) {
6545
  // Iterative form of str2tag_core: the recursive constexpr version is kept
6546
  // for compile-time UDL evaluation of short string literals, but at runtime
6547
  // we may receive arbitrarily long inputs (e.g. fuzzed Content-Type) that
6548
  // would blow the stack with one frame per character.
6549
66.4k
  unsigned int h = 0;
6550
750k
  for (auto c : s) {
6551
750k
    h = (((std::numeric_limits<unsigned int>::max)() >> 6) & h * 33) ^
6552
750k
        static_cast<unsigned char>(c);
6553
750k
  }
6554
66.4k
  return h;
6555
66.4k
}
6556
6557
namespace udl {
6558
6559
0
inline constexpr unsigned int operator""_t(const char *s, size_t l) {
6560
0
  return str2tag_core(s, l, 0);
6561
0
}
6562
6563
} // namespace udl
6564
6565
inline std::string
6566
find_content_type(const std::string &path,
6567
                  const std::map<std::string, std::string> &user_data,
6568
0
                  const std::string &default_content_type) {
6569
0
  auto ext = file_extension(path);
6570
6571
0
  auto it = user_data.find(ext);
6572
0
  if (it != user_data.end()) { return it->second; }
6573
6574
0
  using udl::operator""_t;
6575
6576
0
  switch (str2tag(ext)) {
6577
0
  default: return default_content_type;
6578
6579
0
  case "css"_t: return "text/css";
6580
0
  case "csv"_t: return "text/csv";
6581
0
  case "htm"_t:
6582
0
  case "html"_t: return "text/html";
6583
0
  case "js"_t:
6584
0
  case "mjs"_t: return "text/javascript";
6585
0
  case "txt"_t: return "text/plain";
6586
0
  case "vtt"_t: return "text/vtt";
6587
6588
0
  case "apng"_t: return "image/apng";
6589
0
  case "avif"_t: return "image/avif";
6590
0
  case "bmp"_t: return "image/bmp";
6591
0
  case "gif"_t: return "image/gif";
6592
0
  case "png"_t: return "image/png";
6593
0
  case "svg"_t: return "image/svg+xml";
6594
0
  case "webp"_t: return "image/webp";
6595
0
  case "ico"_t: return "image/x-icon";
6596
0
  case "tif"_t: return "image/tiff";
6597
0
  case "tiff"_t: return "image/tiff";
6598
0
  case "jpg"_t:
6599
0
  case "jpeg"_t: return "image/jpeg";
6600
6601
0
  case "mp4"_t: return "video/mp4";
6602
0
  case "mpeg"_t: return "video/mpeg";
6603
0
  case "webm"_t: return "video/webm";
6604
6605
0
  case "mp3"_t: return "audio/mp3";
6606
0
  case "mpga"_t: return "audio/mpeg";
6607
0
  case "weba"_t: return "audio/webm";
6608
0
  case "wav"_t: return "audio/wave";
6609
6610
0
  case "otf"_t: return "font/otf";
6611
0
  case "ttf"_t: return "font/ttf";
6612
0
  case "woff"_t: return "font/woff";
6613
0
  case "woff2"_t: return "font/woff2";
6614
6615
0
  case "7z"_t: return "application/x-7z-compressed";
6616
0
  case "atom"_t: return "application/atom+xml";
6617
0
  case "pdf"_t: return "application/pdf";
6618
0
  case "json"_t: return "application/json";
6619
0
  case "rss"_t: return "application/rss+xml";
6620
0
  case "tar"_t: return "application/x-tar";
6621
0
  case "xht"_t:
6622
0
  case "xhtml"_t: return "application/xhtml+xml";
6623
0
  case "xslt"_t: return "application/xslt+xml";
6624
0
  case "xml"_t: return "application/xml";
6625
0
  case "gz"_t: return "application/gzip";
6626
0
  case "zip"_t: return "application/zip";
6627
0
  case "wasm"_t: return "application/wasm";
6628
0
  }
6629
0
}
6630
6631
inline std::string
6632
extract_media_type(const std::string &content_type,
6633
227k
                   std::map<std::string, std::string> *params = nullptr) {
6634
  // Extract type/subtype from Content-Type value (RFC 2045)
6635
  // e.g. "application/json; charset=utf-8" -> "application/json"
6636
227k
  auto media_type = content_type;
6637
227k
  auto semicolon_pos = media_type.find(';');
6638
227k
  if (semicolon_pos != std::string::npos) {
6639
2.85k
    auto param_str = media_type.substr(semicolon_pos + 1);
6640
2.85k
    media_type = media_type.substr(0, semicolon_pos);
6641
6642
2.85k
    if (params) {
6643
      // Parse parameters: key=value pairs separated by ';'
6644
1.15k
      split(param_str.data(), param_str.data() + param_str.size(), ';',
6645
4.05k
            [&](const char *b, const char *e) {
6646
4.05k
              std::string key;
6647
4.05k
              std::string val;
6648
6.15k
              split(b, e, '=', [&](const char *b2, const char *e2) {
6649
6.15k
                if (key.empty()) {
6650
3.83k
                  key.assign(b2, e2);
6651
3.83k
                } else {
6652
2.32k
                  val.assign(b2, e2);
6653
2.32k
                }
6654
6.15k
              });
6655
4.05k
              if (!key.empty()) {
6656
3.83k
                params->emplace(trim_copy(key), trim_double_quotes_copy(val));
6657
3.83k
              }
6658
4.05k
            });
6659
1.15k
    }
6660
2.85k
  }
6661
6662
  // Trim whitespace from media type
6663
227k
  return trim_copy(media_type);
6664
227k
}
6665
6666
1.90k
inline bool can_compress_content_type(const std::string &content_type) {
6667
1.90k
  using udl::operator""_t;
6668
6669
1.90k
  auto mime_type = extract_media_type(content_type);
6670
1.90k
  auto tag = str2tag(mime_type);
6671
6672
1.90k
  switch (tag) {
6673
0
  case "image/svg+xml"_t:
6674
0
  case "application/javascript"_t:
6675
0
  case "application/x-javascript"_t:
6676
0
  case "application/json"_t:
6677
0
  case "application/ld+json"_t:
6678
0
  case "application/xml"_t:
6679
0
  case "application/xhtml+xml"_t:
6680
0
  case "application/rss+xml"_t:
6681
0
  case "application/atom+xml"_t:
6682
0
  case "application/xslt+xml"_t:
6683
0
  case "application/protobuf"_t: return true;
6684
6685
0
  case "text/event-stream"_t: return false;
6686
6687
1.90k
  default: return !mime_type.rfind("text/", 0);
6688
1.90k
  }
6689
1.90k
}
6690
6691
inline bool parse_quality(const char *b, const char *e, std::string &token,
6692
223k
                          double &quality) {
6693
223k
  quality = 1.0;
6694
223k
  token.clear();
6695
6696
  // Split on first ';': left = token name, right = parameters
6697
223k
  const char *params_b = nullptr;
6698
223k
  std::size_t params_len = 0;
6699
6700
223k
  divide(
6701
223k
      b, static_cast<std::size_t>(e - b), ';',
6702
223k
      [&](const char *lb, std::size_t llen, const char *rb, std::size_t rlen) {
6703
223k
        auto r = trim(lb, lb + llen, 0, llen);
6704
223k
        if (r.first < r.second) { token.assign(lb + r.first, lb + r.second); }
6705
223k
        params_b = rb;
6706
223k
        params_len = rlen;
6707
223k
      });
6708
6709
223k
  if (token.empty()) { return false; }
6710
222k
  if (params_len == 0) { return true; }
6711
6712
  // Scan parameters for q= (stops on first match)
6713
6.98k
  bool invalid = false;
6714
6.98k
  split_find(params_b, params_b + params_len, ';',
6715
6.98k
             (std::numeric_limits<size_t>::max)(),
6716
10.2k
             [&](const char *pb, const char *pe) -> bool {
6717
               // Match exactly "q=" or "Q=" (not "query=" etc.)
6718
10.2k
               auto len = static_cast<size_t>(pe - pb);
6719
10.2k
               if (len < 2) { return false; }
6720
9.02k
               if ((pb[0] != 'q' && pb[0] != 'Q') || pb[1] != '=') {
6721
3.03k
                 return false;
6722
3.03k
               }
6723
6724
               // Trim the value portion
6725
5.99k
               auto r = trim(pb, pe, 2, len);
6726
5.99k
               if (r.first >= r.second) {
6727
280
                 invalid = true;
6728
280
                 return true;
6729
280
               }
6730
6731
5.71k
               double v = 0.0;
6732
5.71k
               auto res = from_chars(pb + r.first, pb + r.second, v);
6733
5.71k
               if (res.ec != std::errc{} || v < 0.0 || v > 1.0) {
6734
828
                 invalid = true;
6735
828
                 return true;
6736
828
               }
6737
4.88k
               quality = v;
6738
4.88k
               return true;
6739
5.71k
             });
6740
6741
6.98k
  return !invalid;
6742
222k
}
6743
6744
1.90k
inline EncodingType encoding_type(const Request &req, const Response &res) {
6745
1.90k
  if (!can_compress_content_type(res.get_header_value("Content-Type"))) {
6746
79
    return EncodingType::None;
6747
79
  }
6748
6749
1.82k
  const auto &s = req.get_header_value("Accept-Encoding");
6750
1.82k
  if (s.empty()) { return EncodingType::None; }
6751
6752
  // Single-pass: iterate tokens and track the best supported encoding.
6753
  // Server preference breaks ties (br > gzip > zstd).
6754
92
  EncodingType best = EncodingType::None;
6755
92
  double best_q = 0.0; // q=0 means "not acceptable"
6756
6757
  // Server preference: Brotli > Gzip > Zstd (lower = more preferred)
6758
878
  auto priority = [](EncodingType t) -> int {
6759
878
    switch (t) {
6760
0
    case EncodingType::Brotli: return 0;
6761
878
    case EncodingType::Gzip: return 1;
6762
0
    case EncodingType::Zstd: return 2;
6763
0
    default: return 3;
6764
878
    }
6765
878
  };
6766
6767
92
  std::string name;
6768
2.92k
  split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) {
6769
2.92k
    double quality = 1.0;
6770
2.92k
    if (!parse_quality(b, e, name, quality)) { return; }
6771
2.72k
    if (quality <= 0.0) { return; }
6772
6773
2.52k
    EncodingType type = EncodingType::None;
6774
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6775
    if (case_ignore::equal(name, "br")) { type = EncodingType::Brotli; }
6776
#endif
6777
2.52k
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
6778
2.52k
    if (type == EncodingType::None && case_ignore::equal(name, "gzip")) {
6779
676
      type = EncodingType::Gzip;
6780
676
    }
6781
2.52k
#endif
6782
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6783
    if (type == EncodingType::None && case_ignore::equal(name, "zstd")) {
6784
      type = EncodingType::Zstd;
6785
    }
6786
#endif
6787
6788
2.52k
    if (type == EncodingType::None) { return; }
6789
6790
    // Higher q-value wins; for equal q, server preference breaks ties
6791
676
    if (quality > best_q ||
6792
633
        (quality == best_q && priority(type) < priority(best))) {
6793
43
      best_q = quality;
6794
43
      best = type;
6795
43
    }
6796
676
  });
6797
6798
92
  return best;
6799
1.82k
}
6800
6801
37
inline std::unique_ptr<compressor> make_compressor(EncodingType type) {
6802
37
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
6803
37
  if (type == EncodingType::Gzip) {
6804
37
    return detail::make_unique<gzip_compressor>();
6805
37
  }
6806
0
#endif
6807
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6808
  if (type == EncodingType::Brotli) {
6809
    return detail::make_unique<brotli_compressor>();
6810
  }
6811
#endif
6812
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6813
  if (type == EncodingType::Zstd) {
6814
    return detail::make_unique<zstd_compressor>();
6815
  }
6816
#endif
6817
0
  (void)type;
6818
0
  return nullptr;
6819
37
}
6820
6821
37
inline const char *encoding_name(EncodingType type) {
6822
37
  switch (type) {
6823
37
  case EncodingType::Gzip: return "gzip";
6824
0
  case EncodingType::Brotli: return "br";
6825
0
  case EncodingType::Zstd: return "zstd";
6826
0
  default: return "";
6827
37
  }
6828
37
}
6829
6830
inline bool nocompressor::compress(const char *data, size_t data_length,
6831
0
                                   bool /*last*/, Callback callback) {
6832
0
  if (!data_length) { return true; }
6833
0
  return callback(data, data_length);
6834
0
}
6835
6836
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
6837
37
inline gzip_compressor::gzip_compressor() {
6838
37
  std::memset(&strm_, 0, sizeof(strm_));
6839
37
  strm_.zalloc = Z_NULL;
6840
37
  strm_.zfree = Z_NULL;
6841
37
  strm_.opaque = Z_NULL;
6842
6843
37
  is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
6844
37
                           Z_DEFAULT_STRATEGY) == Z_OK;
6845
37
}
6846
6847
37
inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); }
6848
6849
inline bool gzip_compressor::compress(const char *data, size_t data_length,
6850
37
                                      bool last, Callback callback) {
6851
37
  assert(is_valid_);
6852
6853
37
  do {
6854
37
    constexpr size_t max_avail_in =
6855
37
        (std::numeric_limits<decltype(strm_.avail_in)>::max)();
6856
6857
37
    strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
6858
37
        (std::min)(data_length, max_avail_in));
6859
37
    strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
6860
6861
37
    data_length -= strm_.avail_in;
6862
37
    data += strm_.avail_in;
6863
6864
37
    auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH;
6865
37
    auto ret = Z_OK;
6866
6867
37
    std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6868
37
    do {
6869
37
      strm_.avail_out = static_cast<uInt>(buff.size());
6870
37
      strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
6871
6872
37
      ret = deflate(&strm_, flush);
6873
37
      if (ret == Z_STREAM_ERROR) { return false; }
6874
6875
37
      if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
6876
0
        return false;
6877
0
      }
6878
37
    } while (strm_.avail_out == 0);
6879
6880
37
    assert((flush == Z_FINISH && ret == Z_STREAM_END) ||
6881
37
           (flush == Z_NO_FLUSH && ret == Z_OK));
6882
37
    assert(strm_.avail_in == 0);
6883
37
  } while (data_length > 0);
6884
6885
37
  return true;
6886
37
}
6887
6888
520
inline gzip_decompressor::gzip_decompressor() {
6889
520
  std::memset(&strm_, 0, sizeof(strm_));
6890
520
  strm_.zalloc = Z_NULL;
6891
520
  strm_.zfree = Z_NULL;
6892
520
  strm_.opaque = Z_NULL;
6893
6894
  // 15 is the value of wbits, which should be at the maximum possible value
6895
  // to ensure that any gzip stream can be decoded. The offset of 32 specifies
6896
  // that the stream type should be automatically detected either gzip or
6897
  // deflate.
6898
520
  is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
6899
520
}
6900
6901
520
inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); }
6902
6903
520
inline bool gzip_decompressor::is_valid() const { return is_valid_; }
6904
6905
inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
6906
2.94k
                                          Callback callback) {
6907
2.94k
  assert(is_valid_);
6908
6909
2.94k
  auto ret = Z_OK;
6910
6911
2.94k
  do {
6912
2.94k
    constexpr size_t max_avail_in =
6913
2.94k
        (std::numeric_limits<decltype(strm_.avail_in)>::max)();
6914
6915
2.94k
    strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
6916
2.94k
        (std::min)(data_length, max_avail_in));
6917
2.94k
    strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
6918
6919
2.94k
    data_length -= strm_.avail_in;
6920
2.94k
    data += strm_.avail_in;
6921
6922
2.94k
    std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6923
161k
    while (strm_.avail_in > 0 && ret == Z_OK) {
6924
158k
      strm_.avail_out = static_cast<uInt>(buff.size());
6925
158k
      strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
6926
6927
158k
      ret = inflate(&strm_, Z_NO_FLUSH);
6928
6929
158k
      assert(ret != Z_STREAM_ERROR);
6930
158k
      switch (ret) {
6931
1
      case Z_NEED_DICT:
6932
51
      case Z_DATA_ERROR:
6933
51
      case Z_MEM_ERROR: inflateEnd(&strm_); return false;
6934
158k
      }
6935
6936
158k
      if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
6937
55
        return false;
6938
55
      }
6939
158k
    }
6940
6941
2.84k
    if (ret != Z_OK && ret != Z_STREAM_END) { return false; }
6942
6943
2.84k
  } while (data_length > 0);
6944
6945
2.84k
  return true;
6946
2.94k
}
6947
#endif
6948
6949
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6950
inline brotli_compressor::brotli_compressor() {
6951
  state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
6952
}
6953
6954
inline brotli_compressor::~brotli_compressor() {
6955
  BrotliEncoderDestroyInstance(state_);
6956
}
6957
6958
inline bool brotli_compressor::compress(const char *data, size_t data_length,
6959
                                        bool last, Callback callback) {
6960
  std::array<uint8_t, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
6961
6962
  auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS;
6963
  auto available_in = data_length;
6964
  auto next_in = reinterpret_cast<const uint8_t *>(data);
6965
6966
  for (;;) {
6967
    if (last) {
6968
      if (BrotliEncoderIsFinished(state_)) { break; }
6969
    } else {
6970
      if (!available_in) { break; }
6971
    }
6972
6973
    auto available_out = buff.size();
6974
    auto next_out = buff.data();
6975
6976
    if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in,
6977
                                     &available_out, &next_out, nullptr)) {
6978
      return false;
6979
    }
6980
6981
    auto output_bytes = buff.size() - available_out;
6982
    if (output_bytes) {
6983
      callback(reinterpret_cast<const char *>(buff.data()), output_bytes);
6984
    }
6985
  }
6986
6987
  return true;
6988
}
6989
6990
inline brotli_decompressor::brotli_decompressor() {
6991
  decoder_s = BrotliDecoderCreateInstance(0, 0, 0);
6992
  decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
6993
                        : BROTLI_DECODER_RESULT_ERROR;
6994
}
6995
6996
inline brotli_decompressor::~brotli_decompressor() {
6997
  if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); }
6998
}
6999
7000
inline bool brotli_decompressor::is_valid() const { return decoder_s; }
7001
7002
inline bool brotli_decompressor::decompress(const char *data,
7003
                                            size_t data_length,
7004
                                            Callback callback) {
7005
  if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
7006
      decoder_r == BROTLI_DECODER_RESULT_ERROR) {
7007
    return 0;
7008
  }
7009
7010
  auto next_in = reinterpret_cast<const uint8_t *>(data);
7011
  size_t avail_in = data_length;
7012
  size_t total_out;
7013
7014
  decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
7015
7016
  std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
7017
  while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
7018
    char *next_out = buff.data();
7019
    size_t avail_out = buff.size();
7020
7021
    decoder_r = BrotliDecoderDecompressStream(
7022
        decoder_s, &avail_in, &next_in, &avail_out,
7023
        reinterpret_cast<uint8_t **>(&next_out), &total_out);
7024
7025
    if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
7026
7027
    if (!callback(buff.data(), buff.size() - avail_out)) { return false; }
7028
  }
7029
7030
  return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
7031
         decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
7032
}
7033
#endif
7034
7035
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
7036
inline zstd_compressor::zstd_compressor() {
7037
  ctx_ = ZSTD_createCCtx();
7038
  ZSTD_CCtx_setParameter(ctx_, ZSTD_c_compressionLevel, ZSTD_fast);
7039
}
7040
7041
inline zstd_compressor::~zstd_compressor() { ZSTD_freeCCtx(ctx_); }
7042
7043
inline bool zstd_compressor::compress(const char *data, size_t data_length,
7044
                                      bool last, Callback callback) {
7045
  std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
7046
7047
  ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue;
7048
  ZSTD_inBuffer input = {data, data_length, 0};
7049
7050
  bool finished;
7051
  do {
7052
    ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
7053
    size_t const remaining = ZSTD_compressStream2(ctx_, &output, &input, mode);
7054
7055
    if (ZSTD_isError(remaining)) { return false; }
7056
7057
    if (!callback(buff.data(), output.pos)) { return false; }
7058
7059
    finished = last ? (remaining == 0) : (input.pos == input.size);
7060
7061
  } while (!finished);
7062
7063
  return true;
7064
}
7065
7066
inline zstd_decompressor::zstd_decompressor() { ctx_ = ZSTD_createDCtx(); }
7067
7068
inline zstd_decompressor::~zstd_decompressor() { ZSTD_freeDCtx(ctx_); }
7069
7070
inline bool zstd_decompressor::is_valid() const { return ctx_ != nullptr; }
7071
7072
inline bool zstd_decompressor::decompress(const char *data, size_t data_length,
7073
                                          Callback callback) {
7074
  std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
7075
  ZSTD_inBuffer input = {data, data_length, 0};
7076
7077
  while (input.pos < input.size) {
7078
    ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
7079
    size_t const remaining = ZSTD_decompressStream(ctx_, &output, &input);
7080
7081
    if (ZSTD_isError(remaining)) { return false; }
7082
7083
    if (!callback(buff.data(), output.pos)) { return false; }
7084
  }
7085
7086
  return true;
7087
}
7088
#endif
7089
7090
inline std::unique_ptr<decompressor>
7091
556
create_decompressor(const std::string &encoding) {
7092
556
  std::unique_ptr<decompressor> decompressor;
7093
7094
556
  if (encoding == "gzip" || encoding == "deflate") {
7095
520
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
7096
520
    decompressor = detail::make_unique<gzip_decompressor>();
7097
520
#endif
7098
520
  } else if (encoding.find("br") != std::string::npos) {
7099
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
7100
    decompressor = detail::make_unique<brotli_decompressor>();
7101
#endif
7102
23
  } else if (encoding == "zstd" || encoding.find("zstd") != std::string::npos) {
7103
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
7104
    decompressor = detail::make_unique<zstd_decompressor>();
7105
#endif
7106
14
  }
7107
7108
556
  return decompressor;
7109
556
}
7110
7111
// Returns the best available compressor and its Content-Encoding name.
7112
// Priority: Brotli > Gzip > Zstd (matches server-side preference).
7113
inline std::pair<std::unique_ptr<compressor>, const char *>
7114
0
create_compressor() {
7115
0
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
7116
0
  return {detail::make_unique<brotli_compressor>(), "br"};
7117
0
#elif defined(CPPHTTPLIB_ZLIB_SUPPORT)
7118
0
  return {detail::make_unique<gzip_compressor>(), "gzip"};
7119
0
#elif defined(CPPHTTPLIB_ZSTD_SUPPORT)
7120
0
  return {detail::make_unique<zstd_compressor>(), "zstd"};
7121
0
#else
7122
0
  return {nullptr, nullptr};
7123
0
#endif
7124
0
}
7125
7126
64.5k
inline bool is_prohibited_header_name(const std::string &name) {
7127
64.5k
  using udl::operator""_t;
7128
7129
64.5k
  switch (str2tag(name)) {
7130
0
  case "REMOTE_ADDR"_t:
7131
0
  case "REMOTE_PORT"_t:
7132
0
  case "LOCAL_ADDR"_t:
7133
0
  case "LOCAL_PORT"_t: return true;
7134
64.5k
  default: return false;
7135
64.5k
  }
7136
64.5k
}
7137
7138
15.4k
inline bool has_header(const Headers &headers, const std::string &key) {
7139
15.4k
  if (is_prohibited_header_name(key)) { return false; }
7140
15.4k
  return headers.find(key) != headers.end();
7141
15.4k
}
7142
7143
inline const char *get_header_value(const Headers &headers,
7144
                                    const std::string &key, const char *def,
7145
49.1k
                                    size_t id) {
7146
49.1k
  if (is_prohibited_header_name(key)) {
7147
0
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
7148
0
    std::string msg = "Prohibited header name '" + key + "' is specified.";
7149
0
    throw std::invalid_argument(msg);
7150
#else
7151
    return "";
7152
#endif
7153
0
  }
7154
7155
49.1k
  auto rng = headers.equal_range(key);
7156
49.1k
  auto it = rng.first;
7157
49.1k
  std::advance(it, static_cast<ssize_t>(id));
7158
49.1k
  if (it != rng.second) { return it->second.c_str(); }
7159
36.1k
  return def;
7160
49.1k
}
7161
7162
inline size_t get_header_value_count(const Headers &headers,
7163
0
                                     const std::string &key) {
7164
0
  auto r = headers.equal_range(key);
7165
0
  return static_cast<size_t>(std::distance(r.first, r.second));
7166
0
}
7167
7168
template <typename Map>
7169
inline typename Map::mapped_type
7170
0
get_multimap_value(const Map &m, const std::string &key, size_t id) {
7171
0
  auto rng = m.equal_range(key);
7172
0
  auto it = rng.first;
7173
0
  std::advance(it, static_cast<ssize_t>(id));
7174
0
  if (it != rng.second) { return it->second; }
7175
0
  return typename Map::mapped_type();
7176
0
}
Unexecuted instantiation: std::__1::unordered_multimap<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, httplib::detail::case_ignore::hash, httplib::detail::case_ignore::equal_to, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > >::mapped_type httplib::detail::get_multimap_value<std::__1::unordered_multimap<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, httplib::detail::case_ignore::hash, httplib::detail::case_ignore::equal_to, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > > >(std::__1::unordered_multimap<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, httplib::detail::case_ignore::hash, httplib::detail::case_ignore::equal_to, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long)
Unexecuted instantiation: std::__1::multimap<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::less<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > >::mapped_type httplib::detail::get_multimap_value<std::__1::multimap<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::less<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > > >(std::__1::multimap<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::less<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long)
Unexecuted instantiation: std::__1::multimap<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, httplib::FormData, std::__1::less<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, httplib::FormData> > >::mapped_type httplib::detail::get_multimap_value<std::__1::multimap<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, httplib::FormData, std::__1::less<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, httplib::FormData> > > >(std::__1::multimap<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, httplib::FormData, std::__1::less<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, httplib::FormData> > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long)
7177
7178
inline void set_header(Headers &headers, const std::string &key,
7179
16.2k
                       const std::string &val) {
7180
16.2k
  if (fields::is_field_name(key) && fields::is_field_value(val)) {
7181
16.2k
    headers.emplace(key, val);
7182
16.2k
  }
7183
16.2k
}
7184
7185
6.44k
inline bool read_headers(Stream &strm, Headers &headers) {
7186
6.44k
  const auto bufsiz = 2048;
7187
6.44k
  char buf[bufsiz];
7188
6.44k
  stream_line_reader line_reader(strm, buf, bufsiz);
7189
7190
6.44k
  size_t header_count = 0;
7191
7192
20.2k
  for (;;) {
7193
20.2k
    if (!line_reader.getline()) { return false; }
7194
7195
    // Check if the line ends with CRLF.
7196
19.2k
    auto line_terminator_len = 2;
7197
19.2k
    if (line_reader.end_with_crlf()) {
7198
      // Blank line indicates end of headers.
7199
19.1k
      if (line_reader.size() == 2) { break; }
7200
19.1k
    } else {
7201
#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
7202
      // Blank line indicates end of headers.
7203
      if (line_reader.size() == 1) { break; }
7204
      line_terminator_len = 1;
7205
#else
7206
98
      continue; // Skip invalid line.
7207
98
#endif
7208
98
    }
7209
7210
13.9k
    if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
7211
7212
    // Check header count limit
7213
13.9k
    if (header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
7214
7215
    // Exclude line terminator
7216
13.9k
    auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
7217
7218
13.9k
    if (!parse_header(line_reader.ptr(), end,
7219
13.9k
                      [&](const std::string &key, const std::string &val) {
7220
13.7k
                        headers.emplace(key, val);
7221
13.7k
                      })) {
7222
262
      return false;
7223
262
    }
7224
7225
13.7k
    header_count++;
7226
13.7k
  }
7227
7228
  // RFC 9110 Section 8.6: Reject requests with multiple Content-Length
7229
  // headers that have different values to prevent request smuggling.
7230
5.19k
  auto cl_range = headers.equal_range("Content-Length");
7231
5.19k
  if (cl_range.first != cl_range.second) {
7232
976
    const auto &first_val = cl_range.first->second;
7233
1.19k
    for (auto it = std::next(cl_range.first); it != cl_range.second; ++it) {
7234
299
      if (it->second != first_val) { return false; }
7235
299
    }
7236
976
  }
7237
7238
5.11k
  return true;
7239
5.19k
}
7240
7241
inline bool read_websocket_upgrade_response(Stream &strm,
7242
                                            const std::string &expected_accept,
7243
0
                                            std::string &selected_subprotocol) {
7244
0
  // Read status line
7245
0
  const auto bufsiz = 2048;
7246
0
  char buf[bufsiz];
7247
0
  stream_line_reader line_reader(strm, buf, bufsiz);
7248
0
  if (!line_reader.getline()) { return false; }
7249
0
7250
0
  // Check for "HTTP/1.1 101"
7251
0
  auto line = std::string(line_reader.ptr(), line_reader.size());
7252
0
  if (line.find("HTTP/1.1 101") == std::string::npos) { return false; }
7253
0
7254
0
  // Parse headers using existing read_headers
7255
0
  Headers headers;
7256
0
  if (!read_headers(strm, headers)) { return false; }
7257
0
7258
0
  // Verify Upgrade: websocket (case-insensitive)
7259
0
  auto upgrade_it = headers.find("Upgrade");
7260
0
  if (upgrade_it == headers.end()) { return false; }
7261
0
  auto upgrade_val = case_ignore::to_lower(upgrade_it->second);
7262
0
  if (upgrade_val != "websocket") { return false; }
7263
0
7264
0
  // Verify Connection header contains "Upgrade" (case-insensitive)
7265
0
  auto connection_it = headers.find("Connection");
7266
0
  if (connection_it == headers.end()) { return false; }
7267
0
  auto connection_val = case_ignore::to_lower(connection_it->second);
7268
0
  if (connection_val.find("upgrade") == std::string::npos) { return false; }
7269
0
7270
0
  // Verify Sec-WebSocket-Accept header value
7271
0
  auto it = headers.find("Sec-WebSocket-Accept");
7272
0
  if (it == headers.end() || it->second != expected_accept) { return false; }
7273
0
7274
0
  // Extract negotiated subprotocol
7275
0
  auto proto_it = headers.find("Sec-WebSocket-Protocol");
7276
0
  if (proto_it != headers.end()) { selected_subprotocol = proto_it->second; }
7277
0
7278
0
  return true;
7279
0
}
7280
7281
enum class ReadContentResult {
7282
  Success,         // Successfully read the content
7283
  PayloadTooLarge, // The content exceeds the specified payload limit
7284
  Error            // An error occurred while reading the content
7285
};
7286
7287
inline ReadContentResult read_content_with_length(
7288
    Stream &strm, size_t len, DownloadProgress progress,
7289
    ContentReceiverWithProgress out,
7290
1.50k
    size_t payload_max_length = (std::numeric_limits<size_t>::max)()) {
7291
1.50k
  char buf[CPPHTTPLIB_RECV_BUFSIZ];
7292
7293
1.50k
  detail::BodyReader br;
7294
1.50k
  br.stream = &strm;
7295
1.50k
  br.has_content_length = true;
7296
1.50k
  br.content_length = len;
7297
1.50k
  br.payload_max_length = payload_max_length;
7298
1.50k
  br.chunked = false;
7299
1.50k
  br.bytes_read = 0;
7300
1.50k
  br.last_error = Error::Success;
7301
7302
1.50k
  size_t r = 0;
7303
3.69k
  while (r < len) {
7304
3.64k
    auto read_len = static_cast<size_t>(len - r);
7305
3.64k
    auto to_read = (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ);
7306
3.64k
    auto n = detail::read_body_content(&strm, br, buf, to_read);
7307
3.64k
    if (n <= 0) {
7308
      // Check if it was a payload size error
7309
1.35k
      if (br.last_error == Error::ExceedMaxPayloadSize) {
7310
0
        return ReadContentResult::PayloadTooLarge;
7311
0
      }
7312
1.35k
      return ReadContentResult::Error;
7313
1.35k
    }
7314
7315
2.29k
    if (!out(buf, static_cast<size_t>(n), r, len)) {
7316
107
      return ReadContentResult::Error;
7317
107
    }
7318
2.19k
    r += static_cast<size_t>(n);
7319
7320
2.19k
    if (progress) {
7321
0
      if (!progress(r, len)) { return ReadContentResult::Error; }
7322
0
    }
7323
2.19k
  }
7324
7325
49
  return ReadContentResult::Success;
7326
1.50k
}
7327
7328
inline ReadContentResult
7329
read_content_without_length(Stream &strm, size_t payload_max_length,
7330
1.16k
                            ContentReceiverWithProgress out) {
7331
1.16k
  char buf[CPPHTTPLIB_RECV_BUFSIZ];
7332
1.16k
  size_t r = 0;
7333
1.98k
  for (;;) {
7334
1.98k
    auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
7335
1.98k
    if (n == 0) { return ReadContentResult::Success; }
7336
952
    if (n < 0) { return ReadContentResult::Error; }
7337
7338
    // Check if adding this data would exceed the payload limit
7339
952
    if (r > payload_max_length ||
7340
952
        payload_max_length - r < static_cast<size_t>(n)) {
7341
0
      return ReadContentResult::PayloadTooLarge;
7342
0
    }
7343
7344
952
    if (!out(buf, static_cast<size_t>(n), r, 0)) {
7345
127
      return ReadContentResult::Error;
7346
127
    }
7347
825
    r += static_cast<size_t>(n);
7348
825
  }
7349
7350
0
  return ReadContentResult::Success;
7351
1.16k
}
7352
7353
template <typename T>
7354
inline ReadContentResult read_content_chunked(Stream &strm, T &x,
7355
                                              size_t payload_max_length,
7356
2.02k
                                              ContentReceiverWithProgress out) {
7357
2.02k
  detail::ChunkedDecoder dec(strm);
7358
7359
2.02k
  char buf[CPPHTTPLIB_RECV_BUFSIZ];
7360
2.02k
  size_t total_len = 0;
7361
7362
53.9k
  for (;;) {
7363
53.9k
    size_t chunk_offset = 0;
7364
53.9k
    size_t chunk_total = 0;
7365
53.9k
    auto n = dec.read_payload(buf, sizeof(buf), chunk_offset, chunk_total);
7366
53.9k
    if (n < 0) { return ReadContentResult::Error; }
7367
7368
52.4k
    if (n == 0) {
7369
561
      if (!dec.parse_trailers_into(x.trailers, x.headers)) {
7370
554
        return ReadContentResult::Error;
7371
554
      }
7372
7
      return ReadContentResult::Success;
7373
561
    }
7374
7375
51.9k
    if (total_len > payload_max_length ||
7376
51.9k
        payload_max_length - total_len < static_cast<size_t>(n)) {
7377
0
      return ReadContentResult::PayloadTooLarge;
7378
0
    }
7379
7380
51.9k
    if (!out(buf, static_cast<size_t>(n), chunk_offset, chunk_total)) {
7381
5
      return ReadContentResult::Error;
7382
5
    }
7383
7384
51.9k
    total_len += static_cast<size_t>(n);
7385
51.9k
  }
7386
2.02k
}
httplib::detail::ReadContentResult httplib::detail::read_content_chunked<httplib::Request>(httplib::Stream&, httplib::Request&, unsigned long, std::__1::function<bool (char const*, unsigned long, unsigned long, unsigned long)>)
Line
Count
Source
7356
2.02k
                                              ContentReceiverWithProgress out) {
7357
2.02k
  detail::ChunkedDecoder dec(strm);
7358
7359
2.02k
  char buf[CPPHTTPLIB_RECV_BUFSIZ];
7360
2.02k
  size_t total_len = 0;
7361
7362
53.9k
  for (;;) {
7363
53.9k
    size_t chunk_offset = 0;
7364
53.9k
    size_t chunk_total = 0;
7365
53.9k
    auto n = dec.read_payload(buf, sizeof(buf), chunk_offset, chunk_total);
7366
53.9k
    if (n < 0) { return ReadContentResult::Error; }
7367
7368
52.4k
    if (n == 0) {
7369
561
      if (!dec.parse_trailers_into(x.trailers, x.headers)) {
7370
554
        return ReadContentResult::Error;
7371
554
      }
7372
7
      return ReadContentResult::Success;
7373
561
    }
7374
7375
51.9k
    if (total_len > payload_max_length ||
7376
51.9k
        payload_max_length - total_len < static_cast<size_t>(n)) {
7377
0
      return ReadContentResult::PayloadTooLarge;
7378
0
    }
7379
7380
51.9k
    if (!out(buf, static_cast<size_t>(n), chunk_offset, chunk_total)) {
7381
5
      return ReadContentResult::Error;
7382
5
    }
7383
7384
51.9k
    total_len += static_cast<size_t>(n);
7385
51.9k
  }
7386
2.02k
}
Unexecuted instantiation: httplib::detail::ReadContentResult httplib::detail::read_content_chunked<httplib::Response>(httplib::Stream&, httplib::Response&, unsigned long, std::__1::function<bool (char const*, unsigned long, unsigned long, unsigned long)>)
7387
7388
13.9k
inline bool is_chunked_transfer_encoding(const Headers &headers) {
7389
13.9k
  return case_ignore::equal(
7390
13.9k
      get_header_value(headers, "Transfer-Encoding", "", 0), "chunked");
7391
13.9k
}
7392
7393
template <typename T, typename U>
7394
bool prepare_content_receiver(T &x, int &status,
7395
                              ContentReceiverWithProgress receiver,
7396
                              bool decompress, size_t payload_max_length,
7397
3.58k
                              bool &exceed_payload_max_length, U callback) {
7398
3.58k
  if (decompress) {
7399
1.76k
    std::string encoding = x.get_header_value("Content-Encoding");
7400
1.76k
    std::unique_ptr<decompressor> decompressor;
7401
7402
1.76k
    if (!encoding.empty()) {
7403
556
      decompressor = detail::create_decompressor(encoding);
7404
556
      if (!decompressor) {
7405
        // Unsupported encoding or no support compiled in
7406
36
        status = StatusCode::UnsupportedMediaType_415;
7407
36
        return false;
7408
36
      }
7409
556
    }
7410
7411
1.72k
    if (decompressor) {
7412
520
      if (decompressor->is_valid()) {
7413
520
        size_t decompressed_size = 0;
7414
520
        ContentReceiverWithProgress out = [&](const char *buf, size_t n,
7415
2.94k
                                              size_t off, size_t len) {
7416
2.94k
          return decompressor->decompress(
7417
158k
              buf, n, [&](const char *buf2, size_t n2) {
7418
                // Guard against zip-bomb: check
7419
                // decompressed size against limit.
7420
158k
                if (payload_max_length > 0 &&
7421
158k
                    (decompressed_size >= payload_max_length ||
7422
158k
                     n2 > payload_max_length - decompressed_size)) {
7423
13
                  exceed_payload_max_length = true;
7424
13
                  return false;
7425
13
                }
7426
158k
                decompressed_size += n2;
7427
158k
                return receiver(buf2, n2, off, len);
7428
158k
              });
7429
2.94k
        };
7430
520
        return callback(std::move(out));
7431
520
      } else {
7432
0
        status = StatusCode::InternalServerError_500;
7433
0
        return false;
7434
0
      }
7435
520
    }
7436
1.72k
  }
7437
7438
3.02k
  ContentReceiverWithProgress out = [&](const char *buf, size_t n, size_t off,
7439
51.2k
                                        size_t len) {
7440
51.2k
    return receiver(buf, n, off, len);
7441
51.2k
  };
7442
3.02k
  return callback(std::move(out));
7443
3.58k
}
bool httplib::detail::prepare_content_receiver<httplib::Request, httplib::detail::read_content<httplib::Request>(httplib::Stream&, httplib::Request&, unsigned long, int&, std::__1::function<bool (unsigned long, unsigned long)>, std::__1::function<bool (char const*, unsigned long, unsigned long, unsigned long)>, bool)::{lambda(std::__1::function<bool (char const*, unsigned long, unsigned long, unsigned long)> const&)#1}>(httplib::Request&, int&, std::__1::function<bool (char const*, unsigned long, unsigned long, unsigned long)>, bool, unsigned long, bool&, httplib::detail::read_content<httplib::Request>(httplib::Stream&, httplib::Request&, unsigned long, int&, std::__1::function<bool (unsigned long, unsigned long)>, std::__1::function<bool (char const*, unsigned long, unsigned long, unsigned long)>, bool)::{lambda(std::__1::function<bool (char const*, unsigned long, unsigned long, unsigned long)> const&)#1})
Line
Count
Source
7397
3.58k
                              bool &exceed_payload_max_length, U callback) {
7398
3.58k
  if (decompress) {
7399
1.76k
    std::string encoding = x.get_header_value("Content-Encoding");
7400
1.76k
    std::unique_ptr<decompressor> decompressor;
7401
7402
1.76k
    if (!encoding.empty()) {
7403
556
      decompressor = detail::create_decompressor(encoding);
7404
556
      if (!decompressor) {
7405
        // Unsupported encoding or no support compiled in
7406
36
        status = StatusCode::UnsupportedMediaType_415;
7407
36
        return false;
7408
36
      }
7409
556
    }
7410
7411
1.72k
    if (decompressor) {
7412
520
      if (decompressor->is_valid()) {
7413
520
        size_t decompressed_size = 0;
7414
520
        ContentReceiverWithProgress out = [&](const char *buf, size_t n,
7415
520
                                              size_t off, size_t len) {
7416
520
          return decompressor->decompress(
7417
520
              buf, n, [&](const char *buf2, size_t n2) {
7418
                // Guard against zip-bomb: check
7419
                // decompressed size against limit.
7420
520
                if (payload_max_length > 0 &&
7421
520
                    (decompressed_size >= payload_max_length ||
7422
520
                     n2 > payload_max_length - decompressed_size)) {
7423
520
                  exceed_payload_max_length = true;
7424
520
                  return false;
7425
520
                }
7426
520
                decompressed_size += n2;
7427
520
                return receiver(buf2, n2, off, len);
7428
520
              });
7429
520
        };
7430
520
        return callback(std::move(out));
7431
520
      } else {
7432
0
        status = StatusCode::InternalServerError_500;
7433
0
        return false;
7434
0
      }
7435
520
    }
7436
1.72k
  }
7437
7438
3.02k
  ContentReceiverWithProgress out = [&](const char *buf, size_t n, size_t off,
7439
3.02k
                                        size_t len) {
7440
3.02k
    return receiver(buf, n, off, len);
7441
3.02k
  };
7442
3.02k
  return callback(std::move(out));
7443
3.58k
}
Unexecuted instantiation: bool httplib::detail::prepare_content_receiver<httplib::Response, httplib::detail::read_content<httplib::Response>(httplib::Stream&, httplib::Response&, unsigned long, int&, std::__1::function<bool (unsigned long, unsigned long)>, std::__1::function<bool (char const*, unsigned long, unsigned long, unsigned long)>, bool)::{lambda(std::__1::function<bool (char const*, unsigned long, unsigned long, unsigned long)> const&)#1}>(httplib::Response&, int&, std::__1::function<bool (char const*, unsigned long, unsigned long, unsigned long)>, bool, unsigned long, bool&, httplib::detail::read_content<httplib::Response>(httplib::Stream&, httplib::Response&, unsigned long, int&, std::__1::function<bool (unsigned long, unsigned long)>, std::__1::function<bool (char const*, unsigned long, unsigned long, unsigned long)>, bool)::{lambda(std::__1::function<bool (char const*, unsigned long, unsigned long, unsigned long)> const&)#1})
7444
7445
template <typename T>
7446
bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
7447
                  DownloadProgress progress,
7448
3.58k
                  ContentReceiverWithProgress receiver, bool decompress) {
7449
3.58k
  bool exceed_payload_max_length = false;
7450
3.58k
  return prepare_content_receiver(
7451
3.58k
      x, status, std::move(receiver), decompress, payload_max_length,
7452
3.58k
      exceed_payload_max_length, [&](const ContentReceiverWithProgress &out) {
7453
3.54k
        auto ret = true;
7454
        // Note: exceed_payload_max_length may also be set by the decompressor
7455
        // wrapper in prepare_content_receiver when the decompressed payload
7456
        // size exceeds the limit.
7457
7458
3.54k
        if (is_chunked_transfer_encoding(x.headers)) {
7459
2.02k
          auto result = read_content_chunked(strm, x, payload_max_length, out);
7460
2.02k
          if (result == ReadContentResult::Success) {
7461
7
            ret = true;
7462
2.02k
          } else if (result == ReadContentResult::PayloadTooLarge) {
7463
0
            exceed_payload_max_length = true;
7464
0
            ret = false;
7465
2.02k
          } else {
7466
2.02k
            ret = false;
7467
2.02k
          }
7468
2.02k
        } else if (!has_header(x.headers, "Content-Length")) {
7469
0
          auto result =
7470
0
              read_content_without_length(strm, payload_max_length, out);
7471
0
          if (result == ReadContentResult::Success) {
7472
0
            ret = true;
7473
0
          } else if (result == ReadContentResult::PayloadTooLarge) {
7474
0
            exceed_payload_max_length = true;
7475
0
            ret = false;
7476
0
          } else {
7477
0
            ret = false;
7478
0
          }
7479
1.52k
        } else {
7480
1.52k
          auto is_invalid_value = false;
7481
1.52k
          auto len = get_header_value_u64(x.headers, "Content-Length",
7482
1.52k
                                          (std::numeric_limits<size_t>::max)(),
7483
1.52k
                                          0, is_invalid_value);
7484
7485
1.52k
          if (is_invalid_value) {
7486
11
            ret = false;
7487
1.50k
          } else if (len > 0) {
7488
1.50k
            auto result = read_content_with_length(
7489
1.50k
                strm, len, std::move(progress), out, payload_max_length);
7490
1.50k
            ret = (result == ReadContentResult::Success);
7491
1.50k
            if (result == ReadContentResult::PayloadTooLarge) {
7492
0
              exceed_payload_max_length = true;
7493
0
            }
7494
1.50k
          }
7495
1.52k
        }
7496
7497
3.54k
        if (!ret) {
7498
3.49k
          status = exceed_payload_max_length ? StatusCode::PayloadTooLarge_413
7499
3.49k
                                             : StatusCode::BadRequest_400;
7500
3.49k
        }
7501
3.54k
        return ret;
7502
3.54k
      });
7503
3.58k
}
bool httplib::detail::read_content<httplib::Request>(httplib::Stream&, httplib::Request&, unsigned long, int&, std::__1::function<bool (unsigned long, unsigned long)>, std::__1::function<bool (char const*, unsigned long, unsigned long, unsigned long)>, bool)
Line
Count
Source
7448
3.58k
                  ContentReceiverWithProgress receiver, bool decompress) {
7449
3.58k
  bool exceed_payload_max_length = false;
7450
3.58k
  return prepare_content_receiver(
7451
3.58k
      x, status, std::move(receiver), decompress, payload_max_length,
7452
3.58k
      exceed_payload_max_length, [&](const ContentReceiverWithProgress &out) {
7453
3.58k
        auto ret = true;
7454
        // Note: exceed_payload_max_length may also be set by the decompressor
7455
        // wrapper in prepare_content_receiver when the decompressed payload
7456
        // size exceeds the limit.
7457
7458
3.58k
        if (is_chunked_transfer_encoding(x.headers)) {
7459
3.58k
          auto result = read_content_chunked(strm, x, payload_max_length, out);
7460
3.58k
          if (result == ReadContentResult::Success) {
7461
3.58k
            ret = true;
7462
3.58k
          } else if (result == ReadContentResult::PayloadTooLarge) {
7463
3.58k
            exceed_payload_max_length = true;
7464
3.58k
            ret = false;
7465
3.58k
          } else {
7466
3.58k
            ret = false;
7467
3.58k
          }
7468
3.58k
        } else if (!has_header(x.headers, "Content-Length")) {
7469
3.58k
          auto result =
7470
3.58k
              read_content_without_length(strm, payload_max_length, out);
7471
3.58k
          if (result == ReadContentResult::Success) {
7472
3.58k
            ret = true;
7473
3.58k
          } else if (result == ReadContentResult::PayloadTooLarge) {
7474
3.58k
            exceed_payload_max_length = true;
7475
3.58k
            ret = false;
7476
3.58k
          } else {
7477
3.58k
            ret = false;
7478
3.58k
          }
7479
3.58k
        } else {
7480
3.58k
          auto is_invalid_value = false;
7481
3.58k
          auto len = get_header_value_u64(x.headers, "Content-Length",
7482
3.58k
                                          (std::numeric_limits<size_t>::max)(),
7483
3.58k
                                          0, is_invalid_value);
7484
7485
3.58k
          if (is_invalid_value) {
7486
3.58k
            ret = false;
7487
3.58k
          } else if (len > 0) {
7488
3.58k
            auto result = read_content_with_length(
7489
3.58k
                strm, len, std::move(progress), out, payload_max_length);
7490
3.58k
            ret = (result == ReadContentResult::Success);
7491
3.58k
            if (result == ReadContentResult::PayloadTooLarge) {
7492
3.58k
              exceed_payload_max_length = true;
7493
3.58k
            }
7494
3.58k
          }
7495
3.58k
        }
7496
7497
3.58k
        if (!ret) {
7498
3.58k
          status = exceed_payload_max_length ? StatusCode::PayloadTooLarge_413
7499
3.58k
                                             : StatusCode::BadRequest_400;
7500
3.58k
        }
7501
3.58k
        return ret;
7502
3.58k
      });
7503
3.58k
}
Unexecuted instantiation: bool httplib::detail::read_content<httplib::Response>(httplib::Stream&, httplib::Response&, unsigned long, int&, std::__1::function<bool (unsigned long, unsigned long)>, std::__1::function<bool (char const*, unsigned long, unsigned long, unsigned long)>, bool)
7504
7505
inline ssize_t write_request_line(Stream &strm, const std::string &method,
7506
0
                                  const std::string &path) {
7507
0
  std::string s = method;
7508
0
  s += ' ';
7509
0
  s += path;
7510
0
  s += " HTTP/1.1\r\n";
7511
0
  return strm.write(s.data(), s.size());
7512
0
}
7513
7514
6.78k
inline ssize_t write_response_line(Stream &strm, int status) {
7515
6.78k
  std::string s = "HTTP/1.1 ";
7516
6.78k
  s += std::to_string(status);
7517
6.78k
  s += ' ';
7518
6.78k
  s += httplib::status_message(status);
7519
6.78k
  s += "\r\n";
7520
6.78k
  return strm.write(s.data(), s.size());
7521
6.78k
}
7522
7523
6.77k
inline ssize_t write_headers(Stream &strm, const Headers &headers) {
7524
6.77k
  ssize_t write_len = 0;
7525
16.1k
  for (const auto &x : headers) {
7526
16.1k
    std::string s;
7527
16.1k
    s = x.first;
7528
16.1k
    s += ": ";
7529
16.1k
    s += x.second;
7530
16.1k
    s += "\r\n";
7531
7532
16.1k
    auto len = strm.write(s.data(), s.size());
7533
16.1k
    if (len < 0) { return len; }
7534
16.1k
    write_len += len;
7535
16.1k
  }
7536
6.77k
  auto len = strm.write("\r\n");
7537
6.77k
  if (len < 0) { return len; }
7538
6.77k
  write_len += len;
7539
6.77k
  return write_len;
7540
6.77k
}
7541
7542
6.77k
inline bool write_data(Stream &strm, const char *d, size_t l) {
7543
6.77k
  size_t offset = 0;
7544
13.5k
  while (offset < l) {
7545
6.77k
    auto length = strm.write(d + offset, l - offset);
7546
6.77k
    if (length < 0) { return false; }
7547
6.77k
    offset += static_cast<size_t>(length);
7548
6.77k
  }
7549
6.77k
  return true;
7550
6.77k
}
7551
7552
template <typename T>
7553
inline bool write_content_with_progress(Stream &strm,
7554
                                        const ContentProvider &content_provider,
7555
                                        size_t offset, size_t length,
7556
                                        T is_shutting_down,
7557
                                        const UploadProgress &upload_progress,
7558
0
                                        Error &error) {
7559
0
  size_t end_offset = offset + length;
7560
0
  size_t start_offset = offset;
7561
0
  auto ok = true;
7562
0
  DataSink data_sink;
7563
7564
0
  data_sink.write = [&](const char *d, size_t l) -> bool {
7565
0
    if (ok) {
7566
0
      if (write_data(strm, d, l)) {
7567
0
        offset += l;
7568
7569
0
        if (upload_progress && length > 0) {
7570
0
          size_t current_written = offset - start_offset;
7571
0
          if (!upload_progress(current_written, length)) {
7572
0
            ok = false;
7573
0
            return false;
7574
0
          }
7575
0
        }
7576
0
      } else {
7577
0
        ok = false;
7578
0
      }
7579
0
    }
7580
0
    return ok;
7581
0
  };
7582
7583
0
  data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
7584
7585
0
  while (offset < end_offset && !is_shutting_down()) {
7586
0
    if (!strm.wait_writable() || !strm.is_peer_alive()) {
7587
0
      error = Error::Write;
7588
0
      return false;
7589
0
    } else if (!content_provider(offset, end_offset - offset, data_sink)) {
7590
0
      error = Error::Canceled;
7591
0
      return false;
7592
0
    } else if (!ok) {
7593
0
      error = Error::Write;
7594
0
      return false;
7595
0
    }
7596
0
  }
7597
7598
0
  if (offset < end_offset) { // exited due to is_shutting_down(), not completion
7599
0
    error = Error::Write;
7600
0
    return false;
7601
0
  }
7602
7603
0
  error = Error::Success;
7604
0
  return true;
7605
0
}
Unexecuted instantiation: bool httplib::detail::write_content_with_progress<httplib::Server::write_content_with_provider(httplib::Stream&, httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::{lambda()#1}>(httplib::Stream&, std::__1::function<bool (unsigned long, unsigned long, httplib::DataSink&)> const&, unsigned long, unsigned long, httplib::Server::write_content_with_provider(httplib::Stream&, httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::{lambda()#1}, httplib::Server::write_content_with_provider(httplib::Stream&, httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::{lambda()#1}<bool (unsigned long, unsigned long)> const&, httplib::Error&)
Unexecuted instantiation: bool httplib::detail::write_content_with_progress<httplib::ClientImpl::write_content_with_provider(httplib::Stream&, httplib::Request const&, httplib::Error&) const::{lambda()#1}>(httplib::Stream&, std::__1::function<bool (unsigned long, unsigned long, httplib::DataSink&)> const&, unsigned long, unsigned long, httplib::ClientImpl::write_content_with_provider(httplib::Stream&, httplib::Request const&, httplib::Error&) const::{lambda()#1}, std::__1<bool (unsigned long, unsigned long)> const&, httplib::Error&)
7606
7607
template <typename T>
7608
inline bool write_content(Stream &strm, const ContentProvider &content_provider,
7609
                          size_t offset, size_t length, T is_shutting_down,
7610
0
                          Error &error) {
7611
0
  return write_content_with_progress<T>(strm, content_provider, offset, length,
7612
0
                                        is_shutting_down, nullptr, error);
7613
0
}
7614
7615
template <typename T>
7616
inline bool write_content(Stream &strm, const ContentProvider &content_provider,
7617
                          size_t offset, size_t length,
7618
0
                          const T &is_shutting_down) {
7619
0
  auto error = Error::Success;
7620
0
  return write_content(strm, content_provider, offset, length, is_shutting_down,
7621
0
                       error);
7622
0
}
7623
7624
template <typename T>
7625
inline bool
7626
write_content_without_length(Stream &strm,
7627
                             const ContentProvider &content_provider,
7628
0
                             const T &is_shutting_down) {
7629
0
  size_t offset = 0;
7630
0
  auto data_available = true;
7631
0
  auto ok = true;
7632
0
  DataSink data_sink;
7633
7634
0
  data_sink.write = [&](const char *d, size_t l) -> bool {
7635
0
    if (ok) {
7636
0
      offset += l;
7637
0
      if (!write_data(strm, d, l)) { ok = false; }
7638
0
    }
7639
0
    return ok;
7640
0
  };
7641
7642
0
  data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
7643
7644
0
  data_sink.done = [&](void) { data_available = false; };
7645
7646
0
  while (data_available && !is_shutting_down()) {
7647
0
    if (!strm.wait_writable() || !strm.is_peer_alive()) {
7648
0
      return false;
7649
0
    } else if (!content_provider(offset, 0, data_sink)) {
7650
0
      return false;
7651
0
    } else if (!ok) {
7652
0
      return false;
7653
0
    }
7654
0
  }
7655
0
  return !data_available; // true only if done() was called, false if shutting
7656
                          // down
7657
0
}
7658
7659
template <typename T, typename U>
7660
inline bool
7661
write_content_chunked(Stream &strm, const ContentProvider &content_provider,
7662
0
                      const T &is_shutting_down, U &compressor, Error &error) {
7663
0
  size_t offset = 0;
7664
0
  auto data_available = true;
7665
0
  auto ok = true;
7666
0
  DataSink data_sink;
7667
7668
0
  data_sink.write = [&](const char *d, size_t l) -> bool {
7669
0
    if (ok) {
7670
0
      data_available = l > 0;
7671
0
      offset += l;
7672
7673
0
      std::string payload;
7674
0
      if (compressor.compress(d, l, false,
7675
0
                              [&](const char *data, size_t data_len) {
7676
0
                                payload.append(data, data_len);
7677
0
                                return true;
7678
0
                              })) {
7679
0
        if (!payload.empty()) {
7680
          // Emit chunked response header and footer for each chunk
7681
0
          auto chunk =
7682
0
              from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
7683
0
          if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
7684
0
        }
7685
0
      } else {
7686
0
        ok = false;
7687
0
      }
7688
0
    }
7689
0
    return ok;
7690
0
  };
7691
7692
0
  data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
7693
7694
0
  auto done_with_trailer = [&](const Headers *trailer) {
7695
0
    if (!ok) { return; }
7696
7697
0
    data_available = false;
7698
7699
0
    std::string payload;
7700
0
    if (!compressor.compress(nullptr, 0, true,
7701
0
                             [&](const char *data, size_t data_len) {
7702
0
                               payload.append(data, data_len);
7703
0
                               return true;
7704
0
                             })) {
7705
0
      ok = false;
7706
0
      return;
7707
0
    }
7708
7709
0
    if (!payload.empty()) {
7710
      // Emit chunked response header and footer for each chunk
7711
0
      auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
7712
0
      if (!write_data(strm, chunk.data(), chunk.size())) {
7713
0
        ok = false;
7714
0
        return;
7715
0
      }
7716
0
    }
7717
7718
0
    constexpr const char done_marker[] = "0\r\n";
7719
0
    if (!write_data(strm, done_marker, str_len(done_marker))) { ok = false; }
7720
7721
    // Trailer
7722
0
    if (trailer) {
7723
0
      for (const auto &kv : *trailer) {
7724
        // Skip fields with invalid names or values to prevent response
7725
        // splitting via CR/LF injection, matching set_header().
7726
0
        if (!fields::is_field_name(kv.first) ||
7727
0
            !fields::is_field_value(kv.second)) {
7728
0
          continue;
7729
0
        }
7730
0
        std::string field_line = kv.first + ": " + kv.second + "\r\n";
7731
0
        if (!write_data(strm, field_line.data(), field_line.size())) {
7732
0
          ok = false;
7733
0
        }
7734
0
      }
7735
0
    }
7736
7737
0
    constexpr const char crlf[] = "\r\n";
7738
0
    if (!write_data(strm, crlf, str_len(crlf))) { ok = false; }
7739
0
  };
7740
7741
0
  data_sink.done = [&](void) { done_with_trailer(nullptr); };
7742
7743
0
  data_sink.done_with_trailer = [&](const Headers &trailer) {
7744
0
    done_with_trailer(&trailer);
7745
0
  };
7746
7747
0
  while (data_available && !is_shutting_down()) {
7748
0
    if (!strm.wait_writable() || !strm.is_peer_alive()) {
7749
0
      error = Error::Write;
7750
0
      return false;
7751
0
    } else if (!content_provider(offset, 0, data_sink)) {
7752
0
      error = Error::Canceled;
7753
0
      return false;
7754
0
    } else if (!ok) {
7755
0
      error = Error::Write;
7756
0
      return false;
7757
0
    }
7758
0
  }
7759
7760
0
  if (data_available) { // exited due to is_shutting_down(), not done()
7761
0
    error = Error::Write;
7762
0
    return false;
7763
0
  }
7764
7765
0
  error = Error::Success;
7766
0
  return true;
7767
0
}
Unexecuted instantiation: bool httplib::detail::write_content_chunked<httplib::Server::write_content_with_provider(httplib::Stream&, httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::{lambda()#1}, httplib::detail::compressor>(httplib::Stream&, std::__1::function<bool (unsigned long, unsigned long, httplib::DataSink&)> const&, httplib::Server::write_content_with_provider(httplib::Stream&, httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::{lambda()#1} const&, httplib::detail::compressor&, httplib::Error&)
Unexecuted instantiation: bool httplib::detail::write_content_chunked<httplib::ClientImpl::write_content_with_provider(httplib::Stream&, httplib::Request const&, httplib::Error&) const::{lambda()#1}, httplib::detail::compressor>(httplib::Stream&, std::__1::function<bool (unsigned long, unsigned long, httplib::DataSink&)> const&, httplib::ClientImpl::write_content_with_provider(httplib::Stream&, httplib::Request const&, httplib::Error&) const::{lambda()#1} const&, httplib::detail::compressor&, httplib::Error&)
7768
7769
template <typename T, typename U>
7770
inline bool write_content_chunked(Stream &strm,
7771
                                  const ContentProvider &content_provider,
7772
0
                                  const T &is_shutting_down, U &compressor) {
7773
0
  auto error = Error::Success;
7774
0
  return write_content_chunked(strm, content_provider, is_shutting_down,
7775
0
                               compressor, error);
7776
0
}
7777
7778
template <typename T>
7779
inline bool redirect(T &cli, Request &req, Response &res,
7780
                     const std::string &path, const std::string &location,
7781
0
                     Error &error) {
7782
0
  Request new_req = req;
7783
0
  new_req.path = path;
7784
0
  new_req.redirect_count_ -= 1;
7785
0
7786
0
  if (res.status == StatusCode::SeeOther_303 &&
7787
0
      (req.method != "GET" && req.method != "HEAD")) {
7788
0
    new_req.method = "GET";
7789
0
    new_req.body.clear();
7790
0
    new_req.headers.clear();
7791
0
  }
7792
0
7793
0
  Response new_res;
7794
0
7795
0
  auto ret = cli.send(new_req, new_res, error);
7796
0
  if (ret) {
7797
0
    req = std::move(new_req);
7798
0
    res = std::move(new_res);
7799
0
7800
0
    if (res.location.empty()) { res.location = location; }
7801
0
  }
7802
0
  return ret;
7803
0
}
7804
7805
0
inline std::string params_to_query_str(const Params &params) {
7806
0
  std::string query;
7807
0
7808
0
  for (auto it = params.begin(); it != params.end(); ++it) {
7809
0
    if (it != params.begin()) { query += '&'; }
7810
0
    query += encode_query_component(it->first);
7811
0
    query += '=';
7812
0
    query += encode_query_component(it->second);
7813
0
  }
7814
0
  return query;
7815
0
}
7816
7817
inline void parse_query_text(const char *data, std::size_t size,
7818
6.49k
                             Params &params) {
7819
6.49k
  std::set<std::string> cache;
7820
14.0k
  split(data, data + size, '&', [&](const char *b, const char *e) {
7821
14.0k
    std::string kv(b, e);
7822
14.0k
    if (cache.find(kv) != cache.end()) { return; }
7823
5.56k
    cache.insert(std::move(kv));
7824
7825
5.56k
    std::string key;
7826
5.56k
    std::string val;
7827
5.56k
    divide(b, static_cast<std::size_t>(e - b), '=',
7828
5.56k
           [&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data,
7829
5.56k
               std::size_t rhs_size) {
7830
5.56k
             key.assign(lhs_data, lhs_size);
7831
5.56k
             val.assign(rhs_data, rhs_size);
7832
5.56k
           });
7833
7834
5.56k
    if (!key.empty()) {
7835
5.26k
      params.emplace(decode_query_component(key), decode_query_component(val));
7836
5.26k
    }
7837
5.56k
  });
7838
6.49k
}
7839
7840
47
inline void parse_query_text(const std::string &s, Params &params) {
7841
47
  parse_query_text(s.data(), s.size(), params);
7842
47
}
7843
7844
// Normalize a query string by decoding and re-encoding each key/value pair
7845
// while preserving the original parameter order. This avoids double-encoding
7846
// and ensures consistent encoding without reordering (unlike Params which
7847
// uses std::multimap and sorts keys).
7848
0
inline std::string normalize_query_string(const std::string &query) {
7849
0
  std::string result;
7850
0
  split(query.data(), query.data() + query.size(), '&',
7851
0
        [&](const char *b, const char *e) {
7852
0
          std::string key;
7853
0
          std::string val;
7854
0
          divide(b, static_cast<std::size_t>(e - b), '=',
7855
0
                 [&](const char *lhs_data, std::size_t lhs_size,
7856
0
                     const char *rhs_data, std::size_t rhs_size) {
7857
0
                   key.assign(lhs_data, lhs_size);
7858
0
                   val.assign(rhs_data, rhs_size);
7859
0
                 });
7860
0
7861
0
          if (!key.empty()) {
7862
0
            auto dec_key = decode_query_component(key);
7863
0
            auto dec_val = decode_query_component(val);
7864
0
7865
0
            if (!result.empty()) { result += '&'; }
7866
0
            result += encode_query_component(dec_key);
7867
0
            if (!val.empty() || std::find(b, e, '=') != e) {
7868
0
              result += '=';
7869
0
              result += encode_query_component(dec_val);
7870
0
            }
7871
0
          }
7872
0
        });
7873
0
  return result;
7874
0
}
7875
7876
inline bool parse_multipart_boundary(const std::string &content_type,
7877
1.15k
                                     std::string &boundary) {
7878
1.15k
  std::map<std::string, std::string> params;
7879
1.15k
  extract_media_type(content_type, &params);
7880
1.15k
  auto it = params.find("boundary");
7881
1.15k
  if (it == params.end()) { return false; }
7882
1.02k
  boundary = it->second;
7883
1.02k
  return !boundary.empty();
7884
1.15k
}
7885
7886
8.61k
inline void parse_disposition_params(const std::string &s, Params &params) {
7887
8.61k
  std::set<std::string> cache;
7888
36.7k
  split(s.data(), s.data() + s.size(), ';', [&](const char *b, const char *e) {
7889
36.7k
    std::string kv(b, e);
7890
36.7k
    if (cache.find(kv) != cache.end()) { return; }
7891
31.3k
    cache.insert(kv);
7892
7893
31.3k
    std::string key;
7894
31.3k
    std::string val;
7895
61.0k
    split(b, e, '=', [&](const char *b2, const char *e2) {
7896
61.0k
      if (key.empty()) {
7897
30.9k
        key.assign(b2, e2);
7898
30.9k
      } else {
7899
30.0k
        val.assign(b2, e2);
7900
30.0k
      }
7901
61.0k
    });
7902
7903
31.3k
    if (!key.empty()) {
7904
30.9k
      params.emplace(trim_double_quotes_copy((key)),
7905
30.9k
                     trim_double_quotes_copy((val)));
7906
30.9k
    }
7907
31.3k
  });
7908
8.61k
}
7909
7910
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
7911
inline bool parse_range_header(const std::string &s, Ranges &ranges) {
7912
#else
7913
775
inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
7914
775
#endif
7915
17.2k
  auto is_valid = [](const std::string &str) {
7916
17.2k
    return std::all_of(str.cbegin(), str.cend(), is_ascii_digit);
7917
17.2k
  };
7918
7919
775
  if (s.size() > 7 && s.compare(0, 6, "bytes=") == 0) {
7920
747
    const auto pos = static_cast<size_t>(6);
7921
747
    const auto len = static_cast<size_t>(s.size() - 6);
7922
747
    auto all_valid_ranges = true;
7923
13.1k
    split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
7924
13.1k
      if (!all_valid_ranges) { return; }
7925
7926
8.66k
      const auto it = std::find(b, e, '-');
7927
8.66k
      if (it == e) {
7928
11
        all_valid_ranges = false;
7929
11
        return;
7930
11
      }
7931
7932
8.65k
      const auto lhs = std::string(b, it);
7933
8.65k
      const auto rhs = std::string(it + 1, e);
7934
8.65k
      if (!is_valid(lhs) || !is_valid(rhs)) {
7935
14
        all_valid_ranges = false;
7936
14
        return;
7937
14
      }
7938
7939
8.63k
      ssize_t first = -1;
7940
8.63k
      if (!lhs.empty()) {
7941
5.28k
        ssize_t v;
7942
5.28k
        auto res = detail::from_chars(lhs.data(), lhs.data() + lhs.size(), v);
7943
5.28k
        if (res.ec == std::errc{}) { first = v; }
7944
5.28k
      }
7945
7946
8.63k
      ssize_t last = -1;
7947
8.63k
      if (!rhs.empty()) {
7948
4.58k
        ssize_t v;
7949
4.58k
        auto res = detail::from_chars(rhs.data(), rhs.data() + rhs.size(), v);
7950
4.58k
        if (res.ec == std::errc{}) { last = v; }
7951
4.58k
      }
7952
7953
8.63k
      if ((first == -1 && last == -1) ||
7954
8.55k
          (first != -1 && last != -1 && first > last)) {
7955
154
        all_valid_ranges = false;
7956
154
        return;
7957
154
      }
7958
7959
8.48k
      ranges.emplace_back(first, last);
7960
8.48k
    });
7961
747
    return all_valid_ranges && !ranges.empty();
7962
747
  }
7963
28
  return false;
7964
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
7965
}
7966
#else
7967
775
} catch (...) { return false; }
7968
#endif
7969
7970
inline bool parse_accept_header(const std::string &s,
7971
1.18k
                                std::vector<std::string> &content_types) {
7972
1.18k
  content_types.clear();
7973
7974
  // Empty string is considered valid (no preference)
7975
1.18k
  if (s.empty()) { return true; }
7976
7977
  // Check for invalid patterns: leading/trailing commas or consecutive commas
7978
1.17k
  if (s.front() == ',' || s.back() == ',' ||
7979
1.16k
      s.find(",,") != std::string::npos) {
7980
17
    return false;
7981
17
  }
7982
7983
1.15k
  struct AcceptEntry {
7984
1.15k
    std::string media_type;
7985
1.15k
    double quality;
7986
1.15k
    int order;
7987
1.15k
  };
7988
7989
1.15k
  std::vector<AcceptEntry> entries;
7990
1.15k
  int order = 0;
7991
1.15k
  bool has_invalid_entry = false;
7992
7993
  // Split by comma and parse each entry
7994
220k
  split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) {
7995
220k
    std::string entry(b, e);
7996
220k
    entry = trim_copy(entry);
7997
7998
220k
    if (entry.empty()) {
7999
0
      has_invalid_entry = true;
8000
0
      return;
8001
0
    }
8002
8003
220k
    AcceptEntry accept_entry;
8004
220k
    accept_entry.order = order++;
8005
8006
220k
    if (!parse_quality(entry.data(), entry.data() + entry.size(),
8007
220k
                       accept_entry.media_type, accept_entry.quality)) {
8008
1.39k
      has_invalid_entry = true;
8009
1.39k
      return;
8010
1.39k
    }
8011
8012
    // Remove additional parameters from media type
8013
219k
    accept_entry.media_type = extract_media_type(accept_entry.media_type);
8014
8015
    // Basic validation of media type format
8016
219k
    if (accept_entry.media_type.empty()) {
8017
0
      has_invalid_entry = true;
8018
0
      return;
8019
0
    }
8020
8021
    // Check for basic media type format (should contain '/' or be '*')
8022
219k
    if (accept_entry.media_type != "*" &&
8023
210k
        accept_entry.media_type.find('/') == std::string::npos) {
8024
899
      has_invalid_entry = true;
8025
899
      return;
8026
899
    }
8027
8028
218k
    entries.push_back(std::move(accept_entry));
8029
218k
  });
8030
8031
  // Return false if any invalid entry was found
8032
1.15k
  if (has_invalid_entry) { return false; }
8033
8034
  // Sort by quality (descending), then by original order (ascending)
8035
805
  std::sort(entries.begin(), entries.end(),
8036
3.19M
            [](const AcceptEntry &a, const AcceptEntry &b) {
8037
3.19M
              if (a.quality != b.quality) {
8038
12.4k
                return a.quality > b.quality; // Higher quality first
8039
12.4k
              }
8040
3.18M
              return a.order < b.order; // Earlier order first for same quality
8041
3.19M
            });
8042
8043
  // Extract sorted media types
8044
805
  content_types.reserve(entries.size());
8045
212k
  for (auto &entry : entries) {
8046
212k
    content_types.push_back(std::move(entry.media_type));
8047
212k
  }
8048
8049
805
  return true;
8050
1.15k
}
8051
8052
class FormDataParser {
8053
public:
8054
3.35k
  FormDataParser() = default;
8055
8056
1.02k
  void set_boundary(std::string &&boundary) {
8057
1.02k
    boundary_ = std::move(boundary);
8058
1.02k
    dash_boundary_crlf_ = dash_ + boundary_ + crlf_;
8059
1.02k
    crlf_dash_boundary_ = crlf_ + dash_ + boundary_;
8060
1.02k
  }
8061
8062
4
  bool is_valid() const { return is_valid_; }
8063
8064
  bool parse(const char *buf, size_t n, const FormDataHeader &header_callback,
8065
53.9k
             const ContentReceiver &content_callback) {
8066
8067
53.9k
    buf_append(buf, n);
8068
8069
154k
    while (buf_size() > 0) {
8070
154k
      switch (state_) {
8071
1.75k
      case 0: { // Initial boundary
8072
1.75k
        auto pos = buf_find(dash_boundary_crlf_);
8073
1.75k
        if (pos == buf_size()) { return true; }
8074
913
        buf_erase(pos + dash_boundary_crlf_.size());
8075
913
        state_ = 1;
8076
913
        break;
8077
1.75k
      }
8078
25.5k
      case 1: { // New entry
8079
25.5k
        clear_file_info();
8080
25.5k
        state_ = 2;
8081
25.5k
        break;
8082
1.75k
      }
8083
26.0k
      case 2: { // Headers
8084
26.0k
        auto pos = buf_find(crlf_);
8085
26.0k
        if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
8086
95.9k
        while (pos < buf_size()) {
8087
          // Empty line
8088
95.1k
          if (pos == 0) {
8089
25.0k
            if (!header_callback(file_)) {
8090
2
              is_valid_ = false;
8091
2
              return false;
8092
2
            }
8093
25.0k
            buf_erase(crlf_.size());
8094
25.0k
            state_ = 3;
8095
25.0k
            break;
8096
25.0k
          }
8097
8098
70.1k
          const auto header = buf_head(pos);
8099
8100
70.1k
          if (!parse_header(header.data(), header.data() + header.size(),
8101
70.1k
                            [&](const std::string &, const std::string &) {})) {
8102
85
            is_valid_ = false;
8103
85
            return false;
8104
85
          }
8105
8106
          // Parse and emplace space trimmed headers into a map
8107
70.0k
          if (!parse_header(
8108
70.0k
                  header.data(), header.data() + header.size(),
8109
70.0k
                  [&](const std::string &key, const std::string &val) {
8110
70.0k
                    file_.headers.emplace(key, val);
8111
70.0k
                  })) {
8112
0
            is_valid_ = false;
8113
0
            return false;
8114
0
          }
8115
8116
70.0k
          constexpr const char header_content_type[] = "Content-Type:";
8117
8118
70.0k
          if (start_with_case_ignore(header, header_content_type)) {
8119
1.00k
            file_.content_type =
8120
1.00k
                trim_copy(header.substr(str_len(header_content_type)));
8121
69.0k
          } else {
8122
69.0k
            std::string disposition_params;
8123
69.0k
            if (parse_content_disposition(header, disposition_params)) {
8124
8.61k
              Params params;
8125
8.61k
              parse_disposition_params(disposition_params, params);
8126
8127
8.61k
              auto it = params.find("name");
8128
8.61k
              if (it != params.end()) {
8129
8.53k
                file_.name = it->second;
8130
8.53k
              } else {
8131
74
                is_valid_ = false;
8132
74
                return false;
8133
74
              }
8134
8135
8.53k
              it = params.find("filename");
8136
8.53k
              if (it != params.end()) { file_.filename = it->second; }
8137
8138
8.53k
              it = params.find("filename*");
8139
8.53k
              if (it != params.end()) {
8140
                // RFC 5987: only UTF-8 encoding is allowed
8141
3.11k
                const auto &val = it->second;
8142
3.11k
                constexpr const char utf8_prefix[] = "UTF-8''";
8143
3.11k
                constexpr size_t prefix_len = str_len(utf8_prefix);
8144
3.11k
                if (val.size() > prefix_len &&
8145
3.11k
                    start_with_case_ignore(val, utf8_prefix)) {
8146
3.10k
                  file_.filename = decode_path_component(
8147
3.10k
                      val.substr(prefix_len)); // override...
8148
3.10k
                } else {
8149
9
                  is_valid_ = false;
8150
9
                  return false;
8151
9
                }
8152
3.11k
              }
8153
8.53k
            }
8154
69.0k
          }
8155
69.9k
          buf_erase(pos + crlf_.size());
8156
69.9k
          pos = buf_find(crlf_);
8157
69.9k
        }
8158
25.8k
        if (state_ != 3) { return true; }
8159
25.0k
        break;
8160
25.8k
      }
8161
50.1k
      case 3: { // Body
8162
50.1k
        if (crlf_dash_boundary_.size() > buf_size()) { return true; }
8163
50.0k
        auto pos = buf_find(crlf_dash_boundary_);
8164
50.0k
        if (pos < buf_size()) {
8165
24.7k
          if (!content_callback(buf_data(), pos)) {
8166
0
            is_valid_ = false;
8167
0
            return false;
8168
0
          }
8169
24.7k
          buf_erase(pos + crlf_dash_boundary_.size());
8170
24.7k
          state_ = 4;
8171
25.3k
        } else {
8172
25.3k
          auto len = buf_size() - crlf_dash_boundary_.size();
8173
25.3k
          if (len > 0) {
8174
25.2k
            if (!content_callback(buf_data(), len)) {
8175
0
              is_valid_ = false;
8176
0
              return false;
8177
0
            }
8178
25.2k
            buf_erase(len);
8179
25.2k
          }
8180
25.3k
          return true;
8181
25.3k
        }
8182
24.7k
        break;
8183
50.0k
      }
8184
51.1k
      case 4: { // Boundary
8185
51.1k
        if (crlf_.size() > buf_size()) { return true; }
8186
50.9k
        if (buf_start_with(crlf_)) {
8187
24.6k
          buf_erase(crlf_.size());
8188
24.6k
          state_ = 1;
8189
26.3k
        } else {
8190
26.3k
          if (dash_.size() > buf_size()) { return true; }
8191
26.3k
          if (buf_start_with(dash_)) {
8192
236
            buf_erase(dash_.size());
8193
236
            is_valid_ = true;
8194
236
            buf_erase(buf_size()); // Remove epilogue
8195
26.0k
          } else {
8196
26.0k
            return true;
8197
26.0k
          }
8198
26.3k
        }
8199
24.8k
        break;
8200
50.9k
      }
8201
154k
      }
8202
154k
    }
8203
8204
370
    return true;
8205
53.9k
  }
8206
8207
private:
8208
25.5k
  void clear_file_info() {
8209
25.5k
    file_.name.clear();
8210
25.5k
    file_.filename.clear();
8211
25.5k
    file_.content_type.clear();
8212
25.5k
    file_.headers.clear();
8213
25.5k
  }
8214
8215
  bool start_with_case_ignore(const std::string &a, const char *b,
8216
152k
                              size_t offset = 0) const {
8217
152k
    const auto b_len = strlen(b);
8218
152k
    if (a.size() < offset + b_len) { return false; }
8219
505k
    for (size_t i = 0; i < b_len; i++) {
8220
482k
      if (case_ignore::to_lower(a[offset + i]) != case_ignore::to_lower(b[i])) {
8221
22.0k
        return false;
8222
22.0k
      }
8223
482k
    }
8224
22.6k
    return true;
8225
44.7k
  }
8226
8227
  // Parses "Content-Disposition: form-data; <params>" without std::regex.
8228
  // Returns true if header matches, with the params portion in `params_out`.
8229
  bool parse_content_disposition(const std::string &header,
8230
69.0k
                                 std::string &params_out) const {
8231
69.0k
    constexpr const char prefix[] = "Content-Disposition:";
8232
69.0k
    constexpr size_t prefix_len = str_len(prefix);
8233
8234
69.0k
    if (!start_with_case_ignore(header, prefix)) { return false; }
8235
8236
    // Skip whitespace after "Content-Disposition:"
8237
9.91k
    auto pos = prefix_len;
8238
10.6k
    while (pos < header.size() && (header[pos] == ' ' || header[pos] == '\t')) {
8239
745
      pos++;
8240
745
    }
8241
8242
    // Match "form-data;" (case-insensitive)
8243
9.91k
    constexpr const char form_data[] = "form-data;";
8244
9.91k
    constexpr size_t form_data_len = str_len(form_data);
8245
9.91k
    if (!start_with_case_ignore(header, form_data, pos)) { return false; }
8246
8.61k
    pos += form_data_len;
8247
8248
    // Skip whitespace after "form-data;"
8249
9.35k
    while (pos < header.size() && (header[pos] == ' ' || header[pos] == '\t')) {
8250
738
      pos++;
8251
738
    }
8252
8253
8.61k
    params_out = header.substr(pos);
8254
8.61k
    return true;
8255
9.91k
  }
8256
8257
  const std::string dash_ = "--";
8258
  const std::string crlf_ = "\r\n";
8259
  std::string boundary_;
8260
  std::string dash_boundary_crlf_;
8261
  std::string crlf_dash_boundary_;
8262
8263
  size_t state_ = 0;
8264
  bool is_valid_ = false;
8265
  FormData file_;
8266
8267
  // Buffer
8268
  bool start_with(const std::string &a, size_t spos, size_t epos,
8269
9.62M
                  const std::string &b) const {
8270
9.62M
    if (epos - spos < b.size()) { return false; }
8271
19.7M
    for (size_t i = 0; i < b.size(); i++) {
8272
19.5M
      if (a[i + spos] != b[i]) { return false; }
8273
19.5M
    }
8274
145k
    return true;
8275
9.62M
  }
8276
8277
536k
  size_t buf_size() const { return buf_epos_ - buf_spos_; }
8278
8279
49.9k
  const char *buf_data() const { return &buf_[buf_spos_]; }
8280
8281
70.1k
  std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); }
8282
8283
77.2k
  bool buf_start_with(const std::string &s) const {
8284
77.2k
    return start_with(buf_, buf_spos_, buf_epos_, s);
8285
77.2k
  }
8286
8287
147k
  size_t buf_find(const std::string &s) const {
8288
147k
    auto c = s.front();
8289
8290
147k
    size_t off = buf_spos_;
8291
9.57M
    while (off < buf_epos_) {
8292
9.57M
      auto pos = off;
8293
656M
      while (true) {
8294
656M
        if (pos == buf_epos_) { return buf_size(); }
8295
656M
        if (buf_[pos] == c) { break; }
8296
646M
        pos++;
8297
646M
      }
8298
8299
9.55M
      auto remaining_size = buf_epos_ - pos;
8300
9.55M
      if (s.size() > remaining_size) { return buf_size(); }
8301
8302
9.55M
      if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; }
8303
8304
9.43M
      off = pos + 1;
8305
9.43M
    }
8306
8307
290
    return buf_size();
8308
147k
  }
8309
8310
53.9k
  void buf_append(const char *data, size_t n) {
8311
53.9k
    auto remaining_size = buf_size();
8312
53.9k
    if (remaining_size > 0 && buf_spos_ > 0) {
8313
738k
      for (size_t i = 0; i < remaining_size; i++) {
8314
713k
        buf_[i] = buf_[buf_spos_ + i];
8315
713k
      }
8316
25.3k
    }
8317
53.9k
    buf_spos_ = 0;
8318
53.9k
    buf_epos_ = remaining_size;
8319
8320
53.9k
    if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); }
8321
8322
835M
    for (size_t i = 0; i < n; i++) {
8323
835M
      buf_[buf_epos_ + i] = data[i];
8324
835M
    }
8325
53.9k
    buf_epos_ += n;
8326
53.9k
  }
8327
8328
170k
  void buf_erase(size_t size) { buf_spos_ += size; }
8329
8330
  std::string buf_;
8331
  size_t buf_spos_ = 0;
8332
  size_t buf_epos_ = 0;
8333
};
8334
8335
79
inline std::string random_string(size_t length) {
8336
79
  constexpr const char data[] =
8337
79
      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
8338
8339
79
  thread_local auto engine([]() {
8340
    // std::random_device might actually be deterministic on some
8341
    // platforms, but due to lack of support in the c++ standard library,
8342
    // doing better requires either some ugly hacks or breaking portability.
8343
1
    std::random_device seed_gen;
8344
    // Request 128 bits of entropy for initialization
8345
1
    std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
8346
1
    return std::mt19937(seed_sequence);
8347
1
  }());
8348
8349
79
  std::string result;
8350
1.34k
  for (size_t i = 0; i < length; i++) {
8351
1.26k
    result += data[engine() % (sizeof(data) - 1)];
8352
1.26k
  }
8353
79
  return result;
8354
79
}
8355
8356
79
inline std::string make_multipart_data_boundary() {
8357
79
  return "--cpp-httplib-multipart-data-" + detail::random_string(16);
8358
79
}
8359
8360
0
inline bool is_multipart_boundary_chars_valid(const std::string &boundary) {
8361
0
  auto valid = true;
8362
0
  for (size_t i = 0; i < boundary.size(); i++) {
8363
0
    auto c = boundary[i];
8364
0
    if (!is_ascii_alnum(c) && c != '-' && c != '_') {
8365
0
      valid = false;
8366
0
      break;
8367
0
    }
8368
0
  }
8369
0
  return valid;
8370
0
}
8371
8372
// Escape a multipart field name/filename following the WHATWG HTML standard
8373
// ("escape a multipart form-data name"), which is what browsers send:
8374
// '"' -> %22, CR -> %0D, LF -> %0A
8375
// With escape_quote = false, only CR and LF are escaped; this is for header
8376
// values outside a quoted-string (e.g. Content-Type), where '"' is legal.
8377
inline std::string escape_multipart_field(const std::string &s,
8378
0
                                          bool escape_quote = true) {
8379
0
  std::string result;
8380
0
  result.reserve(s.size());
8381
0
  for (auto c : s) {
8382
0
    switch (c) {
8383
0
    case '"':
8384
0
      if (escape_quote) {
8385
0
        result += "%22";
8386
0
      } else {
8387
0
        result += c;
8388
0
      }
8389
0
      break;
8390
0
    case '\r': result += "%0D"; break;
8391
0
    case '\n': result += "%0A"; break;
8392
0
    default: result += c; break;
8393
0
    }
8394
0
  }
8395
0
  return result;
8396
0
}
8397
8398
template <typename T>
8399
inline std::string
8400
serialize_multipart_formdata_item_begin(const T &item,
8401
0
                                        const std::string &boundary) {
8402
0
  std::string body = "--" + boundary + "\r\n";
8403
0
  body += "Content-Disposition: form-data; name=\"" +
8404
0
          escape_multipart_field(item.name) + "\"";
8405
0
  if (!item.filename.empty()) {
8406
0
    body += "; filename=\"" + escape_multipart_field(item.filename) + "\"";
8407
0
  }
8408
0
  body += "\r\n";
8409
0
  if (!item.content_type.empty()) {
8410
0
    body +=
8411
0
        "Content-Type: " + escape_multipart_field(item.content_type, false) +
8412
0
        "\r\n";
8413
0
  }
8414
0
  body += "\r\n";
8415
0
8416
0
  return body;
8417
0
}
Unexecuted instantiation: std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > httplib::detail::serialize_multipart_formdata_item_begin<httplib::UploadFormData>(httplib::UploadFormData const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
Unexecuted instantiation: std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > httplib::detail::serialize_multipart_formdata_item_begin<httplib::FormDataProvider>(httplib::FormDataProvider const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
8418
8419
0
inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; }
8420
8421
inline std::string
8422
0
serialize_multipart_formdata_finish(const std::string &boundary) {
8423
0
  return "--" + boundary + "--\r\n";
8424
0
}
8425
8426
inline std::string
8427
0
serialize_multipart_formdata_get_content_type(const std::string &boundary) {
8428
0
  return "multipart/form-data; boundary=" + boundary;
8429
0
}
8430
8431
inline std::string
8432
serialize_multipart_formdata(const UploadFormDataItems &items,
8433
0
                             const std::string &boundary, bool finish = true) {
8434
0
  std::string body;
8435
0
8436
0
  for (const auto &item : items) {
8437
0
    body += serialize_multipart_formdata_item_begin(item, boundary);
8438
0
    body += item.content + serialize_multipart_formdata_item_end();
8439
0
  }
8440
0
8441
0
  if (finish) { body += serialize_multipart_formdata_finish(boundary); }
8442
0
8443
0
  return body;
8444
0
}
8445
8446
inline size_t get_multipart_content_length(const UploadFormDataItems &items,
8447
0
                                           const std::string &boundary) {
8448
0
  size_t total = 0;
8449
0
  for (const auto &item : items) {
8450
0
    total += serialize_multipart_formdata_item_begin(item, boundary).size();
8451
0
    total += item.content.size();
8452
0
    total += serialize_multipart_formdata_item_end().size();
8453
0
  }
8454
0
  total += serialize_multipart_formdata_finish(boundary).size();
8455
0
  return total;
8456
0
}
8457
8458
struct MultipartSegment {
8459
  const char *data;
8460
  size_t size;
8461
};
8462
8463
// NOTE: items must outlive the returned ContentProvider
8464
//       (safe for synchronous use inside Post/Put/Patch)
8465
inline ContentProvider
8466
make_multipart_content_provider(const UploadFormDataItems &items,
8467
0
                                const std::string &boundary) {
8468
0
  // Own the per-item header strings and the finish string
8469
0
  std::vector<std::string> owned;
8470
0
  owned.reserve(items.size() + 1);
8471
0
  for (const auto &item : items)
8472
0
    owned.push_back(serialize_multipart_formdata_item_begin(item, boundary));
8473
0
  owned.push_back(serialize_multipart_formdata_finish(boundary));
8474
0
8475
0
  // Flat segment list: [header, content, "\r\n"] * N + [finish]
8476
0
  std::vector<MultipartSegment> segs;
8477
0
  segs.reserve(items.size() * 3 + 1);
8478
0
  static const char crlf[] = "\r\n";
8479
0
  for (size_t i = 0; i < items.size(); i++) {
8480
0
    segs.push_back({owned[i].data(), owned[i].size()});
8481
0
    segs.push_back({items[i].content.data(), items[i].content.size()});
8482
0
    segs.push_back({crlf, 2});
8483
0
  }
8484
0
  segs.push_back({owned.back().data(), owned.back().size()});
8485
0
8486
0
  struct MultipartState {
8487
0
    std::vector<std::string> owned;
8488
0
    std::vector<MultipartSegment> segs;
8489
0
    std::vector<char> buf = std::vector<char>(CPPHTTPLIB_SEND_BUFSIZ);
8490
0
  };
8491
0
  auto state = std::make_shared<MultipartState>();
8492
0
  state->owned = std::move(owned);
8493
0
  // `segs` holds raw pointers into owned strings; std::string move preserves
8494
0
  // the data pointer, so these pointers remain valid after the move above.
8495
0
  state->segs = std::move(segs);
8496
0
8497
0
  return [state](size_t offset, size_t length, DataSink &sink) -> bool {
8498
0
    // Buffer multiple small segments into fewer, larger writes to avoid
8499
0
    // excessive TCP packets when there are many form data items (#2410)
8500
0
    auto &buf = state->buf;
8501
0
    auto buf_size = buf.size();
8502
0
    size_t buf_len = 0;
8503
0
    size_t remaining = length;
8504
0
8505
0
    // Find the first segment containing 'offset'
8506
0
    size_t pos = 0;
8507
0
    size_t seg_idx = 0;
8508
0
    for (; seg_idx < state->segs.size(); seg_idx++) {
8509
0
      const auto &seg = state->segs[seg_idx];
8510
0
      if (seg.size > 0 && offset - pos < seg.size) { break; }
8511
0
      pos += seg.size;
8512
0
    }
8513
0
8514
0
    size_t seg_offset = (seg_idx < state->segs.size()) ? offset - pos : 0;
8515
0
8516
0
    for (; seg_idx < state->segs.size() && remaining > 0; seg_idx++) {
8517
0
      const auto &seg = state->segs[seg_idx];
8518
0
      size_t available = seg.size - seg_offset;
8519
0
      size_t to_copy = (std::min)(available, remaining);
8520
0
      const char *src = seg.data + seg_offset;
8521
0
      seg_offset = 0; // only the first segment has a non-zero offset
8522
0
8523
0
      while (to_copy > 0) {
8524
0
        size_t space = buf_size - buf_len;
8525
0
        size_t chunk = (std::min)(to_copy, space);
8526
0
        std::memcpy(buf.data() + buf_len, src, chunk);
8527
0
        buf_len += chunk;
8528
0
        src += chunk;
8529
0
        to_copy -= chunk;
8530
0
        remaining -= chunk;
8531
0
8532
0
        if (buf_len == buf_size) {
8533
0
          if (!sink.write(buf.data(), buf_len)) { return false; }
8534
0
          buf_len = 0;
8535
0
        }
8536
0
      }
8537
0
    }
8538
0
8539
0
    if (buf_len > 0) { return sink.write(buf.data(), buf_len); }
8540
0
    return true;
8541
0
  };
8542
0
}
8543
8544
260
inline void coalesce_ranges(Ranges &ranges, size_t content_length) {
8545
260
  if (ranges.size() <= 1) return;
8546
8547
  // Sort ranges by start position
8548
113
  std::sort(ranges.begin(), ranges.end(),
8549
701
            [](const Range &a, const Range &b) { return a.first < b.first; });
8550
8551
113
  Ranges coalesced;
8552
113
  coalesced.reserve(ranges.size());
8553
8554
460
  for (auto &r : ranges) {
8555
460
    auto first_pos = r.first;
8556
460
    auto last_pos = r.second;
8557
8558
    // Handle special cases like in range_error
8559
460
    if (first_pos == -1 && last_pos == -1) {
8560
0
      first_pos = 0;
8561
0
      last_pos = static_cast<ssize_t>(content_length);
8562
0
    }
8563
8564
460
    if (first_pos == -1) {
8565
0
      first_pos = static_cast<ssize_t>(content_length) - last_pos;
8566
0
      last_pos = static_cast<ssize_t>(content_length) - 1;
8567
0
    }
8568
8569
460
    if (last_pos == -1 || last_pos >= static_cast<ssize_t>(content_length)) {
8570
0
      last_pos = static_cast<ssize_t>(content_length) - 1;
8571
0
    }
8572
8573
    // Skip invalid ranges
8574
460
    if (!(0 <= first_pos && first_pos <= last_pos &&
8575
460
          last_pos < static_cast<ssize_t>(content_length))) {
8576
0
      continue;
8577
0
    }
8578
8579
    // Coalesce with previous range if overlapping or adjacent (but not
8580
    // identical)
8581
460
    if (!coalesced.empty()) {
8582
347
      auto &prev = coalesced.back();
8583
      // Check if current range overlaps or is adjacent to previous range
8584
      // but don't coalesce identical ranges (allow duplicates)
8585
347
      if (first_pos <= prev.second + 1 &&
8586
229
          !(first_pos == prev.first && last_pos == prev.second)) {
8587
        // Extend the previous range
8588
195
        prev.second = (std::max)(prev.second, last_pos);
8589
195
        continue;
8590
195
      }
8591
347
    }
8592
8593
    // Add new range
8594
265
    coalesced.emplace_back(first_pos, last_pos);
8595
265
  }
8596
8597
113
  ranges = std::move(coalesced);
8598
113
}
8599
8600
2.21k
inline bool range_error(Request &req, Response &res) {
8601
2.21k
  if (!req.ranges.empty() && 200 <= res.status && res.status < 300) {
8602
566
    if (res.body.empty() && res.content_provider_ && res.content_length_ == 0) {
8603
0
      req.ranges.clear();
8604
0
      if (res.status == StatusCode::PartialContent_206) {
8605
0
        res.status = StatusCode::OK_200;
8606
0
      }
8607
0
      return false;
8608
0
    }
8609
8610
566
    ssize_t content_len = static_cast<ssize_t>(
8611
566
        res.content_length_ ? res.content_length_ : res.body.size());
8612
8613
566
    std::vector<std::pair<ssize_t, ssize_t>> processed_ranges;
8614
566
    size_t overwrapping_count = 0;
8615
8616
    // NOTE: The following Range check is based on '14.2. Range' in RFC 9110
8617
    // 'HTTP Semantics' to avoid potential denial-of-service attacks.
8618
    // https://www.rfc-editor.org/rfc/rfc9110#section-14.2
8619
8620
    // Too many ranges
8621
566
    if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return true; }
8622
8623
1.18k
    for (auto &r : req.ranges) {
8624
1.18k
      auto &first_pos = r.first;
8625
1.18k
      auto &last_pos = r.second;
8626
8627
1.18k
      if (first_pos == -1 && last_pos == -1) {
8628
0
        first_pos = 0;
8629
0
        last_pos = content_len;
8630
0
      }
8631
8632
1.18k
      if (first_pos == -1) {
8633
409
        first_pos = content_len - last_pos;
8634
409
        last_pos = content_len - 1;
8635
409
      }
8636
8637
      // NOTE: RFC-9110 '14.1.2. Byte Ranges':
8638
      // A client can limit the number of bytes requested without knowing the
8639
      // size of the selected representation. If the last-pos value is absent,
8640
      // or if the value is greater than or equal to the current length of the
8641
      // representation data, the byte range is interpreted as the remainder of
8642
      // the representation (i.e., the server replaces the value of last-pos
8643
      // with a value that is one less than the current length of the selected
8644
      // representation).
8645
      // https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6
8646
1.18k
      if (last_pos == -1 || last_pos >= content_len) {
8647
386
        last_pos = content_len - 1;
8648
386
      }
8649
8650
      // Range must be within content length
8651
1.18k
      if (!(0 <= first_pos && first_pos <= last_pos &&
8652
922
            last_pos <= content_len - 1)) {
8653
262
        return true;
8654
262
      }
8655
8656
      // Request must not have more than two overlapping ranges
8657
1.55k
      for (const auto &processed_range : processed_ranges) {
8658
1.55k
        if (!(last_pos < processed_range.first ||
8659
1.04k
              first_pos > processed_range.second)) {
8660
275
          overwrapping_count++;
8661
275
          if (overwrapping_count > 2) { return true; }
8662
232
          break; // Only count once per range
8663
275
        }
8664
1.55k
      }
8665
8666
879
      processed_ranges.emplace_back(first_pos, last_pos);
8667
879
    }
8668
8669
    // After validation, coalesce overlapping ranges as per RFC 9110
8670
260
    coalesce_ranges(req.ranges, static_cast<size_t>(content_len));
8671
260
  }
8672
8673
1.90k
  return false;
8674
2.21k
}
8675
8676
inline std::pair<size_t, size_t>
8677
412
get_range_offset_and_length(Range r, size_t content_length) {
8678
412
  assert(r.first != -1 && r.second != -1);
8679
412
  assert(0 <= r.first && r.first < static_cast<ssize_t>(content_length));
8680
412
  assert(r.first <= r.second &&
8681
412
         r.second < static_cast<ssize_t>(content_length));
8682
412
  (void)(content_length);
8683
412
  return std::make_pair(static_cast<size_t>(r.first),
8684
412
                        static_cast<size_t>(r.second - r.first) + 1);
8685
412
}
8686
8687
inline std::string make_content_range_header_field(
8688
412
    const std::pair<size_t, size_t> &offset_and_length, size_t content_length) {
8689
412
  auto st = offset_and_length.first;
8690
412
  auto ed = st + offset_and_length.second - 1;
8691
8692
412
  std::string field = "bytes ";
8693
412
  field += std::to_string(st);
8694
412
  field += '-';
8695
412
  field += std::to_string(ed);
8696
412
  field += '/';
8697
412
  field += std::to_string(content_length);
8698
412
  return field;
8699
412
}
8700
8701
template <typename SToken, typename CToken, typename Content>
8702
bool process_multipart_ranges_data(const Request &req,
8703
                                   const std::string &boundary,
8704
                                   const std::string &content_type,
8705
                                   size_t content_length, SToken stoken,
8706
79
                                   CToken ctoken, Content content) {
8707
310
  for (size_t i = 0; i < req.ranges.size(); i++) {
8708
231
    ctoken("--");
8709
231
    stoken(boundary);
8710
231
    ctoken("\r\n");
8711
231
    if (!content_type.empty()) {
8712
231
      ctoken("Content-Type: ");
8713
231
      stoken(content_type);
8714
231
      ctoken("\r\n");
8715
231
    }
8716
8717
231
    auto offset_and_length =
8718
231
        get_range_offset_and_length(req.ranges[i], content_length);
8719
8720
231
    ctoken("Content-Range: ");
8721
231
    stoken(make_content_range_header_field(offset_and_length, content_length));
8722
231
    ctoken("\r\n");
8723
231
    ctoken("\r\n");
8724
8725
231
    if (!content(offset_and_length.first, offset_and_length.second)) {
8726
0
      return false;
8727
0
    }
8728
231
    ctoken("\r\n");
8729
231
  }
8730
8731
79
  ctoken("--");
8732
79
  stoken(boundary);
8733
79
  ctoken("--");
8734
8735
79
  return true;
8736
79
}
Unexecuted instantiation: bool httplib::detail::process_multipart_ranges_data<httplib::detail::get_multipart_ranges_data_length(httplib::Request const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#1}, httplib::detail::get_multipart_ranges_data_length(httplib::Request const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#2}, httplib::detail::get_multipart_ranges_data_length(httplib::Request const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long)::{lambda(unsigned long, unsigned long)#1}>(httplib::Request const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, httplib::detail::get_multipart_ranges_data_length(httplib::Request const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#1}, httplib::detail::get_multipart_ranges_data_length(httplib::Request const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#2}, httplib::detail::get_multipart_ranges_data_length(httplib::Request const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long)::{lambda(unsigned long, unsigned long)#1})
bool httplib::detail::process_multipart_ranges_data<httplib::detail::make_multipart_ranges_data(httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#1}, httplib::detail::make_multipart_ranges_data(httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#2}, httplib::detail::make_multipart_ranges_data(httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)::{lambda(unsigned long, unsigned long)#1}>(httplib::Request const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, httplib::detail::make_multipart_ranges_data(httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#1}, httplib::detail::make_multipart_ranges_data(httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#2}, httplib::detail::make_multipart_ranges_data(httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)::{lambda(unsigned long, unsigned long)#1})
Line
Count
Source
8706
79
                                   CToken ctoken, Content content) {
8707
310
  for (size_t i = 0; i < req.ranges.size(); i++) {
8708
231
    ctoken("--");
8709
231
    stoken(boundary);
8710
231
    ctoken("\r\n");
8711
231
    if (!content_type.empty()) {
8712
231
      ctoken("Content-Type: ");
8713
231
      stoken(content_type);
8714
231
      ctoken("\r\n");
8715
231
    }
8716
8717
231
    auto offset_and_length =
8718
231
        get_range_offset_and_length(req.ranges[i], content_length);
8719
8720
231
    ctoken("Content-Range: ");
8721
231
    stoken(make_content_range_header_field(offset_and_length, content_length));
8722
231
    ctoken("\r\n");
8723
231
    ctoken("\r\n");
8724
8725
231
    if (!content(offset_and_length.first, offset_and_length.second)) {
8726
0
      return false;
8727
0
    }
8728
231
    ctoken("\r\n");
8729
231
  }
8730
8731
79
  ctoken("--");
8732
79
  stoken(boundary);
8733
79
  ctoken("--");
8734
8735
79
  return true;
8736
79
}
Unexecuted instantiation: bool httplib::detail::process_multipart_ranges_data<httplib::detail::write_multipart_ranges_data<httplib::Server::write_content_with_provider(httplib::Stream&, httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::{lambda()#1}>(httplib::Stream&, httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, httplib::Server::write_content_with_provider(httplib::Stream&, httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::{lambda()#1} const&)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#1}, httplib::detail::write_multipart_ranges_data<{lambda()#1}>(httplib::Stream&, httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, {lambda()#1} const)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#2}, httplib::detail::write_multipart_ranges_data<{lambda()#1}>(httplib::Stream&, httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, {lambda()#1} const)::{lambda(unsigned long, unsigned long)#1}>(httplib::Request const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, httplib::Server::write_content_with_provider(httplib::Stream&, httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::{lambda()#1}, httplib::detail::write_multipart_ranges_data<{lambda()#1}>(httplib::Stream&, httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, {lambda()#1} const)::{lambda(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)#2}, httplib::detail::write_multipart_ranges_data<{lambda()#1}>(httplib::Stream&, httplib::Request const&, httplib::Response&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, unsigned long, {lambda()#1} const)::{lambda(unsigned long, unsigned long)#1})
8737
8738
inline void make_multipart_ranges_data(const Request &req, Response &res,
8739
                                       const std::string &boundary,
8740
                                       const std::string &content_type,
8741
                                       size_t content_length,
8742
79
                                       std::string &data) {
8743
79
  process_multipart_ranges_data(
8744
79
      req, boundary, content_type, content_length,
8745
772
      [&](const std::string &token) { data += token; },
8746
2.00k
      [&](const std::string &token) { data += token; },
8747
231
      [&](size_t offset, size_t length) {
8748
231
        assert(offset + length <= content_length);
8749
231
        data += res.body.substr(offset, length);
8750
231
        return true;
8751
231
      });
8752
79
}
8753
8754
inline size_t get_multipart_ranges_data_length(const Request &req,
8755
                                               const std::string &boundary,
8756
                                               const std::string &content_type,
8757
0
                                               size_t content_length) {
8758
0
  size_t data_length = 0;
8759
8760
0
  process_multipart_ranges_data(
8761
0
      req, boundary, content_type, content_length,
8762
0
      [&](const std::string &token) { data_length += token.size(); },
8763
0
      [&](const std::string &token) { data_length += token.size(); },
8764
0
      [&](size_t /*offset*/, size_t length) {
8765
0
        data_length += length;
8766
0
        return true;
8767
0
      });
8768
8769
0
  return data_length;
8770
0
}
8771
8772
template <typename T>
8773
inline bool
8774
write_multipart_ranges_data(Stream &strm, const Request &req, Response &res,
8775
                            const std::string &boundary,
8776
                            const std::string &content_type,
8777
0
                            size_t content_length, const T &is_shutting_down) {
8778
0
  return process_multipart_ranges_data(
8779
0
      req, boundary, content_type, content_length,
8780
0
      [&](const std::string &token) { strm.write(token); },
8781
0
      [&](const std::string &token) { strm.write(token); },
8782
0
      [&](size_t offset, size_t length) {
8783
0
        return write_content(strm, res.content_provider_, offset, length,
8784
0
                             is_shutting_down);
8785
0
      });
8786
0
}
8787
8788
7.90k
inline bool has_framed_body(const Request &req) {
8789
7.90k
  return is_chunked_transfer_encoding(req.headers) ||
8790
5.99k
         req.get_header_value_u64("Content-Length") > 0;
8791
7.90k
}
8792
8793
1.45k
inline bool is_connection_persistent(const Request &req) {
8794
1.45k
  auto conn = req.get_header_value("Connection");
8795
1.45k
  if (conn == "close") { return false; }
8796
1.45k
  if (req.version == "HTTP/1.0" && conn != "Keep-Alive") { return false; }
8797
292
  return true;
8798
1.45k
}
8799
8800
8.73k
inline bool expect_content(const Request &req) {
8801
8.73k
  if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
8802
5.30k
      req.method == "DELETE") {
8803
5.30k
    return true;
8804
5.30k
  }
8805
3.42k
  return has_framed_body(req);
8806
8.73k
}
8807
8808
#ifdef _WIN32
8809
class WSInit {
8810
public:
8811
  WSInit() {
8812
    WSADATA wsaData;
8813
    if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true;
8814
  }
8815
8816
  ~WSInit() {
8817
    if (is_valid_) WSACleanup();
8818
  }
8819
8820
  bool is_valid_ = false;
8821
};
8822
8823
static WSInit wsinit_;
8824
#endif
8825
8826
inline bool parse_www_authenticate(const Response &res,
8827
                                   std::map<std::string, std::string> &auth,
8828
0
                                   bool is_proxy) {
8829
0
  auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
8830
0
  if (res.has_header(auth_key)) {
8831
0
    thread_local auto re =
8832
0
        std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
8833
0
    auto s = res.get_header_value(auth_key);
8834
0
    auto pos = s.find(' ');
8835
0
    if (pos != std::string::npos) {
8836
0
      auto type = s.substr(0, pos);
8837
0
      if (type == "Basic") {
8838
0
        return false;
8839
0
      } else if (type == "Digest") {
8840
0
        s = s.substr(pos + 1);
8841
0
        auto beg = std::sregex_iterator(s.begin(), s.end(), re);
8842
0
        for (auto i = beg; i != std::sregex_iterator(); ++i) {
8843
0
          const auto &m = *i;
8844
0
          auto key = s.substr(static_cast<size_t>(m.position(1)),
8845
0
                              static_cast<size_t>(m.length(1)));
8846
0
          auto val = m.length(2) > 0
8847
0
                         ? s.substr(static_cast<size_t>(m.position(2)),
8848
0
                                    static_cast<size_t>(m.length(2)))
8849
0
                         : s.substr(static_cast<size_t>(m.position(3)),
8850
0
                                    static_cast<size_t>(m.length(3)));
8851
0
          auth[std::move(key)] = std::move(val);
8852
0
        }
8853
0
        return true;
8854
0
      }
8855
0
    }
8856
0
  }
8857
0
  return false;
8858
0
}
8859
8860
class ContentProviderAdapter {
8861
public:
8862
  explicit ContentProviderAdapter(
8863
      ContentProviderWithoutLength &&content_provider)
8864
0
      : content_provider_(std::move(content_provider)) {}
8865
8866
0
  bool operator()(size_t offset, size_t, DataSink &sink) {
8867
0
    return content_provider_(offset, sink);
8868
0
  }
8869
8870
private:
8871
  ContentProviderWithoutLength content_provider_;
8872
};
8873
8874
// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
8875
namespace fields {
8876
8877
1.60M
inline bool is_token_char(char c) {
8878
1.60M
  return is_ascii_alnum(c) || c == '!' || c == '#' || c == '$' || c == '%' ||
8879
129k
         c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' ||
8880
54.4k
         c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
8881
1.60M
}
8882
8883
172k
inline bool is_token(const std::string &s) {
8884
172k
  if (s.empty()) { return false; }
8885
1.60M
  for (auto c : s) {
8886
1.60M
    if (!is_token_char(c)) { return false; }
8887
1.60M
  }
8888
171k
  return true;
8889
171k
}
8890
8891
172k
inline bool is_field_name(const std::string &s) { return is_token(s); }
8892
8893
5.05M
inline bool is_vchar(char c) { return c >= 33 && c <= 126; }
8894
8895
267k
inline bool is_obs_text(char c) { return 128 <= static_cast<unsigned char>(c); }
8896
8897
5.05M
inline bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); }
8898
8899
171k
inline bool is_field_content(const std::string &s) {
8900
171k
  if (s.empty()) { return true; }
8901
8902
145k
  if (s.size() == 1) {
8903
81.4k
    return is_field_vchar(s[0]);
8904
81.4k
  } else if (s.size() == 2) {
8905
4.60k
    return is_field_vchar(s[0]) && is_field_vchar(s[1]);
8906
59.1k
  } else {
8907
59.1k
    size_t i = 0;
8908
8909
59.1k
    if (!is_field_vchar(s[i])) { return false; }
8910
59.1k
    i++;
8911
8912
4.99M
    while (i < s.size() - 1) {
8913
4.93M
      auto c = s[i++];
8914
4.93M
      if (c == ' ' || c == '\t' || is_field_vchar(c)) {
8915
4.93M
      } else {
8916
31
        return false;
8917
31
      }
8918
4.93M
    }
8919
8920
59.0k
    return is_field_vchar(s[i]);
8921
59.1k
  }
8922
145k
}
8923
8924
171k
inline bool is_field_value(const std::string &s) { return is_field_content(s); }
8925
8926
} // namespace fields
8927
8928
inline bool perform_websocket_handshake(Stream &strm, const std::string &host,
8929
                                        int port, bool is_ssl,
8930
                                        const std::string &path,
8931
                                        const Headers &headers,
8932
0
                                        std::string &selected_subprotocol) {
8933
0
  // Validate path and host
8934
0
  if (!fields::is_field_value(path) || !fields::is_field_value(host)) {
8935
0
    return false;
8936
0
  }
8937
0
8938
0
  // Validate user-provided headers
8939
0
  for (const auto &h : headers) {
8940
0
    if (!fields::is_field_name(h.first) || !fields::is_field_value(h.second)) {
8941
0
      return false;
8942
0
    }
8943
0
  }
8944
0
8945
0
  // Generate random Sec-WebSocket-Key
8946
0
  thread_local std::mt19937 rng(std::random_device{}());
8947
0
  std::string key_bytes(16, '\0');
8948
0
  for (size_t i = 0; i < 16; i += 4) {
8949
0
    auto r = rng();
8950
0
    std::memcpy(&key_bytes[i], &r, (std::min)(size_t(4), size_t(16 - i)));
8951
0
  }
8952
0
  auto client_key = base64_encode(key_bytes);
8953
0
8954
0
  // Build upgrade request
8955
0
  std::string req_str = "GET " + path + " HTTP/1.1\r\n";
8956
0
  req_str += "Host: " + make_host_and_port_string(host, port, is_ssl) + "\r\n";
8957
0
  req_str += "Upgrade: websocket\r\n";
8958
0
  req_str += "Connection: Upgrade\r\n";
8959
0
  req_str += "Sec-WebSocket-Key: " + client_key + "\r\n";
8960
0
  req_str += "Sec-WebSocket-Version: 13\r\n";
8961
0
  for (const auto &h : headers) {
8962
0
    req_str += h.first + ": " + h.second + "\r\n";
8963
0
  }
8964
0
  req_str += "\r\n";
8965
0
8966
0
  if (strm.write(req_str.data(), req_str.size()) < 0) { return false; }
8967
0
8968
0
  // Verify 101 response and Sec-WebSocket-Accept header
8969
0
  auto expected_accept = websocket_accept_key(client_key);
8970
0
  return read_websocket_upgrade_response(strm, expected_accept,
8971
0
                                         selected_subprotocol);
8972
0
}
8973
8974
} // namespace detail
8975
8976
/*
8977
 * Group 2: detail namespace - SSL common utilities
8978
 */
8979
8980
#ifdef CPPHTTPLIB_SSL_ENABLED
8981
namespace detail {
8982
8983
class SSLSocketStream final : public Stream {
8984
public:
8985
  SSLSocketStream(
8986
      socket_t sock, tls::session_t session, time_t read_timeout_sec,
8987
      time_t read_timeout_usec, time_t write_timeout_sec,
8988
      time_t write_timeout_usec, time_t max_timeout_msec = 0,
8989
      std::chrono::time_point<std::chrono::steady_clock> start_time =
8990
          (std::chrono::steady_clock::time_point::min)());
8991
  ~SSLSocketStream() override;
8992
8993
  bool is_readable() const override;
8994
  bool wait_readable() const override;
8995
  bool wait_writable() const override;
8996
  bool is_peer_alive() const override;
8997
  ssize_t read(char *ptr, size_t size) override;
8998
  ssize_t write(const char *ptr, size_t size) override;
8999
  void get_remote_ip_and_port(std::string &ip, int &port) const override;
9000
  void get_local_ip_and_port(std::string &ip, int &port) const override;
9001
  socket_t socket() const override;
9002
  time_t duration() const override;
9003
  void set_read_timeout(time_t sec, time_t usec = 0) override;
9004
9005
private:
9006
  socket_t sock_;
9007
  tls::session_t session_;
9008
  time_t read_timeout_sec_;
9009
  time_t read_timeout_usec_;
9010
  time_t write_timeout_sec_;
9011
  time_t write_timeout_usec_;
9012
  time_t max_timeout_msec_;
9013
  const std::chrono::time_point<std::chrono::steady_clock> start_time_;
9014
};
9015
9016
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
9017
inline std::string message_digest(const std::string &s, const EVP_MD *algo) {
9018
  auto context = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(
9019
      EVP_MD_CTX_new(), EVP_MD_CTX_free);
9020
9021
  unsigned int hash_length = 0;
9022
  unsigned char hash[EVP_MAX_MD_SIZE];
9023
9024
  EVP_DigestInit_ex(context.get(), algo, nullptr);
9025
  EVP_DigestUpdate(context.get(), s.c_str(), s.size());
9026
  EVP_DigestFinal_ex(context.get(), hash, &hash_length);
9027
9028
  std::stringstream ss;
9029
  for (auto i = 0u; i < hash_length; ++i) {
9030
    ss << std::hex << std::setw(2) << std::setfill('0')
9031
       << static_cast<unsigned int>(hash[i]);
9032
  }
9033
9034
  return ss.str();
9035
}
9036
9037
inline std::string MD5(const std::string &s) {
9038
  return message_digest(s, EVP_md5());
9039
}
9040
9041
inline std::string SHA_256(const std::string &s) {
9042
  return message_digest(s, EVP_sha256());
9043
}
9044
9045
inline std::string SHA_512(const std::string &s) {
9046
  return message_digest(s, EVP_sha512());
9047
}
9048
#elif defined(CPPHTTPLIB_MBEDTLS_SUPPORT)
9049
namespace {
9050
template <size_t N>
9051
inline std::string hash_to_hex(const unsigned char (&hash)[N]) {
9052
  std::stringstream ss;
9053
  for (size_t i = 0; i < N; ++i) {
9054
    ss << std::hex << std::setw(2) << std::setfill('0')
9055
       << static_cast<unsigned int>(hash[i]);
9056
  }
9057
  return ss.str();
9058
}
9059
} // namespace
9060
9061
inline std::string MD5(const std::string &s) {
9062
  unsigned char hash[16];
9063
#ifdef CPPHTTPLIB_MBEDTLS_V3
9064
  mbedtls_md5(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
9065
              hash);
9066
#else
9067
  mbedtls_md5_ret(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
9068
                  hash);
9069
#endif
9070
  return hash_to_hex(hash);
9071
}
9072
9073
inline std::string SHA_256(const std::string &s) {
9074
  unsigned char hash[32];
9075
#ifdef CPPHTTPLIB_MBEDTLS_V3
9076
  mbedtls_sha256(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
9077
                 hash, 0);
9078
#else
9079
  mbedtls_sha256_ret(reinterpret_cast<const unsigned char *>(s.c_str()),
9080
                     s.size(), hash, 0);
9081
#endif
9082
  return hash_to_hex(hash);
9083
}
9084
9085
inline std::string SHA_512(const std::string &s) {
9086
  unsigned char hash[64];
9087
#ifdef CPPHTTPLIB_MBEDTLS_V3
9088
  mbedtls_sha512(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
9089
                 hash, 0);
9090
#else
9091
  mbedtls_sha512_ret(reinterpret_cast<const unsigned char *>(s.c_str()),
9092
                     s.size(), hash, 0);
9093
#endif
9094
  return hash_to_hex(hash);
9095
}
9096
#elif defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
9097
namespace {
9098
template <size_t N>
9099
inline std::string hash_to_hex(const unsigned char (&hash)[N]) {
9100
  std::stringstream ss;
9101
  for (size_t i = 0; i < N; ++i) {
9102
    ss << std::hex << std::setw(2) << std::setfill('0')
9103
       << static_cast<unsigned int>(hash[i]);
9104
  }
9105
  return ss.str();
9106
}
9107
} // namespace
9108
9109
inline std::string MD5(const std::string &s) {
9110
  unsigned char hash[WC_MD5_DIGEST_SIZE];
9111
  wc_Md5Hash(reinterpret_cast<const unsigned char *>(s.c_str()),
9112
             static_cast<word32>(s.size()), hash);
9113
  return hash_to_hex(hash);
9114
}
9115
9116
inline std::string SHA_256(const std::string &s) {
9117
  unsigned char hash[WC_SHA256_DIGEST_SIZE];
9118
  wc_Sha256Hash(reinterpret_cast<const unsigned char *>(s.c_str()),
9119
                static_cast<word32>(s.size()), hash);
9120
  return hash_to_hex(hash);
9121
}
9122
9123
inline std::string SHA_512(const std::string &s) {
9124
  unsigned char hash[WC_SHA512_DIGEST_SIZE];
9125
  wc_Sha512Hash(reinterpret_cast<const unsigned char *>(s.c_str()),
9126
                static_cast<word32>(s.size()), hash);
9127
  return hash_to_hex(hash);
9128
}
9129
#endif
9130
9131
inline bool is_ip_address(const std::string &host) {
9132
  struct in_addr addr4;
9133
  struct in6_addr addr6;
9134
  return inet_pton(AF_INET, host.c_str(), &addr4) == 1 ||
9135
         inet_pton(AF_INET6, host.c_str(), &addr6) == 1;
9136
}
9137
9138
template <typename T>
9139
inline bool process_server_socket_ssl(
9140
    const std::atomic<socket_t> &svr_sock, tls::session_t session,
9141
    socket_t sock, size_t keep_alive_max_count, time_t keep_alive_timeout_sec,
9142
    time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
9143
    time_t write_timeout_usec, T callback) {
9144
  return process_server_socket_core(
9145
      svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
9146
      [&](bool close_connection, bool &connection_closed) {
9147
        SSLSocketStream strm(sock, session, read_timeout_sec, read_timeout_usec,
9148
                             write_timeout_sec, write_timeout_usec);
9149
        return callback(strm, close_connection, connection_closed);
9150
      });
9151
}
9152
9153
template <typename T>
9154
inline bool process_client_socket_ssl(
9155
    tls::session_t session, socket_t sock, time_t read_timeout_sec,
9156
    time_t read_timeout_usec, time_t write_timeout_sec,
9157
    time_t write_timeout_usec, time_t max_timeout_msec,
9158
    std::chrono::time_point<std::chrono::steady_clock> start_time, T callback) {
9159
  SSLSocketStream strm(sock, session, read_timeout_sec, read_timeout_usec,
9160
                       write_timeout_sec, write_timeout_usec, max_timeout_msec,
9161
                       start_time);
9162
  return callback(strm);
9163
}
9164
9165
inline std::pair<std::string, std::string> make_digest_authentication_header(
9166
    const Request &req, const std::map<std::string, std::string> &auth,
9167
    size_t cnonce_count, const std::string &cnonce, const std::string &username,
9168
    const std::string &password, bool is_proxy = false) {
9169
  std::string nc;
9170
  {
9171
    std::stringstream ss;
9172
    ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count;
9173
    nc = ss.str();
9174
  }
9175
9176
  std::string qop;
9177
  if (auth.find("qop") != auth.end()) {
9178
    qop = auth.at("qop");
9179
    if (qop.find("auth-int") != std::string::npos) {
9180
      qop = "auth-int";
9181
    } else if (qop.find("auth") != std::string::npos) {
9182
      qop = "auth";
9183
    } else {
9184
      qop.clear();
9185
    }
9186
  }
9187
9188
  std::string algo = "MD5";
9189
  if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
9190
9191
  std::string response;
9192
  {
9193
    auto H = algo == "SHA-256"   ? detail::SHA_256
9194
             : algo == "SHA-512" ? detail::SHA_512
9195
                                 : detail::MD5;
9196
9197
    auto A1 = username + ":" + auth.at("realm") + ":" + password;
9198
9199
    auto A2 = req.method + ":" + req.path;
9200
    if (qop == "auth-int") { A2 += ":" + H(req.body); }
9201
9202
    if (qop.empty()) {
9203
      response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2));
9204
    } else {
9205
      response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
9206
                   ":" + qop + ":" + H(A2));
9207
    }
9208
  }
9209
9210
  auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : "";
9211
9212
  auto field = "Digest username=\"" + username + "\", realm=\"" +
9213
               auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
9214
               "\", uri=\"" + req.path + "\", algorithm=" + algo +
9215
               (qop.empty() ? ", response=\""
9216
                            : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" +
9217
                                  cnonce + "\", response=\"") +
9218
               response + "\"" +
9219
               (opaque.empty() ? "" : ", opaque=\"" + opaque + "\"");
9220
9221
  auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
9222
  return std::make_pair(key, field);
9223
}
9224
9225
inline bool match_hostname(const std::string &pattern,
9226
                           const std::string &hostname) {
9227
  // Exact match (case-insensitive)
9228
  if (detail::case_ignore::equal(hostname, pattern)) { return true; }
9229
9230
  // Split both pattern and hostname into components by '.'
9231
  std::vector<std::string> pattern_components;
9232
  if (!pattern.empty()) {
9233
    split(pattern.data(), pattern.data() + pattern.size(), '.',
9234
          [&](const char *b, const char *e) {
9235
            pattern_components.emplace_back(b, e);
9236
          });
9237
  }
9238
9239
  std::vector<std::string> host_components;
9240
  if (!hostname.empty()) {
9241
    split(hostname.data(), hostname.data() + hostname.size(), '.',
9242
          [&](const char *b, const char *e) {
9243
            host_components.emplace_back(b, e);
9244
          });
9245
  }
9246
9247
  // Component count must match
9248
  if (host_components.size() != pattern_components.size()) { return false; }
9249
9250
  // Compare each component with wildcard support
9251
  // Supports: "*" (full wildcard), "prefix*" (partial wildcard)
9252
  // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
9253
  auto itr = pattern_components.begin();
9254
  for (const auto &h : host_components) {
9255
    auto &p = *itr;
9256
    if (!detail::case_ignore::equal(p, h) && p != "*") {
9257
      bool partial_match = false;
9258
      if (!p.empty() && p[p.size() - 1] == '*') {
9259
        const auto prefix_length = p.size() - 1;
9260
        if (prefix_length == 0) {
9261
          partial_match = true;
9262
        } else if (h.size() >= prefix_length) {
9263
          partial_match =
9264
              std::equal(p.begin(),
9265
                         p.begin() + static_cast<std::string::difference_type>(
9266
                                         prefix_length),
9267
                         h.begin(), [](const char ca, const char cb) {
9268
                           return detail::case_ignore::to_lower(ca) ==
9269
                                  detail::case_ignore::to_lower(cb);
9270
                         });
9271
        }
9272
      }
9273
      if (!partial_match) { return false; }
9274
    }
9275
    ++itr;
9276
  }
9277
9278
  return true;
9279
}
9280
9281
#ifdef _WIN32
9282
// Verify certificate using Windows CertGetCertificateChain API.
9283
// This provides real-time certificate validation with Windows Update
9284
// integration, independent of the TLS backend (OpenSSL or MbedTLS).
9285
inline bool
9286
verify_cert_with_windows_schannel(const std::vector<unsigned char> &der_cert,
9287
                                  const std::string &hostname,
9288
                                  bool verify_hostname, uint64_t &out_error) {
9289
  if (der_cert.empty()) { return false; }
9290
9291
  out_error = 0;
9292
9293
  // Create Windows certificate context from DER data
9294
  auto cert_context = CertCreateCertificateContext(
9295
      X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, der_cert.data(),
9296
      static_cast<DWORD>(der_cert.size()));
9297
9298
  if (!cert_context) {
9299
    out_error = GetLastError();
9300
    return false;
9301
  }
9302
9303
  auto cert_guard =
9304
      scope_exit([&] { CertFreeCertificateContext(cert_context); });
9305
9306
  // Setup chain parameters
9307
  CERT_CHAIN_PARA chain_para = {};
9308
  chain_para.cbSize = sizeof(chain_para);
9309
9310
  // Build certificate chain with revocation checking
9311
  PCCERT_CHAIN_CONTEXT chain_context = nullptr;
9312
  auto chain_result = CertGetCertificateChain(
9313
      nullptr, cert_context, nullptr, cert_context->hCertStore, &chain_para,
9314
      CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_END_CERT |
9315
          CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT,
9316
      nullptr, &chain_context);
9317
9318
  if (!chain_result || !chain_context) {
9319
    out_error = GetLastError();
9320
    return false;
9321
  }
9322
9323
  auto chain_guard =
9324
      scope_exit([&] { CertFreeCertificateChain(chain_context); });
9325
9326
  // Check if chain has errors
9327
  if (chain_context->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) {
9328
    out_error = chain_context->TrustStatus.dwErrorStatus;
9329
    return false;
9330
  }
9331
9332
  // Verify SSL policy
9333
  SSL_EXTRA_CERT_CHAIN_POLICY_PARA extra_policy_para = {};
9334
  extra_policy_para.cbSize = sizeof(extra_policy_para);
9335
#ifdef AUTHTYPE_SERVER
9336
  extra_policy_para.dwAuthType = AUTHTYPE_SERVER;
9337
#endif
9338
9339
  std::wstring whost;
9340
  if (verify_hostname) {
9341
    whost = u8string_to_wstring(hostname.c_str());
9342
    extra_policy_para.pwszServerName = const_cast<wchar_t *>(whost.c_str());
9343
  }
9344
9345
  CERT_CHAIN_POLICY_PARA policy_para = {};
9346
  policy_para.cbSize = sizeof(policy_para);
9347
#ifdef CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS
9348
  policy_para.dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS;
9349
#else
9350
  policy_para.dwFlags = 0;
9351
#endif
9352
  policy_para.pvExtraPolicyPara = &extra_policy_para;
9353
9354
  CERT_CHAIN_POLICY_STATUS policy_status = {};
9355
  policy_status.cbSize = sizeof(policy_status);
9356
9357
  if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, chain_context,
9358
                                        &policy_para, &policy_status)) {
9359
    out_error = GetLastError();
9360
    return false;
9361
  }
9362
9363
  if (policy_status.dwError != 0) {
9364
    out_error = policy_status.dwError;
9365
    return false;
9366
  }
9367
9368
  return true;
9369
}
9370
#endif // _WIN32
9371
9372
// Loads CA file/dir configuration and applies the system CA policy to a
9373
// client TLS context. PEM data and native stores are applied to the context
9374
// directly at set time; has_custom_store reflects them for the Auto policy
9375
// decision.
9376
inline bool load_client_ca_config(tls::ctx_t ctx,
9377
                                  const std::string &ca_cert_file_path,
9378
                                  const std::string &ca_cert_dir_path,
9379
                                  bool has_custom_store, SystemCAMode mode,
9380
                                  uint64_t &backend_error) {
9381
  auto ret = true;
9382
9383
  if (!ca_cert_file_path.empty()) {
9384
    if (!tls::load_ca_file(ctx, ca_cert_file_path.c_str())) {
9385
      backend_error = tls::get_error();
9386
      ret = false;
9387
    }
9388
  } else if (!ca_cert_dir_path.empty()) {
9389
    if (!tls::load_ca_dir(ctx, ca_cert_dir_path.c_str())) {
9390
      backend_error = tls::get_error();
9391
      ret = false;
9392
    }
9393
  }
9394
9395
  auto has_custom_ca = !ca_cert_file_path.empty() ||
9396
                       !ca_cert_dir_path.empty() || has_custom_store;
9397
  if (mode == SystemCAMode::Enabled ||
9398
      (mode == SystemCAMode::Auto && !has_custom_ca)) {
9399
    if (!tls::load_system_certs(ctx)) { backend_error = tls::get_error(); }
9400
  }
9401
9402
  return ret;
9403
}
9404
9405
inline bool setup_client_tls_session(const std::string &host, tls::ctx_t ctx,
9406
                                     tls::session_t &session, socket_t sock,
9407
                                     bool server_certificate_verification,
9408
                                     time_t timeout_sec, time_t timeout_usec) {
9409
  using namespace tls;
9410
9411
  if (!ctx) { return false; }
9412
9413
  bool is_ip = is_ip_address(host);
9414
9415
#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
9416
  // Chain verification happens during the handshake even for IP hosts; the
9417
  // certificate identity is verified post-handshake via verify_hostname()
9418
  set_verify_client(ctx, server_certificate_verification);
9419
#endif
9420
9421
  session = create_session(ctx, sock);
9422
  if (!session) { return false; }
9423
9424
  // RFC 6066: SNI must not be set for IP addresses. On Mbed TLS and wolfSSL
9425
  // set_hostname also sets SNI, so it must be skipped for IP hosts as well;
9426
  // their identity is checked post-handshake below instead.
9427
  if (!is_ip) {
9428
    if (server_certificate_verification) {
9429
      set_hostname(session, host.c_str());
9430
    } else {
9431
      set_sni(session, host.c_str());
9432
    }
9433
  }
9434
9435
  if (!connect_nonblocking(session, sock, timeout_sec, timeout_usec, nullptr)) {
9436
    return false;
9437
  }
9438
9439
  if (server_certificate_verification) {
9440
    if (get_verify_result(session) != 0) { return false; }
9441
9442
    // Identity check against the peer certificate, post-handshake for all
9443
    // backends (same as SSLClient). For IP hosts this is the only identity
9444
    // verification since no hostname is bound during the handshake.
9445
    auto server_cert = get_peer_cert(session);
9446
    if (!server_cert) { return false; }
9447
    auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); });
9448
    if (!verify_hostname(server_cert, host.c_str())) { return false; }
9449
  }
9450
9451
  return true;
9452
}
9453
9454
} // namespace detail
9455
#endif // CPPHTTPLIB_SSL_ENABLED
9456
9457
/*
9458
 * Group 3: httplib namespace - Non-SSL public API implementations
9459
 */
9460
9461
0
inline void default_socket_options(socket_t sock) {
9462
0
  set_socket_opt(sock, SOL_SOCKET,
9463
0
#ifdef SO_REUSEPORT
9464
0
                 SO_REUSEPORT,
9465
#else
9466
                 SO_REUSEADDR,
9467
#endif
9468
0
                 1);
9469
0
}
9470
9471
0
inline bool set_socket_opt(socket_t sock, int level, int optname, int optval) {
9472
0
  return detail::set_socket_opt_impl(sock, level, optname, &optval,
9473
0
                                     sizeof(optval));
9474
0
}
9475
9476
0
inline std::string get_bearer_token_auth(const Request &req) {
9477
0
  if (req.has_header("Authorization")) {
9478
0
    constexpr auto bearer_header_prefix_len = detail::str_len("Bearer ");
9479
0
    return req.get_header_value("Authorization")
9480
0
        .substr(bearer_header_prefix_len);
9481
0
  }
9482
0
  return "";
9483
0
}
9484
9485
6.78k
inline const char *status_message(int status) {
9486
6.78k
  switch (status) {
9487
1
  case StatusCode::Continue_100: return "Continue";
9488
0
  case StatusCode::SwitchingProtocol_101: return "Switching Protocol";
9489
0
  case StatusCode::Processing_102: return "Processing";
9490
0
  case StatusCode::EarlyHints_103: return "Early Hints";
9491
1.64k
  case StatusCode::OK_200: return "OK";
9492
0
  case StatusCode::Created_201: return "Created";
9493
0
  case StatusCode::Accepted_202: return "Accepted";
9494
0
  case StatusCode::NonAuthoritativeInformation_203:
9495
0
    return "Non-Authoritative Information";
9496
0
  case StatusCode::NoContent_204: return "No Content";
9497
0
  case StatusCode::ResetContent_205: return "Reset Content";
9498
260
  case StatusCode::PartialContent_206: return "Partial Content";
9499
0
  case StatusCode::MultiStatus_207: return "Multi-Status";
9500
0
  case StatusCode::AlreadyReported_208: return "Already Reported";
9501
0
  case StatusCode::IMUsed_226: return "IM Used";
9502
0
  case StatusCode::MultipleChoices_300: return "Multiple Choices";
9503
0
  case StatusCode::MovedPermanently_301: return "Moved Permanently";
9504
0
  case StatusCode::Found_302: return "Found";
9505
0
  case StatusCode::SeeOther_303: return "See Other";
9506
0
  case StatusCode::NotModified_304: return "Not Modified";
9507
0
  case StatusCode::UseProxy_305: return "Use Proxy";
9508
0
  case StatusCode::unused_306: return "unused";
9509
0
  case StatusCode::TemporaryRedirect_307: return "Temporary Redirect";
9510
0
  case StatusCode::PermanentRedirect_308: return "Permanent Redirect";
9511
4.08k
  case StatusCode::BadRequest_400: return "Bad Request";
9512
0
  case StatusCode::Unauthorized_401: return "Unauthorized";
9513
0
  case StatusCode::PaymentRequired_402: return "Payment Required";
9514
0
  case StatusCode::Forbidden_403: return "Forbidden";
9515
214
  case StatusCode::NotFound_404: return "Not Found";
9516
0
  case StatusCode::MethodNotAllowed_405: return "Method Not Allowed";
9517
0
  case StatusCode::NotAcceptable_406: return "Not Acceptable";
9518
0
  case StatusCode::ProxyAuthenticationRequired_407:
9519
0
    return "Proxy Authentication Required";
9520
0
  case StatusCode::RequestTimeout_408: return "Request Timeout";
9521
0
  case StatusCode::Conflict_409: return "Conflict";
9522
0
  case StatusCode::Gone_410: return "Gone";
9523
0
  case StatusCode::LengthRequired_411: return "Length Required";
9524
0
  case StatusCode::PreconditionFailed_412: return "Precondition Failed";
9525
20
  case StatusCode::PayloadTooLarge_413: return "Payload Too Large";
9526
4
  case StatusCode::UriTooLong_414: return "URI Too Long";
9527
36
  case StatusCode::UnsupportedMediaType_415: return "Unsupported Media Type";
9528
513
  case StatusCode::RangeNotSatisfiable_416: return "Range Not Satisfiable";
9529
0
  case StatusCode::ExpectationFailed_417: return "Expectation Failed";
9530
0
  case StatusCode::ImATeapot_418: return "I'm a teapot";
9531
0
  case StatusCode::MisdirectedRequest_421: return "Misdirected Request";
9532
0
  case StatusCode::UnprocessableContent_422: return "Unprocessable Content";
9533
0
  case StatusCode::Locked_423: return "Locked";
9534
0
  case StatusCode::FailedDependency_424: return "Failed Dependency";
9535
0
  case StatusCode::TooEarly_425: return "Too Early";
9536
0
  case StatusCode::UpgradeRequired_426: return "Upgrade Required";
9537
0
  case StatusCode::PreconditionRequired_428: return "Precondition Required";
9538
0
  case StatusCode::TooManyRequests_429: return "Too Many Requests";
9539
0
  case StatusCode::RequestHeaderFieldsTooLarge_431:
9540
0
    return "Request Header Fields Too Large";
9541
0
  case StatusCode::UnavailableForLegalReasons_451:
9542
0
    return "Unavailable For Legal Reasons";
9543
0
  case StatusCode::NotImplemented_501: return "Not Implemented";
9544
0
  case StatusCode::BadGateway_502: return "Bad Gateway";
9545
0
  case StatusCode::ServiceUnavailable_503: return "Service Unavailable";
9546
0
  case StatusCode::GatewayTimeout_504: return "Gateway Timeout";
9547
0
  case StatusCode::HttpVersionNotSupported_505:
9548
0
    return "HTTP Version Not Supported";
9549
0
  case StatusCode::VariantAlsoNegotiates_506: return "Variant Also Negotiates";
9550
0
  case StatusCode::InsufficientStorage_507: return "Insufficient Storage";
9551
0
  case StatusCode::LoopDetected_508: return "Loop Detected";
9552
0
  case StatusCode::NotExtended_510: return "Not Extended";
9553
0
  case StatusCode::NetworkAuthenticationRequired_511:
9554
0
    return "Network Authentication Required";
9555
9556
0
  default:
9557
0
  case StatusCode::InternalServerError_500: return "Internal Server Error";
9558
6.78k
  }
9559
6.78k
}
9560
9561
0
inline std::string to_string(const Error error) {
9562
0
  switch (error) {
9563
0
  case Error::Success: return "Success (no error)";
9564
0
  case Error::Unknown: return "Unknown";
9565
0
  case Error::Connection: return "Could not establish connection";
9566
0
  case Error::BindIPAddress: return "Failed to bind IP address";
9567
0
  case Error::Read: return "Failed to read connection";
9568
0
  case Error::Write: return "Failed to write connection";
9569
0
  case Error::ExceedRedirectCount: return "Maximum redirect count exceeded";
9570
0
  case Error::Canceled: return "Connection handling canceled";
9571
0
  case Error::SSLConnection: return "SSL connection failed";
9572
0
  case Error::SSLLoadingCerts: return "SSL certificate loading failed";
9573
0
  case Error::SSLServerVerification: return "SSL server verification failed";
9574
0
  case Error::SSLServerHostnameVerification:
9575
0
    return "SSL server hostname verification failed";
9576
0
  case Error::UnsupportedMultipartBoundaryChars:
9577
0
    return "Unsupported HTTP multipart boundary characters";
9578
0
  case Error::Compression: return "Compression failed";
9579
0
  case Error::ConnectionTimeout: return "Connection timed out";
9580
0
  case Error::ProxyConnection: return "Proxy connection failed";
9581
0
  case Error::ConnectionClosed: return "Connection closed by server";
9582
0
  case Error::Timeout: return "Read timeout";
9583
0
  case Error::ResourceExhaustion: return "Resource exhaustion";
9584
0
  case Error::TooManyFormDataFiles: return "Too many form data files";
9585
0
  case Error::ExceedMaxPayloadSize: return "Exceeded maximum payload size";
9586
0
  case Error::ExceedUriMaxLength: return "Exceeded maximum URI length";
9587
0
  case Error::ExceedMaxSocketDescriptorCount:
9588
0
    return "Exceeded maximum socket descriptor count";
9589
0
  case Error::InvalidRequestLine: return "Invalid request line";
9590
0
  case Error::InvalidHTTPMethod: return "Invalid HTTP method";
9591
0
  case Error::InvalidHTTPVersion: return "Invalid HTTP version";
9592
0
  case Error::InvalidHeaders: return "Invalid headers";
9593
0
  case Error::MultipartParsing: return "Multipart parsing failed";
9594
0
  case Error::OpenFile: return "Failed to open file";
9595
0
  case Error::Listen: return "Failed to listen on socket";
9596
0
  case Error::GetSockName: return "Failed to get socket name";
9597
0
  case Error::UnsupportedAddressFamily: return "Unsupported address family";
9598
0
  case Error::HTTPParsing: return "HTTP parsing failed";
9599
0
  case Error::InvalidRangeHeader: return "Invalid Range header";
9600
0
  default: break;
9601
0
  }
9602
0
9603
0
  return "Invalid";
9604
0
}
9605
9606
0
inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
9607
0
  os << to_string(obj);
9608
0
  os << " (" << static_cast<std::underlying_type<Error>::type>(obj) << ')';
9609
0
  return os;
9610
0
}
9611
9612
0
inline std::string hosted_at(const std::string &hostname) {
9613
0
  std::vector<std::string> addrs;
9614
0
  hosted_at(hostname, addrs);
9615
0
  if (addrs.empty()) { return std::string(); }
9616
0
  return addrs[0];
9617
0
}
9618
9619
inline void hosted_at(const std::string &hostname,
9620
0
                      std::vector<std::string> &addrs) {
9621
0
  struct addrinfo hints;
9622
0
  struct addrinfo *result;
9623
0
9624
0
  memset(&hints, 0, sizeof(struct addrinfo));
9625
0
  hints.ai_family = AF_UNSPEC;
9626
0
  hints.ai_socktype = SOCK_STREAM;
9627
0
  hints.ai_protocol = 0;
9628
0
9629
0
  if (detail::getaddrinfo_with_timeout(hostname.c_str(), nullptr, &hints,
9630
0
                                       &result, 0)) {
9631
0
#if defined __linux__ && !defined __ANDROID__
9632
0
    res_init();
9633
0
#endif
9634
0
    return;
9635
0
  }
9636
0
  auto se = detail::scope_exit([&] { freeaddrinfo(result); });
9637
0
9638
0
  for (auto rp = result; rp; rp = rp->ai_next) {
9639
0
    const auto &addr =
9640
0
        *reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
9641
0
    std::string ip;
9642
0
    auto dummy = -1;
9643
0
    if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip,
9644
0
                                dummy)) {
9645
0
      addrs.emplace_back(std::move(ip));
9646
0
    }
9647
0
  }
9648
0
}
9649
9650
0
inline std::string encode_uri_component(const std::string &value) {
9651
0
  std::ostringstream escaped;
9652
0
  escaped.fill('0');
9653
0
  escaped << std::hex;
9654
0
9655
0
  for (auto c : value) {
9656
0
    if (detail::is_ascii_alnum(c) || c == '-' || c == '_' || c == '.' ||
9657
0
        c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')') {
9658
0
      escaped << c;
9659
0
    } else {
9660
0
      escaped << std::uppercase;
9661
0
      escaped << '%' << std::setw(2)
9662
0
              << static_cast<int>(static_cast<unsigned char>(c));
9663
0
      escaped << std::nouppercase;
9664
0
    }
9665
0
  }
9666
0
9667
0
  return escaped.str();
9668
0
}
9669
9670
0
inline std::string encode_uri(const std::string &value) {
9671
0
  std::ostringstream escaped;
9672
0
  escaped.fill('0');
9673
0
  escaped << std::hex;
9674
0
9675
0
  for (auto c : value) {
9676
0
    if (detail::is_ascii_alnum(c) || c == '-' || c == '_' || c == '.' ||
9677
0
        c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')' ||
9678
0
        c == ';' || c == '/' || c == '?' || c == ':' || c == '@' || c == '&' ||
9679
0
        c == '=' || c == '+' || c == '$' || c == ',' || c == '#') {
9680
0
      escaped << c;
9681
0
    } else {
9682
0
      escaped << std::uppercase;
9683
0
      escaped << '%' << std::setw(2)
9684
0
              << static_cast<int>(static_cast<unsigned char>(c));
9685
0
      escaped << std::nouppercase;
9686
0
    }
9687
0
  }
9688
0
9689
0
  return escaped.str();
9690
0
}
9691
9692
0
inline std::string decode_uri_component(const std::string &value) {
9693
0
  std::string result;
9694
0
9695
0
  for (size_t i = 0; i < value.size(); i++) {
9696
0
    if (value[i] == '%' && i + 2 < value.size()) {
9697
0
      auto val = 0;
9698
0
      if (detail::from_hex_to_i(value, i + 1, 2, val)) {
9699
0
        result += static_cast<char>(val);
9700
0
        i += 2;
9701
0
      } else {
9702
0
        result += value[i];
9703
0
      }
9704
0
    } else {
9705
0
      result += value[i];
9706
0
    }
9707
0
  }
9708
0
9709
0
  return result;
9710
0
}
9711
9712
0
inline std::string decode_uri(const std::string &value) {
9713
0
  std::string result;
9714
0
9715
0
  for (size_t i = 0; i < value.size(); i++) {
9716
0
    if (value[i] == '%' && i + 2 < value.size()) {
9717
0
      auto val = 0;
9718
0
      if (detail::from_hex_to_i(value, i + 1, 2, val)) {
9719
0
        result += static_cast<char>(val);
9720
0
        i += 2;
9721
0
      } else {
9722
0
        result += value[i];
9723
0
      }
9724
0
    } else {
9725
0
      result += value[i];
9726
0
    }
9727
0
  }
9728
0
9729
0
  return result;
9730
0
}
9731
9732
0
inline std::string encode_path_component(const std::string &component) {
9733
0
  std::string result;
9734
0
  result.reserve(component.size() * 3);
9735
0
9736
0
  for (size_t i = 0; i < component.size(); i++) {
9737
0
    auto c = static_cast<unsigned char>(component[i]);
9738
0
9739
0
    // Unreserved characters per RFC 3986: ALPHA / DIGIT / "-" / "." / "_" / "~"
9740
0
    if (detail::is_ascii_alnum(static_cast<char>(c)) || c == '-' || c == '.' ||
9741
0
        c == '_' || c == '~') {
9742
0
      result += static_cast<char>(c);
9743
0
    }
9744
0
    // Path-safe sub-delimiters: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" /
9745
0
    // "," / ";" / "="
9746
0
    else if (c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' ||
9747
0
             c == ')' || c == '*' || c == '+' || c == ',' || c == ';' ||
9748
0
             c == '=') {
9749
0
      result += static_cast<char>(c);
9750
0
    }
9751
0
    // Colon is allowed in path segments except first segment
9752
0
    else if (c == ':') {
9753
0
      result += static_cast<char>(c);
9754
0
    }
9755
0
    // @ is allowed in path
9756
0
    else if (c == '@') {
9757
0
      result += static_cast<char>(c);
9758
0
    } else {
9759
0
      result += '%';
9760
0
      char hex[3];
9761
0
      snprintf(hex, sizeof(hex), "%02X", c);
9762
0
      result.append(hex, 2);
9763
0
    }
9764
0
  }
9765
0
  return result;
9766
0
}
9767
9768
9.55k
inline std::string decode_path_component(const std::string &component) {
9769
9.55k
  std::string result;
9770
9.55k
  result.reserve(component.size());
9771
9772
258k
  for (size_t i = 0; i < component.size(); i++) {
9773
249k
    if (component[i] == '%' && i + 1 < component.size()) {
9774
9.02k
      if (component[i + 1] == 'u') {
9775
        // Unicode %uXXXX encoding
9776
5.24k
        auto val = 0;
9777
5.24k
        if (detail::from_hex_to_i(component, i + 2, 4, val)) {
9778
          // 4 digits Unicode codes: val is 0x0000-0xFFFF (from 4 hex digits),
9779
          // so to_utf8 writes at most 3 bytes. buff[4] is safe.
9780
3.73k
          char buff[4];
9781
3.73k
          size_t len = detail::to_utf8(val, buff);
9782
3.73k
          if (len > 0) { result.append(buff, len); }
9783
3.73k
          i += 5; // 'u0000'
9784
3.73k
        } else {
9785
1.50k
          result += component[i];
9786
1.50k
        }
9787
5.24k
      } else {
9788
        // Standard %XX encoding
9789
3.77k
        auto val = 0;
9790
3.77k
        if (detail::from_hex_to_i(component, i + 1, 2, val)) {
9791
          // 2 digits hex codes
9792
350
          result += static_cast<char>(val);
9793
350
          i += 2; // 'XX'
9794
3.42k
        } else {
9795
3.42k
          result += component[i];
9796
3.42k
        }
9797
3.77k
      }
9798
240k
    } else {
9799
240k
      result += component[i];
9800
240k
    }
9801
249k
  }
9802
9.55k
  return result;
9803
9.55k
}
9804
9805
inline std::string encode_query_component(const std::string &component,
9806
0
                                          bool space_as_plus) {
9807
0
  std::string result;
9808
0
  result.reserve(component.size() * 3);
9809
0
9810
0
  for (size_t i = 0; i < component.size(); i++) {
9811
0
    auto c = static_cast<unsigned char>(component[i]);
9812
0
9813
0
    // Unreserved characters per RFC 3986
9814
0
    if (detail::is_ascii_alnum(static_cast<char>(c)) || c == '-' || c == '.' ||
9815
0
        c == '_' || c == '~') {
9816
0
      result += static_cast<char>(c);
9817
0
    }
9818
0
    // Space handling
9819
0
    else if (c == ' ') {
9820
0
      if (space_as_plus) {
9821
0
        result += '+';
9822
0
      } else {
9823
0
        result += "%20";
9824
0
      }
9825
0
    }
9826
0
    // Plus sign handling
9827
0
    else if (c == '+') {
9828
0
      if (space_as_plus) {
9829
0
        result += "%2B";
9830
0
      } else {
9831
0
        result += static_cast<char>(c);
9832
0
      }
9833
0
    }
9834
0
    // Query-safe sub-delimiters (excluding & and = which are query delimiters)
9835
0
    else if (c == '!' || c == '$' || c == '\'' || c == '(' || c == ')' ||
9836
0
             c == '*' || c == ',' || c == ';') {
9837
0
      result += static_cast<char>(c);
9838
0
    }
9839
0
    // Colon and @ are allowed in query
9840
0
    else if (c == ':' || c == '@') {
9841
0
      result += static_cast<char>(c);
9842
0
    }
9843
0
    // Forward slash is allowed in query values
9844
0
    else if (c == '/') {
9845
0
      result += static_cast<char>(c);
9846
0
    }
9847
0
    // Question mark is allowed in query values (after first ?)
9848
0
    else if (c == '?') {
9849
0
      result += static_cast<char>(c);
9850
0
    } else {
9851
0
      result += '%';
9852
0
      char hex[3];
9853
0
      snprintf(hex, sizeof(hex), "%02X", c);
9854
0
      result.append(hex, 2);
9855
0
    }
9856
0
  }
9857
0
  return result;
9858
0
}
9859
9860
inline std::string decode_query_component(const std::string &component,
9861
10.5k
                                          bool plus_as_space) {
9862
10.5k
  std::string result;
9863
10.5k
  result.reserve(component.size());
9864
9865
509k
  for (size_t i = 0; i < component.size(); i++) {
9866
498k
    if (component[i] == '%' && i + 2 < component.size()) {
9867
21.6k
      auto val = 0;
9868
21.6k
      if (detail::from_hex_to_i(component, i + 1, 2, val)) {
9869
1.30k
        result += static_cast<char>(val);
9870
1.30k
        i += 2;
9871
20.3k
      } else {
9872
20.3k
        result += component[i];
9873
20.3k
      }
9874
477k
    } else if (component[i] == '+' && plus_as_space) {
9875
4.67k
      result += ' '; // + becomes space in form-urlencoded
9876
472k
    } else {
9877
472k
      result += component[i];
9878
472k
    }
9879
498k
  }
9880
10.5k
  return result;
9881
10.5k
}
9882
9883
0
inline std::string sanitize_filename(const std::string &filename) {
9884
0
  // Extract basename: find the last path separator (/ or \)
9885
0
  auto pos = filename.find_last_of("/\\");
9886
0
  auto result =
9887
0
      (pos != std::string::npos) ? filename.substr(pos + 1) : filename;
9888
0
9889
0
  // Strip null bytes
9890
0
  result.erase(std::remove(result.begin(), result.end(), '\0'), result.end());
9891
0
9892
0
  // Trim whitespace
9893
0
  {
9894
0
    auto start = result.find_first_not_of(" \t");
9895
0
    auto end = result.find_last_not_of(" \t");
9896
0
    result = (start == std::string::npos)
9897
0
                 ? ""
9898
0
                 : result.substr(start, end - start + 1);
9899
0
  }
9900
0
9901
0
  // Reject . and ..
9902
0
  if (result == "." || result == "..") { return ""; }
9903
0
9904
0
  return result;
9905
0
}
9906
9907
inline std::string append_query_params(const std::string &path,
9908
0
                                       const Params &params) {
9909
0
  std::string path_with_query = path;
9910
0
  thread_local const std::regex re("[^?]+\\?.*");
9911
0
  auto delm = std::regex_match(path, re) ? '&' : '?';
9912
0
  path_with_query += delm + detail::params_to_query_str(params);
9913
0
  return path_with_query;
9914
0
}
9915
9916
// Header utilities
9917
inline std::pair<std::string, std::string>
9918
0
make_range_header(const Ranges &ranges) {
9919
0
  std::string field = "bytes=";
9920
0
  auto i = 0;
9921
0
  for (const auto &r : ranges) {
9922
0
    if (i != 0) { field += ", "; }
9923
0
    if (r.first != -1) { field += std::to_string(r.first); }
9924
0
    field += '-';
9925
0
    if (r.second != -1) { field += std::to_string(r.second); }
9926
0
    i++;
9927
0
  }
9928
0
  return std::make_pair("Range", std::move(field));
9929
0
}
9930
9931
inline std::pair<std::string, std::string>
9932
make_basic_authentication_header(const std::string &username,
9933
0
                                 const std::string &password, bool is_proxy) {
9934
0
  auto field = "Basic " + detail::base64_encode(username + ":" + password);
9935
0
  auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
9936
0
  return std::make_pair(key, std::move(field));
9937
0
}
9938
9939
inline std::pair<std::string, std::string>
9940
make_bearer_token_authentication_header(const std::string &token,
9941
0
                                        bool is_proxy = false) {
9942
0
  auto field = "Bearer " + token;
9943
0
  auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
9944
0
  return std::make_pair(key, std::move(field));
9945
0
}
9946
9947
// Request implementation
9948
inline size_t Request::get_header_value_u64(const std::string &key, size_t def,
9949
11.1k
                                            size_t id) const {
9950
11.1k
  return detail::get_header_value_u64(headers, key, def, id);
9951
11.1k
}
9952
9953
13.9k
inline bool Request::has_header(const std::string &key) const {
9954
13.9k
  return detail::has_header(headers, key);
9955
13.9k
}
9956
9957
inline std::string Request::get_header_value(const std::string &key,
9958
32.7k
                                             const char *def, size_t id) const {
9959
32.7k
  return detail::get_header_value(headers, key, def, id);
9960
32.7k
}
9961
9962
0
inline size_t Request::get_header_value_count(const std::string &key) const {
9963
0
  return detail::get_header_value_count(headers, key);
9964
0
}
9965
9966
inline void Request::set_header(const std::string &key,
9967
0
                                const std::string &val) {
9968
0
  detail::set_header(headers, key, val);
9969
0
}
9970
9971
0
inline bool Request::has_trailer(const std::string &key) const {
9972
0
  return trailers.find(key) != trailers.end();
9973
0
}
9974
9975
inline std::string Request::get_trailer_value(const std::string &key,
9976
0
                                              size_t id) const {
9977
0
  return detail::get_multimap_value(trailers, key, id);
9978
0
}
9979
9980
0
inline size_t Request::get_trailer_value_count(const std::string &key) const {
9981
0
  auto r = trailers.equal_range(key);
9982
0
  return static_cast<size_t>(std::distance(r.first, r.second));
9983
0
}
9984
9985
0
inline bool Request::has_param(const std::string &key) const {
9986
0
  return params.find(key) != params.end();
9987
0
}
9988
9989
inline std::string Request::get_param_value(const std::string &key,
9990
0
                                            size_t id) const {
9991
0
  return detail::get_multimap_value(params, key, id);
9992
0
}
9993
9994
inline std::vector<std::string>
9995
0
Request::get_param_values(const std::string &key) const {
9996
0
  auto rng = params.equal_range(key);
9997
0
  std::vector<std::string> values;
9998
0
  values.reserve(static_cast<size_t>(std::distance(rng.first, rng.second)));
9999
0
  for (auto it = rng.first; it != rng.second; ++it) {
10000
0
    values.push_back(it->second);
10001
0
  }
10002
0
  return values;
10003
0
}
10004
10005
0
inline size_t Request::get_param_value_count(const std::string &key) const {
10006
0
  auto r = params.equal_range(key);
10007
0
  return static_cast<size_t>(std::distance(r.first, r.second));
10008
0
}
10009
10010
3.41k
inline bool Request::is_multipart_form_data() const {
10011
3.41k
  const auto &content_type = get_header_value("Content-Type");
10012
3.41k
  return detail::extract_media_type(content_type) == "multipart/form-data";
10013
3.41k
}
10014
10015
// Multipart FormData implementation
10016
inline std::string MultipartFormData::get_field(const std::string &key,
10017
0
                                                size_t id) const {
10018
0
  auto rng = fields.equal_range(key);
10019
0
  auto it = rng.first;
10020
0
  std::advance(it, static_cast<ssize_t>(id));
10021
0
  if (it != rng.second) { return it->second.content; }
10022
0
  return std::string();
10023
0
}
10024
10025
inline std::vector<std::string>
10026
0
MultipartFormData::get_fields(const std::string &key) const {
10027
0
  std::vector<std::string> values;
10028
0
  auto rng = fields.equal_range(key);
10029
0
  for (auto it = rng.first; it != rng.second; it++) {
10030
0
    values.push_back(it->second.content);
10031
0
  }
10032
0
  return values;
10033
0
}
10034
10035
0
inline bool MultipartFormData::has_field(const std::string &key) const {
10036
0
  return fields.find(key) != fields.end();
10037
0
}
10038
10039
0
inline size_t MultipartFormData::get_field_count(const std::string &key) const {
10040
0
  auto r = fields.equal_range(key);
10041
0
  return static_cast<size_t>(std::distance(r.first, r.second));
10042
0
}
10043
10044
inline FormData MultipartFormData::get_file(const std::string &key,
10045
0
                                            size_t id) const {
10046
0
  return detail::get_multimap_value(files, key, id);
10047
0
}
10048
10049
inline std::vector<FormData>
10050
0
MultipartFormData::get_files(const std::string &key) const {
10051
0
  std::vector<FormData> values;
10052
0
  auto rng = files.equal_range(key);
10053
0
  for (auto it = rng.first; it != rng.second; it++) {
10054
0
    values.push_back(it->second);
10055
0
  }
10056
0
  return values;
10057
0
}
10058
10059
0
inline bool MultipartFormData::has_file(const std::string &key) const {
10060
0
  return files.find(key) != files.end();
10061
0
}
10062
10063
0
inline size_t MultipartFormData::get_file_count(const std::string &key) const {
10064
0
  auto r = files.equal_range(key);
10065
0
  return static_cast<size_t>(std::distance(r.first, r.second));
10066
0
}
10067
10068
// Multipart FormData writer implementation
10069
0
inline bool is_valid_multipart_boundary(const std::string &boundary) {
10070
0
  return detail::is_multipart_boundary_chars_valid(boundary);
10071
0
}
10072
10073
inline MultipartFormDataWriter::MultipartFormDataWriter()
10074
    : boundary_(detail::make_multipart_data_boundary()) {}
10075
10076
inline MultipartFormDataWriter::MultipartFormDataWriter(std::string boundary)
10077
    : boundary_(std::move(boundary)) {}
10078
10079
0
inline const std::string &MultipartFormDataWriter::boundary() const {
10080
0
  return boundary_;
10081
0
}
10082
10083
0
inline std::string MultipartFormDataWriter::content_type() const {
10084
0
  return detail::serialize_multipart_formdata_get_content_type(boundary_);
10085
0
}
10086
10087
inline std::string
10088
0
MultipartFormDataWriter::serialize(const UploadFormDataItems &items) const {
10089
0
  return detail::serialize_multipart_formdata(items, boundary_);
10090
0
}
10091
10092
inline size_t MultipartFormDataWriter::content_length(
10093
0
    const UploadFormDataItems &items) const {
10094
0
  return detail::get_multipart_content_length(items, boundary_);
10095
0
}
10096
10097
inline std::string
10098
0
MultipartFormDataWriter::item_begin(const UploadFormData &item) const {
10099
0
  return detail::serialize_multipart_formdata_item_begin(item, boundary_);
10100
0
}
10101
10102
0
inline std::string MultipartFormDataWriter::item_end() {
10103
0
  return detail::serialize_multipart_formdata_item_end();
10104
0
}
10105
10106
0
inline std::string MultipartFormDataWriter::finish() const {
10107
0
  return detail::serialize_multipart_formdata_finish(boundary_);
10108
0
}
10109
10110
// Response implementation
10111
inline size_t Response::get_header_value_u64(const std::string &key, size_t def,
10112
0
                                             size_t id) const {
10113
0
  return detail::get_header_value_u64(headers, key, def, id);
10114
0
}
10115
10116
6.90k
inline bool Response::has_header(const std::string &key) const {
10117
6.90k
  return headers.find(key) != headers.end();
10118
6.90k
}
10119
10120
inline std::string Response::get_header_value(const std::string &key,
10121
                                              const char *def,
10122
1.90k
                                              size_t id) const {
10123
1.90k
  return detail::get_header_value(headers, key, def, id);
10124
1.90k
}
10125
10126
0
inline size_t Response::get_header_value_count(const std::string &key) const {
10127
0
  return detail::get_header_value_count(headers, key);
10128
0
}
10129
10130
inline void Response::set_header(const std::string &key,
10131
16.2k
                                 const std::string &val) {
10132
16.2k
  detail::set_header(headers, key, val);
10133
16.2k
}
10134
0
inline bool Response::has_trailer(const std::string &key) const {
10135
0
  return trailers.find(key) != trailers.end();
10136
0
}
10137
10138
inline std::string Response::get_trailer_value(const std::string &key,
10139
0
                                               size_t id) const {
10140
0
  return detail::get_multimap_value(trailers, key, id);
10141
0
}
10142
10143
0
inline size_t Response::get_trailer_value_count(const std::string &key) const {
10144
0
  auto r = trailers.equal_range(key);
10145
0
  return static_cast<size_t>(std::distance(r.first, r.second));
10146
0
}
10147
10148
0
inline void Response::set_redirect(const std::string &url, int stat) {
10149
0
  if (detail::fields::is_field_value(url)) {
10150
0
    set_header("Location", url);
10151
0
    if (300 <= stat && stat < 400) {
10152
0
      this->status = stat;
10153
0
    } else {
10154
0
      this->status = StatusCode::Found_302;
10155
0
    }
10156
0
  }
10157
0
}
10158
10159
inline void Response::set_content(const char *s, size_t n,
10160
0
                                  const std::string &content_type) {
10161
0
  body.assign(s, n);
10162
0
10163
0
  auto rng = headers.equal_range("Content-Type");
10164
0
  headers.erase(rng.first, rng.second);
10165
0
  set_header("Content-Type", content_type);
10166
0
}
10167
10168
inline void Response::set_content(const std::string &s,
10169
0
                                  const std::string &content_type) {
10170
0
  set_content(s.data(), s.size(), content_type);
10171
0
}
10172
10173
inline void Response::set_content(std::string &&s,
10174
2.21k
                                  const std::string &content_type) {
10175
2.21k
  body = std::move(s);
10176
10177
2.21k
  auto rng = headers.equal_range("Content-Type");
10178
2.21k
  headers.erase(rng.first, rng.second);
10179
2.21k
  set_header("Content-Type", content_type);
10180
2.21k
}
10181
10182
inline void Response::set_content_provider(
10183
    size_t in_length, const std::string &content_type, ContentProvider provider,
10184
0
    ContentProviderResourceReleaser resource_releaser) {
10185
0
  set_header("Content-Type", content_type);
10186
0
  content_length_ = in_length;
10187
0
  if (in_length > 0) { content_provider_ = std::move(provider); }
10188
0
  content_provider_resource_releaser_ = std::move(resource_releaser);
10189
0
  is_chunked_content_provider_ = false;
10190
0
}
10191
10192
inline void Response::set_content_provider(
10193
    const std::string &content_type, ContentProviderWithoutLength provider,
10194
0
    ContentProviderResourceReleaser resource_releaser) {
10195
0
  set_header("Content-Type", content_type);
10196
0
  content_length_ = 0;
10197
0
  content_provider_ = detail::ContentProviderAdapter(std::move(provider));
10198
0
  content_provider_resource_releaser_ = std::move(resource_releaser);
10199
0
  is_chunked_content_provider_ = false;
10200
0
}
10201
10202
inline void Response::set_chunked_content_provider(
10203
    const std::string &content_type, ContentProviderWithoutLength provider,
10204
0
    ContentProviderResourceReleaser resource_releaser) {
10205
0
  set_header("Content-Type", content_type);
10206
0
  content_length_ = 0;
10207
0
  content_provider_ = detail::ContentProviderAdapter(std::move(provider));
10208
0
  content_provider_resource_releaser_ = std::move(resource_releaser);
10209
0
  is_chunked_content_provider_ = true;
10210
0
}
10211
10212
inline void Response::set_file_content(const std::string &path,
10213
0
                                       const std::string &content_type) {
10214
0
  file_content_path_ = path;
10215
0
  file_content_content_type_ = content_type;
10216
0
}
10217
10218
0
inline void Response::set_file_content(const std::string &path) {
10219
0
  file_content_path_ = path;
10220
0
}
10221
10222
// Result implementation
10223
inline size_t Result::get_request_header_value_u64(const std::string &key,
10224
                                                   size_t def,
10225
0
                                                   size_t id) const {
10226
0
  return detail::get_header_value_u64(request_headers_, key, def, id);
10227
0
}
10228
10229
0
inline bool Result::has_request_header(const std::string &key) const {
10230
0
  return request_headers_.find(key) != request_headers_.end();
10231
0
}
10232
10233
inline std::string Result::get_request_header_value(const std::string &key,
10234
                                                    const char *def,
10235
0
                                                    size_t id) const {
10236
0
  return detail::get_header_value(request_headers_, key, def, id);
10237
0
}
10238
10239
inline size_t
10240
0
Result::get_request_header_value_count(const std::string &key) const {
10241
0
  auto r = request_headers_.equal_range(key);
10242
0
  return static_cast<size_t>(std::distance(r.first, r.second));
10243
0
}
10244
10245
// Stream implementation
10246
6.78k
inline ssize_t Stream::write(const char *ptr) {
10247
6.78k
  return write(ptr, strlen(ptr));
10248
6.78k
}
10249
10250
0
inline ssize_t Stream::write(const std::string &s) {
10251
0
  return write(s.data(), s.size());
10252
0
}
10253
10254
// BodyReader implementation
10255
3.64k
inline ssize_t detail::BodyReader::read(char *buf, size_t len) {
10256
3.64k
  if (!stream) {
10257
0
    last_error = Error::Connection;
10258
0
    return -1;
10259
0
  }
10260
3.64k
  if (eof) { return 0; }
10261
10262
3.64k
  if (!chunked) {
10263
    // Content-Length based reading
10264
3.64k
    if (has_content_length && bytes_read >= content_length) {
10265
0
      eof = true;
10266
0
      return 0;
10267
0
    }
10268
10269
3.64k
    auto to_read = len;
10270
3.64k
    if (has_content_length) {
10271
3.64k
      auto remaining = content_length - bytes_read;
10272
3.64k
      to_read = (std::min)(len, remaining);
10273
3.64k
    }
10274
3.64k
    auto n = stream->read(buf, to_read);
10275
10276
3.64k
    if (n < 0) {
10277
0
      last_error = stream->get_error();
10278
0
      if (last_error == Error::Success) { last_error = Error::Read; }
10279
0
      eof = true;
10280
0
      return n;
10281
0
    }
10282
3.64k
    if (n == 0) {
10283
      // Unexpected EOF before content_length
10284
1.35k
      last_error = stream->get_error();
10285
1.35k
      if (last_error == Error::Success) { last_error = Error::Read; }
10286
1.35k
      eof = true;
10287
1.35k
      return 0;
10288
1.35k
    }
10289
10290
2.29k
    bytes_read += static_cast<size_t>(n);
10291
2.29k
    if (has_content_length && bytes_read >= content_length) { eof = true; }
10292
2.29k
    if (payload_max_length > 0 && bytes_read > payload_max_length) {
10293
0
      last_error = Error::ExceedMaxPayloadSize;
10294
0
      eof = true;
10295
0
      return -1;
10296
0
    }
10297
2.29k
    return n;
10298
2.29k
  }
10299
10300
  // Chunked transfer encoding: delegate to shared decoder instance.
10301
0
  if (!chunked_decoder) { chunked_decoder.reset(new ChunkedDecoder(*stream)); }
10302
10303
0
  size_t chunk_offset = 0;
10304
0
  size_t chunk_total = 0;
10305
0
  auto n = chunked_decoder->read_payload(buf, len, chunk_offset, chunk_total);
10306
0
  if (n < 0) {
10307
0
    last_error = stream->get_error();
10308
0
    if (last_error == Error::Success) { last_error = Error::Read; }
10309
0
    eof = true;
10310
0
    return n;
10311
0
  }
10312
10313
0
  if (n == 0) {
10314
    // Final chunk observed. Leave trailer parsing to the caller (StreamHandle).
10315
0
    eof = true;
10316
0
    return 0;
10317
0
  }
10318
10319
0
  bytes_read += static_cast<size_t>(n);
10320
0
  if (payload_max_length > 0 && bytes_read > payload_max_length) {
10321
0
    last_error = Error::ExceedMaxPayloadSize;
10322
0
    eof = true;
10323
0
    return -1;
10324
0
  }
10325
0
  return n;
10326
0
}
10327
10328
// ThreadPool implementation
10329
inline ThreadPool::ThreadPool(size_t n, size_t max_n, size_t mqr,
10330
                              time_t idle_timeout_sec)
10331
0
    : base_thread_count_(n), max_queued_requests_(mqr),
10332
0
      idle_timeout_sec_(idle_timeout_sec), idle_thread_count_(0),
10333
0
      shutdown_(false) {
10334
0
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10335
0
  if (max_n != 0 && max_n < n) {
10336
0
    std::string msg = "max_threads must be >= base_threads";
10337
0
    throw std::invalid_argument(msg);
10338
0
  }
10339
0
#endif
10340
0
  max_thread_count_ = max_n == 0 ? n : max_n;
10341
0
  threads_.reserve(base_thread_count_);
10342
0
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10343
0
  try {
10344
0
#endif
10345
0
    for (size_t i = 0; i < base_thread_count_; i++) {
10346
0
      threads_.emplace_back(std::thread([this]() { worker(false); }));
10347
0
    }
10348
0
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10349
0
  } catch (...) {
10350
    // If thread creation fails partway (e.g., pthread_create returns EAGAIN),
10351
    // signal the workers we already spawned to exit and join them so the
10352
    // vector destructor does not see joinable threads (which would call
10353
    // std::terminate). Then rethrow so the caller learns of the failure.
10354
0
    {
10355
0
      std::unique_lock<std::mutex> lock(mutex_);
10356
0
      shutdown_ = true;
10357
0
    }
10358
0
    cond_.notify_all();
10359
0
    for (auto &t : threads_) {
10360
0
      if (t.joinable()) { t.join(); }
10361
0
    }
10362
0
    throw;
10363
0
  }
10364
0
#endif
10365
0
}
10366
10367
0
inline bool ThreadPool::enqueue(std::function<void()> fn) {
10368
0
  {
10369
0
    std::unique_lock<std::mutex> lock(mutex_);
10370
0
    if (shutdown_) { return false; }
10371
0
    if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) {
10372
0
      return false;
10373
0
    }
10374
0
    jobs_.push_back(std::move(fn));
10375
10376
    // Spawn a dynamic thread if no idle threads and under max
10377
0
    if (idle_thread_count_ == 0 &&
10378
0
        threads_.size() + dynamic_threads_.size() < max_thread_count_) {
10379
0
      cleanup_finished_threads();
10380
0
      dynamic_threads_.emplace_back(std::thread([this]() { worker(true); }));
10381
0
    }
10382
0
  }
10383
10384
0
  cond_.notify_one();
10385
0
  return true;
10386
0
}
10387
10388
0
inline void ThreadPool::shutdown() {
10389
0
  {
10390
0
    std::unique_lock<std::mutex> lock(mutex_);
10391
0
    shutdown_ = true;
10392
0
  }
10393
10394
0
  cond_.notify_all();
10395
10396
0
  for (auto &t : threads_) {
10397
0
    if (t.joinable()) { t.join(); }
10398
0
  }
10399
10400
  // Move dynamic_threads_ to a local list under the lock to avoid racing
10401
  // with worker threads that call move_to_finished() concurrently.
10402
0
  std::list<std::thread> remaining_dynamic;
10403
0
  {
10404
0
    std::unique_lock<std::mutex> lock(mutex_);
10405
0
    remaining_dynamic = std::move(dynamic_threads_);
10406
0
  }
10407
0
  for (auto &t : remaining_dynamic) {
10408
0
    if (t.joinable()) { t.join(); }
10409
0
  }
10410
10411
0
  std::unique_lock<std::mutex> lock(mutex_);
10412
0
  cleanup_finished_threads();
10413
0
}
10414
10415
0
inline void ThreadPool::move_to_finished(std::thread::id id) {
10416
  // Must be called with mutex_ held
10417
0
  for (auto it = dynamic_threads_.begin(); it != dynamic_threads_.end(); ++it) {
10418
0
    if (it->get_id() == id) {
10419
0
      finished_threads_.push_back(std::move(*it));
10420
0
      dynamic_threads_.erase(it);
10421
0
      return;
10422
0
    }
10423
0
  }
10424
0
}
10425
10426
0
inline void ThreadPool::cleanup_finished_threads() {
10427
  // Must be called with mutex_ held
10428
0
  for (auto &t : finished_threads_) {
10429
0
    if (t.joinable()) { t.join(); }
10430
0
  }
10431
0
  finished_threads_.clear();
10432
0
}
10433
10434
0
inline void ThreadPool::worker(bool is_dynamic) {
10435
0
  for (;;) {
10436
0
    std::function<void()> fn;
10437
0
    {
10438
0
      std::unique_lock<std::mutex> lock(mutex_);
10439
0
      idle_thread_count_++;
10440
10441
0
      if (is_dynamic) {
10442
0
        auto has_work =
10443
0
            cond_.wait_for(lock, std::chrono::seconds(idle_timeout_sec_),
10444
0
                           [&] { return !jobs_.empty() || shutdown_; });
10445
0
        if (!has_work) {
10446
          // Timed out with no work - exit this dynamic thread
10447
0
          idle_thread_count_--;
10448
0
          move_to_finished(std::this_thread::get_id());
10449
0
          break;
10450
0
        }
10451
0
      } else {
10452
0
        cond_.wait(lock, [&] { return !jobs_.empty() || shutdown_; });
10453
0
      }
10454
10455
0
      idle_thread_count_--;
10456
10457
0
      if (shutdown_ && jobs_.empty()) { break; }
10458
10459
0
      fn = std::move(jobs_.front());
10460
0
      jobs_.pop_front();
10461
0
    }
10462
10463
0
    assert(true == static_cast<bool>(fn));
10464
0
    fn();
10465
0
  }
10466
10467
#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) &&   \
10468
    !defined(LIBRESSL_VERSION_NUMBER)
10469
  OPENSSL_thread_stop();
10470
#endif
10471
0
}
10472
10473
/*
10474
 * Group 1 (continued): detail namespace - Stream implementations
10475
 */
10476
10477
namespace detail {
10478
10479
inline void calc_actual_timeout(time_t max_timeout_msec, time_t duration_msec,
10480
                                time_t timeout_sec, time_t timeout_usec,
10481
                                time_t &actual_timeout_sec,
10482
0
                                time_t &actual_timeout_usec) {
10483
0
  auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000);
10484
10485
0
  auto actual_timeout_msec =
10486
0
      (std::min)(max_timeout_msec - duration_msec, timeout_msec);
10487
10488
0
  if (actual_timeout_msec < 0) { actual_timeout_msec = 0; }
10489
10490
0
  actual_timeout_sec = actual_timeout_msec / 1000;
10491
0
  actual_timeout_usec = (actual_timeout_msec % 1000) * 1000;
10492
0
}
10493
10494
// Socket stream implementation
10495
inline SocketStream::SocketStream(
10496
    socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
10497
    time_t write_timeout_sec, time_t write_timeout_usec,
10498
    time_t max_timeout_msec,
10499
    std::chrono::time_point<std::chrono::steady_clock> start_time)
10500
0
    : sock_(sock), read_timeout_sec_(read_timeout_sec),
10501
0
      read_timeout_usec_(read_timeout_usec),
10502
0
      write_timeout_sec_(write_timeout_sec),
10503
0
      write_timeout_usec_(write_timeout_usec),
10504
0
      max_timeout_msec_(max_timeout_msec), start_time_(start_time),
10505
0
      read_buff_(read_buff_size_, 0) {}
10506
10507
0
inline SocketStream::~SocketStream() = default;
10508
10509
0
inline bool SocketStream::is_readable() const {
10510
0
  return read_buff_off_ < read_buff_content_size_;
10511
0
}
10512
10513
0
inline bool SocketStream::wait_readable() const {
10514
0
  if (max_timeout_msec_ <= 0) {
10515
0
    return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
10516
0
  }
10517
10518
0
  time_t read_timeout_sec;
10519
0
  time_t read_timeout_usec;
10520
0
  calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
10521
0
                      read_timeout_usec_, read_timeout_sec, read_timeout_usec);
10522
10523
0
  return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
10524
0
}
10525
10526
0
inline bool SocketStream::wait_writable() const {
10527
0
  return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
10528
0
}
10529
10530
0
inline bool SocketStream::is_peer_alive() const {
10531
0
  return detail::is_socket_alive(sock_);
10532
0
}
10533
10534
0
inline ssize_t SocketStream::read(char *ptr, size_t size) {
10535
#ifdef _WIN32
10536
  size =
10537
      (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
10538
#else
10539
0
  size = (std::min)(size,
10540
0
                    static_cast<size_t>((std::numeric_limits<ssize_t>::max)()));
10541
0
#endif
10542
10543
0
  if (read_buff_off_ < read_buff_content_size_) {
10544
0
    auto remaining_size = read_buff_content_size_ - read_buff_off_;
10545
0
    if (size <= remaining_size) {
10546
0
      memcpy(ptr, read_buff_.data() + read_buff_off_, size);
10547
0
      read_buff_off_ += size;
10548
0
      return static_cast<ssize_t>(size);
10549
0
    } else {
10550
0
      memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size);
10551
0
      read_buff_off_ += remaining_size;
10552
0
      return static_cast<ssize_t>(remaining_size);
10553
0
    }
10554
0
  }
10555
10556
0
  if (!wait_readable()) {
10557
0
    error_ = Error::Timeout;
10558
0
    return -1;
10559
0
  }
10560
10561
0
  read_buff_off_ = 0;
10562
0
  read_buff_content_size_ = 0;
10563
10564
0
  if (size < read_buff_size_) {
10565
0
    auto n = read_socket(sock_, read_buff_.data(), read_buff_size_,
10566
0
                         CPPHTTPLIB_RECV_FLAGS);
10567
0
    if (n <= 0) {
10568
0
      if (n == 0) {
10569
0
        error_ = Error::ConnectionClosed;
10570
0
      } else {
10571
0
        error_ = Error::Read;
10572
0
      }
10573
0
      return n;
10574
0
    } else if (n <= static_cast<ssize_t>(size)) {
10575
0
      memcpy(ptr, read_buff_.data(), static_cast<size_t>(n));
10576
0
      return n;
10577
0
    } else {
10578
0
      memcpy(ptr, read_buff_.data(), size);
10579
0
      read_buff_off_ = size;
10580
0
      read_buff_content_size_ = static_cast<size_t>(n);
10581
0
      return static_cast<ssize_t>(size);
10582
0
    }
10583
0
  } else {
10584
0
    auto n = read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS);
10585
0
    if (n <= 0) {
10586
0
      if (n == 0) {
10587
0
        error_ = Error::ConnectionClosed;
10588
0
      } else {
10589
0
        error_ = Error::Read;
10590
0
      }
10591
0
    }
10592
0
    return n;
10593
0
  }
10594
0
}
10595
10596
0
inline ssize_t SocketStream::write(const char *ptr, size_t size) {
10597
0
  if (!wait_writable()) { return -1; }
10598
10599
#if defined(_WIN32) && !defined(_WIN64)
10600
  size =
10601
      (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
10602
#endif
10603
10604
0
  return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS);
10605
0
}
10606
10607
inline void SocketStream::get_remote_ip_and_port(std::string &ip,
10608
0
                                                 int &port) const {
10609
0
  return detail::get_remote_ip_and_port(sock_, ip, port);
10610
0
}
10611
10612
inline void SocketStream::get_local_ip_and_port(std::string &ip,
10613
0
                                                int &port) const {
10614
0
  return detail::get_local_ip_and_port(sock_, ip, port);
10615
0
}
10616
10617
0
inline socket_t SocketStream::socket() const { return sock_; }
10618
10619
0
inline time_t SocketStream::duration() const {
10620
0
  return std::chrono::duration_cast<std::chrono::milliseconds>(
10621
0
             std::chrono::steady_clock::now() - start_time_)
10622
0
      .count();
10623
0
}
10624
10625
0
inline void SocketStream::set_read_timeout(time_t sec, time_t usec) {
10626
0
  read_timeout_sec_ = sec;
10627
0
  read_timeout_usec_ = usec;
10628
0
}
10629
10630
// Buffer stream implementation
10631
0
inline bool BufferStream::is_readable() const { return true; }
10632
10633
0
inline bool BufferStream::wait_readable() const { return true; }
10634
10635
0
inline bool BufferStream::wait_writable() const { return true; }
10636
10637
0
inline ssize_t BufferStream::read(char *ptr, size_t size) {
10638
#if defined(_MSC_VER) && _MSC_VER < 1910
10639
  auto len_read = buffer._Copy_s(ptr, size, size, position);
10640
#else
10641
0
  auto len_read = buffer.copy(ptr, size, position);
10642
0
#endif
10643
0
  position += static_cast<size_t>(len_read);
10644
0
  return static_cast<ssize_t>(len_read);
10645
0
}
10646
10647
31.5k
inline ssize_t BufferStream::write(const char *ptr, size_t size) {
10648
31.5k
  buffer.append(ptr, size);
10649
31.5k
  return static_cast<ssize_t>(size);
10650
31.5k
}
10651
10652
inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
10653
0
                                                 int & /*port*/) const {}
10654
10655
inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/,
10656
0
                                                int & /*port*/) const {}
10657
10658
0
inline socket_t BufferStream::socket() const { return 0; }
10659
10660
0
inline time_t BufferStream::duration() const { return 0; }
10661
10662
6.77k
inline const std::string &BufferStream::get_buffer() const { return buffer; }
10663
10664
inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern)
10665
0
    : MatcherBase(pattern) {
10666
0
  constexpr const char marker[] = "/:";
10667
10668
  // One past the last ending position of a path param substring
10669
0
  std::size_t last_param_end = 0;
10670
10671
0
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10672
  // Needed to ensure that parameter names are unique during matcher
10673
  // construction
10674
  // If exceptions are disabled, only last duplicate path
10675
  // parameter will be set
10676
0
  std::unordered_set<std::string> param_name_set;
10677
0
#endif
10678
10679
0
  while (true) {
10680
0
    const auto marker_pos = pattern.find(
10681
0
        marker, last_param_end == 0 ? last_param_end : last_param_end - 1);
10682
0
    if (marker_pos == std::string::npos) { break; }
10683
10684
0
    static_fragments_.push_back(
10685
0
        pattern.substr(last_param_end, marker_pos - last_param_end + 1));
10686
10687
0
    const auto param_name_start = marker_pos + str_len(marker);
10688
10689
0
    auto sep_pos = pattern.find(separator, param_name_start);
10690
0
    if (sep_pos == std::string::npos) { sep_pos = pattern.length(); }
10691
10692
0
    auto param_name =
10693
0
        pattern.substr(param_name_start, sep_pos - param_name_start);
10694
10695
0
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
10696
0
    if (param_name_set.find(param_name) != param_name_set.cend()) {
10697
0
      std::string msg = "Encountered path parameter '" + param_name +
10698
0
                        "' multiple times in route pattern '" + pattern + "'.";
10699
0
      throw std::invalid_argument(msg);
10700
0
    }
10701
0
#endif
10702
10703
0
    param_names_.push_back(std::move(param_name));
10704
10705
0
    last_param_end = sep_pos + 1;
10706
0
  }
10707
10708
0
  if (last_param_end < pattern.length()) {
10709
0
    static_fragments_.push_back(pattern.substr(last_param_end));
10710
0
  }
10711
0
}
10712
10713
0
inline bool PathParamsMatcher::match(Request &request) const {
10714
0
  request.matches = std::smatch();
10715
0
  request.path_params.clear();
10716
0
  request.path_params.reserve(param_names_.size());
10717
10718
  // One past the position at which the path matched the pattern last time
10719
0
  std::size_t starting_pos = 0;
10720
0
  for (size_t i = 0; i < static_fragments_.size(); ++i) {
10721
0
    const auto &fragment = static_fragments_[i];
10722
10723
0
    if (starting_pos + fragment.length() > request.path.length()) {
10724
0
      return false;
10725
0
    }
10726
10727
    // Avoid unnecessary allocation by using strncmp instead of substr +
10728
    // comparison
10729
0
    if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(),
10730
0
                     fragment.length()) != 0) {
10731
0
      return false;
10732
0
    }
10733
10734
0
    starting_pos += fragment.length();
10735
10736
    // Should only happen when we have a static fragment after a param
10737
    // Example: '/users/:id/subscriptions'
10738
    // The 'subscriptions' fragment here does not have a corresponding param
10739
0
    if (i >= param_names_.size()) { continue; }
10740
10741
0
    auto sep_pos = request.path.find(separator, starting_pos);
10742
0
    if (sep_pos == std::string::npos) { sep_pos = request.path.length(); }
10743
10744
0
    const auto &param_name = param_names_[i];
10745
10746
0
    request.path_params.emplace(
10747
0
        param_name, request.path.substr(starting_pos, sep_pos - starting_pos));
10748
10749
    // Mark everything up to '/' as matched
10750
0
    starting_pos = sep_pos + 1;
10751
0
  }
10752
  // Returns false if the path is longer than the pattern
10753
0
  return starting_pos >= request.path.length();
10754
0
}
10755
10756
4.28k
inline bool RegexMatcher::match(Request &request) const {
10757
4.28k
  request.path_params.clear();
10758
4.28k
  return std::regex_match(request.path, request.matches, regex_);
10759
4.28k
}
10760
10761
// Enclose IPv6 address in brackets if needed
10762
0
inline std::string prepare_host_string(const std::string &host) {
10763
0
  // Enclose IPv6 address in brackets (but not if already enclosed)
10764
0
  if (host.find(':') == std::string::npos ||
10765
0
      (!host.empty() && host[0] == '[')) {
10766
0
    // IPv4, hostname, or already bracketed IPv6
10767
0
    return host;
10768
0
  } else {
10769
0
    // IPv6 address without brackets
10770
0
    return "[" + host + "]";
10771
0
  }
10772
0
}
10773
10774
inline std::string make_host_and_port_string(const std::string &host, int port,
10775
0
                                             bool is_ssl) {
10776
0
  auto result = prepare_host_string(host);
10777
0
10778
0
  // Append port if not default
10779
0
  if ((!is_ssl && port == 80) || (is_ssl && port == 443)) {
10780
0
    ; // do nothing
10781
0
  } else {
10782
0
    result += ":" + std::to_string(port);
10783
0
  }
10784
0
10785
0
  return result;
10786
0
}
10787
10788
// Create "host:port" string always including port number (for CONNECT method)
10789
inline std::string
10790
0
make_host_and_port_string_always_port(const std::string &host, int port) {
10791
0
  return prepare_host_string(host) + ":" + std::to_string(port);
10792
0
}
10793
10794
bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out);
10795
NormalizedTarget normalize_target(const std::string &host);
10796
bool ip_in_cidr(const IPBytes &ip, const IPBytes &net, int prefix_bits);
10797
bool host_matches_no_proxy(const NormalizedTarget &target,
10798
                           const std::vector<NoProxyEntry> &entries);
10799
10800
0
inline bool ip_in_cidr(const IPBytes &ip, const IPBytes &net, int prefix_bits) {
10801
0
  if (prefix_bits < 0 || prefix_bits > 128) { return false; }
10802
0
  if (prefix_bits == 0) { return true; }
10803
0
  int full_bytes = prefix_bits / 8;
10804
0
  int rem_bits = prefix_bits % 8;
10805
0
  if (full_bytes > 0 && std::memcmp(ip.data(), net.data(),
10806
0
                                    static_cast<size_t>(full_bytes)) != 0) {
10807
0
    return false;
10808
0
  }
10809
0
  if (rem_bits == 0) { return true; }
10810
0
  auto i = static_cast<size_t>(full_bytes);
10811
0
  auto mask = static_cast<uint8_t>(0xFFu << (8 - rem_bits));
10812
0
  return (ip[i] & mask) == (net[i] & mask);
10813
0
}
10814
10815
0
inline bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out) {
10816
0
  if (token.empty()) { return false; }
10817
0
10818
0
  if (token == "*") {
10819
0
    out.kind = NoProxyKind::Wildcard;
10820
0
    return true;
10821
0
  }
10822
0
10823
0
  auto slash = token.find('/');
10824
0
  std::string addr_part =
10825
0
      (slash == std::string::npos) ? token : token.substr(0, slash);
10826
0
  std::string prefix_part =
10827
0
      (slash == std::string::npos) ? std::string() : token.substr(slash + 1);
10828
0
10829
0
  // A bare slash or trailing-slash CIDR like "10.0.0.0/" is malformed;
10830
0
  // don't silently treat it as a /32 (or /128).
10831
0
  if (slash != std::string::npos && prefix_part.empty()) { return false; }
10832
0
10833
0
  // Accept the bracketed IPv6 form ("[::1]", "[fe80::]/10") as well as the
10834
0
  // bare form. Brackets have no meaning for IPv4, so skip the IPv4 attempt
10835
0
  // when brackets are present.
10836
0
  bool bracketed = addr_part.size() >= 2 && addr_part.front() == '[' &&
10837
0
                   addr_part.back() == ']';
10838
0
  if (bracketed) { addr_part = addr_part.substr(1, addr_part.size() - 2); }
10839
0
10840
0
  if (!bracketed) {
10841
0
    struct in_addr v4;
10842
0
    if (inet_pton(AF_INET, addr_part.c_str(), &v4) == 1) {
10843
0
      int prefix = 32;
10844
0
      if (!prefix_part.empty()) {
10845
0
        auto r = from_chars(prefix_part.data(),
10846
0
                            prefix_part.data() + prefix_part.size(), prefix);
10847
0
        if (r.ec != std::errc{} ||
10848
0
            r.ptr != prefix_part.data() + prefix_part.size()) {
10849
0
          return false;
10850
0
        }
10851
0
        if (prefix < 0 || prefix > 32) { return false; }
10852
0
      }
10853
0
      out.kind = NoProxyKind::IPv4Cidr;
10854
0
      std::memcpy(out.net.data(), &v4, sizeof(v4));
10855
0
      out.prefix_bits = prefix;
10856
0
      return true;
10857
0
    }
10858
0
  }
10859
0
10860
0
  struct in6_addr v6;
10861
0
  if (inet_pton(AF_INET6, addr_part.c_str(), &v6) == 1) {
10862
0
    int prefix = 128;
10863
0
    if (!prefix_part.empty()) {
10864
0
      auto r = from_chars(prefix_part.data(),
10865
0
                          prefix_part.data() + prefix_part.size(), prefix);
10866
0
      if (r.ec != std::errc{} ||
10867
0
          r.ptr != prefix_part.data() + prefix_part.size()) {
10868
0
        return false;
10869
0
      }
10870
0
      if (prefix < 0 || prefix > 128) { return false; }
10871
0
    }
10872
0
    out.kind = NoProxyKind::IPv6Cidr;
10873
0
    std::memcpy(out.net.data(), &v6, sizeof(v6));
10874
0
    out.prefix_bits = prefix;
10875
0
    return true;
10876
0
  }
10877
0
10878
0
  // Bracketed entries can only be IPv6. If the IPv6 parse above failed,
10879
0
  // the entry is malformed — don't fall through to the hostname branch.
10880
0
  if (bracketed) { return false; }
10881
0
10882
0
  // A '/' on a non-IP token means a CIDR prefix without an address. Reject.
10883
0
  if (slash != std::string::npos) { return false; }
10884
0
  // Port-specific entries (host:port) are not supported.
10885
0
  if (token.find(':') != std::string::npos) { return false; }
10886
0
10887
0
  std::string hostname = case_ignore::to_lower(token);
10888
0
  while (!hostname.empty() && hostname.front() == '.') {
10889
0
    hostname.erase(hostname.begin());
10890
0
  }
10891
0
  while (!hostname.empty() && hostname.back() == '.') {
10892
0
    hostname.pop_back();
10893
0
  }
10894
0
  if (hostname.empty()) { return false; }
10895
0
10896
0
  out.kind = NoProxyKind::HostnameSuffix;
10897
0
  out.hostname_pattern = std::move(hostname);
10898
0
  return true;
10899
0
}
10900
10901
0
inline NormalizedTarget normalize_target(const std::string &host) {
10902
0
  NormalizedTarget t;
10903
0
  std::string h = host;
10904
10905
0
  if (h.size() >= 2 && h.front() == '[' && h.back() == ']') {
10906
0
    h = h.substr(1, h.size() - 2);
10907
0
  }
10908
10909
  // Strip a single trailing dot so "example.com." canonicalizes to
10910
  // "example.com".
10911
0
  if (!h.empty() && h.back() == '.') { h.pop_back(); }
10912
10913
0
  t.hostname = case_ignore::to_lower(h);
10914
10915
0
  if (!t.hostname.empty()) {
10916
0
    struct in_addr v4;
10917
0
    struct in6_addr v6;
10918
0
    if (inet_pton(AF_INET, t.hostname.c_str(), &v4) == 1) {
10919
0
      t.is_ipv4 = true;
10920
0
      std::memcpy(t.ip.data(), &v4, sizeof(v4));
10921
0
    } else if (inet_pton(AF_INET6, t.hostname.c_str(), &v6) == 1) {
10922
0
      t.is_ipv6 = true;
10923
0
      std::memcpy(t.ip.data(), &v6, sizeof(v6));
10924
0
    }
10925
0
  }
10926
0
  return t;
10927
0
}
10928
10929
inline bool host_matches_no_proxy(const NormalizedTarget &target,
10930
0
                                  const std::vector<NoProxyEntry> &entries) {
10931
0
  if (target.hostname.empty()) { return false; }
10932
0
  for (const auto &e : entries) {
10933
0
    switch (e.kind) {
10934
0
    case NoProxyKind::Wildcard: return true;
10935
0
    case NoProxyKind::IPv4Cidr:
10936
0
      if (target.is_ipv4 && ip_in_cidr(target.ip, e.net, e.prefix_bits)) {
10937
0
        return true;
10938
0
      }
10939
0
      break;
10940
0
    case NoProxyKind::IPv6Cidr:
10941
0
      if (target.is_ipv6 && ip_in_cidr(target.ip, e.net, e.prefix_bits)) {
10942
0
        return true;
10943
0
      }
10944
0
      break;
10945
0
    case NoProxyKind::HostnameSuffix:
10946
0
      if (target.is_ipv4 || target.is_ipv6) { break; }
10947
0
      if (target.hostname == e.hostname_pattern) { return true; }
10948
      // Dot-boundary suffix match: prevents "evilexample.com" from matching
10949
      // an entry of "example.com".
10950
0
      if (target.hostname.size() > e.hostname_pattern.size() + 1) {
10951
0
        auto offset = target.hostname.size() - e.hostname_pattern.size();
10952
0
        if (target.hostname[offset - 1] == '.' &&
10953
0
            target.hostname.compare(offset, e.hostname_pattern.size(),
10954
0
                                    e.hostname_pattern) == 0) {
10955
0
          return true;
10956
0
        }
10957
0
      }
10958
0
      break;
10959
0
    }
10960
0
  }
10961
0
  return false;
10962
0
}
10963
10964
template <typename T>
10965
inline bool check_and_write_headers(Stream &strm, Headers &headers,
10966
0
                                    T header_writer, Error &error) {
10967
0
  for (const auto &h : headers) {
10968
0
    if (!detail::fields::is_field_name(h.first) ||
10969
0
        !detail::fields::is_field_value(h.second)) {
10970
0
      error = Error::InvalidHeaders;
10971
0
      return false;
10972
0
    }
10973
0
  }
10974
0
  if (header_writer(strm, headers) <= 0) {
10975
0
    error = Error::Write;
10976
0
    return false;
10977
0
  }
10978
0
  return true;
10979
0
}
10980
10981
} // namespace detail
10982
10983
/*
10984
 * Group 2 (continued): detail namespace - SSLSocketStream implementation
10985
 */
10986
10987
#ifdef CPPHTTPLIB_SSL_ENABLED
10988
namespace detail {
10989
10990
// SSL socket stream implementation
10991
inline SSLSocketStream::SSLSocketStream(
10992
    socket_t sock, tls::session_t session, time_t read_timeout_sec,
10993
    time_t read_timeout_usec, time_t write_timeout_sec,
10994
    time_t write_timeout_usec, time_t max_timeout_msec,
10995
    std::chrono::time_point<std::chrono::steady_clock> start_time)
10996
    : sock_(sock), session_(session), read_timeout_sec_(read_timeout_sec),
10997
      read_timeout_usec_(read_timeout_usec),
10998
      write_timeout_sec_(write_timeout_sec),
10999
      write_timeout_usec_(write_timeout_usec),
11000
      max_timeout_msec_(max_timeout_msec), start_time_(start_time) {
11001
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
11002
  // Clear AUTO_RETRY for proper non-blocking I/O timeout handling
11003
  // Note: create_session() also clears this, but SSLClient currently
11004
  // uses ssl_new() which does not. Until full TLS API migration is complete,
11005
  // we need to ensure AUTO_RETRY is cleared here regardless of how the
11006
  // SSL session was created.
11007
  SSL_clear_mode(static_cast<SSL *>(session), SSL_MODE_AUTO_RETRY);
11008
#endif
11009
}
11010
11011
inline SSLSocketStream::~SSLSocketStream() = default;
11012
11013
inline bool SSLSocketStream::is_readable() const {
11014
  return tls::pending(session_) > 0;
11015
}
11016
11017
inline bool SSLSocketStream::wait_readable() const {
11018
  if (max_timeout_msec_ <= 0) {
11019
    return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
11020
  }
11021
11022
  time_t read_timeout_sec;
11023
  time_t read_timeout_usec;
11024
  calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
11025
                      read_timeout_usec_, read_timeout_sec, read_timeout_usec);
11026
11027
  return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
11028
}
11029
11030
inline bool SSLSocketStream::wait_writable() const {
11031
  return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
11032
         !tls::is_peer_closed(session_, sock_);
11033
}
11034
11035
inline bool SSLSocketStream::is_peer_alive() const {
11036
  return !tls::is_peer_closed(session_, sock_);
11037
}
11038
11039
inline ssize_t SSLSocketStream::read(char *ptr, size_t size) {
11040
  if (tls::pending(session_) > 0) {
11041
    tls::TlsError err;
11042
    auto ret = tls::read(session_, ptr, size, err);
11043
    if (ret == 0 || err.code == tls::ErrorCode::PeerClosed) {
11044
      error_ = Error::ConnectionClosed;
11045
    }
11046
    return ret;
11047
  } else if (wait_readable()) {
11048
    tls::TlsError err;
11049
    auto ret = tls::read(session_, ptr, size, err);
11050
    if (ret < 0) {
11051
      auto n = 1000;
11052
#ifdef _WIN32
11053
      while (--n >= 0 && (err.code == tls::ErrorCode::WantRead ||
11054
                          (err.code == tls::ErrorCode::SyscallError &&
11055
                           WSAGetLastError() == WSAETIMEDOUT))) {
11056
#else
11057
      while (--n >= 0 && err.code == tls::ErrorCode::WantRead) {
11058
#endif
11059
        if (tls::pending(session_) > 0) {
11060
          return tls::read(session_, ptr, size, err);
11061
        } else if (wait_readable()) {
11062
          std::this_thread::sleep_for(std::chrono::microseconds{10});
11063
          ret = tls::read(session_, ptr, size, err);
11064
          if (ret >= 0) { return ret; }
11065
        } else {
11066
          break;
11067
        }
11068
      }
11069
      assert(ret < 0);
11070
    } else if (ret == 0 || err.code == tls::ErrorCode::PeerClosed) {
11071
      error_ = Error::ConnectionClosed;
11072
    }
11073
    return ret;
11074
  } else {
11075
    error_ = Error::Timeout;
11076
    return -1;
11077
  }
11078
}
11079
11080
inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
11081
  if (wait_writable()) {
11082
    auto handle_size =
11083
        std::min<size_t>(size, (std::numeric_limits<int>::max)());
11084
11085
    tls::TlsError err;
11086
    auto ret = tls::write(session_, ptr, handle_size, err);
11087
    if (ret < 0) {
11088
      auto n = 1000;
11089
#ifdef _WIN32
11090
      while (--n >= 0 && (err.code == tls::ErrorCode::WantWrite ||
11091
                          (err.code == tls::ErrorCode::SyscallError &&
11092
                           WSAGetLastError() == WSAETIMEDOUT))) {
11093
#else
11094
      while (--n >= 0 && err.code == tls::ErrorCode::WantWrite) {
11095
#endif
11096
        if (wait_writable()) {
11097
          std::this_thread::sleep_for(std::chrono::microseconds{10});
11098
          ret = tls::write(session_, ptr, handle_size, err);
11099
          if (ret >= 0) { return ret; }
11100
        } else {
11101
          break;
11102
        }
11103
      }
11104
      assert(ret < 0);
11105
    }
11106
    return ret;
11107
  }
11108
  return -1;
11109
}
11110
11111
inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
11112
                                                    int &port) const {
11113
  detail::get_remote_ip_and_port(sock_, ip, port);
11114
}
11115
11116
inline void SSLSocketStream::get_local_ip_and_port(std::string &ip,
11117
                                                   int &port) const {
11118
  detail::get_local_ip_and_port(sock_, ip, port);
11119
}
11120
11121
inline socket_t SSLSocketStream::socket() const { return sock_; }
11122
11123
inline time_t SSLSocketStream::duration() const {
11124
  return std::chrono::duration_cast<std::chrono::milliseconds>(
11125
             std::chrono::steady_clock::now() - start_time_)
11126
      .count();
11127
}
11128
11129
inline void SSLSocketStream::set_read_timeout(time_t sec, time_t usec) {
11130
  read_timeout_sec_ = sec;
11131
  read_timeout_usec_ = usec;
11132
}
11133
11134
} // namespace detail
11135
#endif // CPPHTTPLIB_SSL_ENABLED
11136
11137
/*
11138
 * Group 4: Server implementation
11139
 */
11140
11141
// HTTP server implementation
11142
inline Server::Server()
11143
2
    : new_task_queue([] {
11144
0
        return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT,
11145
0
                              CPPHTTPLIB_THREAD_POOL_MAX_COUNT);
11146
2
      }) {
11147
2
#ifndef _WIN32
11148
2
  signal(SIGPIPE, SIG_IGN);
11149
2
#endif
11150
2
}
11151
11152
0
inline Server::~Server() = default;
11153
11154
inline std::unique_ptr<detail::MatcherBase>
11155
12
Server::make_matcher(const std::string &pattern) {
11156
12
  if (pattern.find("/:") != std::string::npos) {
11157
0
    return detail::make_unique<detail::PathParamsMatcher>(pattern);
11158
12
  } else {
11159
12
    return detail::make_unique<detail::RegexMatcher>(pattern);
11160
12
  }
11161
12
}
11162
11163
2
inline Server &Server::Get(const std::string &pattern, Handler handler) {
11164
2
  return add_handler(get_handlers_, pattern, std::move(handler));
11165
2
}
11166
11167
2
inline Server &Server::Post(const std::string &pattern, Handler handler) {
11168
2
  return add_handler(post_handlers_, pattern, std::move(handler));
11169
2
}
11170
11171
inline Server &Server::Post(const std::string &pattern,
11172
0
                            HandlerWithContentReader handler) {
11173
0
  return add_handler(post_handlers_for_content_reader_, pattern,
11174
0
                     std::move(handler));
11175
0
}
11176
11177
2
inline Server &Server::Put(const std::string &pattern, Handler handler) {
11178
2
  return add_handler(put_handlers_, pattern, std::move(handler));
11179
2
}
11180
11181
inline Server &Server::Put(const std::string &pattern,
11182
0
                           HandlerWithContentReader handler) {
11183
0
  return add_handler(put_handlers_for_content_reader_, pattern,
11184
0
                     std::move(handler));
11185
0
}
11186
11187
2
inline Server &Server::Patch(const std::string &pattern, Handler handler) {
11188
2
  return add_handler(patch_handlers_, pattern, std::move(handler));
11189
2
}
11190
11191
inline Server &Server::Patch(const std::string &pattern,
11192
0
                             HandlerWithContentReader handler) {
11193
0
  return add_handler(patch_handlers_for_content_reader_, pattern,
11194
0
                     std::move(handler));
11195
0
}
11196
11197
2
inline Server &Server::Delete(const std::string &pattern, Handler handler) {
11198
2
  return add_handler(delete_handlers_, pattern, std::move(handler));
11199
2
}
11200
11201
inline Server &Server::Delete(const std::string &pattern,
11202
0
                              HandlerWithContentReader handler) {
11203
0
  return add_handler(delete_handlers_for_content_reader_, pattern,
11204
0
                     std::move(handler));
11205
0
}
11206
11207
2
inline Server &Server::Options(const std::string &pattern, Handler handler) {
11208
2
  return add_handler(options_handlers_, pattern, std::move(handler));
11209
2
}
11210
11211
inline Server &Server::WebSocket(const std::string &pattern,
11212
0
                                 WebSocketHandler handler) {
11213
0
  websocket_handlers_.push_back(
11214
0
      {make_matcher(pattern), std::move(handler), nullptr});
11215
0
  return *this;
11216
0
}
11217
11218
inline Server &Server::WebSocket(const std::string &pattern,
11219
                                 WebSocketHandler handler,
11220
0
                                 SubProtocolSelector sub_protocol_selector) {
11221
0
  websocket_handlers_.push_back({make_matcher(pattern), std::move(handler),
11222
0
                                 std::move(sub_protocol_selector)});
11223
0
  return *this;
11224
0
}
11225
11226
inline bool Server::set_base_dir(const std::string &dir,
11227
0
                                 const std::string &mount_point) {
11228
0
  return set_mount_point(mount_point, dir);
11229
0
}
11230
11231
inline bool Server::set_mount_point(const std::string &mount_point,
11232
0
                                    const std::string &dir, Headers headers) {
11233
0
  detail::FileStat stat(dir);
11234
0
  if (stat.is_dir()) {
11235
0
    std::string mnt = !mount_point.empty() ? mount_point : "/";
11236
0
    if (!mnt.empty() && mnt[0] == '/') {
11237
0
      std::string resolved_base;
11238
0
      if (detail::canonicalize_path(dir.c_str(), resolved_base)) {
11239
0
#if defined(_WIN32)
11240
0
        if (resolved_base.back() != '\\' && resolved_base.back() != '/') {
11241
0
          resolved_base += '\\';
11242
0
        }
11243
0
#else
11244
0
        if (resolved_base.back() != '/') { resolved_base += '/'; }
11245
0
#endif
11246
0
      }
11247
0
      base_dirs_.push_back(
11248
0
          {std::move(mnt), dir, std::move(resolved_base), std::move(headers)});
11249
0
      return true;
11250
0
    }
11251
0
  }
11252
0
  return false;
11253
0
}
11254
11255
0
inline bool Server::remove_mount_point(const std::string &mount_point) {
11256
0
  for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
11257
0
    if (it->mount_point == mount_point) {
11258
0
      base_dirs_.erase(it);
11259
0
      return true;
11260
0
    }
11261
0
  }
11262
0
  return false;
11263
0
}
11264
11265
inline Server &
11266
Server::set_file_extension_and_mimetype_mapping(const std::string &ext,
11267
0
                                                const std::string &mime) {
11268
0
  file_extension_and_mimetype_map_[ext] = mime;
11269
0
  return *this;
11270
0
}
11271
11272
0
inline Server &Server::set_default_file_mimetype(const std::string &mime) {
11273
0
  default_file_mimetype_ = mime;
11274
0
  return *this;
11275
0
}
11276
11277
0
inline Server &Server::set_file_request_handler(Handler handler) {
11278
0
  file_request_handler_ = std::move(handler);
11279
0
  return *this;
11280
0
}
11281
11282
inline Server &Server::set_error_handler_core(HandlerWithResponse handler,
11283
0
                                              std::true_type) {
11284
0
  error_handler_ = std::move(handler);
11285
0
  return *this;
11286
0
}
11287
11288
inline Server &Server::set_error_handler_core(Handler handler,
11289
0
                                              std::false_type) {
11290
0
  error_handler_ = [handler](const Request &req, Response &res) {
11291
0
    handler(req, res);
11292
0
    return HandlerResponse::Handled;
11293
0
  };
11294
0
  return *this;
11295
0
}
11296
11297
0
inline Server &Server::set_exception_handler(ExceptionHandler handler) {
11298
0
  exception_handler_ = std::move(handler);
11299
0
  return *this;
11300
0
}
11301
11302
0
inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) {
11303
0
  pre_routing_handler_ = std::move(handler);
11304
0
  return *this;
11305
0
}
11306
11307
0
inline Server &Server::set_post_routing_handler(Handler handler) {
11308
0
  post_routing_handler_ = std::move(handler);
11309
0
  return *this;
11310
0
}
11311
11312
0
inline Server &Server::set_pre_request_handler(HandlerWithResponse handler) {
11313
0
  pre_request_handler_ = std::move(handler);
11314
0
  return *this;
11315
0
}
11316
11317
0
inline Server &Server::set_logger(Logger logger) {
11318
0
  logger_ = std::move(logger);
11319
0
  return *this;
11320
0
}
11321
11322
0
inline Server &Server::set_error_logger(ErrorLogger error_logger) {
11323
0
  error_logger_ = std::move(error_logger);
11324
0
  return *this;
11325
0
}
11326
11327
0
inline Server &Server::set_pre_compression_logger(Logger logger) {
11328
0
  pre_compression_logger_ = std::move(logger);
11329
0
  return *this;
11330
0
}
11331
11332
inline Server &
11333
0
Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
11334
0
  expect_100_continue_handler_ = std::move(handler);
11335
0
  return *this;
11336
0
}
11337
11338
0
inline Server &Server::set_start_handler(StartHandler handler) {
11339
0
  start_handler_ = std::move(handler);
11340
0
  return *this;
11341
0
}
11342
11343
0
inline Server &Server::set_address_family(int family) {
11344
0
  address_family_ = family;
11345
0
  return *this;
11346
0
}
11347
11348
0
inline Server &Server::set_tcp_nodelay(bool on) {
11349
0
  tcp_nodelay_ = on;
11350
0
  return *this;
11351
0
}
11352
11353
0
inline Server &Server::set_ipv6_v6only(bool on) {
11354
0
  ipv6_v6only_ = on;
11355
0
  return *this;
11356
0
}
11357
11358
0
inline Server &Server::set_socket_options(SocketOptions socket_options) {
11359
0
  socket_options_ = std::move(socket_options);
11360
0
  return *this;
11361
0
}
11362
11363
0
inline Server &Server::set_default_headers(Headers headers) {
11364
0
  default_headers_ = std::move(headers);
11365
0
  return *this;
11366
0
}
11367
11368
inline Server &Server::set_header_writer(
11369
0
    std::function<ssize_t(Stream &, Headers &)> const &writer) {
11370
0
  header_writer_ = writer;
11371
0
  return *this;
11372
0
}
11373
11374
inline Server &
11375
0
Server::set_trusted_proxies(const std::vector<std::string> &proxies) {
11376
0
  trusted_proxies_ = proxies;
11377
0
  return *this;
11378
0
}
11379
11380
0
inline Server &Server::set_keep_alive_max_count(size_t count) {
11381
0
  keep_alive_max_count_ = count;
11382
0
  return *this;
11383
0
}
11384
11385
0
inline Server &Server::set_keep_alive_timeout(time_t sec) {
11386
0
  keep_alive_timeout_sec_ = sec;
11387
0
  return *this;
11388
0
}
11389
11390
template <class Rep, class Period>
11391
inline Server &Server::set_keep_alive_timeout(
11392
    const std::chrono::duration<Rep, Period> &duration) {
11393
  detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t /*usec*/) {
11394
    set_keep_alive_timeout(sec);
11395
  });
11396
  return *this;
11397
}
11398
11399
0
inline Server &Server::set_read_timeout(time_t sec, time_t usec) {
11400
0
  read_timeout_sec_ = sec;
11401
0
  read_timeout_usec_ = usec;
11402
0
  return *this;
11403
0
}
11404
11405
0
inline Server &Server::set_write_timeout(time_t sec, time_t usec) {
11406
0
  write_timeout_sec_ = sec;
11407
0
  write_timeout_usec_ = usec;
11408
0
  return *this;
11409
0
}
11410
11411
0
inline Server &Server::set_idle_interval(time_t sec, time_t usec) {
11412
0
  idle_interval_sec_ = sec;
11413
0
  idle_interval_usec_ = usec;
11414
0
  return *this;
11415
0
}
11416
11417
0
inline Server &Server::set_payload_max_length(size_t length) {
11418
0
  payload_max_length_ = length;
11419
0
  return *this;
11420
0
}
11421
11422
0
inline Server &Server::set_websocket_max_missed_pongs(int count) {
11423
0
  websocket_max_missed_pongs_ = count;
11424
0
  return *this;
11425
0
}
11426
11427
0
inline Server &Server::set_websocket_ping_interval(time_t sec) {
11428
0
  websocket_ping_interval_sec_ = sec;
11429
0
  return *this;
11430
0
}
11431
11432
template <class Rep, class Period>
11433
inline Server &Server::set_websocket_ping_interval(
11434
    const std::chrono::duration<Rep, Period> &duration) {
11435
  detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t /*usec*/) {
11436
    set_websocket_ping_interval(sec);
11437
  });
11438
  return *this;
11439
}
11440
11441
inline bool Server::bind_to_port(const std::string &host, int port,
11442
0
                                 int socket_flags) {
11443
0
  auto ret = bind_internal(host, port, socket_flags);
11444
0
  if (ret == -1) { is_decommissioned = true; }
11445
0
  return ret >= 0;
11446
0
}
11447
0
inline int Server::bind_to_any_port(const std::string &host, int socket_flags) {
11448
0
  auto ret = bind_internal(host, 0, socket_flags);
11449
0
  if (ret == -1) { is_decommissioned = true; }
11450
0
  return ret;
11451
0
}
11452
11453
0
inline bool Server::listen_after_bind() { return listen_internal(); }
11454
11455
inline bool Server::listen(const std::string &host, int port,
11456
0
                           int socket_flags) {
11457
0
  return bind_to_port(host, port, socket_flags) && listen_internal();
11458
0
}
11459
11460
0
inline bool Server::is_running() const { return is_running_; }
11461
11462
0
inline void Server::wait_until_ready() const {
11463
0
  while (!is_running_ && !is_decommissioned) {
11464
0
    std::this_thread::sleep_for(std::chrono::milliseconds{1});
11465
0
  }
11466
0
}
11467
11468
0
inline void Server::stop() noexcept {
11469
0
  if (is_running_) {
11470
0
    assert(svr_sock_ != INVALID_SOCKET);
11471
0
    std::atomic<socket_t> sock(svr_sock_.exchange(INVALID_SOCKET));
11472
0
    detail::shutdown_socket(sock);
11473
0
    detail::close_socket(sock);
11474
0
  }
11475
0
  is_decommissioned = false;
11476
0
}
11477
11478
0
inline void Server::decommission() { is_decommissioned = true; }
11479
11480
6.77k
inline bool Server::parse_request_line(const char *s, Request &req) const {
11481
6.77k
  auto len = strlen(s);
11482
6.77k
  if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; }
11483
6.71k
  len -= 2;
11484
11485
6.71k
  {
11486
6.71k
    size_t count = 0;
11487
11488
45.8k
    detail::split(s, s + len, ' ', [&](const char *b, const char *e) {
11489
45.8k
      switch (count) {
11490
6.68k
      case 0: req.method = std::string(b, e); break;
11491
6.57k
      case 1: req.target = std::string(b, e); break;
11492
6.56k
      case 2: req.version = std::string(b, e); break;
11493
26.0k
      default: break;
11494
45.8k
      }
11495
45.8k
      count++;
11496
45.8k
    });
11497
11498
6.71k
    if (count != 3) { return false; }
11499
6.71k
  }
11500
11501
6.53k
  thread_local const std::set<std::string> methods{
11502
6.53k
      "GET",     "HEAD",    "POST",  "PUT",   "DELETE",
11503
6.53k
      "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"};
11504
11505
6.53k
  if (methods.find(req.method) == methods.end()) {
11506
46
    output_error_log(Error::InvalidHTTPMethod, &req);
11507
46
    return false;
11508
46
  }
11509
11510
6.48k
  if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") {
11511
37
    output_error_log(Error::InvalidHTTPVersion, &req);
11512
37
    return false;
11513
37
  }
11514
11515
6.44k
  {
11516
    // Skip URL fragment
11517
616k
    for (size_t i = 0; i < req.target.size(); i++) {
11518
610k
      if (req.target[i] == '#') {
11519
33
        req.target.erase(i);
11520
33
        break;
11521
33
      }
11522
610k
    }
11523
11524
6.44k
    detail::divide(req.target, '?',
11525
6.44k
                   [&](const char *lhs_data, std::size_t lhs_size,
11526
6.44k
                       const char *rhs_data, std::size_t rhs_size) {
11527
6.44k
                     req.path =
11528
6.44k
                         decode_path_component(std::string(lhs_data, lhs_size));
11529
6.44k
                     detail::parse_query_text(rhs_data, rhs_size, req.params);
11530
6.44k
                   });
11531
6.44k
  }
11532
11533
6.44k
  return true;
11534
6.48k
}
11535
11536
inline bool Server::write_response(Stream &strm, bool close_connection,
11537
4.87k
                                   Request &req, Response &res) {
11538
  // NOTE: `req.ranges` should be empty, otherwise it will be applied
11539
  // incorrectly to the error content.
11540
4.87k
  req.ranges.clear();
11541
4.87k
  return write_response_core(strm, close_connection, req, res, false);
11542
4.87k
}
11543
11544
inline bool Server::write_response_with_content(Stream &strm,
11545
                                                bool close_connection,
11546
                                                const Request &req,
11547
1.90k
                                                Response &res) {
11548
1.90k
  return write_response_core(strm, close_connection, req, res, true);
11549
1.90k
}
11550
11551
inline bool Server::write_response_core(Stream &strm, bool close_connection,
11552
                                        const Request &req, Response &res,
11553
6.77k
                                        bool need_apply_ranges) {
11554
6.77k
  assert(res.status != -1);
11555
11556
6.77k
  if (400 <= res.status && error_handler_ &&
11557
0
      error_handler_(req, res) == HandlerResponse::Handled) {
11558
0
    need_apply_ranges = true;
11559
0
  }
11560
11561
6.77k
  std::string content_type;
11562
6.77k
  std::string boundary;
11563
6.77k
  if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); }
11564
11565
  // Prepare additional headers
11566
6.77k
  if (close_connection || req.get_header_value("Connection") == "close" ||
11567
6.77k
      400 <= res.status) { // Don't leave connections open after errors
11568
4.87k
    res.set_header("Connection", "close");
11569
4.87k
  } else {
11570
1.90k
    std::string s = "timeout=";
11571
1.90k
    s += std::to_string(keep_alive_timeout_sec_);
11572
1.90k
    s += ", max=";
11573
1.90k
    s += std::to_string(keep_alive_max_count_);
11574
1.90k
    res.set_header("Keep-Alive", s);
11575
1.90k
  }
11576
11577
6.77k
  if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) &&
11578
1.90k
      !res.has_header("Content-Type")) {
11579
0
    res.set_header("Content-Type", "text/plain");
11580
0
  }
11581
11582
6.77k
  if (res.body.empty() && !res.content_length_ && !res.content_provider_ &&
11583
4.87k
      !res.has_header("Content-Length")) {
11584
4.87k
    res.set_header("Content-Length", "0");
11585
4.87k
  }
11586
11587
6.77k
  if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) {
11588
126
    res.set_header("Accept-Ranges", "bytes");
11589
126
  }
11590
11591
6.77k
  if (post_routing_handler_) { post_routing_handler_(req, res); }
11592
11593
  // Response line and headers
11594
6.77k
  detail::BufferStream bstrm;
11595
6.77k
  if (!detail::write_response_line(bstrm, res.status)) { return false; }
11596
6.77k
  if (header_writer_(bstrm, res.headers) <= 0) { return false; }
11597
11598
  // Combine small body with headers to reduce write syscalls
11599
6.77k
  if (req.method != "HEAD" && !res.body.empty() && !res.content_provider_) {
11600
1.87k
    bstrm.write(res.body.data(), res.body.size());
11601
1.87k
  }
11602
11603
  // Log before writing to avoid race condition with client-side code that
11604
  // accesses logger-captured data immediately after receiving the response.
11605
6.77k
  output_log(req, res);
11606
11607
  // Flush buffer
11608
6.77k
  auto &data = bstrm.get_buffer();
11609
6.77k
  if (!detail::write_data(strm, data.data(), data.size())) { return false; }
11610
11611
  // Streaming body
11612
6.77k
  auto ret = true;
11613
6.77k
  if (req.method != "HEAD" && res.content_provider_) {
11614
0
    if (write_content_with_provider(strm, req, res, boundary, content_type)) {
11615
0
      res.content_provider_success_ = true;
11616
0
    } else {
11617
0
      ret = false;
11618
0
    }
11619
0
  }
11620
11621
6.77k
  return ret;
11622
6.77k
}
11623
11624
inline bool
11625
Server::write_content_with_provider(Stream &strm, const Request &req,
11626
                                    Response &res, const std::string &boundary,
11627
0
                                    const std::string &content_type) {
11628
0
  auto is_shutting_down = [this]() {
11629
0
    return this->svr_sock_ == INVALID_SOCKET;
11630
0
  };
11631
11632
0
  if (res.content_length_ > 0) {
11633
0
    if (req.ranges.empty()) {
11634
0
      return detail::write_content(strm, res.content_provider_, 0,
11635
0
                                   res.content_length_, is_shutting_down);
11636
0
    } else if (req.ranges.size() == 1) {
11637
0
      auto offset_and_length = detail::get_range_offset_and_length(
11638
0
          req.ranges[0], res.content_length_);
11639
11640
0
      return detail::write_content(strm, res.content_provider_,
11641
0
                                   offset_and_length.first,
11642
0
                                   offset_and_length.second, is_shutting_down);
11643
0
    } else {
11644
0
      return detail::write_multipart_ranges_data(
11645
0
          strm, req, res, boundary, content_type, res.content_length_,
11646
0
          is_shutting_down);
11647
0
    }
11648
0
  } else {
11649
0
    if (res.is_chunked_content_provider_) {
11650
0
      auto type = detail::encoding_type(req, res);
11651
11652
0
      auto compressor = detail::make_compressor(type);
11653
0
      if (!compressor) {
11654
0
        compressor = detail::make_unique<detail::nocompressor>();
11655
0
      }
11656
11657
0
      return detail::write_content_chunked(strm, res.content_provider_,
11658
0
                                           is_shutting_down, *compressor);
11659
0
    } else {
11660
0
      return detail::write_content_without_length(strm, res.content_provider_,
11661
0
                                                  is_shutting_down);
11662
0
    }
11663
0
  }
11664
0
}
11665
11666
3.35k
inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
11667
3.35k
  FormFields::iterator cur_field;
11668
3.35k
  FormFiles::iterator cur_file;
11669
3.35k
  auto is_text_field = false;
11670
3.35k
  size_t count = 0;
11671
3.35k
  if (read_content_core(
11672
3.35k
          strm, req, res,
11673
          // Regular
11674
109k
          [&](const char *buf, size_t n) {
11675
            // Prevent arithmetic overflow when checking sizes.
11676
            // Avoid computing (req.body.size() + n) directly because
11677
            // adding two unsigned `size_t` values can wrap around and
11678
            // produce a small result instead of indicating overflow.
11679
            // Instead, check using subtraction: ensure `n` does not
11680
            // exceed the remaining capacity `max_size() - size()`.
11681
109k
            if (req.body.size() >= req.body.max_size() ||
11682
109k
                n > req.body.max_size() - req.body.size()) {
11683
0
              return false;
11684
0
            }
11685
11686
            // Limit decompressed body size to payload_max_length_ to protect
11687
            // against "zip bomb" attacks where a small compressed payload
11688
            // decompresses to a massive size.
11689
109k
            if (payload_max_length_ > 0 &&
11690
109k
                (req.body.size() >= payload_max_length_ ||
11691
109k
                 n > payload_max_length_ - req.body.size())) {
11692
0
              return false;
11693
0
            }
11694
11695
109k
            req.body.append(buf, n);
11696
109k
            return true;
11697
109k
          },
11698
          // Multipart FormData
11699
25.0k
          [&](const FormData &file) {
11700
25.0k
            if (count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) {
11701
2
              output_error_log(Error::TooManyFormDataFiles, &req);
11702
2
              return false;
11703
2
            }
11704
11705
25.0k
            if (file.filename.empty()) {
11706
18.9k
              cur_field = req.form.fields.emplace(
11707
18.9k
                  file.name, FormField{file.name, file.content, file.headers});
11708
18.9k
              is_text_field = true;
11709
18.9k
            } else {
11710
6.11k
              cur_file = req.form.files.emplace(file.name, file);
11711
6.11k
              is_text_field = false;
11712
6.11k
            }
11713
25.0k
            return true;
11714
25.0k
          },
11715
49.9k
          [&](const char *buf, size_t n) {
11716
49.9k
            if (is_text_field) {
11717
31.6k
              auto &content = cur_field->second.content;
11718
31.6k
              if (content.size() + n > content.max_size()) { return false; }
11719
31.6k
              content.append(buf, n);
11720
31.6k
            } else {
11721
18.3k
              auto &content = cur_file->second.content;
11722
18.3k
              if (content.size() + n > content.max_size()) { return false; }
11723
18.3k
              content.append(buf, n);
11724
18.3k
            }
11725
49.9k
            return true;
11726
49.9k
          })) {
11727
1.38k
    const auto &content_type = req.get_header_value("Content-Type");
11728
1.38k
    if (detail::extract_media_type(content_type) ==
11729
1.38k
        "application/x-www-form-urlencoded") {
11730
54
      if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
11731
7
        res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414?
11732
7
        output_error_log(Error::ExceedMaxPayloadSize, &req);
11733
7
        return false;
11734
7
      }
11735
47
      detail::parse_query_text(req.body, req.params);
11736
47
    }
11737
1.37k
    return true;
11738
1.38k
  }
11739
1.97k
  return false;
11740
3.35k
}
11741
11742
inline bool Server::read_content_with_content_receiver(
11743
    Stream &strm, Request &req, Response &res, ContentReceiver receiver,
11744
0
    FormDataHeader multipart_header, ContentReceiver multipart_receiver) {
11745
0
  return read_content_core(strm, req, res, std::move(receiver),
11746
0
                           std::move(multipart_header),
11747
0
                           std::move(multipart_receiver));
11748
0
}
11749
11750
inline bool Server::read_content_core(
11751
    Stream &strm, Request &req, Response &res, ContentReceiver receiver,
11752
3.35k
    FormDataHeader multipart_header, ContentReceiver multipart_receiver) const {
11753
3.35k
  detail::FormDataParser multipart_form_data_parser;
11754
3.35k
  ContentReceiverWithProgress out;
11755
11756
3.35k
  if (req.is_multipart_form_data()) {
11757
1.15k
    const auto &content_type = req.get_header_value("Content-Type");
11758
1.15k
    std::string boundary;
11759
1.15k
    if (!detail::parse_multipart_boundary(content_type, boundary)) {
11760
137
      res.status = StatusCode::BadRequest_400;
11761
137
      output_error_log(Error::MultipartParsing, &req);
11762
137
      return false;
11763
137
    }
11764
11765
1.02k
    multipart_form_data_parser.set_boundary(std::move(boundary));
11766
53.9k
    out = [&](const char *buf, size_t n, size_t /*off*/, size_t /*len*/) {
11767
53.9k
      return multipart_form_data_parser.parse(buf, n, multipart_header,
11768
53.9k
                                              multipart_receiver);
11769
53.9k
    };
11770
2.19k
  } else {
11771
2.19k
    out = [receiver](const char *buf, size_t n, size_t /*off*/,
11772
109k
                     size_t /*len*/) { return receiver(buf, n); };
11773
2.19k
  }
11774
11775
  // RFC 9112 §6: no Transfer-Encoding and no Content-Length means no body.
11776
  // For non-SSL builds we still scan non-persistent connections for stray
11777
  // body bytes so the payload limit is enforced (413). On keep-alive,
11778
  // pending bytes may be the next request (issue #2450), so skip.
11779
3.21k
#if !defined(CPPHTTPLIB_SSL_ENABLED)
11780
3.21k
  if (!req.has_header("Content-Length") &&
11781
2.45k
      !detail::is_chunked_transfer_encoding(req.headers)) {
11782
1.45k
    if (!detail::is_connection_persistent(req) && payload_max_length_ > 0 &&
11783
1.16k
        payload_max_length_ < (std::numeric_limits<size_t>::max)()) {
11784
1.16k
      auto has_data = strm.is_readable();
11785
1.16k
      if (!has_data) {
11786
0
        auto s = strm.socket();
11787
0
        if (s != INVALID_SOCKET) {
11788
0
          has_data = detail::select_read(s, 0, 0) > 0;
11789
0
        }
11790
0
      }
11791
1.16k
      if (has_data) {
11792
1.16k
        auto result =
11793
1.16k
            detail::read_content_without_length(strm, payload_max_length_, out);
11794
1.16k
        if (result == detail::ReadContentResult::PayloadTooLarge) {
11795
0
          res.status = StatusCode::PayloadTooLarge_413;
11796
0
          return false;
11797
1.16k
        } else if (result != detail::ReadContentResult::Success) {
11798
127
          return false;
11799
127
        }
11800
1.03k
        return true;
11801
1.16k
      }
11802
1.16k
    }
11803
292
    return true;
11804
1.45k
  }
11805
#else
11806
  if (!req.has_header("Content-Length") &&
11807
      !detail::is_chunked_transfer_encoding(req.headers)) {
11808
    return true;
11809
  }
11810
#endif
11811
11812
1.76k
  if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr,
11813
1.76k
                            out, true)) {
11814
1.71k
    return false;
11815
1.71k
  }
11816
11817
55
  req.body_consumed_ = true;
11818
11819
55
  if (req.is_multipart_form_data()) {
11820
4
    if (!multipart_form_data_parser.is_valid()) {
11821
1
      res.status = StatusCode::BadRequest_400;
11822
1
      output_error_log(Error::MultipartParsing, &req);
11823
1
      return false;
11824
1
    }
11825
4
  }
11826
11827
54
  return true;
11828
55
}
11829
11830
1.59k
inline bool Server::handle_file_request(Request &req, Response &res) {
11831
1.59k
  for (const auto &entry : base_dirs_) {
11832
    // Prefix match
11833
0
    if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) {
11834
0
      std::string sub_path = "/" + req.path.substr(entry.mount_point.size());
11835
0
      if (detail::is_valid_path(sub_path)) {
11836
0
        auto path = entry.base_dir + sub_path;
11837
0
        if (path.back() == '/') { path += "index.html"; }
11838
11839
        // Defense-in-depth: is_valid_path blocks ".." traversal in the URL,
11840
        // but symlinks/junctions can still escape the base directory.
11841
0
        if (!entry.resolved_base_dir.empty()) {
11842
0
          std::string resolved_path;
11843
0
          if (detail::canonicalize_path(path.c_str(), resolved_path) &&
11844
0
              !detail::is_path_within_base(resolved_path,
11845
0
                                           entry.resolved_base_dir)) {
11846
0
            res.status = StatusCode::Forbidden_403;
11847
0
            return true;
11848
0
          }
11849
0
        }
11850
11851
0
        detail::FileStat stat(path);
11852
11853
0
        if (stat.is_dir()) {
11854
0
          res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301);
11855
0
          return true;
11856
0
        }
11857
11858
0
        if (stat.is_file()) {
11859
0
          for (const auto &kv : entry.headers) {
11860
0
            res.set_header(kv.first, kv.second);
11861
0
          }
11862
11863
0
          auto etag = detail::compute_etag(stat);
11864
0
          if (!etag.empty()) { res.set_header("ETag", etag); }
11865
11866
0
          auto mtime = stat.mtime();
11867
11868
0
          auto last_modified = detail::file_mtime_to_http_date(mtime);
11869
0
          if (!last_modified.empty()) {
11870
0
            res.set_header("Last-Modified", last_modified);
11871
0
          }
11872
11873
0
          if (check_if_not_modified(req, res, etag, mtime)) { return true; }
11874
11875
0
          check_if_range(req, etag, mtime);
11876
11877
0
          auto mm = std::make_shared<detail::mmap>(path.c_str());
11878
0
          if (!mm->is_open()) {
11879
0
            output_error_log(Error::OpenFile, &req);
11880
0
            return false;
11881
0
          }
11882
11883
0
          res.set_content_provider(
11884
0
              mm->size(),
11885
0
              detail::find_content_type(path, file_extension_and_mimetype_map_,
11886
0
                                        default_file_mimetype_),
11887
0
              [mm](size_t offset, size_t length, DataSink &sink) -> bool {
11888
0
                sink.write(mm->data() + offset, length);
11889
0
                return true;
11890
0
              });
11891
11892
0
          if (req.method != "HEAD" && file_request_handler_) {
11893
0
            file_request_handler_(req, res);
11894
0
          }
11895
11896
0
          return true;
11897
0
        } else {
11898
0
          output_error_log(Error::OpenFile, &req);
11899
0
        }
11900
0
      }
11901
0
    }
11902
0
  }
11903
1.59k
  return false;
11904
1.59k
}
11905
11906
inline bool Server::check_if_not_modified(const Request &req, Response &res,
11907
                                          const std::string &etag,
11908
0
                                          time_t mtime) const {
11909
  // Handle conditional GET:
11910
  // 1. If-None-Match takes precedence (RFC 9110 Section 13.1.2)
11911
  // 2. If-Modified-Since is checked only when If-None-Match is absent
11912
0
  if (req.has_header("If-None-Match")) {
11913
0
    if (!etag.empty()) {
11914
0
      auto val = req.get_header_value("If-None-Match");
11915
11916
      // NOTE: We use exact string matching here. This works correctly
11917
      // because our server always generates weak ETags (W/"..."), and
11918
      // clients typically send back the same ETag they received.
11919
      // RFC 9110 Section 8.8.3.2 allows weak comparison for
11920
      // If-None-Match, where W/"x" and "x" would match, but this
11921
      // simplified implementation requires exact matches.
11922
0
      auto ret = detail::split_find(val.data(), val.data() + val.size(), ',',
11923
0
                                    [&](const char *b, const char *e) {
11924
0
                                      auto seg_len = static_cast<size_t>(e - b);
11925
0
                                      return (seg_len == 1 && *b == '*') ||
11926
0
                                             (seg_len == etag.size() &&
11927
0
                                              std::equal(b, e, etag.begin()));
11928
0
                                    });
11929
11930
0
      if (ret) {
11931
0
        res.status = StatusCode::NotModified_304;
11932
0
        return true;
11933
0
      }
11934
0
    }
11935
0
  } else if (req.has_header("If-Modified-Since")) {
11936
0
    auto val = req.get_header_value("If-Modified-Since");
11937
0
    auto t = detail::parse_http_date(val);
11938
11939
0
    if (t != static_cast<time_t>(-1) && mtime <= t) {
11940
0
      res.status = StatusCode::NotModified_304;
11941
0
      return true;
11942
0
    }
11943
0
  }
11944
0
  return false;
11945
0
}
11946
11947
inline bool Server::check_if_range(Request &req, const std::string &etag,
11948
0
                                   time_t mtime) const {
11949
  // Handle If-Range for partial content requests (RFC 9110
11950
  // Section 13.1.5). If-Range is only evaluated when Range header is
11951
  // present. If the validator matches, serve partial content; otherwise
11952
  // serve full content.
11953
0
  if (!req.ranges.empty() && req.has_header("If-Range")) {
11954
0
    auto val = req.get_header_value("If-Range");
11955
11956
0
    auto is_valid_range = [&]() {
11957
0
      if (detail::is_strong_etag(val)) {
11958
        // RFC 9110 Section 13.1.5: If-Range requires strong ETag
11959
        // comparison.
11960
0
        return (!etag.empty() && val == etag);
11961
0
      } else if (detail::is_weak_etag(val)) {
11962
        // Weak ETags are not valid for If-Range (RFC 9110 Section 13.1.5)
11963
0
        return false;
11964
0
      } else {
11965
        // HTTP-date comparison
11966
0
        auto t = detail::parse_http_date(val);
11967
0
        return (t != static_cast<time_t>(-1) && mtime <= t);
11968
0
      }
11969
0
    };
11970
11971
0
    if (!is_valid_range()) {
11972
      // Validator doesn't match: ignore Range and serve full content
11973
0
      req.ranges.clear();
11974
0
      return false;
11975
0
    }
11976
0
  }
11977
11978
0
  return true;
11979
0
}
11980
11981
inline socket_t
11982
Server::create_server_socket(const std::string &host, int port,
11983
                             int socket_flags,
11984
0
                             SocketOptions socket_options) const {
11985
0
  return detail::create_socket(
11986
0
      host, std::string(), port, address_family_, socket_flags, tcp_nodelay_,
11987
0
      ipv6_v6only_, std::move(socket_options),
11988
0
      [&](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool {
11989
0
        if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
11990
0
          output_error_log(Error::BindIPAddress, nullptr);
11991
0
          return false;
11992
0
        }
11993
0
        if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) {
11994
0
          output_error_log(Error::Listen, nullptr);
11995
0
          return false;
11996
0
        }
11997
0
        return true;
11998
0
      });
11999
0
}
12000
12001
inline int Server::bind_internal(const std::string &host, int port,
12002
0
                                 int socket_flags) {
12003
0
  if (is_decommissioned) { return -1; }
12004
0
12005
0
  if (!is_valid()) { return -1; }
12006
0
12007
0
  svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
12008
0
  if (svr_sock_ == INVALID_SOCKET) { return -1; }
12009
0
12010
0
  if (port == 0) {
12011
0
    struct sockaddr_storage addr;
12012
0
    socklen_t addr_len = sizeof(addr);
12013
0
    if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr),
12014
0
                    &addr_len) == -1) {
12015
0
      output_error_log(Error::GetSockName, nullptr);
12016
0
      return -1;
12017
0
    }
12018
0
    if (addr.ss_family == AF_INET) {
12019
0
      return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
12020
0
    } else if (addr.ss_family == AF_INET6) {
12021
0
      return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
12022
0
    } else {
12023
0
      output_error_log(Error::UnsupportedAddressFamily, nullptr);
12024
0
      return -1;
12025
0
    }
12026
0
  } else {
12027
0
    return port;
12028
0
  }
12029
0
}
12030
12031
0
inline bool Server::listen_internal() {
12032
0
  if (is_decommissioned) { return false; }
12033
0
12034
0
  auto ret = true;
12035
0
  is_running_ = true;
12036
0
  auto se = detail::scope_exit([&]() { is_running_ = false; });
12037
0
12038
0
  if (start_handler_) { start_handler_(); }
12039
0
12040
0
  {
12041
0
    std::unique_ptr<TaskQueue> task_queue(new_task_queue());
12042
0
12043
0
    while (svr_sock_ != INVALID_SOCKET) {
12044
0
#ifndef _WIN32
12045
0
      if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) {
12046
0
#endif
12047
0
        auto val = detail::select_read(svr_sock_, idle_interval_sec_,
12048
0
                                       idle_interval_usec_);
12049
0
        if (val == 0) { // Timeout
12050
0
          task_queue->on_idle();
12051
0
          continue;
12052
0
        }
12053
0
#ifndef _WIN32
12054
0
      }
12055
0
#endif
12056
0
12057
0
#if defined _WIN32
12058
0
      // sockets connected via WASAccept inherit flags NO_HANDLE_INHERIT,
12059
0
      // OVERLAPPED
12060
0
      socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0);
12061
0
#elif defined SOCK_CLOEXEC
12062
0
      socket_t sock = accept4(svr_sock_, nullptr, nullptr, SOCK_CLOEXEC);
12063
0
#else
12064
0
      socket_t sock = accept(svr_sock_, nullptr, nullptr);
12065
0
#endif
12066
0
12067
0
      if (sock == INVALID_SOCKET) {
12068
0
        if (errno == EMFILE) {
12069
0
          // The per-process limit of open file descriptors has been reached.
12070
0
          // Try to accept new connections after a short sleep.
12071
0
          std::this_thread::sleep_for(std::chrono::microseconds{1});
12072
0
          continue;
12073
0
        } else if (errno == EINTR || errno == EAGAIN) {
12074
0
          continue;
12075
0
        }
12076
0
        if (svr_sock_ != INVALID_SOCKET) {
12077
0
          detail::close_socket(svr_sock_);
12078
0
          ret = false;
12079
0
          output_error_log(Error::Connection, nullptr);
12080
0
        } else {
12081
0
          ; // The server socket was closed by user.
12082
0
        }
12083
0
        break;
12084
0
      }
12085
0
12086
0
      detail::set_socket_opt_time(sock, SOL_SOCKET, SO_RCVTIMEO,
12087
0
                                  read_timeout_sec_, read_timeout_usec_);
12088
0
      detail::set_socket_opt_time(sock, SOL_SOCKET, SO_SNDTIMEO,
12089
0
                                  write_timeout_sec_, write_timeout_usec_);
12090
0
12091
0
      if (tcp_nodelay_) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); }
12092
0
12093
0
      if (!task_queue->enqueue(
12094
0
              [this, sock]() { process_and_close_socket(sock); })) {
12095
0
        output_error_log(Error::ResourceExhaustion, nullptr);
12096
0
        detail::shutdown_socket(sock);
12097
0
        detail::close_socket(sock);
12098
0
      }
12099
0
    }
12100
0
12101
0
    task_queue->shutdown();
12102
0
  }
12103
0
12104
0
  is_decommissioned = !ret;
12105
0
  return ret;
12106
0
}
12107
12108
4.53k
inline bool Server::routing(Request &req, Response &res, Stream &strm) {
12109
4.53k
  if (pre_routing_handler_ &&
12110
0
      pre_routing_handler_(req, res) == HandlerResponse::Handled) {
12111
0
    return true;
12112
0
  }
12113
12114
  // File handler
12115
4.53k
  if ((req.method == "GET" || req.method == "HEAD") &&
12116
1.59k
      handle_file_request(req, res)) {
12117
0
    return true;
12118
0
  }
12119
12120
4.53k
  if (detail::expect_content(req)) {
12121
    // Content reader handler
12122
3.57k
    {
12123
      // Track whether the ContentReader was aborted due to the decompressed
12124
      // payload exceeding `payload_max_length_`.
12125
      // The user handler runs after the lambda returns, so we must restore the
12126
      // 413 status if the handler overwrites it.
12127
3.57k
      bool content_reader_payload_too_large = false;
12128
12129
3.57k
      ContentReader reader(
12130
3.57k
          [&](ContentReceiver receiver) {
12131
0
            auto result = read_content_with_content_receiver(
12132
0
                strm, req, res, std::move(receiver), nullptr, nullptr);
12133
0
            if (!result) {
12134
0
              output_error_log(Error::Read, &req);
12135
0
              if (res.status == StatusCode::PayloadTooLarge_413) {
12136
0
                content_reader_payload_too_large = true;
12137
0
              }
12138
0
            }
12139
0
            return result;
12140
0
          },
12141
3.57k
          [&](FormDataHeader header, ContentReceiver receiver) {
12142
0
            auto result = read_content_with_content_receiver(
12143
0
                strm, req, res, nullptr, std::move(header),
12144
0
                std::move(receiver));
12145
0
            if (!result) {
12146
0
              output_error_log(Error::Read, &req);
12147
0
              if (res.status == StatusCode::PayloadTooLarge_413) {
12148
0
                content_reader_payload_too_large = true;
12149
0
              }
12150
0
            }
12151
0
            return result;
12152
0
          });
12153
12154
3.57k
      bool dispatched = false;
12155
3.57k
      if (req.method == "POST") {
12156
5
        dispatched = dispatch_request_for_content_reader(
12157
5
            req, res, std::move(reader), post_handlers_for_content_reader_);
12158
3.56k
      } else if (req.method == "PUT") {
12159
2.67k
        dispatched = dispatch_request_for_content_reader(
12160
2.67k
            req, res, std::move(reader), put_handlers_for_content_reader_);
12161
2.67k
      } else if (req.method == "PATCH") {
12162
4
        dispatched = dispatch_request_for_content_reader(
12163
4
            req, res, std::move(reader), patch_handlers_for_content_reader_);
12164
889
      } else if (req.method == "DELETE") {
12165
3
        dispatched = dispatch_request_for_content_reader(
12166
3
            req, res, std::move(reader), delete_handlers_for_content_reader_);
12167
3
      }
12168
12169
3.57k
      if (dispatched) {
12170
0
        if (content_reader_payload_too_large) {
12171
          // Enforce the limit: override any status the handler may have set
12172
          // and return false so the error path sends a plain 413 response.
12173
0
          res.status = StatusCode::PayloadTooLarge_413;
12174
0
          res.body.clear();
12175
0
          res.content_length_ = 0;
12176
0
          res.content_provider_ = nullptr;
12177
0
          return false;
12178
0
        }
12179
0
        return true;
12180
0
      }
12181
3.57k
    }
12182
12183
    // NOTE: `req.body` is not read here. For a regular handler the body is
12184
    // read inside dispatch_request(), after the route has matched and the
12185
    // pre-request handler has approved the request, so that a rejected
12186
    // request (e.g. failed authentication) never forces us to buffer a
12187
    // potentially large body.
12188
3.57k
  }
12189
12190
  // Regular handler
12191
4.53k
  if (req.method == "GET" || req.method == "HEAD") {
12192
1.59k
    return dispatch_request(req, res, get_handlers_, strm);
12193
2.94k
  } else if (req.method == "POST") {
12194
5
    return dispatch_request(req, res, post_handlers_, strm);
12195
2.94k
  } else if (req.method == "PUT") {
12196
2.67k
    return dispatch_request(req, res, put_handlers_, strm);
12197
2.67k
  } else if (req.method == "DELETE") {
12198
3
    return dispatch_request(req, res, delete_handlers_, strm);
12199
261
  } else if (req.method == "OPTIONS") {
12200
2
    return dispatch_request(req, res, options_handlers_, strm);
12201
259
  } else if (req.method == "PATCH") {
12202
4
    return dispatch_request(req, res, patch_handlers_, strm);
12203
4
  }
12204
12205
255
  res.status = StatusCode::BadRequest_400;
12206
255
  return false;
12207
4.53k
}
12208
12209
inline bool Server::dispatch_request(Request &req, Response &res,
12210
4.28k
                                     const Handlers &handlers, Stream &strm) {
12211
4.28k
  for (const auto &x : handlers) {
12212
4.28k
    const auto &matcher = x.first;
12213
4.28k
    const auto &handler = x.second;
12214
12215
4.28k
    if (matcher->match(req)) {
12216
4.19k
      req.matched_route = matcher->pattern();
12217
12218
      // Run the pre-request handler before reading the body so a rejected
12219
      // request (e.g. failed authentication) never forces us to buffer a
12220
      // potentially large body. `req.matched_route` is available here.
12221
4.19k
      if (pre_request_handler_ &&
12222
0
          pre_request_handler_(req, res) == HandlerResponse::Handled) {
12223
0
        return true;
12224
0
      }
12225
12226
      // The route matched and the request was approved; read the body now.
12227
4.19k
      if (detail::expect_content(req) && !read_content(strm, req, res)) {
12228
1.98k
        output_error_log(Error::Read, &req);
12229
1.98k
        return false;
12230
1.98k
      }
12231
12232
2.21k
      handler(req, res);
12233
2.21k
      return true;
12234
4.19k
    }
12235
4.28k
  }
12236
87
  return false;
12237
4.28k
}
12238
12239
inline void Server::apply_ranges(const Request &req, Response &res,
12240
                                 std::string &content_type,
12241
1.90k
                                 std::string &boundary) const {
12242
1.90k
  if (req.ranges.size() > 1 && res.status == StatusCode::PartialContent_206) {
12243
79
    auto it = res.headers.find("Content-Type");
12244
79
    if (it != res.headers.end()) {
12245
79
      content_type = it->second;
12246
79
      res.headers.erase(it);
12247
79
    }
12248
12249
79
    boundary = detail::make_multipart_data_boundary();
12250
12251
79
    res.set_header("Content-Type",
12252
79
                   "multipart/byteranges; boundary=" + boundary);
12253
79
  }
12254
12255
1.90k
  auto type = detail::encoding_type(req, res);
12256
12257
1.90k
  if (res.body.empty()) {
12258
0
    if (res.content_length_ > 0) {
12259
0
      size_t length = 0;
12260
0
      if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
12261
0
        length = res.content_length_;
12262
0
      } else if (req.ranges.size() == 1) {
12263
0
        auto offset_and_length = detail::get_range_offset_and_length(
12264
0
            req.ranges[0], res.content_length_);
12265
12266
0
        length = offset_and_length.second;
12267
12268
0
        auto content_range = detail::make_content_range_header_field(
12269
0
            offset_and_length, res.content_length_);
12270
0
        res.set_header("Content-Range", content_range);
12271
0
      } else {
12272
0
        length = detail::get_multipart_ranges_data_length(
12273
0
            req, boundary, content_type, res.content_length_);
12274
0
      }
12275
0
      res.set_header("Content-Length", std::to_string(length));
12276
0
    } else {
12277
0
      if (res.content_provider_) {
12278
0
        if (res.is_chunked_content_provider_) {
12279
0
          res.set_header("Transfer-Encoding", "chunked");
12280
0
          if (type != detail::EncodingType::None) {
12281
0
            res.set_header("Content-Encoding", detail::encoding_name(type));
12282
0
            res.set_header("Vary", "Accept-Encoding");
12283
0
          }
12284
0
        }
12285
0
      }
12286
0
    }
12287
1.90k
  } else {
12288
1.90k
    if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
12289
1.64k
      ;
12290
1.64k
    } else if (req.ranges.size() == 1) {
12291
181
      auto offset_and_length =
12292
181
          detail::get_range_offset_and_length(req.ranges[0], res.body.size());
12293
181
      auto offset = offset_and_length.first;
12294
181
      auto length = offset_and_length.second;
12295
12296
181
      auto content_range = detail::make_content_range_header_field(
12297
181
          offset_and_length, res.body.size());
12298
181
      res.set_header("Content-Range", content_range);
12299
12300
181
      assert(offset + length <= res.body.size());
12301
181
      res.body = res.body.substr(offset, length);
12302
181
    } else {
12303
79
      std::string data;
12304
79
      detail::make_multipart_ranges_data(req, res, boundary, content_type,
12305
79
                                         res.body.size(), data);
12306
79
      res.body.swap(data);
12307
79
    }
12308
12309
1.90k
    if (type != detail::EncodingType::None) {
12310
37
      output_pre_compression_log(req, res);
12311
12312
37
      if (auto compressor = detail::make_compressor(type)) {
12313
37
        std::string compressed;
12314
37
        if (compressor->compress(res.body.data(), res.body.size(), true,
12315
37
                                 [&](const char *data, size_t data_len) {
12316
37
                                   compressed.append(data, data_len);
12317
37
                                   return true;
12318
37
                                 })) {
12319
37
          res.body.swap(compressed);
12320
37
          res.set_header("Content-Encoding", detail::encoding_name(type));
12321
37
          res.set_header("Vary", "Accept-Encoding");
12322
37
        }
12323
37
      }
12324
37
    }
12325
12326
1.90k
    res.content_length_ = res.body.size();
12327
1.90k
    res.set_header("Content-Length", std::to_string(res.content_length_));
12328
1.90k
  }
12329
1.90k
}
12330
12331
inline bool Server::dispatch_request_for_content_reader(
12332
    Request &req, Response &res, ContentReader content_reader,
12333
2.68k
    const HandlersForContentReader &handlers) const {
12334
2.68k
  for (const auto &x : handlers) {
12335
0
    const auto &matcher = x.first;
12336
0
    const auto &handler = x.second;
12337
12338
0
    if (matcher->match(req)) {
12339
0
      req.matched_route = matcher->pattern();
12340
0
      if (!pre_request_handler_ ||
12341
0
          pre_request_handler_(req, res) != HandlerResponse::Handled) {
12342
0
        handler(req, res, content_reader);
12343
0
      }
12344
0
      return true;
12345
0
    }
12346
0
  }
12347
2.68k
  return false;
12348
2.68k
}
12349
12350
inline std::string
12351
get_client_ip(const std::string &x_forwarded_for,
12352
0
              const std::vector<std::string> &trusted_proxies) {
12353
  // X-Forwarded-For is a comma-separated list per RFC 7239
12354
0
  std::vector<std::string> ip_list;
12355
0
  detail::split(x_forwarded_for.data(),
12356
0
                x_forwarded_for.data() + x_forwarded_for.size(), ',',
12357
0
                [&](const char *b, const char *e) {
12358
0
                  auto r = detail::trim(b, e, 0, static_cast<size_t>(e - b));
12359
0
                  ip_list.emplace_back(std::string(b + r.first, b + r.second));
12360
0
                });
12361
12362
  // A malformed X-Forwarded-For (empty, comma-only, whitespace-only) yields
12363
  // no segments. Signal "no client IP derived" with an empty string so the
12364
  // caller can fall back to the connection-level remote address.
12365
0
  if (ip_list.empty()) { return std::string(); }
12366
12367
0
  for (size_t i = 0; i < ip_list.size(); ++i) {
12368
0
    auto ip = ip_list[i];
12369
12370
0
    auto is_trusted_proxy =
12371
0
        std::any_of(trusted_proxies.begin(), trusted_proxies.end(),
12372
0
                    [&](const std::string &proxy) { return ip == proxy; });
12373
12374
0
    if (is_trusted_proxy) {
12375
0
      if (i == 0) {
12376
        // If the trusted proxy is the first IP, there's no preceding client IP
12377
0
        return ip;
12378
0
      } else {
12379
        // Return the IP immediately before the trusted proxy
12380
0
        return ip_list[i - 1];
12381
0
      }
12382
0
    }
12383
0
  }
12384
12385
  // If no trusted proxy is found, return the first IP in the list
12386
0
  return ip_list.front();
12387
0
}
12388
12389
inline bool
12390
Server::process_request(Stream &strm, const std::string &remote_addr,
12391
                        int remote_port, const std::string &local_addr,
12392
                        int local_port, bool close_connection,
12393
                        bool &connection_closed,
12394
                        const std::function<void(Request &)> &setup_request,
12395
6.78k
                        bool *websocket_upgraded) {
12396
6.78k
  std::array<char, 2048> buf{};
12397
12398
6.78k
  detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
12399
12400
  // Connection has been closed on client
12401
6.78k
  if (!line_reader.getline()) { return false; }
12402
12403
6.77k
  Request req;
12404
6.77k
  req.start_time_ = std::chrono::steady_clock::now();
12405
6.77k
  req.remote_addr = remote_addr;
12406
6.77k
  req.remote_port = remote_port;
12407
6.77k
  req.local_addr = local_addr;
12408
6.77k
  req.local_port = local_port;
12409
12410
6.77k
  Response res;
12411
6.77k
  res.version = "HTTP/1.1";
12412
6.77k
  res.headers = default_headers_;
12413
12414
  // Request line and headers
12415
6.77k
  if (!parse_request_line(line_reader.ptr(), req)) {
12416
332
    res.status = StatusCode::BadRequest_400;
12417
332
    output_error_log(Error::InvalidRequestLine, &req);
12418
332
    return write_response(strm, close_connection, req, res);
12419
332
  }
12420
12421
  // Request headers
12422
6.44k
  if (!detail::read_headers(strm, req.headers)) {
12423
1.33k
    res.status = StatusCode::BadRequest_400;
12424
1.33k
    output_error_log(Error::InvalidHeaders, &req);
12425
1.33k
    return write_response(strm, close_connection, req, res);
12426
1.33k
  }
12427
12428
  // RFC 9112 §6.3: Reject requests with both a non-zero Content-Length and
12429
  // any Transfer-Encoding to prevent request smuggling. Content-Length: 0 is
12430
  // tolerated for compatibility with existing clients.
12431
5.11k
  if (req.get_header_value_u64("Content-Length") > 0 &&
12432
840
      req.has_header("Transfer-Encoding")) {
12433
1
    connection_closed = true;
12434
1
    res.status = StatusCode::BadRequest_400;
12435
1
    return write_response(strm, close_connection, req, res);
12436
1
  }
12437
12438
  // Check if the request URI doesn't exceed the limit
12439
5.11k
  if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
12440
4
    connection_closed = true;
12441
4
    res.status = StatusCode::UriTooLong_414;
12442
4
    output_error_log(Error::ExceedUriMaxLength, &req);
12443
4
    return write_response(strm, close_connection, req, res);
12444
4
  }
12445
12446
5.11k
  if (req.get_header_value("Connection") == "close") {
12447
1
    connection_closed = true;
12448
1
  }
12449
12450
5.11k
  if (req.version == "HTTP/1.0" &&
12451
3.38k
      req.get_header_value("Connection") != "Keep-Alive") {
12452
3.38k
    connection_closed = true;
12453
3.38k
  }
12454
12455
5.11k
  if (!trusted_proxies_.empty() && req.has_header("X-Forwarded-For")) {
12456
0
    auto x_forwarded_for = req.get_header_value("X-Forwarded-For");
12457
0
    auto derived = get_client_ip(x_forwarded_for, trusted_proxies_);
12458
0
    req.remote_addr = derived.empty() ? remote_addr : derived;
12459
5.11k
  } else {
12460
5.11k
    req.remote_addr = remote_addr;
12461
5.11k
  }
12462
5.11k
  req.remote_port = remote_port;
12463
12464
5.11k
  req.local_addr = local_addr;
12465
5.11k
  req.local_port = local_port;
12466
12467
5.11k
  if (req.has_header("Accept")) {
12468
1.18k
    const auto &accept_header = req.get_header_value("Accept");
12469
1.18k
    if (!detail::parse_accept_header(accept_header, req.accept_content_types)) {
12470
366
      connection_closed = true;
12471
366
      res.status = StatusCode::BadRequest_400;
12472
366
      output_error_log(Error::HTTPParsing, &req);
12473
366
      return write_response(strm, close_connection, req, res);
12474
366
    }
12475
1.18k
  }
12476
12477
4.74k
  if (req.has_header("Range")) {
12478
775
    const auto &range_header_value = req.get_header_value("Range");
12479
775
    if (!detail::parse_range_header(range_header_value, req.ranges)) {
12480
207
      connection_closed = true;
12481
207
      res.status = StatusCode::RangeNotSatisfiable_416;
12482
207
      output_error_log(Error::InvalidRangeHeader, &req);
12483
207
      return write_response(strm, close_connection, req, res);
12484
207
    }
12485
775
  }
12486
12487
4.53k
  if (setup_request) { setup_request(req); }
12488
12489
4.53k
  if (req.get_header_value("Expect") == "100-continue") {
12490
1
    int status = StatusCode::Continue_100;
12491
1
    if (expect_100_continue_handler_) {
12492
0
      status = expect_100_continue_handler_(req, res);
12493
0
    }
12494
1
    switch (status) {
12495
1
    case StatusCode::Continue_100:
12496
1
    case StatusCode::ExpectationFailed_417:
12497
1
      detail::write_response_line(strm, status);
12498
1
      strm.write("\r\n");
12499
1
      break;
12500
0
    default:
12501
0
      connection_closed = true;
12502
0
      return write_response(strm, true, req, res);
12503
1
    }
12504
1
  }
12505
12506
  // Setup `is_connection_closed` method
12507
4.53k
  auto sock = strm.socket();
12508
4.53k
  req.is_connection_closed = [sock]() {
12509
0
    return !detail::is_socket_alive(sock);
12510
0
  };
12511
12512
  // WebSocket upgrade
12513
  // Check pre_routing_handler_ before upgrading so that authentication
12514
  // and other middleware can reject the request with an HTTP response
12515
  // (e.g., 401) before the protocol switches.
12516
4.53k
  if (detail::is_websocket_upgrade(req)) {
12517
0
    if (pre_routing_handler_ &&
12518
0
        pre_routing_handler_(req, res) == HandlerResponse::Handled) {
12519
0
      if (res.status == -1) { res.status = StatusCode::OK_200; }
12520
0
      return write_response(strm, close_connection, req, res);
12521
0
    }
12522
    // Find matching WebSocket handler
12523
0
    for (const auto &entry : websocket_handlers_) {
12524
0
      if (entry.matcher->match(req)) {
12525
        // Compute accept key
12526
0
        auto client_key = req.get_header_value("Sec-WebSocket-Key");
12527
0
        auto accept_key = detail::websocket_accept_key(client_key);
12528
12529
        // Negotiate subprotocol
12530
0
        std::string selected_subprotocol;
12531
0
        if (entry.sub_protocol_selector) {
12532
0
          auto protocol_header = req.get_header_value("Sec-WebSocket-Protocol");
12533
0
          if (!protocol_header.empty()) {
12534
0
            std::vector<std::string> protocols;
12535
0
            std::istringstream iss(protocol_header);
12536
0
            std::string token;
12537
0
            while (std::getline(iss, token, ',')) {
12538
              // Trim whitespace
12539
0
              auto start = token.find_first_not_of(' ');
12540
0
              auto end = token.find_last_not_of(' ');
12541
0
              if (start != std::string::npos) {
12542
0
                protocols.push_back(token.substr(start, end - start + 1));
12543
0
              }
12544
0
            }
12545
0
            selected_subprotocol = entry.sub_protocol_selector(protocols);
12546
0
          }
12547
0
        }
12548
12549
        // Send 101 Switching Protocols
12550
0
        std::string handshake_response = "HTTP/1.1 101 Switching Protocols\r\n"
12551
0
                                         "Upgrade: websocket\r\n"
12552
0
                                         "Connection: Upgrade\r\n"
12553
0
                                         "Sec-WebSocket-Accept: " +
12554
0
                                         accept_key + "\r\n";
12555
0
        if (!selected_subprotocol.empty()) {
12556
0
          if (!detail::fields::is_field_value(selected_subprotocol)) {
12557
0
            return false;
12558
0
          }
12559
0
          handshake_response +=
12560
0
              "Sec-WebSocket-Protocol: " + selected_subprotocol + "\r\n";
12561
0
        }
12562
0
        handshake_response += "\r\n";
12563
0
        if (strm.write(handshake_response.data(), handshake_response.size()) <
12564
0
            0) {
12565
0
          return false;
12566
0
        }
12567
12568
0
        connection_closed = true;
12569
0
        if (websocket_upgraded) { *websocket_upgraded = true; }
12570
12571
0
        {
12572
          // Use WebSocket-specific read timeout instead of HTTP timeout
12573
0
          strm.set_read_timeout(CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND, 0);
12574
0
          ws::WebSocket ws(strm, req, true, websocket_ping_interval_sec_,
12575
0
                           websocket_max_missed_pongs_);
12576
0
          entry.handler(req, ws);
12577
0
        }
12578
0
        return true;
12579
0
      }
12580
0
    }
12581
    // No matching handler - fall through to 404
12582
0
  }
12583
12584
  // Routing
12585
4.53k
  auto routed = false;
12586
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
12587
  routed = routing(req, res, strm);
12588
#else
12589
4.53k
  try {
12590
4.53k
    routed = routing(req, res, strm);
12591
4.53k
  } catch (std::exception &) {
12592
0
    if (exception_handler_) {
12593
0
      auto ep = std::current_exception();
12594
0
      exception_handler_(req, res, ep);
12595
0
      routed = true;
12596
0
    } else {
12597
0
      res.status = StatusCode::InternalServerError_500;
12598
0
    }
12599
0
  } catch (...) {
12600
0
    if (exception_handler_) {
12601
0
      auto ep = std::current_exception();
12602
0
      exception_handler_(req, res, ep);
12603
0
      routed = true;
12604
0
    } else {
12605
0
      res.status = StatusCode::InternalServerError_500;
12606
0
    }
12607
0
  }
12608
4.53k
#endif
12609
4.53k
  auto ret = false;
12610
4.53k
  if (routed) {
12611
2.21k
    if (res.status == -1) {
12612
2.21k
      res.status = req.ranges.empty() ? StatusCode::OK_200
12613
2.21k
                                      : StatusCode::PartialContent_206;
12614
2.21k
    }
12615
12616
    // Serve file content by using a content provider
12617
2.21k
    auto file_open_error = false;
12618
2.21k
    if (!res.file_content_path_.empty()) {
12619
0
      const auto &path = res.file_content_path_;
12620
0
      auto mm = std::make_shared<detail::mmap>(path.c_str());
12621
0
      if (!mm->is_open()) {
12622
0
        res.body.clear();
12623
0
        res.content_length_ = 0;
12624
0
        res.content_provider_ = nullptr;
12625
0
        res.status = StatusCode::NotFound_404;
12626
0
        output_error_log(Error::OpenFile, &req);
12627
0
        file_open_error = true;
12628
0
      } else {
12629
0
        auto content_type = res.file_content_content_type_;
12630
0
        if (content_type.empty()) {
12631
0
          content_type = detail::find_content_type(
12632
0
              path, file_extension_and_mimetype_map_, default_file_mimetype_);
12633
0
        }
12634
12635
0
        res.set_content_provider(
12636
0
            mm->size(), content_type,
12637
0
            [mm](size_t offset, size_t length, DataSink &sink) -> bool {
12638
0
              sink.write(mm->data() + offset, length);
12639
0
              return true;
12640
0
            });
12641
0
      }
12642
0
    }
12643
12644
2.21k
    if (file_open_error) {
12645
0
      ret = write_response(strm, close_connection, req, res);
12646
2.21k
    } else if (detail::range_error(req, res)) {
12647
306
      res.body.clear();
12648
306
      res.content_length_ = 0;
12649
306
      res.content_provider_ = nullptr;
12650
306
      res.status = StatusCode::RangeNotSatisfiable_416;
12651
306
      ret = write_response(strm, close_connection, req, res);
12652
1.90k
    } else {
12653
1.90k
      ret = write_response_with_content(strm, close_connection, req, res);
12654
1.90k
    }
12655
2.32k
  } else {
12656
2.32k
    if (res.status == -1) { res.status = StatusCode::NotFound_404; }
12657
2.32k
    ret = write_response(strm, close_connection, req, res);
12658
2.32k
  }
12659
12660
  // Drain any unconsumed framed body to prevent request smuggling on
12661
  // keep-alive. Without framing there is no body to drain — reading would
12662
  // consume the next request (issue #2450).
12663
4.53k
  if (!req.body_consumed_ && detail::has_framed_body(req)) {
12664
1.82k
    int dummy_status;
12665
1.82k
    if (!detail::read_content(
12666
1.82k
            strm, req, payload_max_length_, dummy_status, nullptr,
12667
47.1k
            [](const char *, size_t, size_t, size_t) { return true; }, false)) {
12668
1.81k
      connection_closed = true;
12669
1.81k
    }
12670
1.82k
  }
12671
12672
4.53k
  return ret;
12673
4.53k
}
12674
12675
0
inline bool Server::is_valid() const { return true; }
12676
12677
0
inline bool Server::process_and_close_socket(socket_t sock) {
12678
0
  std::string remote_addr;
12679
0
  int remote_port = 0;
12680
0
  detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
12681
12682
0
  std::string local_addr;
12683
0
  int local_port = 0;
12684
0
  detail::get_local_ip_and_port(sock, local_addr, local_port);
12685
12686
0
  bool websocket_upgraded = false;
12687
0
  auto ret = detail::process_server_socket(
12688
0
      svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
12689
0
      read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
12690
0
      write_timeout_usec_,
12691
0
      [&](Stream &strm, bool close_connection, bool &connection_closed) {
12692
0
        return process_request(strm, remote_addr, remote_port, local_addr,
12693
0
                               local_port, close_connection, connection_closed,
12694
0
                               nullptr, &websocket_upgraded);
12695
0
      });
12696
12697
0
  detail::shutdown_socket(sock);
12698
0
  detail::close_socket(sock);
12699
0
  return ret;
12700
0
}
12701
12702
6.77k
inline void Server::output_log(const Request &req, const Response &res) const {
12703
6.77k
  if (logger_) {
12704
0
    std::lock_guard<std::mutex> guard(logger_mutex_);
12705
0
    logger_(req, res);
12706
0
  }
12707
6.77k
}
12708
12709
inline void Server::output_pre_compression_log(const Request &req,
12710
37
                                               const Response &res) const {
12711
37
  if (pre_compression_logger_) {
12712
0
    std::lock_guard<std::mutex> guard(logger_mutex_);
12713
0
    pre_compression_logger_(req, res);
12714
0
  }
12715
37
}
12716
12717
inline void Server::output_error_log(const Error &err,
12718
4.45k
                                     const Request *req) const {
12719
4.45k
  if (error_logger_) {
12720
0
    std::lock_guard<std::mutex> guard(logger_mutex_);
12721
0
    error_logger_(err, req);
12722
0
  }
12723
4.45k
}
12724
12725
/*
12726
 * Group 5: ClientImpl and Client (Universal) implementation
12727
 */
12728
// HTTP client implementation
12729
inline ClientImpl::ClientImpl(const std::string &host)
12730
    : ClientImpl(host, 80, std::string(), std::string()) {}
12731
12732
inline ClientImpl::ClientImpl(const std::string &host, int port)
12733
    : ClientImpl(host, port, std::string(), std::string()) {}
12734
12735
inline ClientImpl::ClientImpl(const std::string &host, int port,
12736
                              const std::string &client_cert_path,
12737
                              const std::string &client_key_path)
12738
    : host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port),
12739
      client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
12740
12741
0
inline ClientImpl::~ClientImpl() {
12742
  // Wait until all the requests in flight are handled.
12743
0
  size_t retry_count = 10;
12744
0
  while (retry_count-- > 0) {
12745
0
    {
12746
0
      std::lock_guard<std::mutex> guard(socket_mutex_);
12747
0
      if (socket_requests_in_flight_ == 0) { break; }
12748
0
    }
12749
0
    std::this_thread::sleep_for(std::chrono::milliseconds{1});
12750
0
  }
12751
12752
0
  std::lock_guard<std::mutex> guard(socket_mutex_);
12753
0
  shutdown_socket(socket_);
12754
0
  close_socket(socket_);
12755
0
}
12756
12757
0
inline bool ClientImpl::is_valid() const { return true; }
12758
12759
0
inline void ClientImpl::copy_settings(const ClientImpl &rhs) {
12760
0
  client_cert_path_ = rhs.client_cert_path_;
12761
0
  client_key_path_ = rhs.client_key_path_;
12762
0
  connection_timeout_sec_ = rhs.connection_timeout_sec_;
12763
0
  read_timeout_sec_ = rhs.read_timeout_sec_;
12764
0
  read_timeout_usec_ = rhs.read_timeout_usec_;
12765
0
  write_timeout_sec_ = rhs.write_timeout_sec_;
12766
0
  write_timeout_usec_ = rhs.write_timeout_usec_;
12767
0
  max_timeout_msec_ = rhs.max_timeout_msec_;
12768
0
  basic_auth_username_ = rhs.basic_auth_username_;
12769
0
  basic_auth_password_ = rhs.basic_auth_password_;
12770
0
  bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
12771
0
  keep_alive_ = rhs.keep_alive_;
12772
0
  follow_location_ = rhs.follow_location_;
12773
0
  path_encode_ = rhs.path_encode_;
12774
0
  address_family_ = rhs.address_family_;
12775
0
  tcp_nodelay_ = rhs.tcp_nodelay_;
12776
0
  ipv6_v6only_ = rhs.ipv6_v6only_;
12777
0
  socket_options_ = rhs.socket_options_;
12778
0
  compress_ = rhs.compress_;
12779
0
  decompress_ = rhs.decompress_;
12780
0
  payload_max_length_ = rhs.payload_max_length_;
12781
0
  has_payload_max_length_ = rhs.has_payload_max_length_;
12782
0
  interface_ = rhs.interface_;
12783
0
  proxy_host_ = rhs.proxy_host_;
12784
0
  proxy_port_ = rhs.proxy_port_;
12785
0
  proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
12786
0
  proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
12787
0
  proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
12788
0
  no_proxy_entries_ = rhs.no_proxy_entries_;
12789
0
  logger_ = rhs.logger_;
12790
0
  error_logger_ = rhs.error_logger_;
12791
0
12792
0
#ifdef CPPHTTPLIB_SSL_ENABLED
12793
0
  digest_auth_username_ = rhs.digest_auth_username_;
12794
0
  digest_auth_password_ = rhs.digest_auth_password_;
12795
0
  proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
12796
0
  proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
12797
0
  ca_cert_file_path_ = rhs.ca_cert_file_path_;
12798
0
  ca_cert_dir_path_ = rhs.ca_cert_dir_path_;
12799
0
  server_certificate_verification_ = rhs.server_certificate_verification_;
12800
0
  server_hostname_verification_ = rhs.server_hostname_verification_;
12801
0
  system_ca_mode_ = rhs.system_ca_mode_;
12802
0
#endif
12803
0
}
12804
12805
inline bool
12806
0
ClientImpl::is_proxy_enabled_for_host(const std::string &host) const {
12807
0
  if (proxy_host_.empty() || proxy_port_ == -1) { return false; }
12808
0
  if (no_proxy_entries_.empty()) { return true; }
12809
  // host_ is const so its normalized form is invariant; cache it. The
12810
  // cross-host path (setup_redirect_client passing next_host) re-normalizes.
12811
0
  if (host == host_) {
12812
0
    if (!host_normalized_valid_) {
12813
0
      host_normalized_ = detail::normalize_target(host_);
12814
0
      host_normalized_valid_ = true;
12815
0
    }
12816
0
    return !detail::host_matches_no_proxy(host_normalized_, no_proxy_entries_);
12817
0
  }
12818
0
  auto target = detail::normalize_target(host);
12819
0
  return !detail::host_matches_no_proxy(target, no_proxy_entries_);
12820
0
}
12821
12822
0
inline socket_t ClientImpl::create_client_socket(Error &error) const {
12823
0
  if (is_proxy_enabled_for_host(host_)) {
12824
0
    return detail::create_client_socket(
12825
0
        proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_,
12826
0
        ipv6_v6only_, socket_options_, connection_timeout_sec_,
12827
0
        connection_timeout_usec_, read_timeout_sec_, read_timeout_usec_,
12828
0
        write_timeout_sec_, write_timeout_usec_, interface_, error);
12829
0
  }
12830
12831
  // Check is custom IP specified for host_
12832
0
  std::string ip;
12833
0
  auto it = addr_map_.find(host_);
12834
0
  if (it != addr_map_.end()) { ip = it->second; }
12835
12836
0
  return detail::create_client_socket(
12837
0
      host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
12838
0
      socket_options_, connection_timeout_sec_, connection_timeout_usec_,
12839
0
      read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
12840
0
      write_timeout_usec_, interface_, error);
12841
0
}
12842
12843
inline bool ClientImpl::create_and_connect_socket(Socket &socket,
12844
0
                                                  Error &error) {
12845
0
  auto sock = create_client_socket(error);
12846
0
  if (sock == INVALID_SOCKET) { return false; }
12847
0
  socket.sock = sock;
12848
0
  return true;
12849
0
}
12850
12851
0
inline bool ClientImpl::ensure_socket_connection(Socket &socket, Error &error) {
12852
0
  return create_and_connect_socket(socket, error);
12853
0
}
12854
12855
inline bool ClientImpl::setup_proxy_connection(
12856
    Socket & /*socket*/,
12857
    std::chrono::time_point<std::chrono::steady_clock> /*start_time*/,
12858
0
    Response & /*res*/, bool & /*success*/, Error & /*error*/) {
12859
0
  return true;
12860
0
}
12861
12862
inline void ClientImpl::shutdown_ssl(Socket & /*socket*/,
12863
0
                                     bool /*shutdown_gracefully*/) {
12864
  // If there are any requests in flight from threads other than us, then it's
12865
  // a thread-unsafe race because individual ssl* objects are not thread-safe.
12866
0
  assert(socket_requests_in_flight_ == 0 ||
12867
0
         socket_requests_are_from_thread_ == std::this_thread::get_id());
12868
0
}
12869
12870
0
inline void ClientImpl::shutdown_socket(Socket &socket) const {
12871
0
  if (socket.sock == INVALID_SOCKET) { return; }
12872
0
  detail::shutdown_socket(socket.sock);
12873
0
}
12874
12875
0
inline void ClientImpl::close_socket(Socket &socket) {
12876
  // If there are requests in flight in another thread, usually closing
12877
  // the socket will be fine and they will simply receive an error when
12878
  // using the closed socket, but it is still a bug since rarely the OS
12879
  // may reassign the socket id to be used for a new socket, and then
12880
  // suddenly they will be operating on a live socket that is different
12881
  // than the one they intended!
12882
0
  assert(socket_requests_in_flight_ == 0 ||
12883
0
         socket_requests_are_from_thread_ == std::this_thread::get_id());
12884
12885
  // It is also a bug if this happens while SSL is still active
12886
#ifdef CPPHTTPLIB_SSL_ENABLED
12887
  assert(socket.ssl == nullptr);
12888
#endif
12889
12890
0
  if (socket.sock == INVALID_SOCKET) { return; }
12891
0
  detail::close_socket(socket.sock);
12892
0
  socket.sock = INVALID_SOCKET;
12893
0
}
12894
12895
0
inline void ClientImpl::disconnect(bool gracefully) {
12896
0
  shutdown_ssl(socket_, gracefully);
12897
0
  shutdown_socket(socket_);
12898
0
  close_socket(socket_);
12899
0
}
12900
12901
inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
12902
                                           Response &res,
12903
0
                                           bool skip_100_continue) const {
12904
0
  std::array<char, 2048> buf{};
12905
0
12906
0
  detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
12907
0
12908
0
  if (!line_reader.getline()) { return false; }
12909
0
12910
0
#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
12911
0
  thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n");
12912
0
#else
12913
0
  thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n");
12914
0
#endif
12915
0
12916
0
  std::cmatch m;
12917
0
  if (!std::regex_match(line_reader.ptr(), m, re)) {
12918
0
    return req.method == "CONNECT";
12919
0
  }
12920
0
  res.version = std::string(m[1]);
12921
0
  res.status = std::stoi(std::string(m[2]));
12922
0
  res.reason = std::string(m[3]);
12923
0
12924
0
  // Ignore '100 Continue' (only when not using Expect: 100-continue explicitly)
12925
0
  while (skip_100_continue && res.status == StatusCode::Continue_100) {
12926
0
    if (!line_reader.getline()) { return false; } // CRLF
12927
0
    if (!line_reader.getline()) { return false; } // next response line
12928
0
12929
0
    if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
12930
0
    res.version = std::string(m[1]);
12931
0
    res.status = std::stoi(std::string(m[2]));
12932
0
    res.reason = std::string(m[3]);
12933
0
  }
12934
0
12935
0
  return true;
12936
0
}
12937
12938
0
inline bool ClientImpl::send(Request &req, Response &res, Error &error) {
12939
0
  std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
12940
0
  auto ret = send_(req, res, error);
12941
0
  if (error == Error::SSLPeerCouldBeClosed_) {
12942
0
    assert(!ret);
12943
0
    ret = send_(req, res, error);
12944
0
    // If still failing with SSLPeerCouldBeClosed_, convert to Read error
12945
0
    if (error == Error::SSLPeerCouldBeClosed_) { error = Error::Read; }
12946
0
  }
12947
0
  return ret;
12948
0
}
12949
12950
0
inline bool ClientImpl::send_(Request &req, Response &res, Error &error) {
12951
0
  {
12952
0
    std::lock_guard<std::mutex> guard(socket_mutex_);
12953
0
12954
0
    // Set this to false immediately - if it ever gets set to true by the end
12955
0
    // of the request, we know another thread instructed us to close the
12956
0
    // socket.
12957
0
    socket_should_be_closed_when_request_is_done_ = false;
12958
0
12959
0
    auto is_alive = false;
12960
0
    if (socket_.is_open()) {
12961
0
      is_alive = detail::is_socket_alive(socket_.sock);
12962
0
12963
0
#ifdef CPPHTTPLIB_SSL_ENABLED
12964
0
      if (is_alive && is_ssl()) {
12965
0
        if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
12966
0
          is_alive = false;
12967
0
        }
12968
0
      }
12969
0
#endif
12970
0
12971
0
      if (!is_alive) {
12972
0
        // Peer seems gone — non-graceful shutdown to avoid SIGPIPE.
12973
0
        disconnect(/*gracefully=*/false);
12974
0
      }
12975
0
    }
12976
0
12977
0
    if (!is_alive) {
12978
0
      if (!ensure_socket_connection(socket_, error)) {
12979
0
        output_error_log(error, &req);
12980
0
        return false;
12981
0
      }
12982
0
12983
0
      {
12984
0
        auto success = true;
12985
0
        if (!setup_proxy_connection(socket_, req.start_time_, res, success,
12986
0
                                    error)) {
12987
0
          if (!success) { output_error_log(error, &req); }
12988
0
          return success;
12989
0
        }
12990
0
      }
12991
0
    }
12992
0
12993
0
    // Mark the current socket as being in use so that it cannot be closed by
12994
0
    // anyone else while this request is ongoing, even though we will be
12995
0
    // releasing the mutex.
12996
0
    if (socket_requests_in_flight_ > 1) {
12997
0
      assert(socket_requests_are_from_thread_ == std::this_thread::get_id());
12998
0
    }
12999
0
    socket_requests_in_flight_ += 1;
13000
0
    socket_requests_are_from_thread_ = std::this_thread::get_id();
13001
0
  }
13002
0
13003
0
  for (const auto &header : default_headers_) {
13004
0
    if (req.headers.find(header.first) == req.headers.end()) {
13005
0
      req.headers.insert(header);
13006
0
    }
13007
0
  }
13008
0
13009
0
  auto ret = false;
13010
0
  auto close_connection = !keep_alive_;
13011
0
13012
0
  auto se = detail::scope_exit([&]() {
13013
0
    // Briefly lock mutex in order to mark that a request is no longer ongoing
13014
0
    std::lock_guard<std::mutex> guard(socket_mutex_);
13015
0
    socket_requests_in_flight_ -= 1;
13016
0
    if (socket_requests_in_flight_ <= 0) {
13017
0
      assert(socket_requests_in_flight_ == 0);
13018
0
      socket_requests_are_from_thread_ = std::thread::id();
13019
0
    }
13020
0
13021
0
    if (socket_should_be_closed_when_request_is_done_ || close_connection ||
13022
0
        !ret) {
13023
0
      disconnect(/*gracefully=*/true);
13024
0
    }
13025
0
  });
13026
0
13027
0
  ret = process_socket(socket_, req.start_time_, [&](Stream &strm) {
13028
0
    return handle_request(strm, req, res, close_connection, error);
13029
0
  });
13030
0
13031
0
  if (!ret) {
13032
0
    if (error == Error::Success) {
13033
0
      error = Error::Unknown;
13034
0
      output_error_log(error, &req);
13035
0
    }
13036
0
  }
13037
0
13038
0
  return ret;
13039
0
}
13040
13041
0
inline Result ClientImpl::send(const Request &req) {
13042
0
  auto req2 = req;
13043
0
  return send_(std::move(req2));
13044
0
}
13045
13046
0
inline Result ClientImpl::send_(Request &&req) {
13047
0
  auto res = detail::make_unique<Response>();
13048
0
  auto error = Error::Success;
13049
0
  auto ret = send(req, *res, error);
13050
0
#ifdef CPPHTTPLIB_SSL_ENABLED
13051
0
  return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers),
13052
0
                last_ssl_error_, last_backend_error_};
13053
0
#else
13054
0
  return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)};
13055
0
#endif
13056
0
}
13057
13058
inline void ClientImpl::prepare_default_headers(Request &r, bool for_stream,
13059
0
                                                const std::string &ct) {
13060
0
  (void)for_stream;
13061
0
  for (const auto &header : default_headers_) {
13062
0
    if (!r.has_header(header.first)) { r.headers.insert(header); }
13063
0
  }
13064
0
13065
0
  if (!r.has_header("Host")) {
13066
0
    if (address_family_ == AF_UNIX) {
13067
0
      r.headers.emplace("Host", "localhost");
13068
0
    } else {
13069
0
      r.headers.emplace(
13070
0
          "Host", detail::make_host_and_port_string(host_, port_, is_ssl()));
13071
0
    }
13072
0
  }
13073
0
13074
0
  if (!r.has_header("Accept")) { r.headers.emplace("Accept", "*/*"); }
13075
0
13076
0
  if (!r.content_receiver) {
13077
0
    if (!r.has_header("Accept-Encoding")) {
13078
0
      std::string accept_encoding;
13079
0
#ifdef CPPHTTPLIB_BROTLI_SUPPORT
13080
0
      accept_encoding = "br";
13081
0
#endif
13082
0
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
13083
0
      if (!accept_encoding.empty()) { accept_encoding += ", "; }
13084
0
      accept_encoding += "gzip, deflate";
13085
0
#endif
13086
0
#ifdef CPPHTTPLIB_ZSTD_SUPPORT
13087
0
      if (!accept_encoding.empty()) { accept_encoding += ", "; }
13088
0
      accept_encoding += "zstd";
13089
0
#endif
13090
0
      r.set_header("Accept-Encoding", accept_encoding);
13091
0
    }
13092
0
13093
0
#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
13094
0
    if (!r.has_header("User-Agent")) {
13095
0
      auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
13096
0
      r.set_header("User-Agent", agent);
13097
0
    }
13098
0
#endif
13099
0
  }
13100
0
13101
0
  if (!r.body.empty()) {
13102
0
    if (!ct.empty() && !r.has_header("Content-Type")) {
13103
0
      r.headers.emplace("Content-Type", ct);
13104
0
    }
13105
0
    if (!r.has_header("Content-Length")) {
13106
0
      r.headers.emplace("Content-Length", std::to_string(r.body.size()));
13107
0
    }
13108
0
  }
13109
0
}
13110
13111
inline ClientImpl::StreamHandle
13112
ClientImpl::open_stream(const std::string &method, const std::string &path,
13113
                        const Params &params, const Headers &headers,
13114
                        const std::string &body,
13115
0
                        const std::string &content_type) {
13116
0
  StreamHandle handle;
13117
0
  handle.response = detail::make_unique<Response>();
13118
0
  handle.error = Error::Success;
13119
0
13120
0
  auto query_path = params.empty() ? path : append_query_params(path, params);
13121
0
  handle.connection_ = detail::make_unique<ClientConnection>();
13122
0
13123
0
  {
13124
0
    std::lock_guard<std::mutex> guard(socket_mutex_);
13125
0
13126
0
    auto is_alive = false;
13127
0
    if (socket_.is_open()) {
13128
0
      is_alive = detail::is_socket_alive(socket_.sock);
13129
0
#ifdef CPPHTTPLIB_SSL_ENABLED
13130
0
      if (is_alive && is_ssl()) {
13131
0
        if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
13132
0
          is_alive = false;
13133
0
        }
13134
0
      }
13135
0
#endif
13136
0
      if (!is_alive) { disconnect(/*gracefully=*/false); }
13137
0
    }
13138
0
13139
0
    if (!is_alive) {
13140
0
      if (!ensure_socket_connection(socket_, handle.error)) {
13141
0
        handle.response.reset();
13142
0
        return handle;
13143
0
      }
13144
0
13145
0
      {
13146
0
        auto success = true;
13147
0
        auto start_time = std::chrono::steady_clock::now();
13148
0
        if (!setup_proxy_connection(socket_, start_time, *handle.response,
13149
0
                                    success, handle.error)) {
13150
0
          if (!success) { handle.response.reset(); }
13151
0
          return handle;
13152
0
        }
13153
0
      }
13154
0
    }
13155
0
13156
0
    transfer_socket_ownership_to_handle(handle);
13157
0
  }
13158
0
13159
0
#ifdef CPPHTTPLIB_SSL_ENABLED
13160
0
  if (is_ssl() && handle.connection_->session) {
13161
0
    handle.socket_stream_ = detail::make_unique<detail::SSLSocketStream>(
13162
0
        handle.connection_->sock, handle.connection_->session,
13163
0
        read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
13164
0
        write_timeout_usec_);
13165
0
  } else {
13166
0
    handle.socket_stream_ = detail::make_unique<detail::SocketStream>(
13167
0
        handle.connection_->sock, read_timeout_sec_, read_timeout_usec_,
13168
0
        write_timeout_sec_, write_timeout_usec_);
13169
0
  }
13170
0
#else
13171
0
  handle.socket_stream_ = detail::make_unique<detail::SocketStream>(
13172
0
      handle.connection_->sock, read_timeout_sec_, read_timeout_usec_,
13173
0
      write_timeout_sec_, write_timeout_usec_);
13174
0
#endif
13175
0
  handle.stream_ = handle.socket_stream_.get();
13176
0
13177
0
  Request req;
13178
0
  req.method = method;
13179
0
  req.path = query_path;
13180
0
  req.headers = headers;
13181
0
  req.body = body;
13182
0
13183
0
  prepare_default_headers(req, true, content_type);
13184
0
13185
0
  auto &strm = *handle.stream_;
13186
0
  if (detail::write_request_line(strm, req.method, req.path) < 0) {
13187
0
    handle.error = Error::Write;
13188
0
    handle.response.reset();
13189
0
    return handle;
13190
0
  }
13191
0
13192
0
  if (!detail::check_and_write_headers(strm, req.headers, header_writer_,
13193
0
                                       handle.error)) {
13194
0
    handle.response.reset();
13195
0
    return handle;
13196
0
  }
13197
0
13198
0
  if (!body.empty()) {
13199
0
    if (strm.write(body.data(), body.size()) < 0) {
13200
0
      handle.error = Error::Write;
13201
0
      handle.response.reset();
13202
0
      return handle;
13203
0
    }
13204
0
  }
13205
0
13206
0
  if (!read_response_line(strm, req, *handle.response) ||
13207
0
      !detail::read_headers(strm, handle.response->headers)) {
13208
0
    handle.error = Error::Read;
13209
0
    handle.response.reset();
13210
0
    return handle;
13211
0
  }
13212
0
13213
0
  handle.body_reader_.stream = handle.stream_;
13214
0
  handle.body_reader_.payload_max_length = payload_max_length_;
13215
0
13216
0
  if (handle.response->has_header("Content-Length")) {
13217
0
    bool is_invalid = false;
13218
0
    auto content_length = detail::get_header_value_u64(
13219
0
        handle.response->headers, "Content-Length", 0, 0, is_invalid);
13220
0
    if (is_invalid) {
13221
0
      handle.error = Error::Read;
13222
0
      handle.response.reset();
13223
0
      return handle;
13224
0
    }
13225
0
    handle.body_reader_.has_content_length = true;
13226
0
    handle.body_reader_.content_length = content_length;
13227
0
  }
13228
0
13229
0
  auto transfer_encoding =
13230
0
      handle.response->get_header_value("Transfer-Encoding");
13231
0
  handle.body_reader_.chunked = (transfer_encoding == "chunked");
13232
0
13233
0
  auto content_encoding = handle.response->get_header_value("Content-Encoding");
13234
0
  if (!content_encoding.empty()) {
13235
0
    handle.decompressor_ = detail::create_decompressor(content_encoding);
13236
0
  }
13237
0
13238
0
  return handle;
13239
0
}
13240
13241
0
inline ssize_t ClientImpl::StreamHandle::read(char *buf, size_t len) {
13242
0
  if (!is_valid() || !response) { return -1; }
13243
0
13244
0
  if (decompressor_) { return read_with_decompression(buf, len); }
13245
0
  auto n = detail::read_body_content(stream_, body_reader_, buf, len);
13246
0
13247
0
  if (n <= 0 && body_reader_.chunked && !trailers_parsed_ && stream_) {
13248
0
    trailers_parsed_ = true;
13249
0
    if (body_reader_.chunked_decoder) {
13250
0
      if (!body_reader_.chunked_decoder->parse_trailers_into(
13251
0
              response->trailers, response->headers)) {
13252
0
        return n;
13253
0
      }
13254
0
    } else {
13255
0
      detail::ChunkedDecoder dec(*stream_);
13256
0
      if (!dec.parse_trailers_into(response->trailers, response->headers)) {
13257
0
        return n;
13258
0
      }
13259
0
    }
13260
0
  }
13261
0
13262
0
  return n;
13263
0
}
13264
13265
inline ssize_t ClientImpl::StreamHandle::read_with_decompression(char *buf,
13266
0
                                                                 size_t len) {
13267
0
  if (decompress_offset_ < decompress_buffer_.size()) {
13268
0
    auto available = decompress_buffer_.size() - decompress_offset_;
13269
0
    auto to_copy = (std::min)(len, available);
13270
0
    std::memcpy(buf, decompress_buffer_.data() + decompress_offset_, to_copy);
13271
0
    decompress_offset_ += to_copy;
13272
0
    decompressed_bytes_read_ += to_copy;
13273
0
    return static_cast<ssize_t>(to_copy);
13274
0
  }
13275
0
13276
0
  decompress_buffer_.clear();
13277
0
  decompress_offset_ = 0;
13278
0
13279
0
  constexpr size_t kDecompressionBufferSize = 8192;
13280
0
  char compressed_buf[kDecompressionBufferSize];
13281
0
13282
0
  while (true) {
13283
0
    auto n = detail::read_body_content(stream_, body_reader_, compressed_buf,
13284
0
                                       sizeof(compressed_buf));
13285
0
13286
0
    if (n <= 0) { return n; }
13287
0
13288
0
    bool decompress_ok = decompressor_->decompress(
13289
0
        compressed_buf, static_cast<size_t>(n),
13290
0
        [this](const char *data, size_t data_len) {
13291
0
          decompress_buffer_.append(data, data_len);
13292
0
          auto limit = body_reader_.payload_max_length;
13293
0
          if (decompressed_bytes_read_ + decompress_buffer_.size() > limit) {
13294
0
            return false;
13295
0
          }
13296
0
          return true;
13297
0
        });
13298
0
13299
0
    if (!decompress_ok) {
13300
0
      body_reader_.last_error = Error::Read;
13301
0
      return -1;
13302
0
    }
13303
0
13304
0
    if (!decompress_buffer_.empty()) { break; }
13305
0
  }
13306
0
13307
0
  auto to_copy = (std::min)(len, decompress_buffer_.size());
13308
0
  std::memcpy(buf, decompress_buffer_.data(), to_copy);
13309
0
  decompress_offset_ = to_copy;
13310
0
  decompressed_bytes_read_ += to_copy;
13311
0
  return static_cast<ssize_t>(to_copy);
13312
0
}
13313
13314
0
inline void ClientImpl::StreamHandle::parse_trailers_if_needed() {
13315
0
  if (!response || !stream_ || !body_reader_.chunked || trailers_parsed_) {
13316
0
    return;
13317
0
  }
13318
0
13319
0
  trailers_parsed_ = true;
13320
0
13321
0
  const auto bufsiz = 128;
13322
0
  char line_buf[bufsiz];
13323
0
  detail::stream_line_reader line_reader(*stream_, line_buf, bufsiz);
13324
0
13325
0
  if (!line_reader.getline()) { return; }
13326
0
13327
0
  if (!detail::parse_trailers(line_reader, response->trailers,
13328
0
                              response->headers)) {
13329
0
    return;
13330
0
  }
13331
0
}
13332
13333
namespace detail {
13334
13335
2.02k
inline ChunkedDecoder::ChunkedDecoder(Stream &s) : strm(s) {}
13336
13337
inline ssize_t ChunkedDecoder::read_payload(char *buf, size_t len,
13338
                                            size_t &out_chunk_offset,
13339
53.9k
                                            size_t &out_chunk_total) {
13340
53.9k
  if (finished) { return 0; }
13341
13342
53.9k
  if (chunk_remaining == 0) {
13343
53.5k
    stream_line_reader lr(strm, line_buf, sizeof(line_buf));
13344
53.5k
    if (!lr.getline()) { return -1; }
13345
13346
    // RFC 9112 §7.1: chunk-size = 1*HEXDIG
13347
52.6k
    const char *p = lr.ptr();
13348
52.6k
    int v = 0;
13349
52.6k
    if (!is_hex(*p, v)) { return -1; }
13350
13351
52.5k
    size_t chunk_len = 0;
13352
52.5k
    constexpr size_t chunk_len_max = (std::numeric_limits<size_t>::max)();
13353
108k
    for (; is_hex(*p, v); ++p) {
13354
56.2k
      if (chunk_len > (chunk_len_max >> 4)) { return -1; }
13355
56.2k
      chunk_len = (chunk_len << 4) | static_cast<size_t>(v);
13356
56.2k
    }
13357
13358
57.9k
    while (is_space_or_tab(*p)) {
13359
5.33k
      ++p;
13360
5.33k
    }
13361
52.5k
    if (*p != '\0' && *p != ';' && *p != '\r' && *p != '\n') { return -1; }
13362
13363
52.5k
    if (chunk_len == 0) {
13364
561
      chunk_remaining = 0;
13365
561
      finished = true;
13366
561
      out_chunk_offset = 0;
13367
561
      out_chunk_total = 0;
13368
561
      return 0;
13369
561
    }
13370
13371
51.9k
    chunk_remaining = chunk_len;
13372
51.9k
    last_chunk_total = chunk_remaining;
13373
51.9k
    last_chunk_offset = 0;
13374
51.9k
  }
13375
13376
52.3k
  auto to_read = (std::min)(chunk_remaining, len);
13377
52.3k
  auto n = strm.read(buf, to_read);
13378
52.3k
  if (n <= 0) { return -1; }
13379
13380
51.9k
  auto offset_before = last_chunk_offset;
13381
51.9k
  last_chunk_offset += static_cast<size_t>(n);
13382
51.9k
  chunk_remaining -= static_cast<size_t>(n);
13383
13384
51.9k
  out_chunk_offset = offset_before;
13385
51.9k
  out_chunk_total = last_chunk_total;
13386
13387
51.9k
  if (chunk_remaining == 0) {
13388
51.5k
    stream_line_reader lr(strm, line_buf, sizeof(line_buf));
13389
51.5k
    if (!lr.getline()) { return -1; }
13390
51.5k
    if (std::strcmp(lr.ptr(), "\r\n") != 0) { return -1; }
13391
51.5k
  }
13392
13393
51.9k
  return n;
13394
51.9k
}
13395
13396
inline bool ChunkedDecoder::parse_trailers_into(Headers &dest,
13397
561
                                                const Headers &src_headers) {
13398
561
  stream_line_reader lr(strm, line_buf, sizeof(line_buf));
13399
561
  if (!lr.getline()) { return false; }
13400
543
  return parse_trailers(lr, dest, src_headers);
13401
561
}
13402
13403
} // namespace detail
13404
13405
inline void
13406
0
ClientImpl::transfer_socket_ownership_to_handle(StreamHandle &handle) {
13407
0
  handle.connection_->sock = socket_.sock;
13408
0
#ifdef CPPHTTPLIB_SSL_ENABLED
13409
0
  handle.connection_->session = socket_.ssl;
13410
0
  socket_.ssl = nullptr;
13411
0
#endif
13412
0
  socket_.sock = INVALID_SOCKET;
13413
0
}
13414
13415
inline bool ClientImpl::handle_request(Stream &strm, Request &req,
13416
                                       Response &res, bool close_connection,
13417
0
                                       Error &error) {
13418
0
  if (req.path.empty()) {
13419
0
    error = Error::Connection;
13420
0
    output_error_log(error, &req);
13421
0
    return false;
13422
0
  }
13423
0
13424
0
  auto req_save = req;
13425
0
13426
0
  bool ret;
13427
0
13428
0
  if (!is_ssl() && is_proxy_enabled_for_host(host_)) {
13429
0
    auto req2 = req;
13430
0
    req2.path = "http://" +
13431
0
                detail::make_host_and_port_string(host_, port_, false) +
13432
0
                req.path;
13433
0
    ret = process_request(strm, req2, res, close_connection, error);
13434
0
    req = std::move(req2);
13435
0
    req.path = req_save.path;
13436
0
  } else {
13437
0
    ret = process_request(strm, req, res, close_connection, error);
13438
0
  }
13439
0
13440
0
  if (!ret) { return false; }
13441
0
13442
0
  if (res.get_header_value("Connection") == "close" ||
13443
0
      (res.version == "HTTP/1.0" && res.reason != "Connection established")) {
13444
0
    // NOTE: this requires a not-entirely-obvious chain of calls to be correct
13445
0
    // for this to be safe.
13446
0
13447
0
    // This is safe to call because handle_request is only called by send_
13448
0
    // which locks the request mutex during the process. It would be a bug
13449
0
    // to call it from a different thread since it's a thread-safety issue
13450
0
    // to do these things to the socket if another thread is using the socket.
13451
0
    std::lock_guard<std::mutex> guard(socket_mutex_);
13452
0
    disconnect(/*gracefully=*/true);
13453
0
  }
13454
0
13455
0
  if (300 < res.status && res.status < 400 && follow_location_) {
13456
0
    req = std::move(req_save);
13457
0
    ret = redirect(req, res, error);
13458
0
  }
13459
0
13460
0
#ifdef CPPHTTPLIB_SSL_ENABLED
13461
0
  if ((res.status == StatusCode::Unauthorized_401 ||
13462
0
       res.status == StatusCode::ProxyAuthenticationRequired_407) &&
13463
0
      req.authorization_count_ < 5) {
13464
0
    auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407;
13465
0
13466
0
    // Only retry when the 407 actually came from a proxy hop: plain HTTP
13467
0
    // through an enabled proxy. HTTPS via CONNECT tunnels the 407 from the
13468
0
    // origin (#2457); direct/bypassed origins have no proxy hop at all.
13469
0
    if (is_proxy && !(!is_ssl() && is_proxy_enabled_for_host(host_))) {
13470
0
      return ret;
13471
0
    }
13472
0
13473
0
    const auto &username =
13474
0
        is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
13475
0
    const auto &password =
13476
0
        is_proxy ? proxy_digest_auth_password_ : digest_auth_password_;
13477
0
13478
0
    if (!username.empty() && !password.empty()) {
13479
0
      std::map<std::string, std::string> auth;
13480
0
      if (detail::parse_www_authenticate(res, auth, is_proxy)) {
13481
0
        Request new_req = req;
13482
0
        new_req.authorization_count_ += 1;
13483
0
        new_req.headers.erase(is_proxy ? "Proxy-Authorization"
13484
0
                                       : "Authorization");
13485
0
        new_req.headers.insert(detail::make_digest_authentication_header(
13486
0
            req, auth, new_req.authorization_count_, detail::random_string(10),
13487
0
            username, password, is_proxy));
13488
0
13489
0
        Response new_res;
13490
0
13491
0
        ret = send(new_req, new_res, error);
13492
0
        if (ret) { res = std::move(new_res); }
13493
0
      }
13494
0
    }
13495
0
  }
13496
0
#endif
13497
0
13498
0
  return ret;
13499
0
}
13500
13501
0
inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
13502
0
  if (req.redirect_count_ == 0) {
13503
0
    error = Error::ExceedRedirectCount;
13504
0
    output_error_log(error, &req);
13505
0
    return false;
13506
0
  }
13507
0
13508
0
  auto location = res.get_header_value("location");
13509
0
  if (location.empty()) { return false; }
13510
0
13511
0
  detail::UrlComponents uc;
13512
0
  if (!detail::parse_url(location, uc)) { return false; }
13513
0
13514
0
  // Only follow http/https redirects
13515
0
  if (!uc.scheme.empty() && uc.scheme != "http" && uc.scheme != "https") {
13516
0
    return false;
13517
0
  }
13518
0
13519
0
  auto scheme = is_ssl() ? "https" : "http";
13520
0
13521
0
  auto next_scheme = std::move(uc.scheme);
13522
0
  auto next_host = std::move(uc.host);
13523
0
  auto port_str = std::move(uc.port);
13524
0
  auto next_path = std::move(uc.path);
13525
0
  auto next_query = std::move(uc.query);
13526
0
13527
0
  auto next_port = port_;
13528
0
  if (!port_str.empty()) {
13529
0
    if (!detail::parse_port(port_str, next_port)) { return false; }
13530
0
  } else if (!next_scheme.empty()) {
13531
0
    next_port = next_scheme == "https" ? 443 : 80;
13532
0
  }
13533
0
13534
0
  if (next_scheme.empty()) { next_scheme = scheme; }
13535
0
  if (next_host.empty()) { next_host = host_; }
13536
0
  if (next_path.empty()) { next_path = "/"; }
13537
0
13538
0
  auto path = decode_path_component(next_path) + next_query;
13539
0
13540
0
  // Same host redirect - use current client
13541
0
  if (next_scheme == scheme && next_host == host_ && next_port == port_) {
13542
0
    return detail::redirect(*this, req, res, path, location, error);
13543
0
  }
13544
0
13545
0
  // Cross-host/scheme redirect - create new client with robust setup
13546
0
  return create_redirect_client(next_scheme, next_host, next_port, req, res,
13547
0
                                path, location, error);
13548
0
}
13549
13550
// New method for robust redirect client creation
13551
inline bool ClientImpl::create_redirect_client(
13552
    const std::string &scheme, const std::string &host, int port, Request &req,
13553
    Response &res, const std::string &path, const std::string &location,
13554
0
    Error &error) {
13555
0
  // Determine if we need SSL
13556
0
  auto need_ssl = (scheme == "https");
13557
0
13558
0
  // Clean up request headers that are host/client specific
13559
0
  // Remove headers that should not be carried over to new host
13560
0
  auto headers_to_remove = std::vector<std::string>{
13561
0
      "Host", "Proxy-Authorization", "Authorization", "Cookie", "Cookie2"};
13562
0
13563
0
  for (const auto &header_name : headers_to_remove) {
13564
0
    auto it = req.headers.find(header_name);
13565
0
    while (it != req.headers.end()) {
13566
0
      it = req.headers.erase(it);
13567
0
      it = req.headers.find(header_name);
13568
0
    }
13569
0
  }
13570
0
13571
0
  // Create appropriate client type and handle redirect
13572
0
  if (need_ssl) {
13573
0
#ifdef CPPHTTPLIB_SSL_ENABLED
13574
0
    // Create SSL client for HTTPS redirect
13575
0
    SSLClient redirect_client(host, port);
13576
0
13577
0
    // Setup basic client configuration first
13578
0
    setup_redirect_client(redirect_client);
13579
0
13580
0
    redirect_client.enable_server_certificate_verification(
13581
0
        server_certificate_verification_);
13582
0
    redirect_client.enable_server_hostname_verification(
13583
0
        server_hostname_verification_);
13584
0
    redirect_client.system_ca_mode_ = system_ca_mode_;
13585
0
13586
0
    // Transfer CA certificate to redirect client
13587
0
    if (!ca_cert_pem_.empty()) {
13588
0
      redirect_client.load_ca_cert_store(ca_cert_pem_.c_str(),
13589
0
                                         ca_cert_pem_.size());
13590
0
    }
13591
0
    if (!ca_cert_file_path_.empty()) {
13592
0
      redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_);
13593
0
    }
13594
0
13595
0
    // Client certificates are set through constructor for SSLClient
13596
0
    // NOTE: SSLClient constructor already takes client_cert_path and
13597
0
    // client_key_path so we need to create it properly if client certs are
13598
0
    // needed
13599
0
13600
0
    // Execute the redirect
13601
0
    return detail::redirect(redirect_client, req, res, path, location, error);
13602
0
#else
13603
0
    // SSL not supported - set appropriate error
13604
0
    error = Error::SSLConnection;
13605
0
    output_error_log(error, &req);
13606
0
    return false;
13607
0
#endif
13608
0
  } else {
13609
0
    // HTTP redirect
13610
0
    ClientImpl redirect_client(host, port);
13611
0
13612
0
    // Setup client with robust configuration
13613
0
    setup_redirect_client(redirect_client);
13614
0
13615
0
    // Execute the redirect
13616
0
    return detail::redirect(redirect_client, req, res, path, location, error);
13617
0
  }
13618
0
}
13619
13620
// New method for robust client setup (based on basic_manual_redirect.cpp
13621
// logic)
13622
template <typename ClientType>
13623
0
inline void ClientImpl::setup_redirect_client(ClientType &client) {
13624
0
  // Copy basic settings first
13625
0
  client.set_connection_timeout(connection_timeout_sec_);
13626
0
  client.set_read_timeout(read_timeout_sec_, read_timeout_usec_);
13627
0
  client.set_write_timeout(write_timeout_sec_, write_timeout_usec_);
13628
0
  client.set_keep_alive(keep_alive_);
13629
0
  client.set_follow_location(
13630
0
      true); // Enable redirects to handle multi-step redirects
13631
0
  client.set_path_encode(path_encode_);
13632
0
  client.set_compress(compress_);
13633
0
  client.set_decompress(decompress_);
13634
0
13635
0
  // NOTE: Authentication credentials (basic auth, bearer token, digest auth)
13636
0
  // are intentionally NOT copied to the redirect client. Per RFC 9110 Section
13637
0
  // 15.4, credentials must not be forwarded when redirecting to a different
13638
0
  // host. This function is only called for cross-host redirects; same-host
13639
0
  // redirects are handled directly in ClientImpl::redirect().
13640
0
13641
0
  // Copy the proxy configuration unconditionally; the per-target bypass is
13642
0
  // re-evaluated at send time, so a later hop to a non-bypassed host can
13643
0
  // still use the proxy.
13644
0
  client.no_proxy_entries_ = no_proxy_entries_;
13645
0
  if (!proxy_host_.empty() && proxy_port_ != -1) {
13646
0
    client.set_proxy(proxy_host_, proxy_port_);
13647
0
13648
0
    if (!proxy_basic_auth_username_.empty()) {
13649
0
      client.set_proxy_basic_auth(proxy_basic_auth_username_,
13650
0
                                  proxy_basic_auth_password_);
13651
0
    }
13652
0
    if (!proxy_bearer_token_auth_token_.empty()) {
13653
0
      client.set_proxy_bearer_token_auth(proxy_bearer_token_auth_token_);
13654
0
    }
13655
0
#ifdef CPPHTTPLIB_SSL_ENABLED
13656
0
    if (!proxy_digest_auth_username_.empty()) {
13657
0
      client.set_proxy_digest_auth(proxy_digest_auth_username_,
13658
0
                                   proxy_digest_auth_password_);
13659
0
    }
13660
0
#endif
13661
0
  }
13662
0
13663
0
  // Copy network and socket settings
13664
0
  client.set_address_family(address_family_);
13665
0
  client.set_tcp_nodelay(tcp_nodelay_);
13666
0
  client.set_ipv6_v6only(ipv6_v6only_);
13667
0
  if (socket_options_) { client.set_socket_options(socket_options_); }
13668
0
  if (!interface_.empty()) { client.set_interface(interface_); }
13669
0
13670
0
  // Copy logging and headers
13671
0
  if (logger_) { client.set_logger(logger_); }
13672
0
  if (error_logger_) { client.set_error_logger(error_logger_); }
13673
0
13674
0
  // NOTE: DO NOT copy default_headers_ as they may contain stale Host headers
13675
0
  // Each new client should generate its own headers based on its target host
13676
0
}
13677
13678
inline bool ClientImpl::write_content_with_provider(Stream &strm,
13679
                                                    const Request &req,
13680
0
                                                    Error &error) const {
13681
0
  auto is_shutting_down = []() { return false; };
13682
0
13683
0
  if (req.is_chunked_content_provider_) {
13684
0
    auto compressor = compress_ ? detail::create_compressor().first
13685
0
                                : std::unique_ptr<detail::compressor>();
13686
0
    if (!compressor) {
13687
0
      compressor = detail::make_unique<detail::nocompressor>();
13688
0
    }
13689
0
13690
0
    return detail::write_content_chunked(strm, req.content_provider_,
13691
0
                                         is_shutting_down, *compressor, error);
13692
0
  } else {
13693
0
    return detail::write_content_with_progress(
13694
0
        strm, req.content_provider_, 0, req.content_length_, is_shutting_down,
13695
0
        req.upload_progress, error);
13696
0
  }
13697
0
}
13698
13699
inline bool ClientImpl::write_request(Stream &strm, Request &req,
13700
                                      bool close_connection, Error &error,
13701
0
                                      bool skip_body) {
13702
0
  // Prepare additional headers
13703
0
  if (close_connection) {
13704
0
    if (!req.has_header("Connection")) {
13705
0
      req.set_header("Connection", "close");
13706
0
    }
13707
0
  }
13708
0
13709
0
  std::string ct_for_defaults;
13710
0
  if (!req.has_header("Content-Type") && !req.body.empty()) {
13711
0
    ct_for_defaults = "text/plain";
13712
0
  }
13713
0
  prepare_default_headers(req, false, ct_for_defaults);
13714
0
13715
0
  if (req.body.empty()) {
13716
0
    if (req.content_provider_) {
13717
0
      if (!req.is_chunked_content_provider_) {
13718
0
        if (!req.has_header("Content-Length")) {
13719
0
          auto length = std::to_string(req.content_length_);
13720
0
          req.set_header("Content-Length", length);
13721
0
        }
13722
0
      }
13723
0
    } else {
13724
0
      if (req.method == "POST" || req.method == "PUT" ||
13725
0
          req.method == "PATCH") {
13726
0
        req.set_header("Content-Length", "0");
13727
0
      }
13728
0
    }
13729
0
  }
13730
0
13731
0
  if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) {
13732
0
    if (!req.has_header("Authorization")) {
13733
0
      req.headers.insert(make_basic_authentication_header(
13734
0
          basic_auth_username_, basic_auth_password_, false));
13735
0
    }
13736
0
  }
13737
0
13738
0
  if (!bearer_token_auth_token_.empty()) {
13739
0
    if (!req.has_header("Authorization")) {
13740
0
      req.headers.insert(make_bearer_token_authentication_header(
13741
0
          bearer_token_auth_token_, false));
13742
0
    }
13743
0
  }
13744
0
13745
0
  // Proxy-Authorization is only sent when the proxy is actually used for
13746
0
  // this target — otherwise NO_PROXY-matched requests would leak proxy
13747
0
  // credentials directly to the destination server.
13748
0
  if (is_proxy_enabled_for_host(host_)) {
13749
0
    if (!proxy_basic_auth_username_.empty() &&
13750
0
        !proxy_basic_auth_password_.empty() &&
13751
0
        !req.has_header("Proxy-Authorization")) {
13752
0
      req.headers.insert(make_basic_authentication_header(
13753
0
          proxy_basic_auth_username_, proxy_basic_auth_password_, true));
13754
0
    }
13755
0
    if (!proxy_bearer_token_auth_token_.empty() &&
13756
0
        !req.has_header("Proxy-Authorization")) {
13757
0
      req.headers.insert(make_bearer_token_authentication_header(
13758
0
          proxy_bearer_token_auth_token_, true));
13759
0
    }
13760
0
  }
13761
0
13762
0
  // Request line and headers
13763
0
  {
13764
0
    detail::BufferStream bstrm;
13765
0
13766
0
    // Extract path and query from req.path
13767
0
    std::string path_part, query_part;
13768
0
    auto query_pos = req.path.find('?');
13769
0
    if (query_pos != std::string::npos) {
13770
0
      path_part = req.path.substr(0, query_pos);
13771
0
      query_part = req.path.substr(query_pos + 1);
13772
0
    } else {
13773
0
      path_part = req.path;
13774
0
      query_part = "";
13775
0
    }
13776
0
13777
0
    // Encode path part. If the original `req.path` already contained a
13778
0
    // query component, preserve its raw query string (including parameter
13779
0
    // order) instead of reparsing and reassembling it which may reorder
13780
0
    // parameters due to container ordering (e.g. `Params` uses
13781
0
    // `std::multimap`). When there is no query in `req.path`, fall back to
13782
0
    // building a query from `req.params` so existing callers that pass
13783
0
    // `Params` continue to work.
13784
0
    auto path_with_query =
13785
0
        path_encode_ ? detail::encode_path(path_part) : path_part;
13786
0
13787
0
    if (!query_part.empty()) {
13788
0
      // Normalize the query string (decode then re-encode) while preserving
13789
0
      // the original parameter order. When path encoding is disabled the
13790
0
      // caller has supplied an already-encoded target and expects the exact
13791
0
      // bytes to be sent on the wire, so skip normalization for the query
13792
0
      // too. Normalizing here would decode-then-re-encode the query and
13793
0
      // corrupt pre-encoded binary payloads (e.g. turning `%20` into `+`,
13794
0
      // which a strict RFC 3986 server decodes back as `+`, not a space).
13795
0
      if (path_encode_) {
13796
0
        auto normalized = detail::normalize_query_string(query_part);
13797
0
        if (!normalized.empty()) { path_with_query += '?' + normalized; }
13798
0
      } else {
13799
0
        path_with_query += '?' + query_part;
13800
0
      }
13801
0
13802
0
      // Still populate req.params for handlers/users who read them.
13803
0
      detail::parse_query_text(query_part, req.params);
13804
0
    } else {
13805
0
      // No query in path; parse any query_part (empty) and append params
13806
0
      // from `req.params` when present (preserves prior behavior for
13807
0
      // callers who provide Params separately).
13808
0
      detail::parse_query_text(query_part, req.params);
13809
0
      if (!req.params.empty()) {
13810
0
        path_with_query = append_query_params(path_with_query, req.params);
13811
0
      }
13812
0
    }
13813
0
13814
0
    // Write request line and headers
13815
0
    detail::write_request_line(bstrm, req.method, path_with_query);
13816
0
    if (!detail::check_and_write_headers(bstrm, req.headers, header_writer_,
13817
0
                                         error)) {
13818
0
      output_error_log(error, &req);
13819
0
      return false;
13820
0
    }
13821
0
13822
0
    // Flush buffer
13823
0
    auto &data = bstrm.get_buffer();
13824
0
    if (!detail::write_data(strm, data.data(), data.size())) {
13825
0
      error = Error::Write;
13826
0
      output_error_log(error, &req);
13827
0
      return false;
13828
0
    }
13829
0
  }
13830
0
13831
0
  // After sending request line and headers, wait briefly for an early server
13832
0
  // response (e.g. 4xx) and avoid sending a potentially large request body
13833
0
  // unnecessarily. This workaround is only enabled on Windows because Unix
13834
0
  // platforms surface write errors (EPIPE) earlier; on Windows kernel send
13835
0
  // buffering can accept large writes even when the peer already responded.
13836
0
  // Check the stream first (which covers SSL via `is_readable()`), then
13837
0
  // fall back to select on the socket. Only perform the wait for very large
13838
0
  // request bodies to avoid interfering with normal small requests and
13839
0
  // reduce side-effects. Poll briefly (up to 50ms as default) for an early
13840
0
  // response. Skip this check when using Expect: 100-continue, as the protocol
13841
0
  // handles early responses properly.
13842
0
#if defined(_WIN32)
13843
0
  if (!skip_body &&
13844
0
      req.body.size() > CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD &&
13845
0
      req.path.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
13846
0
    auto start = std::chrono::high_resolution_clock::now();
13847
0
13848
0
    for (;;) {
13849
0
      // Prefer socket-level readiness to avoid SSL_pending() false-positives
13850
0
      // from SSL internals. If the underlying socket is readable, assume an
13851
0
      // early response may be present.
13852
0
      auto sock = strm.socket();
13853
0
      if (sock != INVALID_SOCKET && detail::select_read(sock, 0, 0) > 0) {
13854
0
        return false;
13855
0
      }
13856
0
13857
0
      // Fallback to stream-level check for non-socket streams or when the
13858
0
      // socket isn't reporting readable. Avoid using `is_readable()` for
13859
0
      // SSL, since `SSL_pending()` may report buffered records that do not
13860
0
      // indicate a complete application-level response yet.
13861
0
      if (!is_ssl() && strm.is_readable()) { return false; }
13862
0
13863
0
      auto now = std::chrono::high_resolution_clock::now();
13864
0
      auto elapsed =
13865
0
          std::chrono::duration_cast<std::chrono::milliseconds>(now - start)
13866
0
              .count();
13867
0
      if (elapsed >= CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND) {
13868
0
        break;
13869
0
      }
13870
0
13871
0
      std::this_thread::sleep_for(std::chrono::milliseconds(1));
13872
0
    }
13873
0
  }
13874
0
#endif
13875
0
13876
0
  // Body
13877
0
  if (skip_body) { return true; }
13878
0
13879
0
  return write_request_body(strm, req, error);
13880
0
}
13881
13882
inline bool ClientImpl::write_request_body(Stream &strm, Request &req,
13883
0
                                           Error &error) {
13884
0
  if (req.body.empty()) {
13885
0
    return write_content_with_provider(strm, req, error);
13886
0
  }
13887
0
13888
0
  if (req.upload_progress) {
13889
0
    auto body_size = req.body.size();
13890
0
    size_t written = 0;
13891
0
    auto data = req.body.data();
13892
0
13893
0
    while (written < body_size) {
13894
0
      size_t to_write = (std::min)(CPPHTTPLIB_SEND_BUFSIZ, body_size - written);
13895
0
      if (!detail::write_data(strm, data + written, to_write)) {
13896
0
        error = Error::Write;
13897
0
        output_error_log(error, &req);
13898
0
        return false;
13899
0
      }
13900
0
      written += to_write;
13901
0
13902
0
      if (!req.upload_progress(written, body_size)) {
13903
0
        error = Error::Canceled;
13904
0
        output_error_log(error, &req);
13905
0
        return false;
13906
0
      }
13907
0
    }
13908
0
  } else {
13909
0
    if (!detail::write_data(strm, req.body.data(), req.body.size())) {
13910
0
      error = Error::Write;
13911
0
      output_error_log(error, &req);
13912
0
      return false;
13913
0
    }
13914
0
  }
13915
0
13916
0
  return true;
13917
0
}
13918
13919
inline std::unique_ptr<Response>
13920
ClientImpl::send_with_content_provider_and_receiver(
13921
    Request &req, const char *body, size_t content_length,
13922
    ContentProvider content_provider,
13923
    ContentProviderWithoutLength content_provider_without_length,
13924
    const std::string &content_type, ContentReceiver content_receiver,
13925
0
    Error &error) {
13926
0
  if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
13927
0
13928
0
  auto enc = compress_
13929
0
                 ? detail::create_compressor()
13930
0
                 : std::pair<std::unique_ptr<detail::compressor>, const char *>(
13931
0
                       nullptr, nullptr);
13932
0
13933
0
  if (enc.second) { req.set_header("Content-Encoding", enc.second); }
13934
0
13935
0
  if (enc.first && !content_provider_without_length) {
13936
0
    auto &compressor = enc.first;
13937
0
13938
0
    if (content_provider) {
13939
0
      auto ok = true;
13940
0
      size_t offset = 0;
13941
0
      DataSink data_sink;
13942
0
13943
0
      data_sink.write = [&](const char *data, size_t data_len) -> bool {
13944
0
        if (ok) {
13945
0
          auto last = offset + data_len == content_length;
13946
0
13947
0
          auto ret = compressor->compress(
13948
0
              data, data_len, last,
13949
0
              [&](const char *compressed_data, size_t compressed_data_len) {
13950
0
                req.body.append(compressed_data, compressed_data_len);
13951
0
                return true;
13952
0
              });
13953
0
13954
0
          if (ret) {
13955
0
            offset += data_len;
13956
0
          } else {
13957
0
            ok = false;
13958
0
          }
13959
0
        }
13960
0
        return ok;
13961
0
      };
13962
0
13963
0
      while (ok && offset < content_length) {
13964
0
        if (!content_provider(offset, content_length - offset, data_sink)) {
13965
0
          error = Error::Canceled;
13966
0
          output_error_log(error, &req);
13967
0
          return nullptr;
13968
0
        }
13969
0
      }
13970
0
    } else {
13971
0
      if (!compressor->compress(body, content_length, true,
13972
0
                                [&](const char *data, size_t data_len) {
13973
0
                                  req.body.append(data, data_len);
13974
0
                                  return true;
13975
0
                                })) {
13976
0
        error = Error::Compression;
13977
0
        output_error_log(error, &req);
13978
0
        return nullptr;
13979
0
      }
13980
0
    }
13981
0
  } else {
13982
0
    if (content_provider) {
13983
0
      req.content_length_ = content_length;
13984
0
      req.content_provider_ = std::move(content_provider);
13985
0
      req.is_chunked_content_provider_ = false;
13986
0
    } else if (content_provider_without_length) {
13987
0
      req.content_length_ = 0;
13988
0
      req.content_provider_ = detail::ContentProviderAdapter(
13989
0
          std::move(content_provider_without_length));
13990
0
      req.is_chunked_content_provider_ = true;
13991
0
      req.set_header("Transfer-Encoding", "chunked");
13992
0
    } else {
13993
0
      req.body.assign(body, content_length);
13994
0
    }
13995
0
  }
13996
0
13997
0
  if (content_receiver) {
13998
0
    req.content_receiver =
13999
0
        [content_receiver](const char *data, size_t data_length,
14000
0
                           size_t /*offset*/, size_t /*total_length*/) {
14001
0
          return content_receiver(data, data_length);
14002
0
        };
14003
0
  }
14004
0
14005
0
  auto res = detail::make_unique<Response>();
14006
0
  return send(req, *res, error) ? std::move(res) : nullptr;
14007
0
}
14008
14009
inline Result ClientImpl::send_with_content_provider_and_receiver(
14010
    const std::string &method, const std::string &path, const Headers &headers,
14011
    const char *body, size_t content_length, ContentProvider content_provider,
14012
    ContentProviderWithoutLength content_provider_without_length,
14013
    const std::string &content_type, ContentReceiver content_receiver,
14014
0
    UploadProgress progress) {
14015
0
  Request req;
14016
0
  req.method = method;
14017
0
  req.headers = headers;
14018
0
  req.path = path;
14019
0
  req.upload_progress = std::move(progress);
14020
0
  if (max_timeout_msec_ > 0) {
14021
0
    req.start_time_ = std::chrono::steady_clock::now();
14022
0
  }
14023
0
14024
0
  auto error = Error::Success;
14025
0
14026
0
  auto res = send_with_content_provider_and_receiver(
14027
0
      req, body, content_length, std::move(content_provider),
14028
0
      std::move(content_provider_without_length), content_type,
14029
0
      std::move(content_receiver), error);
14030
0
14031
0
#ifdef CPPHTTPLIB_SSL_ENABLED
14032
0
  return Result{std::move(res), error, std::move(req.headers), last_ssl_error_,
14033
0
                last_backend_error_};
14034
0
#else
14035
0
  return Result{std::move(res), error, std::move(req.headers)};
14036
0
#endif
14037
0
}
14038
14039
inline void ClientImpl::output_log(const Request &req,
14040
0
                                   const Response &res) const {
14041
0
  if (logger_) {
14042
0
    std::lock_guard<std::mutex> guard(logger_mutex_);
14043
0
    logger_(req, res);
14044
0
  }
14045
0
}
14046
14047
inline void ClientImpl::output_error_log(const Error &err,
14048
0
                                         const Request *req) const {
14049
0
  if (error_logger_) {
14050
0
    std::lock_guard<std::mutex> guard(logger_mutex_);
14051
0
    error_logger_(err, req);
14052
0
  }
14053
0
}
14054
14055
inline bool ClientImpl::process_request(Stream &strm, Request &req,
14056
                                        Response &res, bool close_connection,
14057
0
                                        Error &error) {
14058
0
  // Auto-add Expect: 100-continue for large bodies
14059
0
  if (CPPHTTPLIB_EXPECT_100_THRESHOLD > 0 && !req.has_header("Expect")) {
14060
0
    auto body_size = req.body.empty() ? req.content_length_ : req.body.size();
14061
0
    if (body_size >= CPPHTTPLIB_EXPECT_100_THRESHOLD) {
14062
0
      req.set_header("Expect", "100-continue");
14063
0
    }
14064
0
  }
14065
0
14066
0
  // Check for Expect: 100-continue
14067
0
  auto expect_100_continue = req.get_header_value("Expect") == "100-continue";
14068
0
14069
0
  // Send request (skip body if using Expect: 100-continue)
14070
0
  auto write_request_success =
14071
0
      write_request(strm, req, close_connection, error, expect_100_continue);
14072
0
14073
0
#ifdef CPPHTTPLIB_SSL_ENABLED
14074
0
  if (is_ssl() && !expect_100_continue) {
14075
0
    auto is_proxy_enabled = is_proxy_enabled_for_host(host_);
14076
0
    if (!is_proxy_enabled) {
14077
0
      if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
14078
0
        error = Error::SSLPeerCouldBeClosed_;
14079
0
        output_error_log(error, &req);
14080
0
        return false;
14081
0
      }
14082
0
    }
14083
0
  }
14084
0
#endif
14085
0
14086
0
  // Handle Expect: 100-continue.
14087
0
  //
14088
0
  // Wait for an interim/early response by attempting to read the status line
14089
0
  // under a short timeout, instead of trusting raw socket readability. Over
14090
0
  // TLS, post-handshake records (e.g. session tickets) make the socket
14091
0
  // readable without any HTTP response being available; relying on
14092
0
  // `select_read` there caused the body to be withheld forever and the
14093
0
  // request to fail with `Read` (#2458). If no status line arrives within the
14094
0
  // timeout, send the body anyway (matching curl's behavior).
14095
0
  auto status_line_read = false;
14096
0
  if (expect_100_continue && write_request_success) {
14097
0
    if (CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND > 0) {
14098
0
      time_t sec = CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND / 1000;
14099
0
      time_t usec = (CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND % 1000) * 1000;
14100
0
      strm.set_read_timeout(sec, usec);
14101
0
      status_line_read = read_response_line(strm, req, res, false);
14102
0
      strm.set_read_timeout(read_timeout_sec_, read_timeout_usec_);
14103
0
    }
14104
0
14105
0
    if (!status_line_read) {
14106
0
      // No interim response within the timeout: send the body and handle the
14107
0
      // response as usual.
14108
0
      if (!write_request_body(strm, req, error)) { return false; }
14109
0
      expect_100_continue = false; // Switch to normal response handling
14110
0
    }
14111
0
  }
14112
0
14113
0
  // Receive response and headers
14114
0
  // When using Expect: 100-continue, don't auto-skip `100 Continue` response
14115
0
  if ((!status_line_read &&
14116
0
       !read_response_line(strm, req, res, !expect_100_continue)) ||
14117
0
      !detail::read_headers(strm, res.headers)) {
14118
0
    if (write_request_success) { error = Error::Read; }
14119
0
    output_error_log(error, &req);
14120
0
    return false;
14121
0
  }
14122
0
14123
0
  if (!write_request_success) { return false; }
14124
0
14125
0
  // Handle Expect: 100-continue response
14126
0
  if (expect_100_continue) {
14127
0
    if (res.status == StatusCode::Continue_100) {
14128
0
      // Server accepted, send the body
14129
0
      if (!write_request_body(strm, req, error)) { return false; }
14130
0
14131
0
      // Read the actual response
14132
0
      res.headers.clear();
14133
0
      res.body.clear();
14134
0
      if (!read_response_line(strm, req, res) ||
14135
0
          !detail::read_headers(strm, res.headers)) {
14136
0
        error = Error::Read;
14137
0
        output_error_log(error, &req);
14138
0
        return false;
14139
0
      }
14140
0
    }
14141
0
    // If not 100 Continue, server returned an error; proceed with that response
14142
0
  }
14143
0
14144
0
  // Body
14145
0
  if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
14146
0
      req.method != "CONNECT") {
14147
0
    auto redirect = 300 < res.status && res.status < 400 &&
14148
0
                    res.status != StatusCode::NotModified_304 &&
14149
0
                    follow_location_;
14150
0
14151
0
    if (req.response_handler && !redirect) {
14152
0
      if (!req.response_handler(res)) {
14153
0
        error = Error::Canceled;
14154
0
        output_error_log(error, &req);
14155
0
        return false;
14156
0
      }
14157
0
    }
14158
0
14159
0
    auto out =
14160
0
        req.content_receiver
14161
0
            ? static_cast<ContentReceiverWithProgress>(
14162
0
                  [&](const char *buf, size_t n, size_t off, size_t len) {
14163
0
                    if (redirect) { return true; }
14164
0
                    auto ret = req.content_receiver(buf, n, off, len);
14165
0
                    if (!ret) {
14166
0
                      error = Error::Canceled;
14167
0
                      output_error_log(error, &req);
14168
0
                    }
14169
0
                    return ret;
14170
0
                  })
14171
0
            : static_cast<ContentReceiverWithProgress>(
14172
0
                  [&](const char *buf, size_t n, size_t /*off*/,
14173
0
                      size_t /*len*/) {
14174
0
                    assert(res.body.size() + n <= res.body.max_size());
14175
0
                    if (payload_max_length_ > 0 &&
14176
0
                        (res.body.size() >= payload_max_length_ ||
14177
0
                         n > payload_max_length_ - res.body.size())) {
14178
0
                      return false;
14179
0
                    }
14180
0
                    res.body.append(buf, n);
14181
0
                    return true;
14182
0
                  });
14183
0
14184
0
    auto progress = [&](size_t current, size_t total) {
14185
0
      if (!req.download_progress || redirect) { return true; }
14186
0
      auto ret = req.download_progress(current, total);
14187
0
      if (!ret) {
14188
0
        error = Error::Canceled;
14189
0
        output_error_log(error, &req);
14190
0
      }
14191
0
      return ret;
14192
0
    };
14193
0
14194
0
    if (res.has_header("Content-Length")) {
14195
0
      if (!req.content_receiver) {
14196
0
        auto len = res.get_header_value_u64("Content-Length");
14197
0
        if (len > res.body.max_size()) {
14198
0
          error = Error::Read;
14199
0
          output_error_log(error, &req);
14200
0
          return false;
14201
0
        }
14202
0
        // Cap the reservation by payload_max_length_ to avoid OOM when a
14203
0
        // hostile or malformed server sends an enormous Content-Length.
14204
0
        // The actual body read below is bounded by payload_max_length_,
14205
0
        // so reserving more than that is never useful.
14206
0
        auto reserve_len = static_cast<size_t>(len);
14207
0
        if (payload_max_length_ > 0 && reserve_len > payload_max_length_) {
14208
0
          reserve_len = payload_max_length_;
14209
0
        }
14210
0
        res.body.reserve(reserve_len);
14211
0
      }
14212
0
    }
14213
0
14214
0
    if (res.status != StatusCode::NotModified_304) {
14215
0
      int dummy_status;
14216
0
      auto max_length = (!has_payload_max_length_ && req.content_receiver)
14217
0
                            ? (std::numeric_limits<size_t>::max)()
14218
0
                            : payload_max_length_;
14219
0
      if (!detail::read_content(strm, res, max_length, dummy_status,
14220
0
                                std::move(progress), std::move(out),
14221
0
                                decompress_)) {
14222
0
        if (error != Error::Canceled) { error = Error::Read; }
14223
0
        output_error_log(error, &req);
14224
0
        return false;
14225
0
      }
14226
0
    }
14227
0
  }
14228
0
14229
0
  // Log
14230
0
  output_log(req, res);
14231
0
14232
0
  return true;
14233
0
}
14234
14235
inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
14236
    const std::string &boundary, const UploadFormDataItems &items,
14237
0
    const FormDataProviderItems &provider_items) const {
14238
0
  size_t cur_item = 0;
14239
0
  size_t cur_start = 0;
14240
0
  // cur_item and cur_start are copied to within the std::function and
14241
0
  // maintain state between successive calls
14242
0
  return [&, cur_item, cur_start](size_t offset,
14243
0
                                  DataSink &sink) mutable -> bool {
14244
0
    if (!offset && !items.empty()) {
14245
0
      sink.os << detail::serialize_multipart_formdata(items, boundary, false);
14246
0
      return true;
14247
0
    } else if (cur_item < provider_items.size()) {
14248
0
      if (!cur_start) {
14249
0
        const auto &begin = detail::serialize_multipart_formdata_item_begin(
14250
0
            provider_items[cur_item], boundary);
14251
0
        offset += begin.size();
14252
0
        cur_start = offset;
14253
0
        sink.os << begin;
14254
0
      }
14255
0
14256
0
      DataSink cur_sink;
14257
0
      auto has_data = true;
14258
0
      cur_sink.write = sink.write;
14259
0
      cur_sink.done = [&]() { has_data = false; };
14260
0
14261
0
      if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) {
14262
0
        return false;
14263
0
      }
14264
0
14265
0
      if (!has_data) {
14266
0
        sink.os << detail::serialize_multipart_formdata_item_end();
14267
0
        cur_item++;
14268
0
        cur_start = 0;
14269
0
      }
14270
0
      return true;
14271
0
    } else {
14272
0
      sink.os << detail::serialize_multipart_formdata_finish(boundary);
14273
0
      sink.done();
14274
0
      return true;
14275
0
    }
14276
0
  };
14277
0
}
14278
14279
inline bool ClientImpl::process_socket(
14280
    const Socket &socket,
14281
    std::chrono::time_point<std::chrono::steady_clock> start_time,
14282
0
    std::function<bool(Stream &strm)> callback) {
14283
0
  return detail::process_client_socket(
14284
0
      socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
14285
0
      write_timeout_usec_, max_timeout_msec_, start_time, std::move(callback));
14286
0
}
14287
14288
0
inline bool ClientImpl::is_ssl() const { return false; }
14289
14290
inline Result ClientImpl::Get(const std::string &path,
14291
0
                              DownloadProgress progress) {
14292
0
  return Get(path, Headers(), std::move(progress));
14293
0
}
14294
14295
inline Result ClientImpl::Get(const std::string &path, const Params &params,
14296
0
                              DownloadProgress progress) {
14297
0
  return Get(path, params, Headers(), std::move(progress));
14298
0
}
14299
14300
inline Result ClientImpl::Get(const std::string &path, const Params &params,
14301
                              const Headers &headers,
14302
0
                              DownloadProgress progress) {
14303
0
  if (params.empty()) { return Get(path, headers); }
14304
0
14305
0
  std::string path_with_query = append_query_params(path, params);
14306
0
  return Get(path_with_query, headers, std::move(progress));
14307
0
}
14308
14309
inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
14310
0
                              DownloadProgress progress) {
14311
0
  Request req;
14312
0
  req.method = "GET";
14313
0
  req.path = path;
14314
0
  req.headers = headers;
14315
0
  req.download_progress = std::move(progress);
14316
0
  if (max_timeout_msec_ > 0) {
14317
0
    req.start_time_ = std::chrono::steady_clock::now();
14318
0
  }
14319
0
14320
0
  return send_(std::move(req));
14321
0
}
14322
14323
inline Result ClientImpl::Get(const std::string &path,
14324
                              ContentReceiver content_receiver,
14325
0
                              DownloadProgress progress) {
14326
0
  return Get(path, Headers(), nullptr, std::move(content_receiver),
14327
0
             std::move(progress));
14328
0
}
14329
14330
inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
14331
                              ContentReceiver content_receiver,
14332
0
                              DownloadProgress progress) {
14333
0
  return Get(path, headers, nullptr, std::move(content_receiver),
14334
0
             std::move(progress));
14335
0
}
14336
14337
inline Result ClientImpl::Get(const std::string &path,
14338
                              ResponseHandler response_handler,
14339
                              ContentReceiver content_receiver,
14340
0
                              DownloadProgress progress) {
14341
0
  return Get(path, Headers(), std::move(response_handler),
14342
0
             std::move(content_receiver), std::move(progress));
14343
0
}
14344
14345
inline Result ClientImpl::Get(const std::string &path, const Headers &headers,
14346
                              ResponseHandler response_handler,
14347
                              ContentReceiver content_receiver,
14348
0
                              DownloadProgress progress) {
14349
0
  Request req;
14350
0
  req.method = "GET";
14351
0
  req.path = path;
14352
0
  req.headers = headers;
14353
0
  req.response_handler = std::move(response_handler);
14354
0
  req.content_receiver =
14355
0
      [content_receiver](const char *data, size_t data_length,
14356
0
                         size_t /*offset*/, size_t /*total_length*/) {
14357
0
        return content_receiver(data, data_length);
14358
0
      };
14359
0
  req.download_progress = std::move(progress);
14360
0
  if (max_timeout_msec_ > 0) {
14361
0
    req.start_time_ = std::chrono::steady_clock::now();
14362
0
  }
14363
0
14364
0
  return send_(std::move(req));
14365
0
}
14366
14367
inline Result ClientImpl::Get(const std::string &path, const Params &params,
14368
                              const Headers &headers,
14369
                              ContentReceiver content_receiver,
14370
0
                              DownloadProgress progress) {
14371
0
  return Get(path, params, headers, nullptr, std::move(content_receiver),
14372
0
             std::move(progress));
14373
0
}
14374
14375
inline Result ClientImpl::Get(const std::string &path, const Params &params,
14376
                              const Headers &headers,
14377
                              ResponseHandler response_handler,
14378
                              ContentReceiver content_receiver,
14379
0
                              DownloadProgress progress) {
14380
0
  if (params.empty()) {
14381
0
    return Get(path, headers, std::move(response_handler),
14382
0
               std::move(content_receiver), std::move(progress));
14383
0
  }
14384
0
14385
0
  std::string path_with_query = append_query_params(path, params);
14386
0
  return Get(path_with_query, headers, std::move(response_handler),
14387
0
             std::move(content_receiver), std::move(progress));
14388
0
}
14389
14390
0
inline Result ClientImpl::Head(const std::string &path) {
14391
0
  return Head(path, Headers());
14392
0
}
14393
14394
inline Result ClientImpl::Head(const std::string &path,
14395
0
                               const Headers &headers) {
14396
0
  Request req;
14397
0
  req.method = "HEAD";
14398
0
  req.headers = headers;
14399
0
  req.path = path;
14400
0
  if (max_timeout_msec_ > 0) {
14401
0
    req.start_time_ = std::chrono::steady_clock::now();
14402
0
  }
14403
0
14404
0
  return send_(std::move(req));
14405
0
}
14406
14407
0
inline Result ClientImpl::Post(const std::string &path) {
14408
0
  return Post(path, std::string(), std::string());
14409
0
}
14410
14411
inline Result ClientImpl::Post(const std::string &path,
14412
0
                               const Headers &headers) {
14413
0
  return Post(path, headers, nullptr, 0, std::string());
14414
0
}
14415
14416
inline Result ClientImpl::Post(const std::string &path, const char *body,
14417
                               size_t content_length,
14418
                               const std::string &content_type,
14419
0
                               UploadProgress progress) {
14420
0
  return Post(path, Headers(), body, content_length, content_type, progress);
14421
0
}
14422
14423
inline Result ClientImpl::Post(const std::string &path, const std::string &body,
14424
                               const std::string &content_type,
14425
0
                               UploadProgress progress) {
14426
0
  return Post(path, Headers(), body, content_type, progress);
14427
0
}
14428
14429
0
inline Result ClientImpl::Post(const std::string &path, const Params &params) {
14430
0
  return Post(path, Headers(), params);
14431
0
}
14432
14433
inline Result ClientImpl::Post(const std::string &path, size_t content_length,
14434
                               ContentProvider content_provider,
14435
                               const std::string &content_type,
14436
0
                               UploadProgress progress) {
14437
0
  return Post(path, Headers(), content_length, std::move(content_provider),
14438
0
              content_type, progress);
14439
0
}
14440
14441
inline Result ClientImpl::Post(const std::string &path, size_t content_length,
14442
                               ContentProvider content_provider,
14443
                               const std::string &content_type,
14444
                               ContentReceiver content_receiver,
14445
0
                               UploadProgress progress) {
14446
0
  return Post(path, Headers(), content_length, std::move(content_provider),
14447
0
              content_type, std::move(content_receiver), progress);
14448
0
}
14449
14450
inline Result ClientImpl::Post(const std::string &path,
14451
                               ContentProviderWithoutLength content_provider,
14452
                               const std::string &content_type,
14453
0
                               UploadProgress progress) {
14454
0
  return Post(path, Headers(), std::move(content_provider), content_type,
14455
0
              progress);
14456
0
}
14457
14458
inline Result ClientImpl::Post(const std::string &path,
14459
                               ContentProviderWithoutLength content_provider,
14460
                               const std::string &content_type,
14461
                               ContentReceiver content_receiver,
14462
0
                               UploadProgress progress) {
14463
0
  return Post(path, Headers(), std::move(content_provider), content_type,
14464
0
              std::move(content_receiver), progress);
14465
0
}
14466
14467
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14468
0
                               const Params &params) {
14469
0
  auto query = detail::params_to_query_str(params);
14470
0
  return Post(path, headers, query, "application/x-www-form-urlencoded");
14471
0
}
14472
14473
inline Result ClientImpl::Post(const std::string &path,
14474
                               const UploadFormDataItems &items,
14475
0
                               UploadProgress progress) {
14476
0
  return Post(path, Headers(), items, progress);
14477
0
}
14478
14479
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14480
                               const UploadFormDataItems &items,
14481
0
                               UploadProgress progress) {
14482
0
  const auto &boundary = detail::make_multipart_data_boundary();
14483
0
  const auto &content_type =
14484
0
      detail::serialize_multipart_formdata_get_content_type(boundary);
14485
0
  auto content_length = detail::get_multipart_content_length(items, boundary);
14486
0
  return Post(path, headers, content_length,
14487
0
              detail::make_multipart_content_provider(items, boundary),
14488
0
              content_type, progress);
14489
0
}
14490
14491
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14492
                               const UploadFormDataItems &items,
14493
                               const std::string &boundary,
14494
0
                               UploadProgress progress) {
14495
0
  if (!detail::is_multipart_boundary_chars_valid(boundary)) {
14496
0
    return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
14497
0
  }
14498
0
14499
0
  const auto &content_type =
14500
0
      detail::serialize_multipart_formdata_get_content_type(boundary);
14501
0
  auto content_length = detail::get_multipart_content_length(items, boundary);
14502
0
  return Post(path, headers, content_length,
14503
0
              detail::make_multipart_content_provider(items, boundary),
14504
0
              content_type, progress);
14505
0
}
14506
14507
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14508
                               const char *body, size_t content_length,
14509
                               const std::string &content_type,
14510
0
                               UploadProgress progress) {
14511
0
  return send_with_content_provider_and_receiver(
14512
0
      "POST", path, headers, body, content_length, nullptr, nullptr,
14513
0
      content_type, nullptr, progress);
14514
0
}
14515
14516
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14517
                               const std::string &body,
14518
                               const std::string &content_type,
14519
0
                               UploadProgress progress) {
14520
0
  return send_with_content_provider_and_receiver(
14521
0
      "POST", path, headers, body.data(), body.size(), nullptr, nullptr,
14522
0
      content_type, nullptr, progress);
14523
0
}
14524
14525
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14526
                               size_t content_length,
14527
                               ContentProvider content_provider,
14528
                               const std::string &content_type,
14529
0
                               UploadProgress progress) {
14530
0
  return send_with_content_provider_and_receiver(
14531
0
      "POST", path, headers, nullptr, content_length,
14532
0
      std::move(content_provider), nullptr, content_type, nullptr, progress);
14533
0
}
14534
14535
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14536
                               size_t content_length,
14537
                               ContentProvider content_provider,
14538
                               const std::string &content_type,
14539
                               ContentReceiver content_receiver,
14540
0
                               DownloadProgress progress) {
14541
0
  return send_with_content_provider_and_receiver(
14542
0
      "POST", path, headers, nullptr, content_length,
14543
0
      std::move(content_provider), nullptr, content_type,
14544
0
      std::move(content_receiver), std::move(progress));
14545
0
}
14546
14547
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14548
                               ContentProviderWithoutLength content_provider,
14549
                               const std::string &content_type,
14550
0
                               UploadProgress progress) {
14551
0
  return send_with_content_provider_and_receiver(
14552
0
      "POST", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14553
0
      content_type, nullptr, progress);
14554
0
}
14555
14556
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14557
                               ContentProviderWithoutLength content_provider,
14558
                               const std::string &content_type,
14559
                               ContentReceiver content_receiver,
14560
0
                               DownloadProgress progress) {
14561
0
  return send_with_content_provider_and_receiver(
14562
0
      "POST", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14563
0
      content_type, std::move(content_receiver), std::move(progress));
14564
0
}
14565
14566
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14567
                               const UploadFormDataItems &items,
14568
                               const FormDataProviderItems &provider_items,
14569
0
                               UploadProgress progress) {
14570
0
  const auto &boundary = detail::make_multipart_data_boundary();
14571
0
  const auto &content_type =
14572
0
      detail::serialize_multipart_formdata_get_content_type(boundary);
14573
0
  return send_with_content_provider_and_receiver(
14574
0
      "POST", path, headers, nullptr, 0, nullptr,
14575
0
      get_multipart_content_provider(boundary, items, provider_items),
14576
0
      content_type, nullptr, progress);
14577
0
}
14578
14579
inline Result ClientImpl::Post(const std::string &path, const Headers &headers,
14580
                               const std::string &body,
14581
                               const std::string &content_type,
14582
                               ContentReceiver content_receiver,
14583
0
                               DownloadProgress progress) {
14584
0
  Request req;
14585
0
  req.method = "POST";
14586
0
  req.path = path;
14587
0
  req.headers = headers;
14588
0
  req.body = body;
14589
0
  req.content_receiver =
14590
0
      [content_receiver](const char *data, size_t data_length,
14591
0
                         size_t /*offset*/, size_t /*total_length*/) {
14592
0
        return content_receiver(data, data_length);
14593
0
      };
14594
0
  req.download_progress = std::move(progress);
14595
0
14596
0
  if (max_timeout_msec_ > 0) {
14597
0
    req.start_time_ = std::chrono::steady_clock::now();
14598
0
  }
14599
0
14600
0
  if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
14601
0
14602
0
  return send_(std::move(req));
14603
0
}
14604
14605
0
inline Result ClientImpl::Put(const std::string &path) {
14606
0
  return Put(path, std::string(), std::string());
14607
0
}
14608
14609
0
inline Result ClientImpl::Put(const std::string &path, const Headers &headers) {
14610
0
  return Put(path, headers, nullptr, 0, std::string());
14611
0
}
14612
14613
inline Result ClientImpl::Put(const std::string &path, const char *body,
14614
                              size_t content_length,
14615
                              const std::string &content_type,
14616
0
                              UploadProgress progress) {
14617
0
  return Put(path, Headers(), body, content_length, content_type, progress);
14618
0
}
14619
14620
inline Result ClientImpl::Put(const std::string &path, const std::string &body,
14621
                              const std::string &content_type,
14622
0
                              UploadProgress progress) {
14623
0
  return Put(path, Headers(), body, content_type, progress);
14624
0
}
14625
14626
0
inline Result ClientImpl::Put(const std::string &path, const Params &params) {
14627
0
  return Put(path, Headers(), params);
14628
0
}
14629
14630
inline Result ClientImpl::Put(const std::string &path, size_t content_length,
14631
                              ContentProvider content_provider,
14632
                              const std::string &content_type,
14633
0
                              UploadProgress progress) {
14634
0
  return Put(path, Headers(), content_length, std::move(content_provider),
14635
0
             content_type, progress);
14636
0
}
14637
14638
inline Result ClientImpl::Put(const std::string &path, size_t content_length,
14639
                              ContentProvider content_provider,
14640
                              const std::string &content_type,
14641
                              ContentReceiver content_receiver,
14642
0
                              UploadProgress progress) {
14643
0
  return Put(path, Headers(), content_length, std::move(content_provider),
14644
0
             content_type, std::move(content_receiver), progress);
14645
0
}
14646
14647
inline Result ClientImpl::Put(const std::string &path,
14648
                              ContentProviderWithoutLength content_provider,
14649
                              const std::string &content_type,
14650
0
                              UploadProgress progress) {
14651
0
  return Put(path, Headers(), std::move(content_provider), content_type,
14652
0
             progress);
14653
0
}
14654
14655
inline Result ClientImpl::Put(const std::string &path,
14656
                              ContentProviderWithoutLength content_provider,
14657
                              const std::string &content_type,
14658
                              ContentReceiver content_receiver,
14659
0
                              UploadProgress progress) {
14660
0
  return Put(path, Headers(), std::move(content_provider), content_type,
14661
0
             std::move(content_receiver), progress);
14662
0
}
14663
14664
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14665
0
                              const Params &params) {
14666
0
  auto query = detail::params_to_query_str(params);
14667
0
  return Put(path, headers, query, "application/x-www-form-urlencoded");
14668
0
}
14669
14670
inline Result ClientImpl::Put(const std::string &path,
14671
                              const UploadFormDataItems &items,
14672
0
                              UploadProgress progress) {
14673
0
  return Put(path, Headers(), items, progress);
14674
0
}
14675
14676
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14677
                              const UploadFormDataItems &items,
14678
0
                              UploadProgress progress) {
14679
0
  const auto &boundary = detail::make_multipart_data_boundary();
14680
0
  const auto &content_type =
14681
0
      detail::serialize_multipart_formdata_get_content_type(boundary);
14682
0
  auto content_length = detail::get_multipart_content_length(items, boundary);
14683
0
  return Put(path, headers, content_length,
14684
0
             detail::make_multipart_content_provider(items, boundary),
14685
0
             content_type, progress);
14686
0
}
14687
14688
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14689
                              const UploadFormDataItems &items,
14690
                              const std::string &boundary,
14691
0
                              UploadProgress progress) {
14692
0
  if (!detail::is_multipart_boundary_chars_valid(boundary)) {
14693
0
    return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
14694
0
  }
14695
0
14696
0
  const auto &content_type =
14697
0
      detail::serialize_multipart_formdata_get_content_type(boundary);
14698
0
  auto content_length = detail::get_multipart_content_length(items, boundary);
14699
0
  return Put(path, headers, content_length,
14700
0
             detail::make_multipart_content_provider(items, boundary),
14701
0
             content_type, progress);
14702
0
}
14703
14704
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14705
                              const char *body, size_t content_length,
14706
                              const std::string &content_type,
14707
0
                              UploadProgress progress) {
14708
0
  return send_with_content_provider_and_receiver(
14709
0
      "PUT", path, headers, body, content_length, nullptr, nullptr,
14710
0
      content_type, nullptr, progress);
14711
0
}
14712
14713
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14714
                              const std::string &body,
14715
                              const std::string &content_type,
14716
0
                              UploadProgress progress) {
14717
0
  return send_with_content_provider_and_receiver(
14718
0
      "PUT", path, headers, body.data(), body.size(), nullptr, nullptr,
14719
0
      content_type, nullptr, progress);
14720
0
}
14721
14722
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14723
                              size_t content_length,
14724
                              ContentProvider content_provider,
14725
                              const std::string &content_type,
14726
0
                              UploadProgress progress) {
14727
0
  return send_with_content_provider_and_receiver(
14728
0
      "PUT", path, headers, nullptr, content_length,
14729
0
      std::move(content_provider), nullptr, content_type, nullptr, progress);
14730
0
}
14731
14732
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14733
                              size_t content_length,
14734
                              ContentProvider content_provider,
14735
                              const std::string &content_type,
14736
                              ContentReceiver content_receiver,
14737
0
                              UploadProgress progress) {
14738
0
  return send_with_content_provider_and_receiver(
14739
0
      "PUT", path, headers, nullptr, content_length,
14740
0
      std::move(content_provider), nullptr, content_type,
14741
0
      std::move(content_receiver), progress);
14742
0
}
14743
14744
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14745
                              ContentProviderWithoutLength content_provider,
14746
                              const std::string &content_type,
14747
0
                              UploadProgress progress) {
14748
0
  return send_with_content_provider_and_receiver(
14749
0
      "PUT", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14750
0
      content_type, nullptr, progress);
14751
0
}
14752
14753
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14754
                              ContentProviderWithoutLength content_provider,
14755
                              const std::string &content_type,
14756
                              ContentReceiver content_receiver,
14757
0
                              UploadProgress progress) {
14758
0
  return send_with_content_provider_and_receiver(
14759
0
      "PUT", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14760
0
      content_type, std::move(content_receiver), progress);
14761
0
}
14762
14763
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14764
                              const UploadFormDataItems &items,
14765
                              const FormDataProviderItems &provider_items,
14766
0
                              UploadProgress progress) {
14767
0
  const auto &boundary = detail::make_multipart_data_boundary();
14768
0
  const auto &content_type =
14769
0
      detail::serialize_multipart_formdata_get_content_type(boundary);
14770
0
  return send_with_content_provider_and_receiver(
14771
0
      "PUT", path, headers, nullptr, 0, nullptr,
14772
0
      get_multipart_content_provider(boundary, items, provider_items),
14773
0
      content_type, nullptr, progress);
14774
0
}
14775
14776
inline Result ClientImpl::Put(const std::string &path, const Headers &headers,
14777
                              const std::string &body,
14778
                              const std::string &content_type,
14779
                              ContentReceiver content_receiver,
14780
0
                              DownloadProgress progress) {
14781
0
  Request req;
14782
0
  req.method = "PUT";
14783
0
  req.path = path;
14784
0
  req.headers = headers;
14785
0
  req.body = body;
14786
0
  req.content_receiver =
14787
0
      [content_receiver](const char *data, size_t data_length,
14788
0
                         size_t /*offset*/, size_t /*total_length*/) {
14789
0
        return content_receiver(data, data_length);
14790
0
      };
14791
0
  req.download_progress = std::move(progress);
14792
0
14793
0
  if (max_timeout_msec_ > 0) {
14794
0
    req.start_time_ = std::chrono::steady_clock::now();
14795
0
  }
14796
0
14797
0
  if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
14798
0
14799
0
  return send_(std::move(req));
14800
0
}
14801
14802
0
inline Result ClientImpl::Patch(const std::string &path) {
14803
0
  return Patch(path, std::string(), std::string());
14804
0
}
14805
14806
inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14807
0
                                UploadProgress progress) {
14808
0
  return Patch(path, headers, nullptr, 0, std::string(), progress);
14809
0
}
14810
14811
inline Result ClientImpl::Patch(const std::string &path, const char *body,
14812
                                size_t content_length,
14813
                                const std::string &content_type,
14814
0
                                UploadProgress progress) {
14815
0
  return Patch(path, Headers(), body, content_length, content_type, progress);
14816
0
}
14817
14818
inline Result ClientImpl::Patch(const std::string &path,
14819
                                const std::string &body,
14820
                                const std::string &content_type,
14821
0
                                UploadProgress progress) {
14822
0
  return Patch(path, Headers(), body, content_type, progress);
14823
0
}
14824
14825
0
inline Result ClientImpl::Patch(const std::string &path, const Params &params) {
14826
0
  return Patch(path, Headers(), params);
14827
0
}
14828
14829
inline Result ClientImpl::Patch(const std::string &path, size_t content_length,
14830
                                ContentProvider content_provider,
14831
                                const std::string &content_type,
14832
0
                                UploadProgress progress) {
14833
0
  return Patch(path, Headers(), content_length, std::move(content_provider),
14834
0
               content_type, progress);
14835
0
}
14836
14837
inline Result ClientImpl::Patch(const std::string &path, size_t content_length,
14838
                                ContentProvider content_provider,
14839
                                const std::string &content_type,
14840
                                ContentReceiver content_receiver,
14841
0
                                UploadProgress progress) {
14842
0
  return Patch(path, Headers(), content_length, std::move(content_provider),
14843
0
               content_type, std::move(content_receiver), progress);
14844
0
}
14845
14846
inline Result ClientImpl::Patch(const std::string &path,
14847
                                ContentProviderWithoutLength content_provider,
14848
                                const std::string &content_type,
14849
0
                                UploadProgress progress) {
14850
0
  return Patch(path, Headers(), std::move(content_provider), content_type,
14851
0
               progress);
14852
0
}
14853
14854
inline Result ClientImpl::Patch(const std::string &path,
14855
                                ContentProviderWithoutLength content_provider,
14856
                                const std::string &content_type,
14857
                                ContentReceiver content_receiver,
14858
0
                                UploadProgress progress) {
14859
0
  return Patch(path, Headers(), std::move(content_provider), content_type,
14860
0
               std::move(content_receiver), progress);
14861
0
}
14862
14863
inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14864
0
                                const Params &params) {
14865
0
  auto query = detail::params_to_query_str(params);
14866
0
  return Patch(path, headers, query, "application/x-www-form-urlencoded");
14867
0
}
14868
14869
inline Result ClientImpl::Patch(const std::string &path,
14870
                                const UploadFormDataItems &items,
14871
0
                                UploadProgress progress) {
14872
0
  return Patch(path, Headers(), items, progress);
14873
0
}
14874
14875
inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14876
                                const UploadFormDataItems &items,
14877
0
                                UploadProgress progress) {
14878
0
  const auto &boundary = detail::make_multipart_data_boundary();
14879
0
  const auto &content_type =
14880
0
      detail::serialize_multipart_formdata_get_content_type(boundary);
14881
0
  auto content_length = detail::get_multipart_content_length(items, boundary);
14882
0
  return Patch(path, headers, content_length,
14883
0
               detail::make_multipart_content_provider(items, boundary),
14884
0
               content_type, progress);
14885
0
}
14886
14887
inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14888
                                const UploadFormDataItems &items,
14889
                                const std::string &boundary,
14890
0
                                UploadProgress progress) {
14891
0
  if (!detail::is_multipart_boundary_chars_valid(boundary)) {
14892
0
    return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
14893
0
  }
14894
0
14895
0
  const auto &content_type =
14896
0
      detail::serialize_multipart_formdata_get_content_type(boundary);
14897
0
  auto content_length = detail::get_multipart_content_length(items, boundary);
14898
0
  return Patch(path, headers, content_length,
14899
0
               detail::make_multipart_content_provider(items, boundary),
14900
0
               content_type, progress);
14901
0
}
14902
14903
inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14904
                                const char *body, size_t content_length,
14905
                                const std::string &content_type,
14906
0
                                UploadProgress progress) {
14907
0
  return send_with_content_provider_and_receiver(
14908
0
      "PATCH", path, headers, body, content_length, nullptr, nullptr,
14909
0
      content_type, nullptr, progress);
14910
0
}
14911
14912
inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14913
                                const std::string &body,
14914
                                const std::string &content_type,
14915
0
                                UploadProgress progress) {
14916
0
  return send_with_content_provider_and_receiver(
14917
0
      "PATCH", path, headers, body.data(), body.size(), nullptr, nullptr,
14918
0
      content_type, nullptr, progress);
14919
0
}
14920
14921
inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14922
                                size_t content_length,
14923
                                ContentProvider content_provider,
14924
                                const std::string &content_type,
14925
0
                                UploadProgress progress) {
14926
0
  return send_with_content_provider_and_receiver(
14927
0
      "PATCH", path, headers, nullptr, content_length,
14928
0
      std::move(content_provider), nullptr, content_type, nullptr, progress);
14929
0
}
14930
14931
inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14932
                                size_t content_length,
14933
                                ContentProvider content_provider,
14934
                                const std::string &content_type,
14935
                                ContentReceiver content_receiver,
14936
0
                                UploadProgress progress) {
14937
0
  return send_with_content_provider_and_receiver(
14938
0
      "PATCH", path, headers, nullptr, content_length,
14939
0
      std::move(content_provider), nullptr, content_type,
14940
0
      std::move(content_receiver), progress);
14941
0
}
14942
14943
inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14944
                                ContentProviderWithoutLength content_provider,
14945
                                const std::string &content_type,
14946
0
                                UploadProgress progress) {
14947
0
  return send_with_content_provider_and_receiver(
14948
0
      "PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14949
0
      content_type, nullptr, progress);
14950
0
}
14951
14952
inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14953
                                ContentProviderWithoutLength content_provider,
14954
                                const std::string &content_type,
14955
                                ContentReceiver content_receiver,
14956
0
                                UploadProgress progress) {
14957
0
  return send_with_content_provider_and_receiver(
14958
0
      "PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider),
14959
0
      content_type, std::move(content_receiver), progress);
14960
0
}
14961
14962
inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14963
                                const UploadFormDataItems &items,
14964
                                const FormDataProviderItems &provider_items,
14965
0
                                UploadProgress progress) {
14966
0
  const auto &boundary = detail::make_multipart_data_boundary();
14967
0
  const auto &content_type =
14968
0
      detail::serialize_multipart_formdata_get_content_type(boundary);
14969
0
  return send_with_content_provider_and_receiver(
14970
0
      "PATCH", path, headers, nullptr, 0, nullptr,
14971
0
      get_multipart_content_provider(boundary, items, provider_items),
14972
0
      content_type, nullptr, progress);
14973
0
}
14974
14975
inline Result ClientImpl::Patch(const std::string &path, const Headers &headers,
14976
                                const std::string &body,
14977
                                const std::string &content_type,
14978
                                ContentReceiver content_receiver,
14979
0
                                DownloadProgress progress) {
14980
0
  Request req;
14981
0
  req.method = "PATCH";
14982
0
  req.path = path;
14983
0
  req.headers = headers;
14984
0
  req.body = body;
14985
0
  req.content_receiver =
14986
0
      [content_receiver](const char *data, size_t data_length,
14987
0
                         size_t /*offset*/, size_t /*total_length*/) {
14988
0
        return content_receiver(data, data_length);
14989
0
      };
14990
0
  req.download_progress = std::move(progress);
14991
0
14992
0
  if (max_timeout_msec_ > 0) {
14993
0
    req.start_time_ = std::chrono::steady_clock::now();
14994
0
  }
14995
0
14996
0
  if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
14997
0
14998
0
  return send_(std::move(req));
14999
0
}
15000
15001
inline Result ClientImpl::Delete(const std::string &path,
15002
0
                                 DownloadProgress progress) {
15003
0
  return Delete(path, Headers(), std::string(), std::string(), progress);
15004
0
}
15005
15006
inline Result ClientImpl::Delete(const std::string &path,
15007
                                 const Headers &headers,
15008
0
                                 DownloadProgress progress) {
15009
0
  return Delete(path, headers, std::string(), std::string(), progress);
15010
0
}
15011
15012
inline Result ClientImpl::Delete(const std::string &path, const char *body,
15013
                                 size_t content_length,
15014
                                 const std::string &content_type,
15015
0
                                 DownloadProgress progress) {
15016
0
  return Delete(path, Headers(), body, content_length, content_type, progress);
15017
0
}
15018
15019
inline Result ClientImpl::Delete(const std::string &path,
15020
                                 const std::string &body,
15021
                                 const std::string &content_type,
15022
0
                                 DownloadProgress progress) {
15023
0
  return Delete(path, Headers(), body.data(), body.size(), content_type,
15024
0
                progress);
15025
0
}
15026
15027
inline Result ClientImpl::Delete(const std::string &path,
15028
                                 const Headers &headers,
15029
                                 const std::string &body,
15030
                                 const std::string &content_type,
15031
0
                                 DownloadProgress progress) {
15032
0
  return Delete(path, headers, body.data(), body.size(), content_type,
15033
0
                progress);
15034
0
}
15035
15036
inline Result ClientImpl::Delete(const std::string &path, const Params &params,
15037
0
                                 DownloadProgress progress) {
15038
0
  return Delete(path, Headers(), params, progress);
15039
0
}
15040
15041
inline Result ClientImpl::Delete(const std::string &path,
15042
                                 const Headers &headers, const Params &params,
15043
0
                                 DownloadProgress progress) {
15044
0
  auto query = detail::params_to_query_str(params);
15045
0
  return Delete(path, headers, query, "application/x-www-form-urlencoded",
15046
0
                progress);
15047
0
}
15048
15049
inline Result ClientImpl::Delete(const std::string &path,
15050
                                 const Headers &headers, const char *body,
15051
                                 size_t content_length,
15052
                                 const std::string &content_type,
15053
0
                                 DownloadProgress progress) {
15054
0
  Request req;
15055
0
  req.method = "DELETE";
15056
0
  req.headers = headers;
15057
0
  req.path = path;
15058
0
  req.download_progress = std::move(progress);
15059
0
  if (max_timeout_msec_ > 0) {
15060
0
    req.start_time_ = std::chrono::steady_clock::now();
15061
0
  }
15062
0
15063
0
  if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
15064
0
  req.body.assign(body, content_length);
15065
0
15066
0
  return send_(std::move(req));
15067
0
}
15068
15069
0
inline Result ClientImpl::Options(const std::string &path) {
15070
0
  return Options(path, Headers());
15071
0
}
15072
15073
inline Result ClientImpl::Options(const std::string &path,
15074
0
                                  const Headers &headers) {
15075
0
  Request req;
15076
0
  req.method = "OPTIONS";
15077
0
  req.headers = headers;
15078
0
  req.path = path;
15079
0
  if (max_timeout_msec_ > 0) {
15080
0
    req.start_time_ = std::chrono::steady_clock::now();
15081
0
  }
15082
0
15083
0
  return send_(std::move(req));
15084
0
}
15085
15086
0
inline void ClientImpl::stop() {
15087
0
  std::lock_guard<std::mutex> guard(socket_mutex_);
15088
0
15089
0
  // If there is anything ongoing right now, the ONLY thread-safe thing we can
15090
0
  // do is to shutdown_socket, so that threads using this socket suddenly
15091
0
  // discover they can't read/write any more and error out. Everything else
15092
0
  // (closing the socket, shutting ssl down) is unsafe because these actions
15093
0
  // are not thread-safe.
15094
0
  if (socket_requests_in_flight_ > 0) {
15095
0
    shutdown_socket(socket_);
15096
0
15097
0
    // Aside from that, we set a flag for the socket to be closed when we're
15098
0
    // done.
15099
0
    socket_should_be_closed_when_request_is_done_ = true;
15100
0
    return;
15101
0
  }
15102
0
15103
0
  disconnect(/*gracefully=*/true);
15104
0
}
15105
15106
0
inline std::string ClientImpl::host() const { return host_; }
15107
15108
0
inline int ClientImpl::port() const { return port_; }
15109
15110
0
inline size_t ClientImpl::is_socket_open() const {
15111
0
  std::lock_guard<std::mutex> guard(socket_mutex_);
15112
0
  return socket_.is_open();
15113
0
}
15114
15115
0
inline socket_t ClientImpl::socket() const { return socket_.sock; }
15116
15117
0
inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) {
15118
0
  connection_timeout_sec_ = sec;
15119
0
  connection_timeout_usec_ = usec;
15120
0
}
15121
15122
0
inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) {
15123
0
  read_timeout_sec_ = sec;
15124
0
  read_timeout_usec_ = usec;
15125
0
}
15126
15127
0
inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) {
15128
0
  write_timeout_sec_ = sec;
15129
0
  write_timeout_usec_ = usec;
15130
0
}
15131
15132
0
inline void ClientImpl::set_max_timeout(time_t msec) {
15133
0
  max_timeout_msec_ = msec;
15134
0
}
15135
15136
inline void ClientImpl::set_basic_auth(const std::string &username,
15137
0
                                       const std::string &password) {
15138
0
  basic_auth_username_ = username;
15139
0
  basic_auth_password_ = password;
15140
0
}
15141
15142
0
inline void ClientImpl::set_bearer_token_auth(const std::string &token) {
15143
0
  bearer_token_auth_token_ = token;
15144
0
}
15145
15146
0
inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; }
15147
15148
0
inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; }
15149
15150
0
inline void ClientImpl::set_path_encode(bool on) { path_encode_ = on; }
15151
15152
inline void
15153
0
ClientImpl::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
15154
0
  addr_map_ = std::move(addr_map);
15155
0
}
15156
15157
0
inline void ClientImpl::set_default_headers(Headers headers) {
15158
0
  default_headers_ = std::move(headers);
15159
0
}
15160
15161
inline void ClientImpl::set_header_writer(
15162
0
    std::function<ssize_t(Stream &, Headers &)> const &writer) {
15163
0
  header_writer_ = writer;
15164
0
}
15165
15166
0
inline void ClientImpl::set_address_family(int family) {
15167
0
  address_family_ = family;
15168
0
}
15169
15170
0
inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
15171
15172
0
inline void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
15173
15174
0
inline void ClientImpl::set_socket_options(SocketOptions socket_options) {
15175
0
  socket_options_ = std::move(socket_options);
15176
0
}
15177
15178
0
inline void ClientImpl::set_compress(bool on) { compress_ = on; }
15179
15180
0
inline void ClientImpl::set_decompress(bool on) { decompress_ = on; }
15181
15182
0
inline void ClientImpl::set_payload_max_length(size_t length) {
15183
0
  payload_max_length_ = length;
15184
0
  has_payload_max_length_ = true;
15185
0
}
15186
15187
0
inline void ClientImpl::set_interface(const std::string &intf) {
15188
0
  interface_ = intf;
15189
0
}
15190
15191
0
inline void ClientImpl::set_proxy(const std::string &host, int port) {
15192
0
  proxy_host_ = host;
15193
0
  proxy_port_ = port;
15194
0
  std::lock_guard<std::mutex> guard(socket_mutex_);
15195
0
  disconnect(/*gracefully=*/true);
15196
0
}
15197
15198
inline void ClientImpl::set_proxy_basic_auth(const std::string &username,
15199
0
                                             const std::string &password) {
15200
0
  proxy_basic_auth_username_ = username;
15201
0
  proxy_basic_auth_password_ = password;
15202
0
}
15203
15204
0
inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) {
15205
0
  proxy_bearer_token_auth_token_ = token;
15206
0
}
15207
15208
0
inline void ClientImpl::set_no_proxy(const std::vector<std::string> &patterns) {
15209
0
  std::vector<detail::NoProxyEntry> parsed;
15210
0
  parsed.reserve(patterns.size());
15211
0
  for (const auto &p : patterns) {
15212
0
    auto trimmed = detail::trim_copy(p);
15213
0
    if (trimmed.empty()) { continue; }
15214
0
    detail::NoProxyEntry entry;
15215
0
    if (detail::parse_no_proxy_entry(trimmed, entry)) {
15216
0
      parsed.push_back(std::move(entry));
15217
0
    }
15218
0
  }
15219
0
  no_proxy_entries_ = std::move(parsed);
15220
0
  std::lock_guard<std::mutex> guard(socket_mutex_);
15221
0
  disconnect(/*gracefully=*/true);
15222
0
}
15223
15224
#ifdef CPPHTTPLIB_SSL_ENABLED
15225
inline void ClientImpl::set_digest_auth(const std::string &username,
15226
                                        const std::string &password) {
15227
  digest_auth_username_ = username;
15228
  digest_auth_password_ = password;
15229
}
15230
15231
inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path,
15232
                                         const std::string &ca_cert_dir_path) {
15233
  ca_cert_file_path_ = ca_cert_file_path;
15234
  ca_cert_dir_path_ = ca_cert_dir_path;
15235
}
15236
15237
inline void ClientImpl::set_proxy_digest_auth(const std::string &username,
15238
                                              const std::string &password) {
15239
  proxy_digest_auth_username_ = username;
15240
  proxy_digest_auth_password_ = password;
15241
}
15242
15243
inline void ClientImpl::enable_server_certificate_verification(bool enabled) {
15244
  server_certificate_verification_ = enabled;
15245
}
15246
15247
inline void ClientImpl::enable_server_hostname_verification(bool enabled) {
15248
  server_hostname_verification_ = enabled;
15249
}
15250
15251
inline void ClientImpl::enable_system_ca(bool enabled) {
15252
  system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled;
15253
}
15254
#endif
15255
15256
0
inline void ClientImpl::set_logger(Logger logger) {
15257
0
  logger_ = std::move(logger);
15258
0
}
15259
15260
0
inline void ClientImpl::set_error_logger(ErrorLogger error_logger) {
15261
0
  error_logger_ = std::move(error_logger);
15262
0
}
15263
15264
/*
15265
 * SSL/TLS Common Implementation
15266
 */
15267
15268
inline ClientConnection::~ClientConnection() {
15269
#ifdef CPPHTTPLIB_SSL_ENABLED
15270
  if (session) {
15271
    tls::shutdown(session, true);
15272
    tls::free_session(session);
15273
    session = nullptr;
15274
  }
15275
#endif
15276
15277
  if (sock != INVALID_SOCKET) {
15278
    detail::close_socket(sock);
15279
    sock = INVALID_SOCKET;
15280
  }
15281
}
15282
15283
// Universal client implementation
15284
inline Client::Client(const std::string &scheme_host_port)
15285
    : Client(scheme_host_port, std::string(), std::string()) {}
15286
15287
inline Client::Client(const std::string &scheme_host_port,
15288
                      const std::string &client_cert_path,
15289
                      const std::string &client_key_path) {
15290
  detail::UrlComponents uc;
15291
  if (detail::parse_url(scheme_host_port, uc) && !uc.host.empty()) {
15292
    auto &scheme = uc.scheme;
15293
15294
#ifdef CPPHTTPLIB_SSL_ENABLED
15295
    if (!scheme.empty() && (scheme != "http" && scheme != "https")) {
15296
#else
15297
    if (!scheme.empty() && scheme != "http") {
15298
#endif
15299
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
15300
      std::string msg = "'" + scheme + "' scheme is not supported.";
15301
      throw std::invalid_argument(msg);
15302
#endif
15303
      return;
15304
    }
15305
15306
    auto is_ssl = scheme == "https";
15307
15308
    auto host = std::move(uc.host);
15309
15310
    auto port = is_ssl ? 443 : 80;
15311
    if (!uc.port.empty() && !detail::parse_port(uc.port, port)) { return; }
15312
15313
    if (is_ssl) {
15314
#ifdef CPPHTTPLIB_SSL_ENABLED
15315
      cli_ = detail::make_unique<SSLClient>(host, port, client_cert_path,
15316
                                            client_key_path);
15317
      is_ssl_ = is_ssl;
15318
#endif
15319
    } else {
15320
      cli_ = detail::make_unique<ClientImpl>(host, port, client_cert_path,
15321
                                             client_key_path);
15322
    }
15323
  } else {
15324
    // NOTE: Update TEST(UniversalClientImplTest, Ipv6LiteralAddress)
15325
    // if port param below changes.
15326
    cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80,
15327
                                           client_cert_path, client_key_path);
15328
  }
15329
}
15330
15331
inline Client::Client(const std::string &host, int port)
15332
    : Client(host, port, std::string(), std::string()) {}
15333
15334
inline Client::Client(const std::string &host, int port,
15335
                      const std::string &client_cert_path,
15336
                      const std::string &client_key_path)
15337
    : cli_(detail::make_unique<ClientImpl>(host, port, client_cert_path,
15338
                                           client_key_path)) {}
15339
15340
inline Client::~Client() = default;
15341
15342
0
inline bool Client::is_valid() const {
15343
0
  return cli_ != nullptr && cli_->is_valid();
15344
0
}
15345
15346
0
inline Result Client::Get(const std::string &path, DownloadProgress progress) {
15347
0
  return cli_->Get(path, std::move(progress));
15348
0
}
15349
inline Result Client::Get(const std::string &path, const Headers &headers,
15350
0
                          DownloadProgress progress) {
15351
0
  return cli_->Get(path, headers, std::move(progress));
15352
0
}
15353
inline Result Client::Get(const std::string &path,
15354
                          ContentReceiver content_receiver,
15355
0
                          DownloadProgress progress) {
15356
0
  return cli_->Get(path, std::move(content_receiver), std::move(progress));
15357
0
}
15358
inline Result Client::Get(const std::string &path, const Headers &headers,
15359
                          ContentReceiver content_receiver,
15360
0
                          DownloadProgress progress) {
15361
0
  return cli_->Get(path, headers, std::move(content_receiver),
15362
0
                   std::move(progress));
15363
0
}
15364
inline Result Client::Get(const std::string &path,
15365
                          ResponseHandler response_handler,
15366
                          ContentReceiver content_receiver,
15367
0
                          DownloadProgress progress) {
15368
0
  return cli_->Get(path, std::move(response_handler),
15369
0
                   std::move(content_receiver), std::move(progress));
15370
0
}
15371
inline Result Client::Get(const std::string &path, const Headers &headers,
15372
                          ResponseHandler response_handler,
15373
                          ContentReceiver content_receiver,
15374
0
                          DownloadProgress progress) {
15375
0
  return cli_->Get(path, headers, std::move(response_handler),
15376
0
                   std::move(content_receiver), std::move(progress));
15377
0
}
15378
inline Result Client::Get(const std::string &path, const Params &params,
15379
0
                          DownloadProgress progress) {
15380
0
  return cli_->Get(path, params, std::move(progress));
15381
0
}
15382
inline Result Client::Get(const std::string &path, const Params &params,
15383
0
                          const Headers &headers, DownloadProgress progress) {
15384
0
  return cli_->Get(path, params, headers, std::move(progress));
15385
0
}
15386
inline Result Client::Get(const std::string &path, const Params &params,
15387
                          const Headers &headers,
15388
                          ContentReceiver content_receiver,
15389
0
                          DownloadProgress progress) {
15390
0
  return cli_->Get(path, params, headers, std::move(content_receiver),
15391
0
                   std::move(progress));
15392
0
}
15393
inline Result Client::Get(const std::string &path, const Params &params,
15394
                          const Headers &headers,
15395
                          ResponseHandler response_handler,
15396
                          ContentReceiver content_receiver,
15397
0
                          DownloadProgress progress) {
15398
0
  return cli_->Get(path, params, headers, std::move(response_handler),
15399
0
                   std::move(content_receiver), std::move(progress));
15400
0
}
15401
15402
0
inline Result Client::Head(const std::string &path) { return cli_->Head(path); }
15403
0
inline Result Client::Head(const std::string &path, const Headers &headers) {
15404
0
  return cli_->Head(path, headers);
15405
0
}
15406
15407
0
inline Result Client::Post(const std::string &path) { return cli_->Post(path); }
15408
0
inline Result Client::Post(const std::string &path, const Headers &headers) {
15409
0
  return cli_->Post(path, headers);
15410
0
}
15411
inline Result Client::Post(const std::string &path, const char *body,
15412
                           size_t content_length,
15413
                           const std::string &content_type,
15414
0
                           UploadProgress progress) {
15415
0
  return cli_->Post(path, body, content_length, content_type, progress);
15416
0
}
15417
inline Result Client::Post(const std::string &path, const Headers &headers,
15418
                           const char *body, size_t content_length,
15419
                           const std::string &content_type,
15420
0
                           UploadProgress progress) {
15421
0
  return cli_->Post(path, headers, body, content_length, content_type,
15422
0
                    progress);
15423
0
}
15424
inline Result Client::Post(const std::string &path, const std::string &body,
15425
                           const std::string &content_type,
15426
0
                           UploadProgress progress) {
15427
0
  return cli_->Post(path, body, content_type, progress);
15428
0
}
15429
inline Result Client::Post(const std::string &path, const Headers &headers,
15430
                           const std::string &body,
15431
                           const std::string &content_type,
15432
0
                           UploadProgress progress) {
15433
0
  return cli_->Post(path, headers, body, content_type, progress);
15434
0
}
15435
inline Result Client::Post(const std::string &path, size_t content_length,
15436
                           ContentProvider content_provider,
15437
                           const std::string &content_type,
15438
0
                           UploadProgress progress) {
15439
0
  return cli_->Post(path, content_length, std::move(content_provider),
15440
0
                    content_type, progress);
15441
0
}
15442
inline Result Client::Post(const std::string &path, size_t content_length,
15443
                           ContentProvider content_provider,
15444
                           const std::string &content_type,
15445
                           ContentReceiver content_receiver,
15446
0
                           UploadProgress progress) {
15447
0
  return cli_->Post(path, content_length, std::move(content_provider),
15448
0
                    content_type, std::move(content_receiver), progress);
15449
0
}
15450
inline Result Client::Post(const std::string &path,
15451
                           ContentProviderWithoutLength content_provider,
15452
                           const std::string &content_type,
15453
0
                           UploadProgress progress) {
15454
0
  return cli_->Post(path, std::move(content_provider), content_type, progress);
15455
0
}
15456
inline Result Client::Post(const std::string &path,
15457
                           ContentProviderWithoutLength content_provider,
15458
                           const std::string &content_type,
15459
                           ContentReceiver content_receiver,
15460
0
                           UploadProgress progress) {
15461
0
  return cli_->Post(path, std::move(content_provider), content_type,
15462
0
                    std::move(content_receiver), progress);
15463
0
}
15464
inline Result Client::Post(const std::string &path, const Headers &headers,
15465
                           size_t content_length,
15466
                           ContentProvider content_provider,
15467
                           const std::string &content_type,
15468
0
                           UploadProgress progress) {
15469
0
  return cli_->Post(path, headers, content_length, std::move(content_provider),
15470
0
                    content_type, progress);
15471
0
}
15472
inline Result Client::Post(const std::string &path, const Headers &headers,
15473
                           size_t content_length,
15474
                           ContentProvider content_provider,
15475
                           const std::string &content_type,
15476
                           ContentReceiver content_receiver,
15477
0
                           DownloadProgress progress) {
15478
0
  return cli_->Post(path, headers, content_length, std::move(content_provider),
15479
0
                    content_type, std::move(content_receiver), progress);
15480
0
}
15481
inline Result Client::Post(const std::string &path, const Headers &headers,
15482
                           ContentProviderWithoutLength content_provider,
15483
                           const std::string &content_type,
15484
0
                           UploadProgress progress) {
15485
0
  return cli_->Post(path, headers, std::move(content_provider), content_type,
15486
0
                    progress);
15487
0
}
15488
inline Result Client::Post(const std::string &path, const Headers &headers,
15489
                           ContentProviderWithoutLength content_provider,
15490
                           const std::string &content_type,
15491
                           ContentReceiver content_receiver,
15492
0
                           DownloadProgress progress) {
15493
0
  return cli_->Post(path, headers, std::move(content_provider), content_type,
15494
0
                    std::move(content_receiver), progress);
15495
0
}
15496
0
inline Result Client::Post(const std::string &path, const Params &params) {
15497
0
  return cli_->Post(path, params);
15498
0
}
15499
inline Result Client::Post(const std::string &path, const Headers &headers,
15500
0
                           const Params &params) {
15501
0
  return cli_->Post(path, headers, params);
15502
0
}
15503
inline Result Client::Post(const std::string &path,
15504
                           const UploadFormDataItems &items,
15505
0
                           UploadProgress progress) {
15506
0
  return cli_->Post(path, items, progress);
15507
0
}
15508
inline Result Client::Post(const std::string &path, const Headers &headers,
15509
                           const UploadFormDataItems &items,
15510
0
                           UploadProgress progress) {
15511
0
  return cli_->Post(path, headers, items, progress);
15512
0
}
15513
inline Result Client::Post(const std::string &path, const Headers &headers,
15514
                           const UploadFormDataItems &items,
15515
                           const std::string &boundary,
15516
0
                           UploadProgress progress) {
15517
0
  return cli_->Post(path, headers, items, boundary, progress);
15518
0
}
15519
inline Result Client::Post(const std::string &path, const Headers &headers,
15520
                           const UploadFormDataItems &items,
15521
                           const FormDataProviderItems &provider_items,
15522
0
                           UploadProgress progress) {
15523
0
  return cli_->Post(path, headers, items, provider_items, progress);
15524
0
}
15525
inline Result Client::Post(const std::string &path, const Headers &headers,
15526
                           const std::string &body,
15527
                           const std::string &content_type,
15528
                           ContentReceiver content_receiver,
15529
0
                           DownloadProgress progress) {
15530
0
  return cli_->Post(path, headers, body, content_type,
15531
0
                    std::move(content_receiver), progress);
15532
0
}
15533
15534
0
inline Result Client::Put(const std::string &path) { return cli_->Put(path); }
15535
0
inline Result Client::Put(const std::string &path, const Headers &headers) {
15536
0
  return cli_->Put(path, headers);
15537
0
}
15538
inline Result Client::Put(const std::string &path, const char *body,
15539
                          size_t content_length,
15540
                          const std::string &content_type,
15541
0
                          UploadProgress progress) {
15542
0
  return cli_->Put(path, body, content_length, content_type, progress);
15543
0
}
15544
inline Result Client::Put(const std::string &path, const Headers &headers,
15545
                          const char *body, size_t content_length,
15546
                          const std::string &content_type,
15547
0
                          UploadProgress progress) {
15548
0
  return cli_->Put(path, headers, body, content_length, content_type, progress);
15549
0
}
15550
inline Result Client::Put(const std::string &path, const std::string &body,
15551
                          const std::string &content_type,
15552
0
                          UploadProgress progress) {
15553
0
  return cli_->Put(path, body, content_type, progress);
15554
0
}
15555
inline Result Client::Put(const std::string &path, const Headers &headers,
15556
                          const std::string &body,
15557
                          const std::string &content_type,
15558
0
                          UploadProgress progress) {
15559
0
  return cli_->Put(path, headers, body, content_type, progress);
15560
0
}
15561
inline Result Client::Put(const std::string &path, size_t content_length,
15562
                          ContentProvider content_provider,
15563
                          const std::string &content_type,
15564
0
                          UploadProgress progress) {
15565
0
  return cli_->Put(path, content_length, std::move(content_provider),
15566
0
                   content_type, progress);
15567
0
}
15568
inline Result Client::Put(const std::string &path, size_t content_length,
15569
                          ContentProvider content_provider,
15570
                          const std::string &content_type,
15571
                          ContentReceiver content_receiver,
15572
0
                          UploadProgress progress) {
15573
0
  return cli_->Put(path, content_length, std::move(content_provider),
15574
0
                   content_type, std::move(content_receiver), progress);
15575
0
}
15576
inline Result Client::Put(const std::string &path,
15577
                          ContentProviderWithoutLength content_provider,
15578
                          const std::string &content_type,
15579
0
                          UploadProgress progress) {
15580
0
  return cli_->Put(path, std::move(content_provider), content_type, progress);
15581
0
}
15582
inline Result Client::Put(const std::string &path,
15583
                          ContentProviderWithoutLength content_provider,
15584
                          const std::string &content_type,
15585
                          ContentReceiver content_receiver,
15586
0
                          UploadProgress progress) {
15587
0
  return cli_->Put(path, std::move(content_provider), content_type,
15588
0
                   std::move(content_receiver), progress);
15589
0
}
15590
inline Result Client::Put(const std::string &path, const Headers &headers,
15591
                          size_t content_length,
15592
                          ContentProvider content_provider,
15593
                          const std::string &content_type,
15594
0
                          UploadProgress progress) {
15595
0
  return cli_->Put(path, headers, content_length, std::move(content_provider),
15596
0
                   content_type, progress);
15597
0
}
15598
inline Result Client::Put(const std::string &path, const Headers &headers,
15599
                          size_t content_length,
15600
                          ContentProvider content_provider,
15601
                          const std::string &content_type,
15602
                          ContentReceiver content_receiver,
15603
0
                          UploadProgress progress) {
15604
0
  return cli_->Put(path, headers, content_length, std::move(content_provider),
15605
0
                   content_type, std::move(content_receiver), progress);
15606
0
}
15607
inline Result Client::Put(const std::string &path, const Headers &headers,
15608
                          ContentProviderWithoutLength content_provider,
15609
                          const std::string &content_type,
15610
0
                          UploadProgress progress) {
15611
0
  return cli_->Put(path, headers, std::move(content_provider), content_type,
15612
0
                   progress);
15613
0
}
15614
inline Result Client::Put(const std::string &path, const Headers &headers,
15615
                          ContentProviderWithoutLength content_provider,
15616
                          const std::string &content_type,
15617
                          ContentReceiver content_receiver,
15618
0
                          UploadProgress progress) {
15619
0
  return cli_->Put(path, headers, std::move(content_provider), content_type,
15620
0
                   std::move(content_receiver), progress);
15621
0
}
15622
0
inline Result Client::Put(const std::string &path, const Params &params) {
15623
0
  return cli_->Put(path, params);
15624
0
}
15625
inline Result Client::Put(const std::string &path, const Headers &headers,
15626
0
                          const Params &params) {
15627
0
  return cli_->Put(path, headers, params);
15628
0
}
15629
inline Result Client::Put(const std::string &path,
15630
                          const UploadFormDataItems &items,
15631
0
                          UploadProgress progress) {
15632
0
  return cli_->Put(path, items, progress);
15633
0
}
15634
inline Result Client::Put(const std::string &path, const Headers &headers,
15635
                          const UploadFormDataItems &items,
15636
0
                          UploadProgress progress) {
15637
0
  return cli_->Put(path, headers, items, progress);
15638
0
}
15639
inline Result Client::Put(const std::string &path, const Headers &headers,
15640
                          const UploadFormDataItems &items,
15641
                          const std::string &boundary,
15642
0
                          UploadProgress progress) {
15643
0
  return cli_->Put(path, headers, items, boundary, progress);
15644
0
}
15645
inline Result Client::Put(const std::string &path, const Headers &headers,
15646
                          const UploadFormDataItems &items,
15647
                          const FormDataProviderItems &provider_items,
15648
0
                          UploadProgress progress) {
15649
0
  return cli_->Put(path, headers, items, provider_items, progress);
15650
0
}
15651
inline Result Client::Put(const std::string &path, const Headers &headers,
15652
                          const std::string &body,
15653
                          const std::string &content_type,
15654
                          ContentReceiver content_receiver,
15655
0
                          DownloadProgress progress) {
15656
0
  return cli_->Put(path, headers, body, content_type, content_receiver,
15657
0
                   progress);
15658
0
}
15659
15660
0
inline Result Client::Patch(const std::string &path) {
15661
0
  return cli_->Patch(path);
15662
0
}
15663
0
inline Result Client::Patch(const std::string &path, const Headers &headers) {
15664
0
  return cli_->Patch(path, headers);
15665
0
}
15666
inline Result Client::Patch(const std::string &path, const char *body,
15667
                            size_t content_length,
15668
                            const std::string &content_type,
15669
0
                            UploadProgress progress) {
15670
0
  return cli_->Patch(path, body, content_length, content_type, progress);
15671
0
}
15672
inline Result Client::Patch(const std::string &path, const Headers &headers,
15673
                            const char *body, size_t content_length,
15674
                            const std::string &content_type,
15675
0
                            UploadProgress progress) {
15676
0
  return cli_->Patch(path, headers, body, content_length, content_type,
15677
0
                     progress);
15678
0
}
15679
inline Result Client::Patch(const std::string &path, const std::string &body,
15680
                            const std::string &content_type,
15681
0
                            UploadProgress progress) {
15682
0
  return cli_->Patch(path, body, content_type, progress);
15683
0
}
15684
inline Result Client::Patch(const std::string &path, const Headers &headers,
15685
                            const std::string &body,
15686
                            const std::string &content_type,
15687
0
                            UploadProgress progress) {
15688
0
  return cli_->Patch(path, headers, body, content_type, progress);
15689
0
}
15690
inline Result Client::Patch(const std::string &path, size_t content_length,
15691
                            ContentProvider content_provider,
15692
                            const std::string &content_type,
15693
0
                            UploadProgress progress) {
15694
0
  return cli_->Patch(path, content_length, std::move(content_provider),
15695
0
                     content_type, progress);
15696
0
}
15697
inline Result Client::Patch(const std::string &path, size_t content_length,
15698
                            ContentProvider content_provider,
15699
                            const std::string &content_type,
15700
                            ContentReceiver content_receiver,
15701
0
                            UploadProgress progress) {
15702
0
  return cli_->Patch(path, content_length, std::move(content_provider),
15703
0
                     content_type, std::move(content_receiver), progress);
15704
0
}
15705
inline Result Client::Patch(const std::string &path,
15706
                            ContentProviderWithoutLength content_provider,
15707
                            const std::string &content_type,
15708
0
                            UploadProgress progress) {
15709
0
  return cli_->Patch(path, std::move(content_provider), content_type, progress);
15710
0
}
15711
inline Result Client::Patch(const std::string &path,
15712
                            ContentProviderWithoutLength content_provider,
15713
                            const std::string &content_type,
15714
                            ContentReceiver content_receiver,
15715
0
                            UploadProgress progress) {
15716
0
  return cli_->Patch(path, std::move(content_provider), content_type,
15717
0
                     std::move(content_receiver), progress);
15718
0
}
15719
inline Result Client::Patch(const std::string &path, const Headers &headers,
15720
                            size_t content_length,
15721
                            ContentProvider content_provider,
15722
                            const std::string &content_type,
15723
0
                            UploadProgress progress) {
15724
0
  return cli_->Patch(path, headers, content_length, std::move(content_provider),
15725
0
                     content_type, progress);
15726
0
}
15727
inline Result Client::Patch(const std::string &path, const Headers &headers,
15728
                            size_t content_length,
15729
                            ContentProvider content_provider,
15730
                            const std::string &content_type,
15731
                            ContentReceiver content_receiver,
15732
0
                            UploadProgress progress) {
15733
0
  return cli_->Patch(path, headers, content_length, std::move(content_provider),
15734
0
                     content_type, std::move(content_receiver), progress);
15735
0
}
15736
inline Result Client::Patch(const std::string &path, const Headers &headers,
15737
                            ContentProviderWithoutLength content_provider,
15738
                            const std::string &content_type,
15739
0
                            UploadProgress progress) {
15740
0
  return cli_->Patch(path, headers, std::move(content_provider), content_type,
15741
0
                     progress);
15742
0
}
15743
inline Result Client::Patch(const std::string &path, const Headers &headers,
15744
                            ContentProviderWithoutLength content_provider,
15745
                            const std::string &content_type,
15746
                            ContentReceiver content_receiver,
15747
0
                            UploadProgress progress) {
15748
0
  return cli_->Patch(path, headers, std::move(content_provider), content_type,
15749
0
                     std::move(content_receiver), progress);
15750
0
}
15751
0
inline Result Client::Patch(const std::string &path, const Params &params) {
15752
0
  return cli_->Patch(path, params);
15753
0
}
15754
inline Result Client::Patch(const std::string &path, const Headers &headers,
15755
0
                            const Params &params) {
15756
0
  return cli_->Patch(path, headers, params);
15757
0
}
15758
inline Result Client::Patch(const std::string &path,
15759
                            const UploadFormDataItems &items,
15760
0
                            UploadProgress progress) {
15761
0
  return cli_->Patch(path, items, progress);
15762
0
}
15763
inline Result Client::Patch(const std::string &path, const Headers &headers,
15764
                            const UploadFormDataItems &items,
15765
0
                            UploadProgress progress) {
15766
0
  return cli_->Patch(path, headers, items, progress);
15767
0
}
15768
inline Result Client::Patch(const std::string &path, const Headers &headers,
15769
                            const UploadFormDataItems &items,
15770
                            const std::string &boundary,
15771
0
                            UploadProgress progress) {
15772
0
  return cli_->Patch(path, headers, items, boundary, progress);
15773
0
}
15774
inline Result Client::Patch(const std::string &path, const Headers &headers,
15775
                            const UploadFormDataItems &items,
15776
                            const FormDataProviderItems &provider_items,
15777
0
                            UploadProgress progress) {
15778
0
  return cli_->Patch(path, headers, items, provider_items, progress);
15779
0
}
15780
inline Result Client::Patch(const std::string &path, const Headers &headers,
15781
                            const std::string &body,
15782
                            const std::string &content_type,
15783
                            ContentReceiver content_receiver,
15784
0
                            DownloadProgress progress) {
15785
0
  return cli_->Patch(path, headers, body, content_type, content_receiver,
15786
0
                     progress);
15787
0
}
15788
15789
inline Result Client::Delete(const std::string &path,
15790
0
                             DownloadProgress progress) {
15791
0
  return cli_->Delete(path, progress);
15792
0
}
15793
inline Result Client::Delete(const std::string &path, const Headers &headers,
15794
0
                             DownloadProgress progress) {
15795
0
  return cli_->Delete(path, headers, progress);
15796
0
}
15797
inline Result Client::Delete(const std::string &path, const char *body,
15798
                             size_t content_length,
15799
                             const std::string &content_type,
15800
0
                             DownloadProgress progress) {
15801
0
  return cli_->Delete(path, body, content_length, content_type, progress);
15802
0
}
15803
inline Result Client::Delete(const std::string &path, const Headers &headers,
15804
                             const char *body, size_t content_length,
15805
                             const std::string &content_type,
15806
0
                             DownloadProgress progress) {
15807
0
  return cli_->Delete(path, headers, body, content_length, content_type,
15808
0
                      progress);
15809
0
}
15810
inline Result Client::Delete(const std::string &path, const std::string &body,
15811
                             const std::string &content_type,
15812
0
                             DownloadProgress progress) {
15813
0
  return cli_->Delete(path, body, content_type, progress);
15814
0
}
15815
inline Result Client::Delete(const std::string &path, const Headers &headers,
15816
                             const std::string &body,
15817
                             const std::string &content_type,
15818
0
                             DownloadProgress progress) {
15819
0
  return cli_->Delete(path, headers, body, content_type, progress);
15820
0
}
15821
inline Result Client::Delete(const std::string &path, const Params &params,
15822
0
                             DownloadProgress progress) {
15823
0
  return cli_->Delete(path, params, progress);
15824
0
}
15825
inline Result Client::Delete(const std::string &path, const Headers &headers,
15826
0
                             const Params &params, DownloadProgress progress) {
15827
0
  return cli_->Delete(path, headers, params, progress);
15828
0
}
15829
15830
0
inline Result Client::Options(const std::string &path) {
15831
0
  return cli_->Options(path);
15832
0
}
15833
0
inline Result Client::Options(const std::string &path, const Headers &headers) {
15834
0
  return cli_->Options(path, headers);
15835
0
}
15836
15837
inline ClientImpl::StreamHandle
15838
Client::open_stream(const std::string &method, const std::string &path,
15839
                    const Params &params, const Headers &headers,
15840
0
                    const std::string &body, const std::string &content_type) {
15841
0
  return cli_->open_stream(method, path, params, headers, body, content_type);
15842
0
}
15843
15844
0
inline bool Client::send(Request &req, Response &res, Error &error) {
15845
0
  return cli_->send(req, res, error);
15846
0
}
15847
15848
0
inline Result Client::send(const Request &req) { return cli_->send(req); }
15849
15850
0
inline void Client::stop() { cli_->stop(); }
15851
15852
0
inline std::string Client::host() const { return cli_->host(); }
15853
15854
0
inline int Client::port() const { return cli_->port(); }
15855
15856
0
inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
15857
15858
0
inline socket_t Client::socket() const { return cli_->socket(); }
15859
15860
inline void
15861
0
Client::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
15862
0
  cli_->set_hostname_addr_map(std::move(addr_map));
15863
0
}
15864
15865
0
inline void Client::set_default_headers(Headers headers) {
15866
0
  cli_->set_default_headers(std::move(headers));
15867
0
}
15868
15869
inline void Client::set_header_writer(
15870
0
    std::function<ssize_t(Stream &, Headers &)> const &writer) {
15871
0
  cli_->set_header_writer(writer);
15872
0
}
15873
15874
0
inline void Client::set_address_family(int family) {
15875
0
  cli_->set_address_family(family);
15876
0
}
15877
15878
0
inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); }
15879
15880
0
inline void Client::set_socket_options(SocketOptions socket_options) {
15881
0
  cli_->set_socket_options(std::move(socket_options));
15882
0
}
15883
15884
0
inline void Client::set_connection_timeout(time_t sec, time_t usec) {
15885
0
  cli_->set_connection_timeout(sec, usec);
15886
0
}
15887
15888
0
inline void Client::set_read_timeout(time_t sec, time_t usec) {
15889
0
  cli_->set_read_timeout(sec, usec);
15890
0
}
15891
15892
0
inline void Client::set_write_timeout(time_t sec, time_t usec) {
15893
0
  cli_->set_write_timeout(sec, usec);
15894
0
}
15895
15896
inline void Client::set_basic_auth(const std::string &username,
15897
0
                                   const std::string &password) {
15898
0
  cli_->set_basic_auth(username, password);
15899
0
}
15900
0
inline void Client::set_bearer_token_auth(const std::string &token) {
15901
0
  cli_->set_bearer_token_auth(token);
15902
0
}
15903
15904
0
inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); }
15905
0
inline void Client::set_follow_location(bool on) {
15906
0
  cli_->set_follow_location(on);
15907
0
}
15908
15909
0
inline void Client::set_path_encode(bool on) { cli_->set_path_encode(on); }
15910
15911
0
inline void Client::set_compress(bool on) { cli_->set_compress(on); }
15912
15913
0
inline void Client::set_decompress(bool on) { cli_->set_decompress(on); }
15914
15915
0
inline void Client::set_payload_max_length(size_t length) {
15916
0
  cli_->set_payload_max_length(length);
15917
0
}
15918
15919
0
inline void Client::set_interface(const std::string &intf) {
15920
0
  cli_->set_interface(intf);
15921
0
}
15922
15923
0
inline void Client::set_proxy(const std::string &host, int port) {
15924
0
  cli_->set_proxy(host, port);
15925
0
}
15926
inline void Client::set_proxy_basic_auth(const std::string &username,
15927
0
                                         const std::string &password) {
15928
0
  cli_->set_proxy_basic_auth(username, password);
15929
0
}
15930
0
inline void Client::set_proxy_bearer_token_auth(const std::string &token) {
15931
0
  cli_->set_proxy_bearer_token_auth(token);
15932
0
}
15933
0
inline void Client::set_no_proxy(const std::vector<std::string> &patterns) {
15934
0
  cli_->set_no_proxy(patterns);
15935
0
}
15936
15937
0
inline void Client::set_logger(Logger logger) {
15938
0
  cli_->set_logger(std::move(logger));
15939
0
}
15940
15941
0
inline void Client::set_error_logger(ErrorLogger error_logger) {
15942
0
  cli_->set_error_logger(std::move(error_logger));
15943
0
}
15944
15945
/*
15946
 * Group 6: SSL Server and Client implementation
15947
 */
15948
15949
#ifdef CPPHTTPLIB_SSL_ENABLED
15950
15951
// SSL HTTP server implementation
15952
inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
15953
                            const char *client_ca_cert_file_path,
15954
                            const char *client_ca_cert_dir_path,
15955
                            const char *private_key_password) {
15956
  using namespace tls;
15957
15958
  ctx_ = create_server_context();
15959
  if (!ctx_) { return; }
15960
15961
  // Load server certificate and private key
15962
  if (!set_server_cert_file(ctx_, cert_path, private_key_path,
15963
                            private_key_password)) {
15964
    last_ssl_error_ = static_cast<int>(get_error());
15965
    free_context(ctx_);
15966
    ctx_ = nullptr;
15967
    return;
15968
  }
15969
15970
  // Load client CA certificates for client authentication
15971
  if (client_ca_cert_file_path || client_ca_cert_dir_path) {
15972
    if (!set_client_ca_file(ctx_, client_ca_cert_file_path,
15973
                            client_ca_cert_dir_path)) {
15974
      last_ssl_error_ = static_cast<int>(get_error());
15975
      free_context(ctx_);
15976
      ctx_ = nullptr;
15977
      return;
15978
    }
15979
    // Enable client certificate verification
15980
    set_verify_client(ctx_, true);
15981
  }
15982
}
15983
15984
inline SSLServer::SSLServer(const PemMemory &pem) {
15985
  using namespace tls;
15986
  ctx_ = create_server_context();
15987
  if (ctx_) {
15988
    if (!set_server_cert_pem(ctx_, pem.cert_pem, pem.key_pem,
15989
                             pem.private_key_password)) {
15990
      last_ssl_error_ = static_cast<int>(get_error());
15991
      free_context(ctx_);
15992
      ctx_ = nullptr;
15993
    } else if (pem.client_ca_pem && pem.client_ca_pem_len > 0) {
15994
      if (!load_ca_pem(ctx_, pem.client_ca_pem, pem.client_ca_pem_len)) {
15995
        last_ssl_error_ = static_cast<int>(get_error());
15996
        free_context(ctx_);
15997
        ctx_ = nullptr;
15998
      } else {
15999
        set_verify_client(ctx_, true);
16000
      }
16001
    }
16002
  }
16003
}
16004
16005
inline SSLServer::SSLServer(const tls::ContextSetupCallback &setup_callback) {
16006
  using namespace tls;
16007
  ctx_ = create_server_context();
16008
  if (ctx_) {
16009
    if (!setup_callback(ctx_)) {
16010
      free_context(ctx_);
16011
      ctx_ = nullptr;
16012
    }
16013
  }
16014
}
16015
16016
inline SSLServer::~SSLServer() {
16017
  if (ctx_) { tls::free_context(ctx_); }
16018
}
16019
16020
inline bool SSLServer::is_valid() const { return ctx_ != nullptr; }
16021
16022
inline bool SSLServer::process_and_close_socket(socket_t sock) {
16023
  using namespace tls;
16024
16025
  // Create TLS session with mutex protection
16026
  session_t session = nullptr;
16027
  {
16028
    std::lock_guard<std::mutex> guard(ctx_mutex_);
16029
    session = create_session(static_cast<ctx_t>(ctx_), sock);
16030
  }
16031
16032
  if (!session) {
16033
    last_ssl_error_ = static_cast<int>(get_error());
16034
    detail::shutdown_socket(sock);
16035
    detail::close_socket(sock);
16036
    return false;
16037
  }
16038
16039
  // Use scope_exit to ensure cleanup on all paths (including exceptions)
16040
  bool handshake_done = false;
16041
  bool ret = false;
16042
  bool websocket_upgraded = false;
16043
  auto cleanup = detail::scope_exit([&] {
16044
    if (handshake_done) { shutdown(session, !websocket_upgraded && ret); }
16045
    free_session(session);
16046
    detail::shutdown_socket(sock);
16047
    detail::close_socket(sock);
16048
  });
16049
16050
  // Perform TLS accept handshake with timeout
16051
  TlsError tls_err;
16052
  if (!accept_nonblocking(session, sock, read_timeout_sec_, read_timeout_usec_,
16053
                          &tls_err)) {
16054
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
16055
    // Map TlsError to legacy ssl_error for backward compatibility
16056
    if (tls_err.code == ErrorCode::WantRead) {
16057
      last_ssl_error_ = SSL_ERROR_WANT_READ;
16058
    } else if (tls_err.code == ErrorCode::WantWrite) {
16059
      last_ssl_error_ = SSL_ERROR_WANT_WRITE;
16060
    } else {
16061
      last_ssl_error_ = SSL_ERROR_SSL;
16062
    }
16063
#else
16064
    last_ssl_error_ = static_cast<int>(get_error());
16065
#endif
16066
    return false;
16067
  }
16068
16069
  handshake_done = true;
16070
16071
  std::string remote_addr;
16072
  int remote_port = 0;
16073
  detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
16074
16075
  std::string local_addr;
16076
  int local_port = 0;
16077
  detail::get_local_ip_and_port(sock, local_addr, local_port);
16078
16079
  ret = detail::process_server_socket_ssl(
16080
      svr_sock_, session, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
16081
      read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
16082
      write_timeout_usec_,
16083
      [&](Stream &strm, bool close_connection, bool &connection_closed) {
16084
        return process_request(
16085
            strm, remote_addr, remote_port, local_addr, local_port,
16086
            close_connection, connection_closed,
16087
            [&](Request &req) { req.ssl = session; }, &websocket_upgraded);
16088
      });
16089
16090
  return ret;
16091
}
16092
16093
inline bool SSLServer::update_certs_pem(const char *cert_pem,
16094
                                        const char *key_pem,
16095
                                        const char *client_ca_pem,
16096
                                        const char *password) {
16097
  if (!ctx_) { return false; }
16098
  std::lock_guard<std::mutex> guard(ctx_mutex_);
16099
  if (!tls::update_server_cert(ctx_, cert_pem, key_pem, password)) {
16100
    return false;
16101
  }
16102
  if (client_ca_pem) {
16103
    return tls::update_server_client_ca(ctx_, client_ca_pem);
16104
  }
16105
  return true;
16106
}
16107
16108
// SSL HTTP client implementation
16109
inline SSLClient::~SSLClient() {
16110
  // Make sure to shut down SSL since shutdown_ssl will resolve to the
16111
  // base function rather than the derived function once we get to the
16112
  // base class destructor, and won't free the SSL (causing a leak).
16113
  // This must happen before the context is freed below: some backends
16114
  // (e.g. mbedTLS) have the SSL session borrow a raw pointer into the
16115
  // context, so freeing the context first leaves close_notify reading
16116
  // freed memory.
16117
  shutdown_ssl_impl(socket_, true);
16118
  if (ctx_) {
16119
    tls::free_context(ctx_);
16120
    ctx_ = nullptr;
16121
  }
16122
}
16123
16124
inline bool SSLClient::is_valid() const { return ctx_ != nullptr; }
16125
16126
inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
16127
  shutdown_ssl_impl(socket, shutdown_gracefully);
16128
}
16129
16130
inline void SSLClient::shutdown_ssl_impl(Socket &socket,
16131
                                         bool shutdown_gracefully) {
16132
  if (socket.sock == INVALID_SOCKET) {
16133
    assert(socket.ssl == nullptr);
16134
    return;
16135
  }
16136
  if (socket.ssl) {
16137
    tls::shutdown(socket.ssl, shutdown_gracefully);
16138
    {
16139
      std::lock_guard<std::mutex> guard(ctx_mutex_);
16140
      tls::free_session(socket.ssl);
16141
    }
16142
    socket.ssl = nullptr;
16143
  }
16144
  assert(socket.ssl == nullptr);
16145
}
16146
16147
inline bool SSLClient::process_socket(
16148
    const Socket &socket,
16149
    std::chrono::time_point<std::chrono::steady_clock> start_time,
16150
    std::function<bool(Stream &strm)> callback) {
16151
  assert(socket.ssl);
16152
  return detail::process_client_socket_ssl(
16153
      socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
16154
      write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, start_time,
16155
      std::move(callback));
16156
}
16157
16158
inline bool SSLClient::is_ssl() const { return true; }
16159
16160
inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) {
16161
  if (!is_valid()) {
16162
    error = Error::SSLConnection;
16163
    return false;
16164
  }
16165
  return ClientImpl::create_and_connect_socket(socket, error);
16166
}
16167
16168
inline bool SSLClient::setup_proxy_connection(
16169
    Socket &socket,
16170
    std::chrono::time_point<std::chrono::steady_clock> start_time,
16171
    Response &res, bool &success, Error &error) {
16172
  if (!is_proxy_enabled_for_host(host_)) { return true; }
16173
16174
  if (!connect_with_proxy(socket, start_time, res, success, error)) {
16175
    return false;
16176
  }
16177
16178
  if (!initialize_ssl(socket, error)) {
16179
    success = false;
16180
    return false;
16181
  }
16182
16183
  return true;
16184
}
16185
16186
// Assumes that socket_mutex_ is locked and that there are no requests in
16187
// flight
16188
inline bool SSLClient::connect_with_proxy(
16189
    Socket &socket,
16190
    std::chrono::time_point<std::chrono::steady_clock> start_time,
16191
    Response &res, bool &success, Error &error) {
16192
  success = true;
16193
  Response proxy_res;
16194
  if (!detail::process_client_socket(
16195
          socket.sock, read_timeout_sec_, read_timeout_usec_,
16196
          write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
16197
          start_time, [&](Stream &strm) {
16198
            Request req2;
16199
            req2.method = "CONNECT";
16200
            req2.path =
16201
                detail::make_host_and_port_string_always_port(host_, port_);
16202
            if (max_timeout_msec_ > 0) {
16203
              req2.start_time_ = std::chrono::steady_clock::now();
16204
            }
16205
            return process_request(strm, req2, proxy_res, false, error);
16206
          })) {
16207
    // Thread-safe to close everything because we are assuming there are no
16208
    // requests in flight
16209
    shutdown_ssl(socket, true);
16210
    shutdown_socket(socket);
16211
    close_socket(socket);
16212
    success = false;
16213
    return false;
16214
  }
16215
16216
  if (proxy_res.status == StatusCode::ProxyAuthenticationRequired_407) {
16217
    if (!proxy_digest_auth_username_.empty() &&
16218
        !proxy_digest_auth_password_.empty()) {
16219
      std::map<std::string, std::string> auth;
16220
      if (detail::parse_www_authenticate(proxy_res, auth, true)) {
16221
        // Close the current socket and create a new one for the authenticated
16222
        // request
16223
        shutdown_ssl(socket, true);
16224
        shutdown_socket(socket);
16225
        close_socket(socket);
16226
16227
        // Create a new socket for the authenticated CONNECT request
16228
        if (!ensure_socket_connection(socket, error)) {
16229
          success = false;
16230
          output_error_log(error, nullptr);
16231
          return false;
16232
        }
16233
16234
        proxy_res = Response();
16235
        if (!detail::process_client_socket(
16236
                socket.sock, read_timeout_sec_, read_timeout_usec_,
16237
                write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
16238
                start_time, [&](Stream &strm) {
16239
                  Request req3;
16240
                  req3.method = "CONNECT";
16241
                  req3.path = detail::make_host_and_port_string_always_port(
16242
                      host_, port_);
16243
                  req3.headers.insert(detail::make_digest_authentication_header(
16244
                      req3, auth, 1, detail::random_string(10),
16245
                      proxy_digest_auth_username_, proxy_digest_auth_password_,
16246
                      true));
16247
                  if (max_timeout_msec_ > 0) {
16248
                    req3.start_time_ = std::chrono::steady_clock::now();
16249
                  }
16250
                  return process_request(strm, req3, proxy_res, false, error);
16251
                })) {
16252
          // Thread-safe to close everything because we are assuming there are
16253
          // no requests in flight
16254
          shutdown_ssl(socket, true);
16255
          shutdown_socket(socket);
16256
          close_socket(socket);
16257
          success = false;
16258
          return false;
16259
        }
16260
      }
16261
    }
16262
  }
16263
16264
  // If status code is not 200, proxy request is failed.
16265
  // Set error to ProxyConnection and return proxy response
16266
  // as the response of the request
16267
  if (proxy_res.status != StatusCode::OK_200) {
16268
    error = Error::ProxyConnection;
16269
    output_error_log(error, nullptr);
16270
    res = std::move(proxy_res);
16271
    // Thread-safe to close everything because we are assuming there are
16272
    // no requests in flight
16273
    shutdown_ssl(socket, true);
16274
    shutdown_socket(socket);
16275
    close_socket(socket);
16276
    return false;
16277
  }
16278
16279
  return true;
16280
}
16281
16282
inline bool SSLClient::ensure_socket_connection(Socket &socket, Error &error) {
16283
  if (!ClientImpl::ensure_socket_connection(socket, error)) { return false; }
16284
16285
  if (is_proxy_enabled_for_host(host_)) { return true; }
16286
16287
  if (!initialize_ssl(socket, error)) {
16288
    shutdown_socket(socket);
16289
    close_socket(socket);
16290
    return false;
16291
  }
16292
16293
  return true;
16294
}
16295
16296
// SSL HTTP client implementation
16297
inline SSLClient::SSLClient(const std::string &host)
16298
    : SSLClient(host, 443, std::string(), std::string()) {}
16299
16300
inline SSLClient::SSLClient(const std::string &host, int port)
16301
    : SSLClient(host, port, std::string(), std::string()) {}
16302
16303
inline void SSLClient::init_ctx() {
16304
  ctx_ = tls::create_client_context();
16305
  if (ctx_) { tls::set_min_version(ctx_, tls::Version::TLS1_2); }
16306
}
16307
16308
inline void SSLClient::reset_ctx_on_error() {
16309
  last_backend_error_ = tls::get_error();
16310
  tls::free_context(ctx_);
16311
  ctx_ = nullptr;
16312
}
16313
16314
inline SSLClient::SSLClient(const std::string &host, int port,
16315
                            const std::string &client_cert_path,
16316
                            const std::string &client_key_path,
16317
                            const std::string &private_key_password)
16318
    : ClientImpl(host, port, client_cert_path, client_key_path) {
16319
  init_ctx();
16320
  if (!ctx_) { return; }
16321
16322
  if (!client_cert_path.empty() && !client_key_path.empty()) {
16323
    const char *password =
16324
        private_key_password.empty() ? nullptr : private_key_password.c_str();
16325
    if (!tls::set_client_cert_file(ctx_, client_cert_path.c_str(),
16326
                                   client_key_path.c_str(), password)) {
16327
      reset_ctx_on_error();
16328
    }
16329
  }
16330
}
16331
16332
inline SSLClient::SSLClient(const std::string &host, int port,
16333
                            const PemMemory &pem)
16334
    : ClientImpl(host, port) {
16335
  init_ctx();
16336
  if (!ctx_) { return; }
16337
16338
  if (pem.cert_pem && pem.key_pem) {
16339
    if (!tls::set_client_cert_pem(ctx_, pem.cert_pem, pem.key_pem,
16340
                                  pem.private_key_password)) {
16341
      reset_ctx_on_error();
16342
    }
16343
  }
16344
}
16345
16346
inline void SSLClient::set_ca_cert_store(tls::ca_store_t ca_cert_store) {
16347
  if (ca_cert_store && ctx_) {
16348
    // set_ca_store takes ownership of ca_cert_store
16349
    tls::set_ca_store(ctx_, ca_cert_store);
16350
    ca_cert_store_set_ = true;
16351
  } else if (ca_cert_store) {
16352
    tls::free_ca_store(ca_cert_store);
16353
  }
16354
}
16355
16356
inline void
16357
SSLClient::set_server_certificate_verifier(tls::VerifyCallback verifier) {
16358
  if (!ctx_) { return; }
16359
  tls::set_verify_callback(ctx_, verifier);
16360
}
16361
16362
inline void SSLClient::set_session_verifier(
16363
    std::function<SSLVerifierResponse(tls::session_t)> verifier) {
16364
  session_verifier_ = std::move(verifier);
16365
}
16366
16367
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
16368
inline void SSLClient::enable_windows_certificate_verification(bool enabled) {
16369
  enable_windows_cert_verification_ = enabled;
16370
}
16371
#endif
16372
16373
inline void SSLClient::load_ca_cert_store(const char *ca_cert,
16374
                                          std::size_t size) {
16375
  if (ctx_ && ca_cert && size > 0) {
16376
    ca_cert_pem_.assign(ca_cert, size); // Store for redirect transfer
16377
    tls::load_ca_pem(ctx_, ca_cert, size);
16378
  }
16379
}
16380
16381
inline bool SSLClient::load_certs() {
16382
  auto ret = true;
16383
16384
  std::call_once(initialize_cert_, [&]() {
16385
    std::lock_guard<std::mutex> guard(ctx_mutex_);
16386
16387
    ret = detail::load_client_ca_config(
16388
        ctx_, ca_cert_file_path_, ca_cert_dir_path_,
16389
        !ca_cert_pem_.empty() || ca_cert_store_set_, system_ca_mode_,
16390
        last_backend_error_);
16391
  });
16392
16393
  return ret;
16394
}
16395
16396
inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
16397
  using namespace tls;
16398
16399
  // Load CA certificates if server verification is enabled
16400
  if (server_certificate_verification_) {
16401
    if (!load_certs()) {
16402
      error = Error::SSLLoadingCerts;
16403
      output_error_log(error, nullptr);
16404
      return false;
16405
    }
16406
  }
16407
16408
  bool is_ip = detail::is_ip_address(host_);
16409
16410
#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
16411
  // MbedTLS/wolfSSL need explicit verification mode (OpenSSL uses
16412
  // SSL_VERIFY_NONE by default and performs all verification post-handshake).
16413
  // Chain verification happens during the handshake even for IP hosts; the
16414
  // certificate identity is verified post-handshake via verify_hostname().
16415
  set_verify_client(ctx_, server_certificate_verification_);
16416
#endif
16417
16418
  // Create TLS session
16419
  session_t session = nullptr;
16420
  {
16421
    std::lock_guard<std::mutex> guard(ctx_mutex_);
16422
    session = create_session(ctx_, socket.sock);
16423
  }
16424
16425
  if (!session) {
16426
    error = Error::SSLConnection;
16427
    last_backend_error_ = get_error();
16428
    return false;
16429
  }
16430
16431
  // Use scope_exit to ensure session is freed on error paths
16432
  bool success = false;
16433
  auto session_guard = detail::scope_exit([&] {
16434
    if (!success) { free_session(session); }
16435
  });
16436
16437
  // Set SNI extension (skip for IP addresses per RFC 6066).
16438
  // On MbedTLS, set_sni also enables hostname verification internally.
16439
  // On OpenSSL, set_sni only sets SNI; verification is done post-handshake.
16440
  if (!is_ip) {
16441
    if (!set_sni(session, host_.c_str())) {
16442
      error = Error::SSLConnection;
16443
      last_backend_error_ = get_error();
16444
      return false;
16445
    }
16446
  }
16447
16448
  // Perform non-blocking TLS handshake with timeout
16449
  TlsError tls_err;
16450
  if (!connect_nonblocking(session, socket.sock, connection_timeout_sec_,
16451
                           connection_timeout_usec_, &tls_err)) {
16452
    last_ssl_error_ = static_cast<int>(tls_err.code);
16453
    last_backend_error_ = tls_err.backend_code;
16454
    if (tls_err.code == ErrorCode::CertVerifyFailed) {
16455
      error = Error::SSLServerVerification;
16456
    } else if (tls_err.code == ErrorCode::HostnameMismatch) {
16457
      error = Error::SSLServerHostnameVerification;
16458
    } else {
16459
      error = Error::SSLConnection;
16460
    }
16461
    output_error_log(error, nullptr);
16462
    return false;
16463
  }
16464
16465
  // Post-handshake session verifier callback
16466
  auto verification_status = SSLVerifierResponse::NoDecisionMade;
16467
  if (session_verifier_) { verification_status = session_verifier_(session); }
16468
16469
  if (verification_status == SSLVerifierResponse::CertificateRejected) {
16470
    last_backend_error_ = get_error();
16471
    error = Error::SSLServerVerification;
16472
    output_error_log(error, nullptr);
16473
    return false;
16474
  }
16475
16476
  // Default server certificate verification
16477
  if (verification_status == SSLVerifierResponse::NoDecisionMade &&
16478
      server_certificate_verification_) {
16479
    verify_result_ = tls::get_verify_result(session);
16480
    if (verify_result_ != 0) {
16481
      last_backend_error_ = static_cast<uint64_t>(verify_result_);
16482
      error = Error::SSLServerVerification;
16483
      output_error_log(error, nullptr);
16484
      return false;
16485
    }
16486
16487
    auto server_cert = get_peer_cert(session);
16488
    if (!server_cert) {
16489
      last_backend_error_ = get_error();
16490
      error = Error::SSLServerVerification;
16491
      output_error_log(error, nullptr);
16492
      return false;
16493
    }
16494
    auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); });
16495
16496
    // Hostname verification (post-handshake for all cases).
16497
    // On OpenSSL, verification is always post-handshake (SSL_VERIFY_NONE).
16498
    // On MbedTLS, set_sni already enabled hostname verification during
16499
    // handshake for non-IP hosts, but this check is still needed for IP
16500
    // addresses where SNI is not set.
16501
    if (server_hostname_verification_) {
16502
      if (!verify_hostname(server_cert, host_.c_str())) {
16503
        last_backend_error_ = hostname_mismatch_code();
16504
        error = Error::SSLServerHostnameVerification;
16505
        output_error_log(error, nullptr);
16506
        return false;
16507
      }
16508
    }
16509
16510
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
16511
    // Additional Windows Schannel verification.
16512
    // This provides real-time certificate validation with Windows Update
16513
    // integration, working with both OpenSSL and MbedTLS backends.
16514
    // Skip when a custom CA cert is specified, as the Windows certificate
16515
    // store would not know about user-provided CA certificates. Also skip
16516
    // when system CA trust is explicitly disabled.
16517
    if (enable_windows_cert_verification_ &&
16518
        system_ca_mode_ != SystemCAMode::Disabled &&
16519
        ca_cert_file_path_.empty() && ca_cert_dir_path_.empty() &&
16520
        ca_cert_pem_.empty() && !ca_cert_store_set_) {
16521
      std::vector<unsigned char> der;
16522
      if (get_cert_der(server_cert, der)) {
16523
        uint64_t wincrypt_error = 0;
16524
        if (!detail::verify_cert_with_windows_schannel(
16525
                der, host_, server_hostname_verification_, wincrypt_error)) {
16526
          last_backend_error_ = wincrypt_error;
16527
          error = Error::SSLServerVerification;
16528
          output_error_log(error, nullptr);
16529
          return false;
16530
        }
16531
      }
16532
    }
16533
#endif
16534
  }
16535
16536
  success = true;
16537
  socket.ssl = session;
16538
  return true;
16539
}
16540
16541
inline void Client::set_digest_auth(const std::string &username,
16542
                                    const std::string &password) {
16543
  cli_->set_digest_auth(username, password);
16544
}
16545
16546
inline void Client::set_proxy_digest_auth(const std::string &username,
16547
                                          const std::string &password) {
16548
  cli_->set_proxy_digest_auth(username, password);
16549
}
16550
16551
inline void Client::enable_server_certificate_verification(bool enabled) {
16552
  cli_->enable_server_certificate_verification(enabled);
16553
}
16554
16555
inline void Client::enable_server_hostname_verification(bool enabled) {
16556
  cli_->enable_server_hostname_verification(enabled);
16557
}
16558
16559
inline void Client::enable_system_ca(bool enabled) {
16560
  cli_->enable_system_ca(enabled);
16561
}
16562
16563
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
16564
inline void Client::enable_windows_certificate_verification(bool enabled) {
16565
  if (is_ssl_) {
16566
    static_cast<SSLClient &>(*cli_).enable_windows_certificate_verification(
16567
        enabled);
16568
  }
16569
}
16570
#endif
16571
16572
inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path,
16573
                                     const std::string &ca_cert_dir_path) {
16574
  cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path);
16575
}
16576
16577
inline void Client::set_ca_cert_store(tls::ca_store_t ca_cert_store) {
16578
  if (is_ssl_) {
16579
    static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
16580
  } else if (ca_cert_store) {
16581
    tls::free_ca_store(ca_cert_store);
16582
  }
16583
}
16584
16585
inline void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) {
16586
  if (is_ssl_) {
16587
    // Use the PEM-based path so the CA data is retained for redirect transfer
16588
    static_cast<SSLClient &>(*cli_).load_ca_cert_store(ca_cert, size);
16589
  }
16590
}
16591
16592
inline void
16593
Client::set_server_certificate_verifier(tls::VerifyCallback verifier) {
16594
  if (is_ssl_) {
16595
    static_cast<SSLClient &>(*cli_).set_server_certificate_verifier(
16596
        std::move(verifier));
16597
  }
16598
}
16599
16600
inline void Client::set_session_verifier(
16601
    std::function<SSLVerifierResponse(tls::session_t)> verifier) {
16602
  if (is_ssl_) {
16603
    static_cast<SSLClient &>(*cli_).set_session_verifier(std::move(verifier));
16604
  }
16605
}
16606
16607
inline tls::ctx_t Client::tls_context() const {
16608
  if (is_ssl_) { return static_cast<SSLClient &>(*cli_).tls_context(); }
16609
  return nullptr;
16610
}
16611
16612
#endif // CPPHTTPLIB_SSL_ENABLED
16613
16614
/*
16615
 * Group 7: TLS abstraction layer - Common API
16616
 */
16617
16618
#ifdef CPPHTTPLIB_SSL_ENABLED
16619
16620
namespace tls {
16621
16622
// Helper for PeerCert construction
16623
inline PeerCert get_peer_cert_from_session(const_session_t session) {
16624
  return PeerCert(get_peer_cert(session));
16625
}
16626
16627
namespace impl {
16628
16629
inline VerifyCallback &get_verify_callback() {
16630
  static thread_local VerifyCallback callback;
16631
  return callback;
16632
}
16633
16634
inline VerifyCallback &get_mbedtls_verify_callback() {
16635
  static thread_local VerifyCallback callback;
16636
  return callback;
16637
}
16638
16639
// Check if a string is an IPv4 address
16640
inline bool is_ipv4_address(const std::string &str) {
16641
  int dots = 0;
16642
  for (char c : str) {
16643
    if (c == '.') {
16644
      dots++;
16645
    } else if (!detail::is_ascii_digit(c)) {
16646
      return false;
16647
    }
16648
  }
16649
  return dots == 3;
16650
}
16651
16652
// Parse IPv4 address string to bytes
16653
inline bool parse_ipv4(const std::string &str, unsigned char *out) {
16654
  const char *p = str.c_str();
16655
  for (int i = 0; i < 4; i++) {
16656
    if (i > 0) {
16657
      if (*p != '.') { return false; }
16658
      p++;
16659
    }
16660
    int val = 0;
16661
    int digits = 0;
16662
    while (detail::is_ascii_digit(*p)) {
16663
      val = val * 10 + (*p - '0');
16664
      if (val > 255) { return false; }
16665
      p++;
16666
      digits++;
16667
    }
16668
    if (digits == 0) { return false; }
16669
    // Reject leading zeros (e.g., "01.002.03.04") to prevent ambiguity
16670
    if (digits > 1 && *(p - digits) == '0') { return false; }
16671
    out[i] = static_cast<unsigned char>(val);
16672
  }
16673
  return *p == '\0';
16674
}
16675
16676
// Parse an IP literal (IPv4 or IPv6) into raw network-order bytes.
16677
// `out` must have room for at least 16 bytes. Returns the address length
16678
// (4 for IPv4, 16 for IPv6) on success, or 0 if the string is not an IP
16679
// literal. Used to match a host against iPAddress SANs the same way the
16680
// OpenSSL backend does via X509_check_ip.
16681
inline size_t parse_ip_address(const std::string &str, unsigned char *out) {
16682
  if (is_ipv4_address(str)) { return parse_ipv4(str, out) ? 4 : 0; }
16683
  struct in6_addr addr6 = {};
16684
  if (inet_pton(AF_INET6, str.c_str(), &addr6) == 1) {
16685
    memcpy(out, &addr6, 16);
16686
    return 16;
16687
  }
16688
  return 0;
16689
}
16690
16691
#ifdef _WIN32
16692
// Enumerate Windows system certificates and call callback with DER data
16693
template <typename Callback>
16694
inline bool enumerate_windows_system_certs(Callback cb) {
16695
  bool loaded = false;
16696
  static const wchar_t *store_names[] = {L"ROOT", L"CA"};
16697
  for (auto store_name : store_names) {
16698
    HCERTSTORE hStore = CertOpenSystemStoreW(0, store_name);
16699
    if (hStore) {
16700
      PCCERT_CONTEXT pContext = nullptr;
16701
      while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
16702
             nullptr) {
16703
        if (cb(pContext->pbCertEncoded, pContext->cbCertEncoded)) {
16704
          loaded = true;
16705
        }
16706
      }
16707
      CertCloseStore(hStore, 0);
16708
    }
16709
  }
16710
  return loaded;
16711
}
16712
#endif
16713
16714
#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
16715
// Enumerate macOS Keychain certificates and call callback with DER data
16716
template <typename Callback>
16717
inline bool enumerate_macos_keychain_certs(Callback cb) {
16718
  bool loaded = false;
16719
  const SecTrustSettingsDomain domains[] = {
16720
      kSecTrustSettingsDomainSystem,
16721
      kSecTrustSettingsDomainAdmin,
16722
      kSecTrustSettingsDomainUser,
16723
  };
16724
  for (auto domain : domains) {
16725
    CFArrayRef certs = nullptr;
16726
    OSStatus status = SecTrustSettingsCopyCertificates(domain, &certs);
16727
    if (status != errSecSuccess || !certs) {
16728
      if (certs) CFRelease(certs);
16729
      continue;
16730
    }
16731
    CFIndex count = CFArrayGetCount(certs);
16732
    for (CFIndex i = 0; i < count; i++) {
16733
      SecCertificateRef cert =
16734
          (SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
16735
      CFDataRef data = SecCertificateCopyData(cert);
16736
      if (data) {
16737
        if (cb(CFDataGetBytePtr(data),
16738
               static_cast<size_t>(CFDataGetLength(data)))) {
16739
          loaded = true;
16740
        }
16741
        CFRelease(data);
16742
      }
16743
    }
16744
    CFRelease(certs);
16745
  }
16746
  return loaded;
16747
}
16748
#endif
16749
16750
#if !defined(_WIN32) && !(defined(__APPLE__) &&                                \
16751
                          defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN))
16752
// Common CA certificate file paths on Linux/Unix
16753
inline const char **system_ca_paths() {
16754
  static const char *paths[] = {
16755
      "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu
16756
      "/etc/pki/tls/certs/ca-bundle.crt",   // RHEL/CentOS
16757
      "/etc/ssl/ca-bundle.pem",             // OpenSUSE
16758
      "/etc/pki/tls/cacert.pem",            // OpenELEC
16759
      "/etc/ssl/cert.pem",                  // Alpine, FreeBSD
16760
      nullptr};
16761
  return paths;
16762
}
16763
16764
// Common CA certificate directory paths on Linux/Unix
16765
inline const char **system_ca_dirs() {
16766
  static const char *dirs[] = {"/etc/ssl/certs",             // Debian/Ubuntu
16767
                               "/etc/pki/tls/certs",         // RHEL/CentOS
16768
                               "/usr/share/ca-certificates", // Other
16769
                               nullptr};
16770
  return dirs;
16771
}
16772
#endif
16773
16774
} // namespace impl
16775
16776
inline bool set_client_ca_file(ctx_t ctx, const char *ca_file,
16777
                               const char *ca_dir) {
16778
  if (!ctx) { return false; }
16779
16780
  bool success = true;
16781
  if (ca_file && *ca_file) {
16782
    if (!load_ca_file(ctx, ca_file)) { success = false; }
16783
  }
16784
  if (ca_dir && *ca_dir) {
16785
    if (!load_ca_dir(ctx, ca_dir)) { success = false; }
16786
  }
16787
16788
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
16789
  // Set CA list for client certificate request (CertificateRequest message)
16790
  if (ca_file && *ca_file) {
16791
    auto list = SSL_load_client_CA_file(ca_file);
16792
    if (list) { SSL_CTX_set_client_CA_list(static_cast<SSL_CTX *>(ctx), list); }
16793
  }
16794
#endif
16795
16796
  return success;
16797
}
16798
16799
inline bool set_server_cert_pem(ctx_t ctx, const char *cert, const char *key,
16800
                                const char *password) {
16801
  return set_client_cert_pem(ctx, cert, key, password);
16802
}
16803
16804
inline bool set_server_cert_file(ctx_t ctx, const char *cert_path,
16805
                                 const char *key_path, const char *password) {
16806
  return set_client_cert_file(ctx, cert_path, key_path, password);
16807
}
16808
16809
// PeerCert implementation
16810
inline PeerCert::PeerCert() = default;
16811
16812
inline PeerCert::PeerCert(cert_t cert) : cert_(cert) {}
16813
16814
inline PeerCert::PeerCert(PeerCert &&other) noexcept : cert_(other.cert_) {
16815
  other.cert_ = nullptr;
16816
}
16817
16818
inline PeerCert &PeerCert::operator=(PeerCert &&other) noexcept {
16819
  if (this != &other) {
16820
    if (cert_) { free_cert(cert_); }
16821
    cert_ = other.cert_;
16822
    other.cert_ = nullptr;
16823
  }
16824
  return *this;
16825
}
16826
16827
inline PeerCert::~PeerCert() {
16828
  if (cert_) { free_cert(cert_); }
16829
}
16830
16831
inline PeerCert::operator bool() const { return cert_ != nullptr; }
16832
16833
inline std::string PeerCert::subject_cn() const {
16834
  return cert_ ? get_cert_subject_cn(cert_) : std::string();
16835
}
16836
16837
inline std::string PeerCert::issuer_name() const {
16838
  return cert_ ? get_cert_issuer_name(cert_) : std::string();
16839
}
16840
16841
inline bool PeerCert::check_hostname(const char *hostname) const {
16842
  return cert_ ? verify_hostname(cert_, hostname) : false;
16843
}
16844
16845
inline std::vector<SanEntry> PeerCert::sans() const {
16846
  std::vector<SanEntry> result;
16847
  if (cert_) { get_cert_sans(cert_, result); }
16848
  return result;
16849
}
16850
16851
inline bool PeerCert::validity(time_t &not_before, time_t &not_after) const {
16852
  return cert_ ? get_cert_validity(cert_, not_before, not_after) : false;
16853
}
16854
16855
inline std::string PeerCert::serial() const {
16856
  return cert_ ? get_cert_serial(cert_) : std::string();
16857
}
16858
16859
// VerifyContext method implementations
16860
inline std::string VerifyContext::subject_cn() const {
16861
  return cert ? get_cert_subject_cn(cert) : std::string();
16862
}
16863
16864
inline std::string VerifyContext::issuer_name() const {
16865
  return cert ? get_cert_issuer_name(cert) : std::string();
16866
}
16867
16868
inline bool VerifyContext::check_hostname(const char *hostname) const {
16869
  return cert ? verify_hostname(cert, hostname) : false;
16870
}
16871
16872
inline std::vector<SanEntry> VerifyContext::sans() const {
16873
  std::vector<SanEntry> result;
16874
  if (cert) { get_cert_sans(cert, result); }
16875
  return result;
16876
}
16877
16878
inline bool VerifyContext::validity(time_t &not_before,
16879
                                    time_t &not_after) const {
16880
  return cert ? get_cert_validity(cert, not_before, not_after) : false;
16881
}
16882
16883
inline std::string VerifyContext::serial() const {
16884
  return cert ? get_cert_serial(cert) : std::string();
16885
}
16886
16887
// TlsError static method implementation
16888
inline std::string TlsError::verify_error_to_string(long error_code) {
16889
  return verify_error_string(error_code);
16890
}
16891
16892
} // namespace tls
16893
16894
// Request::peer_cert() implementation
16895
inline tls::PeerCert Request::peer_cert() const {
16896
  return tls::get_peer_cert_from_session(ssl);
16897
}
16898
16899
// Request::sni() implementation
16900
inline std::string Request::sni() const {
16901
  if (!ssl) { return std::string(); }
16902
  const char *s = tls::get_sni(ssl);
16903
  return s ? std::string(s) : std::string();
16904
}
16905
16906
#endif // CPPHTTPLIB_SSL_ENABLED
16907
16908
/*
16909
 * Group 8: TLS abstraction layer - OpenSSL backend
16910
 */
16911
16912
/*
16913
 * OpenSSL Backend Implementation
16914
 */
16915
16916
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
16917
namespace tls {
16918
16919
namespace impl {
16920
16921
// Helper to map OpenSSL SSL_get_error to ErrorCode
16922
inline ErrorCode map_ssl_error(int ssl_error, int &out_errno) {
16923
  switch (ssl_error) {
16924
  case SSL_ERROR_NONE: return ErrorCode::Success;
16925
  case SSL_ERROR_WANT_READ: return ErrorCode::WantRead;
16926
  case SSL_ERROR_WANT_WRITE: return ErrorCode::WantWrite;
16927
  case SSL_ERROR_ZERO_RETURN: return ErrorCode::PeerClosed;
16928
  case SSL_ERROR_SYSCALL: out_errno = errno; return ErrorCode::SyscallError;
16929
  case SSL_ERROR_SSL:
16930
  default: return ErrorCode::Fatal;
16931
  }
16932
}
16933
16934
// Helper: Create client CA list from PEM string
16935
// Returns a new STACK_OF(X509_NAME)* or nullptr on failure
16936
// Caller takes ownership of returned list
16937
inline STACK_OF(X509_NAME) *
16938
    create_client_ca_list_from_pem(const char *ca_pem) {
16939
  if (!ca_pem) { return nullptr; }
16940
16941
  auto ca_list = sk_X509_NAME_new_null();
16942
  if (!ca_list) { return nullptr; }
16943
16944
  BIO *bio = BIO_new_mem_buf(ca_pem, -1);
16945
  if (!bio) {
16946
    sk_X509_NAME_pop_free(ca_list, X509_NAME_free);
16947
    return nullptr;
16948
  }
16949
16950
  X509 *cert = nullptr;
16951
  while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) !=
16952
         nullptr) {
16953
    const X509_NAME *name = X509_get_subject_name(cert);
16954
    if (name) {
16955
      sk_X509_NAME_push(ca_list, X509_NAME_dup(const_cast<X509_NAME *>(name)));
16956
    }
16957
    X509_free(cert);
16958
  }
16959
  BIO_free(bio);
16960
16961
  return ca_list;
16962
}
16963
16964
// OpenSSL verify callback wrapper
16965
inline int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
16966
  auto &callback = get_verify_callback();
16967
  if (!callback) { return preverify_ok; }
16968
16969
  // Get SSL object from X509_STORE_CTX
16970
  auto ssl = static_cast<SSL *>(
16971
      X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
16972
  if (!ssl) { return preverify_ok; }
16973
16974
  // Get current certificate and depth
16975
  auto cert = X509_STORE_CTX_get_current_cert(ctx);
16976
  int depth = X509_STORE_CTX_get_error_depth(ctx);
16977
  int error = X509_STORE_CTX_get_error(ctx);
16978
16979
  // Build context
16980
  VerifyContext verify_ctx;
16981
  verify_ctx.session = static_cast<session_t>(ssl);
16982
  verify_ctx.cert = static_cast<cert_t>(cert);
16983
  verify_ctx.depth = depth;
16984
  verify_ctx.preverify_ok = (preverify_ok != 0);
16985
  verify_ctx.error_code = error;
16986
  verify_ctx.error_string =
16987
      (error != X509_V_OK) ? X509_verify_cert_error_string(error) : nullptr;
16988
16989
  return callback(verify_ctx) ? 1 : 0;
16990
}
16991
16992
// X509_STORE_get0_objects is deprecated since OpenSSL 4.0 because it is not
16993
// thread-safe; X509_STORE_get1_objects (OpenSSL 3.3+) returns a snapshot
16994
// that must be released with release_store_objects
16995
#if !defined(OPENSSL_IS_BORINGSSL) && !defined(LIBRESSL_VERSION_NUMBER) &&     \
16996
    OPENSSL_VERSION_NUMBER >= 0x30300000L
16997
#define CPPHTTPLIB_HAS_X509_STORE_GET1_OBJECTS
16998
#endif
16999
17000
inline STACK_OF(X509_OBJECT) * get_store_objects(X509_STORE *store) {
17001
#ifdef CPPHTTPLIB_HAS_X509_STORE_GET1_OBJECTS
17002
  return X509_STORE_get1_objects(store);
17003
#else
17004
  return X509_STORE_get0_objects(store);
17005
#endif
17006
}
17007
17008
inline void release_store_objects(STACK_OF(X509_OBJECT) * objs) {
17009
#ifdef CPPHTTPLIB_HAS_X509_STORE_GET1_OBJECTS
17010
  sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free);
17011
#else
17012
  (void)objs; // get0 variant returns an internal pointer; nothing to free
17013
#endif
17014
}
17015
17016
} // namespace impl
17017
17018
inline ctx_t create_client_context() {
17019
  SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
17020
  if (ctx) {
17021
    // Disable auto-retry to properly handle non-blocking I/O
17022
    SSL_CTX_clear_mode(ctx, SSL_MODE_AUTO_RETRY);
17023
    // Set minimum TLS version
17024
    SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
17025
  }
17026
  return static_cast<ctx_t>(ctx);
17027
}
17028
17029
inline void free_context(ctx_t ctx) {
17030
  if (ctx) { SSL_CTX_free(static_cast<SSL_CTX *>(ctx)); }
17031
}
17032
17033
inline bool set_min_version(ctx_t ctx, Version version) {
17034
  if (!ctx) return false;
17035
  return SSL_CTX_set_min_proto_version(static_cast<SSL_CTX *>(ctx),
17036
                                       static_cast<int>(version)) == 1;
17037
}
17038
17039
inline bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) {
17040
  if (!ctx || !pem || len == 0) return false;
17041
17042
  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17043
  auto store = SSL_CTX_get_cert_store(ssl_ctx);
17044
  if (!store) return false;
17045
17046
  auto bio = BIO_new_mem_buf(pem, static_cast<int>(len));
17047
  if (!bio) return false;
17048
17049
  bool ok = true;
17050
  X509 *cert = nullptr;
17051
  while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) !=
17052
         nullptr) {
17053
    if (X509_STORE_add_cert(store, cert) != 1) {
17054
      // Ignore duplicate errors
17055
      auto err = ERR_peek_last_error();
17056
      if (ERR_GET_REASON(err) != X509_R_CERT_ALREADY_IN_HASH_TABLE) {
17057
        ok = false;
17058
      }
17059
    }
17060
    X509_free(cert);
17061
    if (!ok) break;
17062
  }
17063
  BIO_free(bio);
17064
17065
  // Clear any "no more certificates" errors
17066
  ERR_clear_error();
17067
  return ok;
17068
}
17069
17070
inline bool load_ca_file(ctx_t ctx, const char *file_path) {
17071
  if (!ctx || !file_path) return false;
17072
  return SSL_CTX_load_verify_locations(static_cast<SSL_CTX *>(ctx), file_path,
17073
                                       nullptr) == 1;
17074
}
17075
17076
inline bool load_ca_dir(ctx_t ctx, const char *dir_path) {
17077
  if (!ctx || !dir_path) return false;
17078
  return SSL_CTX_load_verify_locations(static_cast<SSL_CTX *>(ctx), nullptr,
17079
                                       dir_path) == 1;
17080
}
17081
17082
inline bool load_system_certs(ctx_t ctx) {
17083
  if (!ctx) return false;
17084
  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17085
17086
#ifdef _WIN32
17087
  // Windows: Load from system certificate store (ROOT and CA)
17088
  auto store = SSL_CTX_get_cert_store(ssl_ctx);
17089
  if (!store) return false;
17090
17091
  bool loaded_any = false;
17092
  static const wchar_t *store_names[] = {L"ROOT", L"CA"};
17093
  for (auto store_name : store_names) {
17094
    auto hStore = CertOpenSystemStoreW(NULL, store_name);
17095
    if (!hStore) continue;
17096
17097
    PCCERT_CONTEXT pContext = nullptr;
17098
    while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
17099
           nullptr) {
17100
      const unsigned char *data = pContext->pbCertEncoded;
17101
      auto x509 = d2i_X509(nullptr, &data, pContext->cbCertEncoded);
17102
      if (x509) {
17103
        if (X509_STORE_add_cert(store, x509) == 1) { loaded_any = true; }
17104
        X509_free(x509);
17105
      }
17106
    }
17107
    CertCloseStore(hStore, 0);
17108
  }
17109
  return loaded_any;
17110
17111
#elif defined(__APPLE__)
17112
#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
17113
  // macOS: Load from Keychain
17114
  auto store = SSL_CTX_get_cert_store(ssl_ctx);
17115
  if (!store) return false;
17116
17117
  bool loaded_any = false;
17118
  const SecTrustSettingsDomain domains[] = {
17119
      kSecTrustSettingsDomainSystem,
17120
      kSecTrustSettingsDomainAdmin,
17121
      kSecTrustSettingsDomainUser,
17122
  };
17123
  for (auto domain : domains) {
17124
    CFArrayRef certs = nullptr;
17125
    if (SecTrustSettingsCopyCertificates(domain, &certs) != errSecSuccess ||
17126
        !certs) {
17127
      if (certs) CFRelease(certs);
17128
      continue;
17129
    }
17130
    auto count = CFArrayGetCount(certs);
17131
    for (CFIndex i = 0; i < count; i++) {
17132
      auto cert = reinterpret_cast<SecCertificateRef>(
17133
          const_cast<void *>(CFArrayGetValueAtIndex(certs, i)));
17134
      CFDataRef der = SecCertificateCopyData(cert);
17135
      if (der) {
17136
        const unsigned char *data = CFDataGetBytePtr(der);
17137
        auto x509 = d2i_X509(nullptr, &data, CFDataGetLength(der));
17138
        if (x509) {
17139
          if (X509_STORE_add_cert(store, x509) == 1) { loaded_any = true; }
17140
          X509_free(x509);
17141
        }
17142
        CFRelease(der);
17143
      }
17144
    }
17145
    CFRelease(certs);
17146
  }
17147
  return loaded_any || SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
17148
#else
17149
  return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
17150
#endif
17151
17152
#else
17153
  // Other Unix: use default verify paths
17154
  return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
17155
#endif
17156
}
17157
17158
inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
17159
                                const char *password) {
17160
  if (!ctx || !cert || !key) return false;
17161
17162
  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17163
17164
  // Load certificate
17165
  auto cert_bio = BIO_new_mem_buf(cert, -1);
17166
  if (!cert_bio) return false;
17167
17168
  auto x509 = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr);
17169
  BIO_free(cert_bio);
17170
  if (!x509) return false;
17171
17172
  auto cert_ok = SSL_CTX_use_certificate(ssl_ctx, x509) == 1;
17173
  X509_free(x509);
17174
  if (!cert_ok) return false;
17175
17176
  // Load private key
17177
  auto key_bio = BIO_new_mem_buf(key, -1);
17178
  if (!key_bio) return false;
17179
17180
  auto pkey = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr,
17181
                                      password ? const_cast<char *>(password)
17182
                                               : nullptr);
17183
  BIO_free(key_bio);
17184
  if (!pkey) return false;
17185
17186
  auto key_ok = SSL_CTX_use_PrivateKey(ssl_ctx, pkey) == 1;
17187
  EVP_PKEY_free(pkey);
17188
17189
  return key_ok && SSL_CTX_check_private_key(ssl_ctx) == 1;
17190
}
17191
17192
inline bool set_client_cert_file(ctx_t ctx, const char *cert_path,
17193
                                 const char *key_path, const char *password) {
17194
  if (!ctx || !cert_path || !key_path) return false;
17195
17196
  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17197
17198
  if (password && password[0] != '\0') {
17199
    SSL_CTX_set_default_passwd_cb_userdata(
17200
        ssl_ctx, reinterpret_cast<void *>(const_cast<char *>(password)));
17201
  }
17202
17203
  return SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_path) == 1 &&
17204
         SSL_CTX_use_PrivateKey_file(ssl_ctx, key_path, SSL_FILETYPE_PEM) == 1;
17205
}
17206
17207
inline ctx_t create_server_context() {
17208
  SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
17209
  if (ctx) {
17210
    SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION |
17211
                                 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
17212
    SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
17213
  }
17214
  return static_cast<ctx_t>(ctx);
17215
}
17216
17217
inline void set_verify_client(ctx_t ctx, bool require) {
17218
  if (!ctx) return;
17219
  SSL_CTX_set_verify(static_cast<SSL_CTX *>(ctx),
17220
                     require
17221
                         ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
17222
                         : SSL_VERIFY_NONE,
17223
                     nullptr);
17224
}
17225
17226
inline session_t create_session(ctx_t ctx, socket_t sock) {
17227
  if (!ctx || sock == INVALID_SOCKET) return nullptr;
17228
17229
  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17230
  SSL *ssl = SSL_new(ssl_ctx);
17231
  if (!ssl) return nullptr;
17232
17233
  // Disable auto-retry for proper non-blocking I/O handling
17234
  SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
17235
17236
  auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
17237
  if (!bio) {
17238
    SSL_free(ssl);
17239
    return nullptr;
17240
  }
17241
17242
  SSL_set_bio(ssl, bio, bio);
17243
  return static_cast<session_t>(ssl);
17244
}
17245
17246
inline void free_session(session_t session) {
17247
  if (session) { SSL_free(static_cast<SSL *>(session)); }
17248
}
17249
17250
inline bool set_sni(session_t session, const char *hostname) {
17251
  if (!session || !hostname) return false;
17252
17253
  auto ssl = static_cast<SSL *>(session);
17254
17255
  // Set SNI (Server Name Indication) only - does not enable verification
17256
#if defined(OPENSSL_IS_BORINGSSL)
17257
  return SSL_set_tlsext_host_name(ssl, hostname) == 1;
17258
#else
17259
  // Direct call instead of macro to suppress -Wold-style-cast warning
17260
  return SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name,
17261
                  static_cast<void *>(const_cast<char *>(hostname))) == 1;
17262
#endif
17263
}
17264
17265
inline bool set_hostname(session_t session, const char *hostname) {
17266
  if (!session || !hostname) return false;
17267
17268
  auto ssl = static_cast<SSL *>(session);
17269
17270
  // Enable hostname verification
17271
  auto param = SSL_get0_param(ssl);
17272
  if (!param) return false;
17273
17274
  if (detail::is_ip_address(hostname)) {
17275
    // RFC 6066: SNI must not be set for IP addresses; verify against the
17276
    // certificate's IP SANs instead of its DNS names
17277
    if (X509_VERIFY_PARAM_set1_ip_asc(param, hostname) != 1) { return false; }
17278
  } else {
17279
    // Set SNI (Server Name Indication)
17280
    if (!set_sni(session, hostname)) { return false; }
17281
17282
    X509_VERIFY_PARAM_set_hostflags(param,
17283
                                    X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
17284
    if (X509_VERIFY_PARAM_set1_host(param, hostname, 0) != 1) { return false; }
17285
  }
17286
17287
  SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr);
17288
  return true;
17289
}
17290
17291
inline TlsError connect(session_t session) {
17292
  if (!session) { return TlsError(); }
17293
17294
  auto ssl = static_cast<SSL *>(session);
17295
  auto ret = SSL_connect(ssl);
17296
17297
  TlsError err;
17298
  if (ret == 1) {
17299
    err.code = ErrorCode::Success;
17300
  } else {
17301
    auto ssl_err = SSL_get_error(ssl, ret);
17302
    err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
17303
    err.backend_code = ERR_get_error();
17304
  }
17305
  return err;
17306
}
17307
17308
inline TlsError accept(session_t session) {
17309
  if (!session) { return TlsError(); }
17310
17311
  auto ssl = static_cast<SSL *>(session);
17312
  auto ret = SSL_accept(ssl);
17313
17314
  TlsError err;
17315
  if (ret == 1) {
17316
    err.code = ErrorCode::Success;
17317
  } else {
17318
    auto ssl_err = SSL_get_error(ssl, ret);
17319
    err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
17320
    err.backend_code = ERR_get_error();
17321
  }
17322
  return err;
17323
}
17324
17325
inline bool connect_nonblocking(session_t session, socket_t sock,
17326
                                time_t timeout_sec, time_t timeout_usec,
17327
                                TlsError *err) {
17328
  if (!session) {
17329
    if (err) { err->code = ErrorCode::Fatal; }
17330
    return false;
17331
  }
17332
17333
  auto ssl = static_cast<SSL *>(session);
17334
  auto bio = SSL_get_rbio(ssl);
17335
17336
  // Set non-blocking mode for handshake
17337
  detail::set_nonblocking(sock, true);
17338
  if (bio) { BIO_set_nbio(bio, 1); }
17339
17340
  auto cleanup = detail::scope_exit([&]() {
17341
    // Restore blocking mode after handshake
17342
    if (bio) { BIO_set_nbio(bio, 0); }
17343
    detail::set_nonblocking(sock, false);
17344
  });
17345
17346
  auto res = 0;
17347
  while ((res = SSL_connect(ssl)) != 1) {
17348
    auto ssl_err = SSL_get_error(ssl, res);
17349
    switch (ssl_err) {
17350
    case SSL_ERROR_WANT_READ:
17351
      if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
17352
        continue;
17353
      }
17354
      break;
17355
    case SSL_ERROR_WANT_WRITE:
17356
      if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
17357
        continue;
17358
      }
17359
      break;
17360
    default: break;
17361
    }
17362
    if (err) {
17363
      err->code = impl::map_ssl_error(ssl_err, err->sys_errno);
17364
      err->backend_code = ERR_get_error();
17365
    }
17366
    return false;
17367
  }
17368
  if (err) { err->code = ErrorCode::Success; }
17369
  return true;
17370
}
17371
17372
inline bool accept_nonblocking(session_t session, socket_t sock,
17373
                               time_t timeout_sec, time_t timeout_usec,
17374
                               TlsError *err) {
17375
  if (!session) {
17376
    if (err) { err->code = ErrorCode::Fatal; }
17377
    return false;
17378
  }
17379
17380
  auto ssl = static_cast<SSL *>(session);
17381
  auto bio = SSL_get_rbio(ssl);
17382
17383
  // Set non-blocking mode for handshake
17384
  detail::set_nonblocking(sock, true);
17385
  if (bio) { BIO_set_nbio(bio, 1); }
17386
17387
  auto cleanup = detail::scope_exit([&]() {
17388
    // Restore blocking mode after handshake
17389
    if (bio) { BIO_set_nbio(bio, 0); }
17390
    detail::set_nonblocking(sock, false);
17391
  });
17392
17393
  auto res = 0;
17394
  while ((res = SSL_accept(ssl)) != 1) {
17395
    auto ssl_err = SSL_get_error(ssl, res);
17396
    switch (ssl_err) {
17397
    case SSL_ERROR_WANT_READ:
17398
      if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
17399
        continue;
17400
      }
17401
      break;
17402
    case SSL_ERROR_WANT_WRITE:
17403
      if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
17404
        continue;
17405
      }
17406
      break;
17407
    default: break;
17408
    }
17409
    if (err) {
17410
      err->code = impl::map_ssl_error(ssl_err, err->sys_errno);
17411
      err->backend_code = ERR_get_error();
17412
    }
17413
    return false;
17414
  }
17415
  if (err) { err->code = ErrorCode::Success; }
17416
  return true;
17417
}
17418
17419
inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
17420
  if (!session || !buf) {
17421
    err.code = ErrorCode::Fatal;
17422
    return -1;
17423
  }
17424
17425
  auto ssl = static_cast<SSL *>(session);
17426
  constexpr auto max_len =
17427
      static_cast<size_t>((std::numeric_limits<int>::max)());
17428
  if (len > max_len) { len = max_len; }
17429
  auto ret = SSL_read(ssl, buf, static_cast<int>(len));
17430
17431
  if (ret > 0) {
17432
    err.code = ErrorCode::Success;
17433
    return ret;
17434
  }
17435
17436
  auto ssl_err = SSL_get_error(ssl, ret);
17437
  err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
17438
  if (err.code == ErrorCode::PeerClosed) {
17439
    return 0;
17440
  } // Gracefully handle the peer closed state.
17441
  if (err.code == ErrorCode::Fatal) { err.backend_code = ERR_get_error(); }
17442
  return -1;
17443
}
17444
17445
inline ssize_t write(session_t session, const void *buf, size_t len,
17446
                     TlsError &err) {
17447
  if (!session || !buf) {
17448
    err.code = ErrorCode::Fatal;
17449
    return -1;
17450
  }
17451
17452
  auto ssl = static_cast<SSL *>(session);
17453
  auto ret = SSL_write(ssl, buf, static_cast<int>(len));
17454
17455
  if (ret > 0) {
17456
    err.code = ErrorCode::Success;
17457
    return ret;
17458
  }
17459
17460
  auto ssl_err = SSL_get_error(ssl, ret);
17461
  err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
17462
  if (err.code == ErrorCode::Fatal) { err.backend_code = ERR_get_error(); }
17463
  return -1;
17464
}
17465
17466
inline int pending(const_session_t session) {
17467
  if (!session) return 0;
17468
  return SSL_pending(static_cast<SSL *>(const_cast<void *>(session)));
17469
}
17470
17471
inline void shutdown(session_t session, bool graceful) {
17472
  if (!session) return;
17473
17474
  auto ssl = static_cast<SSL *>(session);
17475
  if (graceful) {
17476
    // First call sends close_notify
17477
    if (SSL_shutdown(ssl) == 0) {
17478
      // Second call waits for peer's close_notify
17479
      SSL_shutdown(ssl);
17480
    }
17481
  }
17482
}
17483
17484
inline bool is_peer_closed(session_t session, socket_t sock) {
17485
  if (!session) return true;
17486
17487
  // Temporarily set socket to non-blocking to avoid blocking on SSL_peek
17488
  detail::set_nonblocking(sock, true);
17489
  auto se = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
17490
17491
  auto ssl = static_cast<SSL *>(session);
17492
  char buf;
17493
  auto ret = SSL_peek(ssl, &buf, 1);
17494
  if (ret > 0) return false;
17495
17496
  auto err = SSL_get_error(ssl, ret);
17497
  return err == SSL_ERROR_ZERO_RETURN;
17498
}
17499
17500
inline cert_t get_peer_cert(const_session_t session) {
17501
  if (!session) return nullptr;
17502
  return static_cast<cert_t>(SSL_get1_peer_certificate(
17503
      static_cast<SSL *>(const_cast<void *>(session))));
17504
}
17505
17506
inline void free_cert(cert_t cert) {
17507
  if (cert) { X509_free(static_cast<X509 *>(cert)); }
17508
}
17509
17510
inline bool verify_hostname(cert_t cert, const char *hostname) {
17511
  if (!cert || !hostname) return false;
17512
17513
  auto x509 = static_cast<X509 *>(cert);
17514
17515
  // Use X509_check_ip_asc for IP addresses, X509_check_host for DNS names
17516
  if (detail::is_ip_address(hostname)) {
17517
    return X509_check_ip_asc(x509, hostname, 0) == 1;
17518
  }
17519
  return X509_check_host(x509, hostname, strlen(hostname), 0, nullptr) == 1;
17520
}
17521
17522
inline uint64_t hostname_mismatch_code() {
17523
  return static_cast<uint64_t>(X509_V_ERR_HOSTNAME_MISMATCH);
17524
}
17525
17526
inline long get_verify_result(const_session_t session) {
17527
  if (!session) return X509_V_ERR_UNSPECIFIED;
17528
  return SSL_get_verify_result(static_cast<SSL *>(const_cast<void *>(session)));
17529
}
17530
17531
inline std::string get_cert_subject_cn(cert_t cert) {
17532
  if (!cert) return "";
17533
  auto x509 = static_cast<X509 *>(cert);
17534
  auto subject_name = X509_get_subject_name(x509);
17535
  if (!subject_name) return "";
17536
17537
  // X509_NAME_get_text_by_NID is deprecated since OpenSSL 4.0
17538
  auto idx = X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1);
17539
  if (idx < 0) return "";
17540
17541
  auto entry = X509_NAME_get_entry(subject_name, idx);
17542
  if (!entry) return "";
17543
17544
  auto data = X509_NAME_ENTRY_get_data(entry);
17545
  if (!data) return "";
17546
17547
  return std::string(
17548
      reinterpret_cast<const char *>(ASN1_STRING_get0_data(data)),
17549
      static_cast<size_t>(ASN1_STRING_length(data)));
17550
}
17551
17552
inline std::string get_cert_issuer_name(cert_t cert) {
17553
  if (!cert) return "";
17554
  auto x509 = static_cast<X509 *>(cert);
17555
  auto issuer_name = X509_get_issuer_name(x509);
17556
  if (!issuer_name) return "";
17557
17558
  char buf[256];
17559
  X509_NAME_oneline(issuer_name, buf, sizeof(buf));
17560
  return std::string(buf);
17561
}
17562
17563
inline bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
17564
  sans.clear();
17565
  if (!cert) return false;
17566
  auto x509 = static_cast<X509 *>(cert);
17567
17568
  auto names = static_cast<GENERAL_NAMES *>(
17569
      X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
17570
  if (!names) return true; // No SANs is valid
17571
17572
  auto count = sk_GENERAL_NAME_num(names);
17573
  for (decltype(count) i = 0; i < count; i++) {
17574
    auto gen = sk_GENERAL_NAME_value(names, i);
17575
    if (!gen) continue;
17576
17577
    SanEntry entry;
17578
    switch (gen->type) {
17579
    case GEN_DNS:
17580
      entry.type = SanType::DNS;
17581
      if (gen->d.dNSName) {
17582
        entry.value = std::string(
17583
            reinterpret_cast<const char *>(
17584
                ASN1_STRING_get0_data(gen->d.dNSName)),
17585
            static_cast<size_t>(ASN1_STRING_length(gen->d.dNSName)));
17586
      }
17587
      break;
17588
    case GEN_IPADD:
17589
      entry.type = SanType::IP;
17590
      if (gen->d.iPAddress) {
17591
        auto data = ASN1_STRING_get0_data(gen->d.iPAddress);
17592
        auto len = ASN1_STRING_length(gen->d.iPAddress);
17593
        if (len == 4) {
17594
          // IPv4
17595
          char buf[INET_ADDRSTRLEN];
17596
          inet_ntop(AF_INET, data, buf, sizeof(buf));
17597
          entry.value = buf;
17598
        } else if (len == 16) {
17599
          // IPv6
17600
          char buf[INET6_ADDRSTRLEN];
17601
          inet_ntop(AF_INET6, data, buf, sizeof(buf));
17602
          entry.value = buf;
17603
        }
17604
      }
17605
      break;
17606
    case GEN_EMAIL:
17607
      entry.type = SanType::EMAIL;
17608
      if (gen->d.rfc822Name) {
17609
        entry.value = std::string(
17610
            reinterpret_cast<const char *>(
17611
                ASN1_STRING_get0_data(gen->d.rfc822Name)),
17612
            static_cast<size_t>(ASN1_STRING_length(gen->d.rfc822Name)));
17613
      }
17614
      break;
17615
    case GEN_URI:
17616
      entry.type = SanType::URI;
17617
      if (gen->d.uniformResourceIdentifier) {
17618
        entry.value = std::string(
17619
            reinterpret_cast<const char *>(
17620
                ASN1_STRING_get0_data(gen->d.uniformResourceIdentifier)),
17621
            static_cast<size_t>(
17622
                ASN1_STRING_length(gen->d.uniformResourceIdentifier)));
17623
      }
17624
      break;
17625
    default: entry.type = SanType::OTHER; break;
17626
    }
17627
17628
    if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
17629
  }
17630
17631
  GENERAL_NAMES_free(names);
17632
  return true;
17633
}
17634
17635
inline bool get_cert_validity(cert_t cert, time_t &not_before,
17636
                              time_t &not_after) {
17637
  if (!cert) return false;
17638
  auto x509 = static_cast<X509 *>(cert);
17639
17640
  auto nb = X509_get0_notBefore(x509);
17641
  auto na = X509_get0_notAfter(x509);
17642
  if (!nb || !na) return false;
17643
17644
  ASN1_TIME *epoch = ASN1_TIME_new();
17645
  if (!epoch) return false;
17646
  auto se = detail::scope_exit([&] { ASN1_TIME_free(epoch); });
17647
17648
  if (!ASN1_TIME_set(epoch, 0)) return false;
17649
17650
  int pday, psec;
17651
17652
  if (!ASN1_TIME_diff(&pday, &psec, epoch, nb)) return false;
17653
  not_before = 86400 * (time_t)pday + psec;
17654
17655
  if (!ASN1_TIME_diff(&pday, &psec, epoch, na)) return false;
17656
  not_after = 86400 * (time_t)pday + psec;
17657
17658
  return true;
17659
}
17660
17661
inline std::string get_cert_serial(cert_t cert) {
17662
  if (!cert) return "";
17663
  auto x509 = static_cast<X509 *>(cert);
17664
17665
  auto serial = X509_get_serialNumber(x509);
17666
  if (!serial) return "";
17667
17668
  auto bn = ASN1_INTEGER_to_BN(serial, nullptr);
17669
  if (!bn) return "";
17670
17671
  auto hex = BN_bn2hex(bn);
17672
  BN_free(bn);
17673
  if (!hex) return "";
17674
17675
  std::string result(hex);
17676
  OPENSSL_free(hex);
17677
  return result;
17678
}
17679
17680
inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
17681
  if (!cert) return false;
17682
  auto x509 = static_cast<X509 *>(cert);
17683
  auto len = i2d_X509(x509, nullptr);
17684
  if (len < 0) return false;
17685
  der.resize(static_cast<size_t>(len));
17686
  auto p = der.data();
17687
  i2d_X509(x509, &p);
17688
  return true;
17689
}
17690
17691
inline const char *get_sni(const_session_t session) {
17692
  if (!session) return nullptr;
17693
  auto ssl = static_cast<SSL *>(const_cast<void *>(session));
17694
  return SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
17695
}
17696
17697
inline uint64_t peek_error() { return ERR_peek_last_error(); }
17698
17699
inline uint64_t get_error() { return ERR_get_error(); }
17700
17701
inline std::string error_string(uint64_t code) {
17702
  char buf[256];
17703
  ERR_error_string_n(static_cast<unsigned long>(code), buf, sizeof(buf));
17704
  return std::string(buf);
17705
}
17706
17707
inline ca_store_t create_ca_store(const char *pem, size_t len) {
17708
  auto mem = BIO_new_mem_buf(pem, static_cast<int>(len));
17709
  if (!mem) { return nullptr; }
17710
  auto mem_guard = detail::scope_exit([&] { BIO_free_all(mem); });
17711
17712
  auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
17713
  if (!inf) { return nullptr; }
17714
17715
  auto store = X509_STORE_new();
17716
  if (store) {
17717
    for (auto i = 0; i < static_cast<int>(sk_X509_INFO_num(inf)); i++) {
17718
      auto itmp = sk_X509_INFO_value(inf, i);
17719
      if (!itmp) { continue; }
17720
      if (itmp->x509) { X509_STORE_add_cert(store, itmp->x509); }
17721
      if (itmp->crl) { X509_STORE_add_crl(store, itmp->crl); }
17722
    }
17723
  }
17724
17725
  sk_X509_INFO_pop_free(inf, X509_INFO_free);
17726
  return static_cast<ca_store_t>(store);
17727
}
17728
17729
inline void free_ca_store(ca_store_t store) {
17730
  if (store) { X509_STORE_free(static_cast<X509_STORE *>(store)); }
17731
}
17732
17733
inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
17734
  if (!ctx || !store) { return false; }
17735
  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17736
  auto x509_store = static_cast<X509_STORE *>(store);
17737
17738
  // Check if same store is already set
17739
  if (SSL_CTX_get_cert_store(ssl_ctx) == x509_store) { return true; }
17740
17741
  // SSL_CTX_set_cert_store takes ownership and frees the old store
17742
  SSL_CTX_set_cert_store(ssl_ctx, x509_store);
17743
  return true;
17744
}
17745
17746
inline size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
17747
  certs.clear();
17748
  if (!ctx) { return 0; }
17749
  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17750
17751
  auto store = SSL_CTX_get_cert_store(ssl_ctx);
17752
  if (!store) { return 0; }
17753
17754
  auto objs = impl::get_store_objects(store);
17755
  if (!objs) { return 0; }
17756
  auto se = detail::scope_exit([&] { impl::release_store_objects(objs); });
17757
17758
  auto count = sk_X509_OBJECT_num(objs);
17759
  for (decltype(count) i = 0; i < count; i++) {
17760
    auto obj = sk_X509_OBJECT_value(objs, i);
17761
    if (!obj) { continue; }
17762
    if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
17763
      auto x509 = X509_OBJECT_get0_X509(obj);
17764
      if (x509) {
17765
        // Increment reference count so caller can free it
17766
        X509_up_ref(x509);
17767
        certs.push_back(static_cast<cert_t>(x509));
17768
      }
17769
    }
17770
  }
17771
  return certs.size();
17772
}
17773
17774
inline std::vector<std::string> get_ca_names(ctx_t ctx) {
17775
  std::vector<std::string> names;
17776
  if (!ctx) { return names; }
17777
  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17778
17779
  auto store = SSL_CTX_get_cert_store(ssl_ctx);
17780
  if (!store) { return names; }
17781
17782
  auto objs = impl::get_store_objects(store);
17783
  if (!objs) { return names; }
17784
  auto se = detail::scope_exit([&] { impl::release_store_objects(objs); });
17785
17786
  auto count = sk_X509_OBJECT_num(objs);
17787
  for (decltype(count) i = 0; i < count; i++) {
17788
    auto obj = sk_X509_OBJECT_value(objs, i);
17789
    if (!obj) { continue; }
17790
    if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
17791
      auto x509 = X509_OBJECT_get0_X509(obj);
17792
      if (x509) {
17793
        auto subject = X509_get_subject_name(x509);
17794
        if (subject) {
17795
          char buf[512];
17796
          X509_NAME_oneline(subject, buf, sizeof(buf));
17797
          names.push_back(buf);
17798
        }
17799
      }
17800
    }
17801
  }
17802
  return names;
17803
}
17804
17805
inline bool update_server_cert(ctx_t ctx, const char *cert_pem,
17806
                               const char *key_pem, const char *password) {
17807
  if (!ctx || !cert_pem || !key_pem) { return false; }
17808
  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17809
17810
  // Load certificate from PEM
17811
  auto cert_bio = BIO_new_mem_buf(cert_pem, -1);
17812
  if (!cert_bio) { return false; }
17813
  auto cert = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr);
17814
  BIO_free(cert_bio);
17815
  if (!cert) { return false; }
17816
17817
  // Load private key from PEM
17818
  auto key_bio = BIO_new_mem_buf(key_pem, -1);
17819
  if (!key_bio) {
17820
    X509_free(cert);
17821
    return false;
17822
  }
17823
  auto key = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr,
17824
                                     password ? const_cast<char *>(password)
17825
                                              : nullptr);
17826
  BIO_free(key_bio);
17827
  if (!key) {
17828
    X509_free(cert);
17829
    return false;
17830
  }
17831
17832
  // Update certificate and key
17833
  auto ret = SSL_CTX_use_certificate(ssl_ctx, cert) == 1 &&
17834
             SSL_CTX_use_PrivateKey(ssl_ctx, key) == 1;
17835
17836
  X509_free(cert);
17837
  EVP_PKEY_free(key);
17838
  return ret;
17839
}
17840
17841
inline bool update_server_client_ca(ctx_t ctx, const char *ca_pem) {
17842
  if (!ctx || !ca_pem) { return false; }
17843
  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17844
17845
  // Create new X509_STORE from PEM
17846
  auto store = create_ca_store(ca_pem, strlen(ca_pem));
17847
  if (!store) { return false; }
17848
17849
  // SSL_CTX_set_cert_store takes ownership
17850
  SSL_CTX_set_cert_store(ssl_ctx, static_cast<X509_STORE *>(store));
17851
17852
  // Set client CA list for client certificate request
17853
  auto ca_list = impl::create_client_ca_list_from_pem(ca_pem);
17854
  if (ca_list) {
17855
    // SSL_CTX_set_client_CA_list takes ownership of ca_list
17856
    SSL_CTX_set_client_CA_list(ssl_ctx, ca_list);
17857
  }
17858
17859
  return true;
17860
}
17861
17862
inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
17863
  if (!ctx) { return false; }
17864
  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
17865
17866
  impl::get_verify_callback() = std::move(callback);
17867
17868
  if (impl::get_verify_callback()) {
17869
    SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, impl::openssl_verify_callback);
17870
  } else {
17871
    SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
17872
  }
17873
  return true;
17874
}
17875
17876
inline long get_verify_error(const_session_t session) {
17877
  if (!session) { return -1; }
17878
  auto ssl = static_cast<SSL *>(const_cast<void *>(session));
17879
  return SSL_get_verify_result(ssl);
17880
}
17881
17882
inline std::string verify_error_string(long error_code) {
17883
  if (error_code == X509_V_OK) { return ""; }
17884
  const char *str = X509_verify_cert_error_string(static_cast<int>(error_code));
17885
  return str ? str : "unknown error";
17886
}
17887
17888
} // namespace tls
17889
17890
#endif // CPPHTTPLIB_OPENSSL_SUPPORT
17891
17892
/*
17893
 * Group 9: TLS abstraction layer - Mbed TLS backend
17894
 */
17895
17896
/*
17897
 * Mbed TLS Backend Implementation
17898
 */
17899
17900
#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
17901
namespace tls {
17902
17903
namespace impl {
17904
17905
// Mbed TLS session wrapper
17906
struct MbedTlsSession {
17907
  mbedtls_ssl_context ssl;
17908
  socket_t sock = INVALID_SOCKET;
17909
  std::string hostname;     // For client: set via set_sni
17910
  std::string sni_hostname; // For server: received from client via SNI callback
17911
17912
  MbedTlsSession() { mbedtls_ssl_init(&ssl); }
17913
17914
  ~MbedTlsSession() { mbedtls_ssl_free(&ssl); }
17915
17916
  MbedTlsSession(const MbedTlsSession &) = delete;
17917
  MbedTlsSession &operator=(const MbedTlsSession &) = delete;
17918
};
17919
17920
// Thread-local error code accessor for Mbed TLS (since it doesn't have an error
17921
// queue)
17922
inline int &mbedtls_last_error() {
17923
  static thread_local int err = 0;
17924
  return err;
17925
}
17926
17927
// Helper to map Mbed TLS error to ErrorCode
17928
inline ErrorCode map_mbedtls_error(int ret, int &out_errno) {
17929
  if (ret == 0) { return ErrorCode::Success; }
17930
  if (ret == MBEDTLS_ERR_SSL_WANT_READ) { return ErrorCode::WantRead; }
17931
  if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) { return ErrorCode::WantWrite; }
17932
  if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
17933
    return ErrorCode::PeerClosed;
17934
  }
17935
  if (ret == MBEDTLS_ERR_NET_CONN_RESET || ret == MBEDTLS_ERR_NET_SEND_FAILED ||
17936
      ret == MBEDTLS_ERR_NET_RECV_FAILED) {
17937
    out_errno = errno;
17938
    return ErrorCode::SyscallError;
17939
  }
17940
  if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) {
17941
    return ErrorCode::CertVerifyFailed;
17942
  }
17943
  return ErrorCode::Fatal;
17944
}
17945
17946
// BIO-like send callback for Mbed TLS
17947
inline int mbedtls_net_send_cb(void *ctx, const unsigned char *buf,
17948
                               size_t len) {
17949
  auto sock = *static_cast<socket_t *>(ctx);
17950
#ifdef _WIN32
17951
  auto ret =
17952
      send(sock, reinterpret_cast<const char *>(buf), static_cast<int>(len), 0);
17953
  if (ret == SOCKET_ERROR) {
17954
    int err = WSAGetLastError();
17955
    if (err == WSAEWOULDBLOCK) { return MBEDTLS_ERR_SSL_WANT_WRITE; }
17956
    return MBEDTLS_ERR_NET_SEND_FAILED;
17957
  }
17958
#else
17959
  auto ret = send(sock, buf, len, 0);
17960
  if (ret < 0) {
17961
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
17962
      return MBEDTLS_ERR_SSL_WANT_WRITE;
17963
    }
17964
    return MBEDTLS_ERR_NET_SEND_FAILED;
17965
  }
17966
#endif
17967
  return static_cast<int>(ret);
17968
}
17969
17970
// BIO-like recv callback for Mbed TLS
17971
inline int mbedtls_net_recv_cb(void *ctx, unsigned char *buf, size_t len) {
17972
  auto sock = *static_cast<socket_t *>(ctx);
17973
#ifdef _WIN32
17974
  auto ret =
17975
      recv(sock, reinterpret_cast<char *>(buf), static_cast<int>(len), 0);
17976
  if (ret == SOCKET_ERROR) {
17977
    int err = WSAGetLastError();
17978
    if (err == WSAEWOULDBLOCK) { return MBEDTLS_ERR_SSL_WANT_READ; }
17979
    return MBEDTLS_ERR_NET_RECV_FAILED;
17980
  }
17981
#else
17982
  auto ret = recv(sock, buf, len, 0);
17983
  if (ret < 0) {
17984
    if (errno == EAGAIN || errno == EWOULDBLOCK) {
17985
      return MBEDTLS_ERR_SSL_WANT_READ;
17986
    }
17987
    return MBEDTLS_ERR_NET_RECV_FAILED;
17988
  }
17989
#endif
17990
  if (ret == 0) { return MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY; }
17991
  return static_cast<int>(ret);
17992
}
17993
17994
// MbedTlsContext constructor/destructor implementations
17995
inline MbedTlsContext::MbedTlsContext() {
17996
  mbedtls_ssl_config_init(&conf);
17997
  mbedtls_entropy_init(&entropy);
17998
  mbedtls_ctr_drbg_init(&ctr_drbg);
17999
  mbedtls_x509_crt_init(&ca_chain);
18000
  mbedtls_x509_crt_init(&own_cert);
18001
  mbedtls_pk_init(&own_key);
18002
}
18003
18004
inline MbedTlsContext::~MbedTlsContext() {
18005
  mbedtls_pk_free(&own_key);
18006
  mbedtls_x509_crt_free(&own_cert);
18007
  mbedtls_x509_crt_free(&ca_chain);
18008
  mbedtls_ctr_drbg_free(&ctr_drbg);
18009
  mbedtls_entropy_free(&entropy);
18010
  mbedtls_ssl_config_free(&conf);
18011
}
18012
18013
// Thread-local storage for SNI captured during handshake
18014
// This is needed because the SNI callback doesn't have a way to pass
18015
// session-specific data before the session is fully set up
18016
inline std::string &mbedpending_sni() {
18017
  static thread_local std::string sni;
18018
  return sni;
18019
}
18020
18021
// SNI callback for Mbed TLS server to capture client's SNI hostname
18022
inline int mbedtls_sni_callback(void *p_ctx, mbedtls_ssl_context *ssl,
18023
                                const unsigned char *name, size_t name_len) {
18024
  (void)p_ctx;
18025
  (void)ssl;
18026
18027
  // Store SNI name in thread-local storage
18028
  // It will be retrieved and stored in the session after handshake
18029
  if (name && name_len > 0) {
18030
    mbedpending_sni().assign(reinterpret_cast<const char *>(name), name_len);
18031
  } else {
18032
    mbedpending_sni().clear();
18033
  }
18034
  return 0; // Accept any SNI
18035
}
18036
18037
inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt,
18038
                                   int cert_depth, uint32_t *flags);
18039
18040
// MbedTLS verify callback wrapper
18041
inline int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt,
18042
                                   int cert_depth, uint32_t *flags) {
18043
  auto &callback = get_verify_callback();
18044
  if (!callback) { return 0; } // Continue with default verification
18045
18046
  // data points to the MbedTlsSession
18047
  auto *session = static_cast<MbedTlsSession *>(data);
18048
18049
  // Build context
18050
  VerifyContext verify_ctx;
18051
  verify_ctx.session = static_cast<session_t>(session);
18052
  verify_ctx.cert = static_cast<cert_t>(crt);
18053
  verify_ctx.depth = cert_depth;
18054
  verify_ctx.preverify_ok = (*flags == 0);
18055
  verify_ctx.error_code = static_cast<long>(*flags);
18056
18057
  // Convert Mbed TLS flags to error string
18058
  static thread_local char error_buf[256];
18059
  if (*flags != 0) {
18060
    mbedtls_x509_crt_verify_info(error_buf, sizeof(error_buf), "", *flags);
18061
    verify_ctx.error_string = error_buf;
18062
  } else {
18063
    verify_ctx.error_string = nullptr;
18064
  }
18065
18066
  bool accepted = callback(verify_ctx);
18067
18068
  if (accepted) {
18069
    *flags = 0; // Clear all error flags
18070
    return 0;
18071
  }
18072
  return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
18073
}
18074
18075
} // namespace impl
18076
18077
inline ctx_t create_client_context() {
18078
  auto ctx = new (std::nothrow) impl::MbedTlsContext();
18079
  if (!ctx) { return nullptr; }
18080
18081
  ctx->is_server = false;
18082
18083
  // Seed the random number generator
18084
  const char *pers = "httplib_client";
18085
  int ret = mbedtls_ctr_drbg_seed(
18086
      &ctx->ctr_drbg, mbedtls_entropy_func, &ctx->entropy,
18087
      reinterpret_cast<const unsigned char *>(pers), strlen(pers));
18088
  if (ret != 0) {
18089
    impl::mbedtls_last_error() = ret;
18090
    delete ctx;
18091
    return nullptr;
18092
  }
18093
18094
  // Set up SSL config for client
18095
  ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_CLIENT,
18096
                                    MBEDTLS_SSL_TRANSPORT_STREAM,
18097
                                    MBEDTLS_SSL_PRESET_DEFAULT);
18098
  if (ret != 0) {
18099
    impl::mbedtls_last_error() = ret;
18100
    delete ctx;
18101
    return nullptr;
18102
  }
18103
18104
  // Set random number generator
18105
  mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
18106
18107
  // Default: verify peer certificate
18108
  mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
18109
18110
  // Set minimum TLS version to 1.2
18111
#ifdef CPPHTTPLIB_MBEDTLS_V3
18112
  mbedtls_ssl_conf_min_tls_version(&ctx->conf, MBEDTLS_SSL_VERSION_TLS1_2);
18113
#else
18114
  mbedtls_ssl_conf_min_version(&ctx->conf, MBEDTLS_SSL_MAJOR_VERSION_3,
18115
                               MBEDTLS_SSL_MINOR_VERSION_3);
18116
#endif
18117
18118
  return static_cast<ctx_t>(ctx);
18119
}
18120
18121
inline ctx_t create_server_context() {
18122
  auto ctx = new (std::nothrow) impl::MbedTlsContext();
18123
  if (!ctx) { return nullptr; }
18124
18125
  ctx->is_server = true;
18126
18127
  // Seed the random number generator
18128
  const char *pers = "httplib_server";
18129
  int ret = mbedtls_ctr_drbg_seed(
18130
      &ctx->ctr_drbg, mbedtls_entropy_func, &ctx->entropy,
18131
      reinterpret_cast<const unsigned char *>(pers), strlen(pers));
18132
  if (ret != 0) {
18133
    impl::mbedtls_last_error() = ret;
18134
    delete ctx;
18135
    return nullptr;
18136
  }
18137
18138
  // Set up SSL config for server
18139
  ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_SERVER,
18140
                                    MBEDTLS_SSL_TRANSPORT_STREAM,
18141
                                    MBEDTLS_SSL_PRESET_DEFAULT);
18142
  if (ret != 0) {
18143
    impl::mbedtls_last_error() = ret;
18144
    delete ctx;
18145
    return nullptr;
18146
  }
18147
18148
  // Set random number generator
18149
  mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
18150
18151
  // Default: don't verify client
18152
  mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_NONE);
18153
18154
  // Set minimum TLS version to 1.2
18155
#ifdef CPPHTTPLIB_MBEDTLS_V3
18156
  mbedtls_ssl_conf_min_tls_version(&ctx->conf, MBEDTLS_SSL_VERSION_TLS1_2);
18157
#else
18158
  mbedtls_ssl_conf_min_version(&ctx->conf, MBEDTLS_SSL_MAJOR_VERSION_3,
18159
                               MBEDTLS_SSL_MINOR_VERSION_3);
18160
#endif
18161
18162
  // Set SNI callback to capture client's SNI hostname
18163
  mbedtls_ssl_conf_sni(&ctx->conf, impl::mbedtls_sni_callback, nullptr);
18164
18165
  return static_cast<ctx_t>(ctx);
18166
}
18167
18168
inline void free_context(ctx_t ctx) {
18169
  if (ctx) { delete static_cast<impl::MbedTlsContext *>(ctx); }
18170
}
18171
18172
inline bool set_min_version(ctx_t ctx, Version version) {
18173
  if (!ctx) { return false; }
18174
  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18175
18176
#ifdef CPPHTTPLIB_MBEDTLS_V3
18177
  // Mbed TLS 3.x uses mbedtls_ssl_protocol_version enum
18178
  mbedtls_ssl_protocol_version min_ver = MBEDTLS_SSL_VERSION_TLS1_2;
18179
  if (version >= Version::TLS1_3) {
18180
#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
18181
    min_ver = MBEDTLS_SSL_VERSION_TLS1_3;
18182
#endif
18183
  }
18184
  mbedtls_ssl_conf_min_tls_version(&mctx->conf, min_ver);
18185
#else
18186
  // Mbed TLS 2.x uses major/minor version numbers
18187
  int major = MBEDTLS_SSL_MAJOR_VERSION_3;
18188
  int minor = MBEDTLS_SSL_MINOR_VERSION_3; // TLS 1.2
18189
  if (version >= Version::TLS1_3) {
18190
#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
18191
    minor = MBEDTLS_SSL_MINOR_VERSION_4; // TLS 1.3
18192
#else
18193
    minor = MBEDTLS_SSL_MINOR_VERSION_3; // Fall back to TLS 1.2
18194
#endif
18195
  }
18196
  mbedtls_ssl_conf_min_version(&mctx->conf, major, minor);
18197
#endif
18198
  return true;
18199
}
18200
18201
inline bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) {
18202
  if (!ctx || !pem) { return false; }
18203
  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18204
18205
  // mbedtls_x509_crt_parse expects null-terminated string for PEM
18206
  // Add null terminator if not present
18207
  std::string pem_str(pem, len);
18208
  int ret = mbedtls_x509_crt_parse(
18209
      &mctx->ca_chain, reinterpret_cast<const unsigned char *>(pem_str.c_str()),
18210
      pem_str.size() + 1);
18211
  if (ret != 0) {
18212
    impl::mbedtls_last_error() = ret;
18213
    return false;
18214
  }
18215
18216
  mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
18217
  return true;
18218
}
18219
18220
inline bool load_ca_file(ctx_t ctx, const char *file_path) {
18221
  if (!ctx || !file_path) { return false; }
18222
  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18223
18224
  int ret = mbedtls_x509_crt_parse_file(&mctx->ca_chain, file_path);
18225
  if (ret != 0) {
18226
    impl::mbedtls_last_error() = ret;
18227
    return false;
18228
  }
18229
18230
  mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
18231
  return true;
18232
}
18233
18234
inline bool load_ca_dir(ctx_t ctx, const char *dir_path) {
18235
  if (!ctx || !dir_path) { return false; }
18236
  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18237
18238
  int ret = mbedtls_x509_crt_parse_path(&mctx->ca_chain, dir_path);
18239
  if (ret < 0) { // Returns number of certs on success, negative on error
18240
    impl::mbedtls_last_error() = ret;
18241
    return false;
18242
  }
18243
18244
  mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
18245
  return true;
18246
}
18247
18248
inline bool load_system_certs(ctx_t ctx) {
18249
  if (!ctx) { return false; }
18250
  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18251
  bool loaded = false;
18252
18253
#ifdef _WIN32
18254
  loaded = impl::enumerate_windows_system_certs(
18255
      [&](const unsigned char *data, size_t len) {
18256
        return mbedtls_x509_crt_parse_der(&mctx->ca_chain, data, len) == 0;
18257
      });
18258
#elif defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
18259
  loaded = impl::enumerate_macos_keychain_certs(
18260
      [&](const unsigned char *data, size_t len) {
18261
        return mbedtls_x509_crt_parse_der(&mctx->ca_chain, data, len) == 0;
18262
      });
18263
#else
18264
  for (auto path = impl::system_ca_paths(); *path; ++path) {
18265
    if (mbedtls_x509_crt_parse_file(&mctx->ca_chain, *path) >= 0) {
18266
      loaded = true;
18267
      break;
18268
    }
18269
  }
18270
18271
  if (!loaded) {
18272
    for (auto dir = impl::system_ca_dirs(); *dir; ++dir) {
18273
      if (mbedtls_x509_crt_parse_path(&mctx->ca_chain, *dir) >= 0) {
18274
        loaded = true;
18275
        break;
18276
      }
18277
    }
18278
  }
18279
#endif
18280
18281
  if (loaded) {
18282
    mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
18283
  }
18284
  return loaded;
18285
}
18286
18287
inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
18288
                                const char *password) {
18289
  if (!ctx || !cert || !key) { return false; }
18290
  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18291
18292
  // Parse certificate
18293
  std::string cert_str(cert);
18294
  int ret = mbedtls_x509_crt_parse(
18295
      &mctx->own_cert,
18296
      reinterpret_cast<const unsigned char *>(cert_str.c_str()),
18297
      cert_str.size() + 1);
18298
  if (ret != 0) {
18299
    impl::mbedtls_last_error() = ret;
18300
    return false;
18301
  }
18302
18303
  // Parse private key
18304
  std::string key_str(key);
18305
  const unsigned char *pwd =
18306
      password ? reinterpret_cast<const unsigned char *>(password) : nullptr;
18307
  size_t pwd_len = password ? strlen(password) : 0;
18308
18309
#ifdef CPPHTTPLIB_MBEDTLS_V3
18310
  ret = mbedtls_pk_parse_key(
18311
      &mctx->own_key, reinterpret_cast<const unsigned char *>(key_str.c_str()),
18312
      key_str.size() + 1, pwd, pwd_len, mbedtls_ctr_drbg_random,
18313
      &mctx->ctr_drbg);
18314
#else
18315
  ret = mbedtls_pk_parse_key(
18316
      &mctx->own_key, reinterpret_cast<const unsigned char *>(key_str.c_str()),
18317
      key_str.size() + 1, pwd, pwd_len);
18318
#endif
18319
  if (ret != 0) {
18320
    impl::mbedtls_last_error() = ret;
18321
    return false;
18322
  }
18323
18324
  // Verify that the certificate and private key match
18325
#ifdef CPPHTTPLIB_MBEDTLS_V3
18326
  ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key,
18327
                              mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
18328
#else
18329
  ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key);
18330
#endif
18331
  if (ret != 0) {
18332
    impl::mbedtls_last_error() = ret;
18333
    return false;
18334
  }
18335
18336
  ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key);
18337
  if (ret != 0) {
18338
    impl::mbedtls_last_error() = ret;
18339
    return false;
18340
  }
18341
18342
  return true;
18343
}
18344
18345
inline bool set_client_cert_file(ctx_t ctx, const char *cert_path,
18346
                                 const char *key_path, const char *password) {
18347
  if (!ctx || !cert_path || !key_path) { return false; }
18348
  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18349
18350
  // Parse certificate file
18351
  int ret = mbedtls_x509_crt_parse_file(&mctx->own_cert, cert_path);
18352
  if (ret != 0) {
18353
    impl::mbedtls_last_error() = ret;
18354
    return false;
18355
  }
18356
18357
  // Parse private key file
18358
#ifdef CPPHTTPLIB_MBEDTLS_V3
18359
  ret = mbedtls_pk_parse_keyfile(&mctx->own_key, key_path, password,
18360
                                 mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
18361
#else
18362
  ret = mbedtls_pk_parse_keyfile(&mctx->own_key, key_path, password);
18363
#endif
18364
  if (ret != 0) {
18365
    impl::mbedtls_last_error() = ret;
18366
    return false;
18367
  }
18368
18369
  // Verify that the certificate and private key match
18370
#ifdef CPPHTTPLIB_MBEDTLS_V3
18371
  ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key,
18372
                              mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
18373
#else
18374
  ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key);
18375
#endif
18376
  if (ret != 0) {
18377
    impl::mbedtls_last_error() = ret;
18378
    return false;
18379
  }
18380
18381
  ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key);
18382
  if (ret != 0) {
18383
    impl::mbedtls_last_error() = ret;
18384
    return false;
18385
  }
18386
18387
  return true;
18388
}
18389
18390
inline void set_verify_client(ctx_t ctx, bool require) {
18391
  if (!ctx) { return; }
18392
  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18393
  mctx->verify_client = require;
18394
  if (require) {
18395
    mbedtls_ssl_conf_authmode(&mctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
18396
  } else {
18397
    // If a verify callback is set, use OPTIONAL mode to ensure the callback
18398
    // is called (matching OpenSSL behavior). Otherwise use NONE.
18399
    mbedtls_ssl_conf_authmode(&mctx->conf, mctx->has_verify_callback
18400
                                               ? MBEDTLS_SSL_VERIFY_OPTIONAL
18401
                                               : MBEDTLS_SSL_VERIFY_NONE);
18402
  }
18403
}
18404
18405
inline session_t create_session(ctx_t ctx, socket_t sock) {
18406
  if (!ctx || sock == INVALID_SOCKET) { return nullptr; }
18407
  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
18408
18409
  auto session = new (std::nothrow) impl::MbedTlsSession();
18410
  if (!session) { return nullptr; }
18411
18412
  session->sock = sock;
18413
18414
  int ret = mbedtls_ssl_setup(&session->ssl, &mctx->conf);
18415
  if (ret != 0) {
18416
    impl::mbedtls_last_error() = ret;
18417
    delete session;
18418
    return nullptr;
18419
  }
18420
18421
  // Explicitly opt out of in-handshake hostname verification by default;
18422
  // since Mbed TLS 3.6.4 a client handshake with certificate verification
18423
  // fails outright when no hostname was set. set_sni() installs the real
18424
  // hostname for DNS hosts; for IP hosts (where SNI must not be set) the
18425
  // caller verifies the certificate identity post-handshake via
18426
  // verify_hostname().
18427
  mbedtls_ssl_set_hostname(&session->ssl, nullptr);
18428
18429
  // Set BIO callbacks
18430
  mbedtls_ssl_set_bio(&session->ssl, &session->sock, impl::mbedtls_net_send_cb,
18431
                      impl::mbedtls_net_recv_cb, nullptr);
18432
18433
  // Set per-session verify callback with session pointer if callback is
18434
  // registered
18435
  if (mctx->has_verify_callback) {
18436
    mbedtls_ssl_set_verify(&session->ssl, impl::mbedtls_verify_callback,
18437
                           session);
18438
  }
18439
18440
  return static_cast<session_t>(session);
18441
}
18442
18443
inline void free_session(session_t session) {
18444
  if (session) { delete static_cast<impl::MbedTlsSession *>(session); }
18445
}
18446
18447
inline bool set_sni(session_t session, const char *hostname) {
18448
  if (!session || !hostname) { return false; }
18449
  auto msession = static_cast<impl::MbedTlsSession *>(session);
18450
18451
  int ret = mbedtls_ssl_set_hostname(&msession->ssl, hostname);
18452
  if (ret != 0) {
18453
    impl::mbedtls_last_error() = ret;
18454
    return false;
18455
  }
18456
18457
  msession->hostname = hostname;
18458
  return true;
18459
}
18460
18461
inline bool set_hostname(session_t session, const char *hostname) {
18462
  // In Mbed TLS, set_hostname also sets up hostname verification
18463
  return set_sni(session, hostname);
18464
}
18465
18466
inline TlsError connect(session_t session) {
18467
  TlsError err;
18468
  if (!session) {
18469
    err.code = ErrorCode::Fatal;
18470
    return err;
18471
  }
18472
18473
  auto msession = static_cast<impl::MbedTlsSession *>(session);
18474
  int ret = mbedtls_ssl_handshake(&msession->ssl);
18475
18476
  if (ret == 0) {
18477
    err.code = ErrorCode::Success;
18478
  } else {
18479
    err.code = impl::map_mbedtls_error(ret, err.sys_errno);
18480
    err.backend_code = static_cast<uint64_t>(-ret);
18481
    impl::mbedtls_last_error() = ret;
18482
  }
18483
18484
  return err;
18485
}
18486
18487
inline TlsError accept(session_t session) {
18488
  // Same as connect for Mbed TLS - handshake works for both client and server
18489
  auto result = connect(session);
18490
18491
  // After successful handshake, capture SNI from thread-local storage
18492
  if (result.code == ErrorCode::Success && session) {
18493
    auto msession = static_cast<impl::MbedTlsSession *>(session);
18494
    msession->sni_hostname = std::move(impl::mbedpending_sni());
18495
    impl::mbedpending_sni().clear();
18496
  }
18497
18498
  return result;
18499
}
18500
18501
inline bool connect_nonblocking(session_t session, socket_t sock,
18502
                                time_t timeout_sec, time_t timeout_usec,
18503
                                TlsError *err) {
18504
  if (!session) {
18505
    if (err) { err->code = ErrorCode::Fatal; }
18506
    return false;
18507
  }
18508
18509
  auto msession = static_cast<impl::MbedTlsSession *>(session);
18510
18511
  // Set socket to non-blocking mode
18512
  detail::set_nonblocking(sock, true);
18513
  auto cleanup =
18514
      detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
18515
18516
  int ret;
18517
  while ((ret = mbedtls_ssl_handshake(&msession->ssl)) != 0) {
18518
    if (ret == MBEDTLS_ERR_SSL_WANT_READ) {
18519
      if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
18520
        continue;
18521
      }
18522
    } else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
18523
      if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
18524
        continue;
18525
      }
18526
    }
18527
18528
    // TlsError or timeout
18529
    if (err) {
18530
      err->code = impl::map_mbedtls_error(ret, err->sys_errno);
18531
      err->backend_code = static_cast<uint64_t>(-ret);
18532
    }
18533
    impl::mbedtls_last_error() = ret;
18534
    return false;
18535
  }
18536
18537
  if (err) { err->code = ErrorCode::Success; }
18538
  return true;
18539
}
18540
18541
inline bool accept_nonblocking(session_t session, socket_t sock,
18542
                               time_t timeout_sec, time_t timeout_usec,
18543
                               TlsError *err) {
18544
  // Same implementation as connect for Mbed TLS
18545
  bool result =
18546
      connect_nonblocking(session, sock, timeout_sec, timeout_usec, err);
18547
18548
  // After successful handshake, capture SNI from thread-local storage
18549
  if (result && session) {
18550
    auto msession = static_cast<impl::MbedTlsSession *>(session);
18551
    msession->sni_hostname = std::move(impl::mbedpending_sni());
18552
    impl::mbedpending_sni().clear();
18553
  }
18554
18555
  return result;
18556
}
18557
18558
inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
18559
  if (!session || !buf) {
18560
    err.code = ErrorCode::Fatal;
18561
    return -1;
18562
  }
18563
18564
  auto msession = static_cast<impl::MbedTlsSession *>(session);
18565
  int ret =
18566
      mbedtls_ssl_read(&msession->ssl, static_cast<unsigned char *>(buf), len);
18567
18568
  if (ret > 0) {
18569
    err.code = ErrorCode::Success;
18570
    return static_cast<ssize_t>(ret);
18571
  }
18572
18573
  if (ret == 0) {
18574
    err.code = ErrorCode::PeerClosed;
18575
    return 0;
18576
  }
18577
18578
  err.code = impl::map_mbedtls_error(ret, err.sys_errno);
18579
  err.backend_code = static_cast<uint64_t>(-ret);
18580
  impl::mbedtls_last_error() = ret;
18581
  // mbedTLS signals a clean close_notify via a negative error code rather
18582
  // than 0; surface it as a clean EOF the way OpenSSL/wolfSSL do.
18583
  if (err.code == ErrorCode::PeerClosed) { return 0; }
18584
  return -1;
18585
}
18586
18587
inline ssize_t write(session_t session, const void *buf, size_t len,
18588
                     TlsError &err) {
18589
  if (!session || !buf) {
18590
    err.code = ErrorCode::Fatal;
18591
    return -1;
18592
  }
18593
18594
  auto msession = static_cast<impl::MbedTlsSession *>(session);
18595
  int ret = mbedtls_ssl_write(&msession->ssl,
18596
                              static_cast<const unsigned char *>(buf), len);
18597
18598
  if (ret > 0) {
18599
    err.code = ErrorCode::Success;
18600
    return static_cast<ssize_t>(ret);
18601
  }
18602
18603
  if (ret == 0) {
18604
    err.code = ErrorCode::PeerClosed;
18605
    return 0;
18606
  }
18607
18608
  err.code = impl::map_mbedtls_error(ret, err.sys_errno);
18609
  err.backend_code = static_cast<uint64_t>(-ret);
18610
  impl::mbedtls_last_error() = ret;
18611
  return -1;
18612
}
18613
18614
inline int pending(const_session_t session) {
18615
  if (!session) { return 0; }
18616
  auto msession =
18617
      static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
18618
  return static_cast<int>(mbedtls_ssl_get_bytes_avail(&msession->ssl));
18619
}
18620
18621
inline void shutdown(session_t session, bool graceful) {
18622
  if (!session) { return; }
18623
  auto msession = static_cast<impl::MbedTlsSession *>(session);
18624
18625
  if (graceful) {
18626
    // Try to send close_notify, but don't block forever
18627
    int ret;
18628
    int attempts = 0;
18629
    while ((ret = mbedtls_ssl_close_notify(&msession->ssl)) != 0 &&
18630
           attempts < 3) {
18631
      if (ret != MBEDTLS_ERR_SSL_WANT_READ &&
18632
          ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
18633
        break;
18634
      }
18635
      attempts++;
18636
    }
18637
  }
18638
}
18639
18640
inline bool is_peer_closed(session_t session, socket_t sock) {
18641
  if (!session || sock == INVALID_SOCKET) { return true; }
18642
  auto msession = static_cast<impl::MbedTlsSession *>(session);
18643
18644
  // Check if there's already decrypted data available in the TLS buffer
18645
  // If so, the connection is definitely alive
18646
  if (mbedtls_ssl_get_bytes_avail(&msession->ssl) > 0) { return false; }
18647
18648
  // Set socket to non-blocking to avoid blocking on read
18649
  detail::set_nonblocking(sock, true);
18650
  auto cleanup =
18651
      detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
18652
18653
  // Try a 1-byte read to check connection status
18654
  // Note: This will consume the byte if data is available, but for the
18655
  // purpose of checking if peer is closed, this should be acceptable
18656
  // since we're only called when we expect the connection might be closing
18657
  unsigned char buf;
18658
  int ret = mbedtls_ssl_read(&msession->ssl, &buf, 1);
18659
18660
  // If we got data or WANT_READ (would block), connection is alive
18661
  if (ret > 0 || ret == MBEDTLS_ERR_SSL_WANT_READ) { return false; }
18662
18663
  // If we get a peer close notify or a connection reset, the peer is closed
18664
  return ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY ||
18665
         ret == MBEDTLS_ERR_NET_CONN_RESET || ret == 0;
18666
}
18667
18668
inline cert_t get_peer_cert(const_session_t session) {
18669
  if (!session) { return nullptr; }
18670
  auto msession =
18671
      static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
18672
18673
  // Mbed TLS returns a pointer to the internal peer cert chain.
18674
  // WARNING: This pointer is only valid while the session is active.
18675
  // Do not use the certificate after calling free_session().
18676
  const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(&msession->ssl);
18677
  return const_cast<mbedtls_x509_crt *>(cert);
18678
}
18679
18680
inline void free_cert(cert_t cert) {
18681
  // Mbed TLS: peer certificate is owned by the SSL context.
18682
  // No-op here, but callers should still call this for cross-backend
18683
  // portability.
18684
  (void)cert;
18685
}
18686
18687
inline bool verify_hostname(cert_t cert, const char *hostname) {
18688
  if (!cert || !hostname) { return false; }
18689
  auto mcert = static_cast<const mbedtls_x509_crt *>(cert);
18690
  std::string host_str(hostname);
18691
18692
  // Check if hostname is an IP address (IPv4 or IPv6)
18693
  unsigned char ip_bytes[16];
18694
  auto ip_len = impl::parse_ip_address(host_str, ip_bytes);
18695
  auto is_ip = ip_len > 0;
18696
18697
  // Check Subject Alternative Names (SAN)
18698
  // In Mbed TLS 3.x, subject_alt_names contains raw values without ASN.1 tags
18699
  // - DNS names: raw string bytes
18700
  // - IP addresses: raw IP bytes (4 for IPv4, 16 for IPv6)
18701
  const mbedtls_x509_sequence *san = &mcert->subject_alt_names;
18702
  while (san != nullptr && san->buf.p != nullptr && san->buf.len > 0) {
18703
    const unsigned char *p = san->buf.p;
18704
    size_t len = san->buf.len;
18705
18706
    if (is_ip) {
18707
      // For an IP host, only a matching iPAddress SAN of the same family
18708
      // (4 bytes for IPv4, 16 bytes for IPv6) may authenticate it.
18709
      if (len == ip_len && memcmp(p, ip_bytes, ip_len) == 0) { return true; }
18710
    } else {
18711
      // Check if this SAN is a DNS name (printable ASCII string)
18712
      bool is_dns = len > 0;
18713
      for (size_t i = 0; i < len && is_dns; i++) {
18714
        if (p[i] < 32 || p[i] > 126) { is_dns = false; }
18715
      }
18716
      if (is_dns) {
18717
        std::string san_name(reinterpret_cast<const char *>(p), len);
18718
        if (detail::match_hostname(san_name, host_str)) { return true; }
18719
      }
18720
    }
18721
    san = san->next;
18722
  }
18723
18724
  // Fallback: Check Common Name (CN) in subject. Skipped for IP-literal hosts:
18725
  // an IP identity is only valid via an iPAddress SAN, never the CN (RFC 9110;
18726
  // the OpenSSL backend's X509_check_ip behaves the same way).
18727
  if (!is_ip) {
18728
    char cn[256];
18729
    int ret = mbedtls_x509_dn_gets(cn, sizeof(cn), &mcert->subject);
18730
    if (ret > 0) {
18731
      std::string cn_str(cn);
18732
18733
      // Look for "CN=" in the DN string
18734
      size_t cn_pos = cn_str.find("CN=");
18735
      if (cn_pos != std::string::npos) {
18736
        size_t start = cn_pos + 3;
18737
        size_t end = cn_str.find(',', start);
18738
        std::string cn_value =
18739
            cn_str.substr(start, end == std::string::npos ? end : end - start);
18740
18741
        if (detail::match_hostname(cn_value, host_str)) { return true; }
18742
      }
18743
    }
18744
  }
18745
18746
  return false;
18747
}
18748
18749
inline uint64_t hostname_mismatch_code() {
18750
  return static_cast<uint64_t>(MBEDTLS_X509_BADCERT_CN_MISMATCH);
18751
}
18752
18753
inline long get_verify_result(const_session_t session) {
18754
  if (!session) { return -1; }
18755
  auto msession =
18756
      static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
18757
  uint32_t flags = mbedtls_ssl_get_verify_result(&msession->ssl);
18758
  // Return 0 (X509_V_OK equivalent) if verification passed
18759
  return flags == 0 ? 0 : static_cast<long>(flags);
18760
}
18761
18762
inline std::string get_cert_subject_cn(cert_t cert) {
18763
  if (!cert) return "";
18764
  auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18765
18766
  // Find the CN in the subject
18767
  const mbedtls_x509_name *name = &x509->subject;
18768
  while (name != nullptr) {
18769
    if (MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid) == 0) {
18770
      return std::string(reinterpret_cast<const char *>(name->val.p),
18771
                         name->val.len);
18772
    }
18773
    name = name->next;
18774
  }
18775
  return "";
18776
}
18777
18778
inline std::string get_cert_issuer_name(cert_t cert) {
18779
  if (!cert) return "";
18780
  auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18781
18782
  // Build a human-readable issuer name string
18783
  char buf[512];
18784
  int ret = mbedtls_x509_dn_gets(buf, sizeof(buf), &x509->issuer);
18785
  if (ret < 0) return "";
18786
  return std::string(buf);
18787
}
18788
18789
inline bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
18790
  sans.clear();
18791
  if (!cert) return false;
18792
  auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18793
18794
  // Parse the Subject Alternative Name extension
18795
  const mbedtls_x509_sequence *cur = &x509->subject_alt_names;
18796
  while (cur != nullptr) {
18797
    if (cur->buf.len > 0) {
18798
      // Mbed TLS stores SAN as ASN.1 sequences
18799
      // The tag byte indicates the type
18800
      const unsigned char *p = cur->buf.p;
18801
      size_t len = cur->buf.len;
18802
18803
      // First byte is the tag
18804
      unsigned char tag = *p;
18805
      p++;
18806
      len--;
18807
18808
      // Parse length (simple single-byte length assumed)
18809
      if (len > 0 && *p < 0x80) {
18810
        size_t value_len = *p;
18811
        p++;
18812
        len--;
18813
18814
        if (value_len <= len) {
18815
          SanEntry entry;
18816
          // ASN.1 context tags for GeneralName
18817
          switch (tag & 0x1F) {
18818
          case 2: // dNSName
18819
            entry.type = SanType::DNS;
18820
            entry.value =
18821
                std::string(reinterpret_cast<const char *>(p), value_len);
18822
            break;
18823
          case 7: // iPAddress
18824
            entry.type = SanType::IP;
18825
            if (value_len == 4) {
18826
              // IPv4
18827
              char buf[16];
18828
              snprintf(buf, sizeof(buf), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
18829
              entry.value = buf;
18830
            } else if (value_len == 16) {
18831
              // IPv6
18832
              char buf[64];
18833
              snprintf(buf, sizeof(buf),
18834
                       "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
18835
                       "%02x%02x:%02x%02x:%02x%02x:%02x%02x",
18836
                       p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8],
18837
                       p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
18838
              entry.value = buf;
18839
            }
18840
            break;
18841
          case 1: // rfc822Name (email)
18842
            entry.type = SanType::EMAIL;
18843
            entry.value =
18844
                std::string(reinterpret_cast<const char *>(p), value_len);
18845
            break;
18846
          case 6: // uniformResourceIdentifier
18847
            entry.type = SanType::URI;
18848
            entry.value =
18849
                std::string(reinterpret_cast<const char *>(p), value_len);
18850
            break;
18851
          default: entry.type = SanType::OTHER; break;
18852
          }
18853
18854
          if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
18855
        }
18856
      }
18857
    }
18858
    cur = cur->next;
18859
  }
18860
  return true;
18861
}
18862
18863
inline bool get_cert_validity(cert_t cert, time_t &not_before,
18864
                              time_t &not_after) {
18865
  if (!cert) return false;
18866
  auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18867
18868
  // Convert mbedtls_x509_time to time_t
18869
  auto to_time_t = [](const mbedtls_x509_time &t) -> time_t {
18870
    struct tm tm_time = {};
18871
    tm_time.tm_year = t.year - 1900;
18872
    tm_time.tm_mon = t.mon - 1;
18873
    tm_time.tm_mday = t.day;
18874
    tm_time.tm_hour = t.hour;
18875
    tm_time.tm_min = t.min;
18876
    tm_time.tm_sec = t.sec;
18877
#ifdef _WIN32
18878
    return _mkgmtime(&tm_time);
18879
#else
18880
    return timegm(&tm_time);
18881
#endif
18882
  };
18883
18884
  not_before = to_time_t(x509->valid_from);
18885
  not_after = to_time_t(x509->valid_to);
18886
  return true;
18887
}
18888
18889
inline std::string get_cert_serial(cert_t cert) {
18890
  if (!cert) return "";
18891
  auto x509 = static_cast<mbedtls_x509_crt *>(cert);
18892
18893
  // Convert serial number to hex string
18894
  std::string result;
18895
  result.reserve(x509->serial.len * 2);
18896
  for (size_t i = 0; i < x509->serial.len; i++) {
18897
    char hex[3];
18898
    snprintf(hex, sizeof(hex), "%02X", x509->serial.p[i]);
18899
    result += hex;
18900
  }
18901
  return result;
18902
}
18903
18904
inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
18905
  if (!cert) return false;
18906
  auto crt = static_cast<mbedtls_x509_crt *>(cert);
18907
  if (!crt->raw.p || crt->raw.len == 0) return false;
18908
  der.assign(crt->raw.p, crt->raw.p + crt->raw.len);
18909
  return true;
18910
}
18911
18912
inline const char *get_sni(const_session_t session) {
18913
  if (!session) return nullptr;
18914
  auto msession = static_cast<const impl::MbedTlsSession *>(session);
18915
18916
  // For server: return SNI received from client during handshake
18917
  if (!msession->sni_hostname.empty()) {
18918
    return msession->sni_hostname.c_str();
18919
  }
18920
18921
  // For client: return the hostname set via set_sni
18922
  if (!msession->hostname.empty()) { return msession->hostname.c_str(); }
18923
18924
  return nullptr;
18925
}
18926
18927
inline uint64_t peek_error() {
18928
  // Mbed TLS doesn't have an error queue, return the last error
18929
  return static_cast<uint64_t>(-impl::mbedtls_last_error());
18930
}
18931
18932
inline uint64_t get_error() {
18933
  // Mbed TLS doesn't have an error queue, return and clear the last error
18934
  uint64_t err = static_cast<uint64_t>(-impl::mbedtls_last_error());
18935
  impl::mbedtls_last_error() = 0;
18936
  return err;
18937
}
18938
18939
inline std::string error_string(uint64_t code) {
18940
  char buf[256];
18941
  mbedtls_strerror(-static_cast<int>(code), buf, sizeof(buf));
18942
  return std::string(buf);
18943
}
18944
18945
inline ca_store_t create_ca_store(const char *pem, size_t len) {
18946
  auto *ca_chain = new (std::nothrow) mbedtls_x509_crt;
18947
  if (!ca_chain) { return nullptr; }
18948
18949
  mbedtls_x509_crt_init(ca_chain);
18950
18951
  // mbedtls_x509_crt_parse expects null-terminated PEM
18952
  int ret = mbedtls_x509_crt_parse(ca_chain,
18953
                                   reinterpret_cast<const unsigned char *>(pem),
18954
                                   len + 1); // +1 for null terminator
18955
  if (ret != 0) {
18956
    // Try without +1 in case PEM is already null-terminated
18957
    ret = mbedtls_x509_crt_parse(
18958
        ca_chain, reinterpret_cast<const unsigned char *>(pem), len);
18959
    if (ret != 0) {
18960
      mbedtls_x509_crt_free(ca_chain);
18961
      delete ca_chain;
18962
      return nullptr;
18963
    }
18964
  }
18965
18966
  return static_cast<ca_store_t>(ca_chain);
18967
}
18968
18969
inline void free_ca_store(ca_store_t store) {
18970
  if (store) {
18971
    auto *ca_chain = static_cast<mbedtls_x509_crt *>(store);
18972
    mbedtls_x509_crt_free(ca_chain);
18973
    delete ca_chain;
18974
  }
18975
}
18976
18977
inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
18978
  if (!ctx || !store) { return false; }
18979
  auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
18980
  auto *ca_chain = static_cast<mbedtls_x509_crt *>(store);
18981
18982
  // Free existing CA chain
18983
  mbedtls_x509_crt_free(&mbed_ctx->ca_chain);
18984
  mbedtls_x509_crt_init(&mbed_ctx->ca_chain);
18985
18986
  // Copy the CA chain (deep copy)
18987
  // Parse from the raw data of the source cert
18988
  mbedtls_x509_crt *src = ca_chain;
18989
  while (src != nullptr) {
18990
    int ret = mbedtls_x509_crt_parse_der(&mbed_ctx->ca_chain, src->raw.p,
18991
                                         src->raw.len);
18992
    if (ret != 0) {
18993
      free_ca_store(store);
18994
      return false;
18995
    }
18996
    src = src->next;
18997
  }
18998
18999
  // This function takes ownership of the store; the chain was deep-copied
19000
  // above, so release the source
19001
  free_ca_store(store);
19002
19003
  // Update the SSL config to use the new CA chain
19004
  mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr);
19005
  return true;
19006
}
19007
19008
inline size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
19009
  certs.clear();
19010
  if (!ctx) { return 0; }
19011
  auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
19012
19013
  // Iterate through the CA chain
19014
  mbedtls_x509_crt *cert = &mbed_ctx->ca_chain;
19015
  while (cert != nullptr && cert->raw.len > 0) {
19016
    // Create a copy of the certificate for the caller
19017
    auto *copy = new mbedtls_x509_crt;
19018
    mbedtls_x509_crt_init(copy);
19019
    int ret = mbedtls_x509_crt_parse_der(copy, cert->raw.p, cert->raw.len);
19020
    if (ret == 0) {
19021
      certs.push_back(static_cast<cert_t>(copy));
19022
    } else {
19023
      mbedtls_x509_crt_free(copy);
19024
      delete copy;
19025
    }
19026
    cert = cert->next;
19027
  }
19028
  return certs.size();
19029
}
19030
19031
inline std::vector<std::string> get_ca_names(ctx_t ctx) {
19032
  std::vector<std::string> names;
19033
  if (!ctx) { return names; }
19034
  auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
19035
19036
  // Iterate through the CA chain
19037
  mbedtls_x509_crt *cert = &mbed_ctx->ca_chain;
19038
  while (cert != nullptr && cert->raw.len > 0) {
19039
    char buf[512];
19040
    int ret = mbedtls_x509_dn_gets(buf, sizeof(buf), &cert->subject);
19041
    if (ret > 0) { names.push_back(buf); }
19042
    cert = cert->next;
19043
  }
19044
  return names;
19045
}
19046
19047
inline bool update_server_cert(ctx_t ctx, const char *cert_pem,
19048
                               const char *key_pem, const char *password) {
19049
  if (!ctx || !cert_pem || !key_pem) { return false; }
19050
  auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
19051
19052
  // Free existing certificate and key
19053
  mbedtls_x509_crt_free(&mbed_ctx->own_cert);
19054
  mbedtls_pk_free(&mbed_ctx->own_key);
19055
  mbedtls_x509_crt_init(&mbed_ctx->own_cert);
19056
  mbedtls_pk_init(&mbed_ctx->own_key);
19057
19058
  // Parse certificate PEM
19059
  int ret = mbedtls_x509_crt_parse(
19060
      &mbed_ctx->own_cert, reinterpret_cast<const unsigned char *>(cert_pem),
19061
      strlen(cert_pem) + 1);
19062
  if (ret != 0) {
19063
    impl::mbedtls_last_error() = ret;
19064
    return false;
19065
  }
19066
19067
  // Parse private key PEM
19068
#ifdef CPPHTTPLIB_MBEDTLS_V3
19069
  ret = mbedtls_pk_parse_key(
19070
      &mbed_ctx->own_key, reinterpret_cast<const unsigned char *>(key_pem),
19071
      strlen(key_pem) + 1,
19072
      password ? reinterpret_cast<const unsigned char *>(password) : nullptr,
19073
      password ? strlen(password) : 0, mbedtls_ctr_drbg_random,
19074
      &mbed_ctx->ctr_drbg);
19075
#else
19076
  ret = mbedtls_pk_parse_key(
19077
      &mbed_ctx->own_key, reinterpret_cast<const unsigned char *>(key_pem),
19078
      strlen(key_pem) + 1,
19079
      password ? reinterpret_cast<const unsigned char *>(password) : nullptr,
19080
      password ? strlen(password) : 0);
19081
#endif
19082
  if (ret != 0) {
19083
    impl::mbedtls_last_error() = ret;
19084
    return false;
19085
  }
19086
19087
  // Configure SSL to use the new certificate and key
19088
  ret = mbedtls_ssl_conf_own_cert(&mbed_ctx->conf, &mbed_ctx->own_cert,
19089
                                  &mbed_ctx->own_key);
19090
  if (ret != 0) {
19091
    impl::mbedtls_last_error() = ret;
19092
    return false;
19093
  }
19094
19095
  return true;
19096
}
19097
19098
inline bool update_server_client_ca(ctx_t ctx, const char *ca_pem) {
19099
  if (!ctx || !ca_pem) { return false; }
19100
  auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
19101
19102
  // Free existing CA chain
19103
  mbedtls_x509_crt_free(&mbed_ctx->ca_chain);
19104
  mbedtls_x509_crt_init(&mbed_ctx->ca_chain);
19105
19106
  // Parse CA PEM
19107
  int ret = mbedtls_x509_crt_parse(
19108
      &mbed_ctx->ca_chain, reinterpret_cast<const unsigned char *>(ca_pem),
19109
      strlen(ca_pem) + 1);
19110
  if (ret != 0) {
19111
    impl::mbedtls_last_error() = ret;
19112
    return false;
19113
  }
19114
19115
  // Update SSL config to use new CA chain
19116
  mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr);
19117
  return true;
19118
}
19119
19120
inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
19121
  if (!ctx) { return false; }
19122
  auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
19123
19124
  impl::get_verify_callback() = std::move(callback);
19125
  mbed_ctx->has_verify_callback =
19126
      static_cast<bool>(impl::get_verify_callback());
19127
19128
  if (mbed_ctx->has_verify_callback) {
19129
    // Set OPTIONAL mode to ensure callback is called even when verification
19130
    // is disabled (matching OpenSSL behavior where SSL_VERIFY_PEER is set)
19131
    mbedtls_ssl_conf_authmode(&mbed_ctx->conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
19132
    mbedtls_ssl_conf_verify(&mbed_ctx->conf, impl::mbedtls_verify_callback,
19133
                            nullptr);
19134
  } else {
19135
    mbedtls_ssl_conf_verify(&mbed_ctx->conf, nullptr, nullptr);
19136
  }
19137
  return true;
19138
}
19139
19140
inline long get_verify_error(const_session_t session) {
19141
  if (!session) { return -1; }
19142
  auto *msession =
19143
      static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
19144
  return static_cast<long>(mbedtls_ssl_get_verify_result(&msession->ssl));
19145
}
19146
19147
inline std::string verify_error_string(long error_code) {
19148
  if (error_code == 0) { return ""; }
19149
  char buf[256];
19150
  mbedtls_x509_crt_verify_info(buf, sizeof(buf), "",
19151
                               static_cast<uint32_t>(error_code));
19152
  // Remove trailing newline if present
19153
  std::string result(buf);
19154
  while (!result.empty() && (result.back() == '\n' || result.back() == ' ')) {
19155
    result.pop_back();
19156
  }
19157
  return result;
19158
}
19159
19160
} // namespace tls
19161
19162
#endif // CPPHTTPLIB_MBEDTLS_SUPPORT
19163
19164
/*
19165
 * Group 10: TLS abstraction layer - wolfSSL backend
19166
 */
19167
19168
/*
19169
 * wolfSSL Backend Implementation
19170
 */
19171
19172
#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT
19173
namespace tls {
19174
19175
namespace impl {
19176
19177
// wolfSSL session wrapper
19178
struct WolfSSLSession {
19179
  WOLFSSL *ssl = nullptr;
19180
  socket_t sock = INVALID_SOCKET;
19181
  std::string hostname;     // For client: set via set_sni
19182
  std::string sni_hostname; // For server: received from client via SNI callback
19183
19184
  WolfSSLSession() = default;
19185
19186
  ~WolfSSLSession() {
19187
    if (ssl) { wolfSSL_free(ssl); }
19188
  }
19189
19190
  WolfSSLSession(const WolfSSLSession &) = delete;
19191
  WolfSSLSession &operator=(const WolfSSLSession &) = delete;
19192
};
19193
19194
// Thread-local error code accessor for wolfSSL
19195
inline uint64_t &wolfssl_last_error() {
19196
  static thread_local uint64_t err = 0;
19197
  return err;
19198
}
19199
19200
// Helper to map wolfSSL error to ErrorCode.
19201
// ssl_error is the value from wolfSSL_get_error().
19202
// raw_ret is the raw return value from the wolfSSL call (for low-level error).
19203
inline ErrorCode map_wolfssl_error(WOLFSSL *ssl, int ssl_error,
19204
                                   int &out_errno) {
19205
  switch (ssl_error) {
19206
  case SSL_ERROR_NONE: return ErrorCode::Success;
19207
  case SSL_ERROR_WANT_READ: return ErrorCode::WantRead;
19208
  case SSL_ERROR_WANT_WRITE: return ErrorCode::WantWrite;
19209
  case SSL_ERROR_ZERO_RETURN: return ErrorCode::PeerClosed;
19210
  case SSL_ERROR_SYSCALL: out_errno = errno; return ErrorCode::SyscallError;
19211
  default:
19212
    if (ssl) {
19213
      // wolfSSL stores the low-level error code as a negative value.
19214
      // DOMAIN_NAME_MISMATCH (-322) indicates hostname verification failure.
19215
      int low_err = ssl_error; // wolfSSL_get_error returns the low-level code
19216
      if (low_err == DOMAIN_NAME_MISMATCH) {
19217
        return ErrorCode::HostnameMismatch;
19218
      }
19219
      // Check verify result to distinguish cert verification from generic SSL
19220
      // errors.
19221
      long vr = wolfSSL_get_verify_result(ssl);
19222
      if (vr != 0) { return ErrorCode::CertVerifyFailed; }
19223
    }
19224
    return ErrorCode::Fatal;
19225
  }
19226
}
19227
19228
// WolfSSLContext constructor/destructor implementations
19229
inline WolfSSLContext::WolfSSLContext() { wolfSSL_Init(); }
19230
19231
inline WolfSSLContext::~WolfSSLContext() {
19232
  if (ctx) { wolfSSL_CTX_free(ctx); }
19233
}
19234
19235
// Thread-local storage for SNI captured during handshake
19236
inline std::string &wolfssl_pending_sni() {
19237
  static thread_local std::string sni;
19238
  return sni;
19239
}
19240
19241
// SNI callback for wolfSSL server to capture client's SNI hostname
19242
inline int wolfssl_sni_callback(WOLFSSL *ssl, int *ret, void *exArg) {
19243
  (void)ret;
19244
  (void)exArg;
19245
19246
  void *name_data = nullptr;
19247
  unsigned short name_len =
19248
      wolfSSL_SNI_GetRequest(ssl, WOLFSSL_SNI_HOST_NAME, &name_data);
19249
19250
  if (name_data && name_len > 0) {
19251
    wolfssl_pending_sni().assign(static_cast<const char *>(name_data),
19252
                                 name_len);
19253
  } else {
19254
    wolfssl_pending_sni().clear();
19255
  }
19256
  return 0; // Continue regardless
19257
}
19258
19259
// wolfSSL verify callback wrapper
19260
inline int wolfssl_verify_callback(int preverify_ok,
19261
                                   WOLFSSL_X509_STORE_CTX *x509_ctx) {
19262
  auto &callback = get_verify_callback();
19263
  if (!callback) { return preverify_ok; }
19264
19265
  WOLFSSL_X509 *cert = wolfSSL_X509_STORE_CTX_get_current_cert(x509_ctx);
19266
  int depth = wolfSSL_X509_STORE_CTX_get_error_depth(x509_ctx);
19267
  int err = wolfSSL_X509_STORE_CTX_get_error(x509_ctx);
19268
19269
  // Get the WOLFSSL object from the X509_STORE_CTX
19270
  WOLFSSL *ssl = static_cast<WOLFSSL *>(wolfSSL_X509_STORE_CTX_get_ex_data(
19271
      x509_ctx, wolfSSL_get_ex_data_X509_STORE_CTX_idx()));
19272
19273
  VerifyContext verify_ctx;
19274
  verify_ctx.session = static_cast<session_t>(ssl);
19275
  verify_ctx.cert = static_cast<cert_t>(cert);
19276
  verify_ctx.depth = depth;
19277
  verify_ctx.preverify_ok = (preverify_ok != 0);
19278
  verify_ctx.error_code = static_cast<long>(err);
19279
19280
  if (err != 0) {
19281
    verify_ctx.error_string = wolfSSL_X509_verify_cert_error_string(err);
19282
  } else {
19283
    verify_ctx.error_string = nullptr;
19284
  }
19285
19286
  bool accepted = callback(verify_ctx);
19287
  return accepted ? 1 : 0;
19288
}
19289
19290
inline void set_wolfssl_password_cb(WOLFSSL_CTX *ctx, const char *password) {
19291
  wolfSSL_CTX_set_default_passwd_cb_userdata(ctx, const_cast<char *>(password));
19292
  wolfSSL_CTX_set_default_passwd_cb(
19293
      ctx, [](char *buf, int size, int /*rwflag*/, void *userdata) -> int {
19294
        auto *pwd = static_cast<const char *>(userdata);
19295
        if (!pwd) return 0;
19296
        auto len = static_cast<int>(strlen(pwd));
19297
        if (len > size) len = size;
19298
        memcpy(buf, pwd, static_cast<size_t>(len));
19299
        return len;
19300
      });
19301
}
19302
19303
} // namespace impl
19304
19305
inline ctx_t create_client_context() {
19306
  auto ctx = new (std::nothrow) impl::WolfSSLContext();
19307
  if (!ctx) { return nullptr; }
19308
19309
  ctx->is_server = false;
19310
19311
  WOLFSSL_METHOD *method = wolfTLSv1_2_client_method();
19312
  if (!method) {
19313
    delete ctx;
19314
    return nullptr;
19315
  }
19316
19317
  ctx->ctx = wolfSSL_CTX_new(method);
19318
  if (!ctx->ctx) {
19319
    delete ctx;
19320
    return nullptr;
19321
  }
19322
19323
  // Default: verify peer certificate
19324
  wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_PEER, nullptr);
19325
19326
  return static_cast<ctx_t>(ctx);
19327
}
19328
19329
inline ctx_t create_server_context() {
19330
  auto ctx = new (std::nothrow) impl::WolfSSLContext();
19331
  if (!ctx) { return nullptr; }
19332
19333
  ctx->is_server = true;
19334
19335
  WOLFSSL_METHOD *method = wolfTLSv1_2_server_method();
19336
  if (!method) {
19337
    delete ctx;
19338
    return nullptr;
19339
  }
19340
19341
  ctx->ctx = wolfSSL_CTX_new(method);
19342
  if (!ctx->ctx) {
19343
    delete ctx;
19344
    return nullptr;
19345
  }
19346
19347
  // Default: don't verify client
19348
  wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_NONE, nullptr);
19349
19350
  // Enable SNI on server
19351
  wolfSSL_CTX_SNI_SetOptions(ctx->ctx, WOLFSSL_SNI_HOST_NAME,
19352
                             WOLFSSL_SNI_CONTINUE_ON_MISMATCH);
19353
  wolfSSL_CTX_set_servername_callback(ctx->ctx, impl::wolfssl_sni_callback);
19354
19355
  return static_cast<ctx_t>(ctx);
19356
}
19357
19358
inline void free_context(ctx_t ctx) {
19359
  if (ctx) { delete static_cast<impl::WolfSSLContext *>(ctx); }
19360
}
19361
19362
inline bool set_min_version(ctx_t ctx, Version version) {
19363
  if (!ctx) { return false; }
19364
  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19365
19366
  int min_ver = WOLFSSL_TLSV1_2;
19367
  if (version >= Version::TLS1_3) { min_ver = WOLFSSL_TLSV1_3; }
19368
19369
  return wolfSSL_CTX_SetMinVersion(wctx->ctx, min_ver) == WOLFSSL_SUCCESS;
19370
}
19371
19372
inline bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) {
19373
  if (!ctx || !pem) { return false; }
19374
  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19375
19376
  int ret = wolfSSL_CTX_load_verify_buffer(
19377
      wctx->ctx, reinterpret_cast<const unsigned char *>(pem),
19378
      static_cast<long>(len), SSL_FILETYPE_PEM);
19379
  if (ret != SSL_SUCCESS) {
19380
    impl::wolfssl_last_error() =
19381
        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19382
    return false;
19383
  }
19384
  wctx->ca_pem_data_.append(pem, len);
19385
  return true;
19386
}
19387
19388
inline bool load_ca_file(ctx_t ctx, const char *file_path) {
19389
  if (!ctx || !file_path) { return false; }
19390
  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19391
19392
  int ret = wolfSSL_CTX_load_verify_locations(wctx->ctx, file_path, nullptr);
19393
  if (ret != SSL_SUCCESS) {
19394
    impl::wolfssl_last_error() =
19395
        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19396
    return false;
19397
  }
19398
  return true;
19399
}
19400
19401
inline bool load_ca_dir(ctx_t ctx, const char *dir_path) {
19402
  if (!ctx || !dir_path) { return false; }
19403
  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19404
19405
  int ret = wolfSSL_CTX_load_verify_locations(wctx->ctx, nullptr, dir_path);
19406
  // wolfSSL may fail if the directory doesn't contain properly hashed certs.
19407
  // Unlike OpenSSL which lazily loads certs from directories, wolfSSL scans
19408
  // immediately. Return true even on failure since the CA file may have
19409
  // already been loaded, matching OpenSSL's lenient behavior.
19410
  (void)ret;
19411
  return true;
19412
}
19413
19414
inline bool load_system_certs(ctx_t ctx) {
19415
  if (!ctx) { return false; }
19416
  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19417
  bool loaded = false;
19418
19419
#ifdef _WIN32
19420
  loaded = impl::enumerate_windows_system_certs(
19421
      [&](const unsigned char *data, size_t len) {
19422
        return wolfSSL_CTX_load_verify_buffer(wctx->ctx, data,
19423
                                              static_cast<long>(len),
19424
                                              SSL_FILETYPE_ASN1) == SSL_SUCCESS;
19425
      });
19426
#elif defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
19427
  loaded = impl::enumerate_macos_keychain_certs(
19428
      [&](const unsigned char *data, size_t len) {
19429
        return wolfSSL_CTX_load_verify_buffer(wctx->ctx, data,
19430
                                              static_cast<long>(len),
19431
                                              SSL_FILETYPE_ASN1) == SSL_SUCCESS;
19432
      });
19433
#else
19434
  for (auto path = impl::system_ca_paths(); *path; ++path) {
19435
    if (wolfSSL_CTX_load_verify_locations(wctx->ctx, *path, nullptr) ==
19436
        SSL_SUCCESS) {
19437
      loaded = true;
19438
      break;
19439
    }
19440
  }
19441
19442
  if (!loaded) {
19443
    for (auto dir = impl::system_ca_dirs(); *dir; ++dir) {
19444
      if (wolfSSL_CTX_load_verify_locations(wctx->ctx, nullptr, *dir) ==
19445
          SSL_SUCCESS) {
19446
        loaded = true;
19447
        break;
19448
      }
19449
    }
19450
  }
19451
#endif
19452
19453
  return loaded;
19454
}
19455
19456
inline bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
19457
                                const char *password) {
19458
  if (!ctx || !cert || !key) { return false; }
19459
  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19460
19461
  // Load certificate
19462
  int ret = wolfSSL_CTX_use_certificate_buffer(
19463
      wctx->ctx, reinterpret_cast<const unsigned char *>(cert),
19464
      static_cast<long>(strlen(cert)), SSL_FILETYPE_PEM);
19465
  if (ret != SSL_SUCCESS) {
19466
    impl::wolfssl_last_error() =
19467
        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19468
    return false;
19469
  }
19470
19471
  // Set password callback if password is provided
19472
  if (password) { impl::set_wolfssl_password_cb(wctx->ctx, password); }
19473
19474
  // Load private key
19475
  ret = wolfSSL_CTX_use_PrivateKey_buffer(
19476
      wctx->ctx, reinterpret_cast<const unsigned char *>(key),
19477
      static_cast<long>(strlen(key)), SSL_FILETYPE_PEM);
19478
  if (ret != SSL_SUCCESS) {
19479
    impl::wolfssl_last_error() =
19480
        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19481
    return false;
19482
  }
19483
19484
  // Verify that the certificate and private key match
19485
  return wolfSSL_CTX_check_private_key(wctx->ctx) == SSL_SUCCESS;
19486
}
19487
19488
inline bool set_client_cert_file(ctx_t ctx, const char *cert_path,
19489
                                 const char *key_path, const char *password) {
19490
  if (!ctx || !cert_path || !key_path) { return false; }
19491
  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19492
19493
  // Load certificate file
19494
  int ret =
19495
      wolfSSL_CTX_use_certificate_file(wctx->ctx, cert_path, SSL_FILETYPE_PEM);
19496
  if (ret != SSL_SUCCESS) {
19497
    impl::wolfssl_last_error() =
19498
        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19499
    return false;
19500
  }
19501
19502
  // Set password callback if password is provided
19503
  if (password) { impl::set_wolfssl_password_cb(wctx->ctx, password); }
19504
19505
  // Load private key file
19506
  ret = wolfSSL_CTX_use_PrivateKey_file(wctx->ctx, key_path, SSL_FILETYPE_PEM);
19507
  if (ret != SSL_SUCCESS) {
19508
    impl::wolfssl_last_error() =
19509
        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19510
    return false;
19511
  }
19512
19513
  // Verify that the certificate and private key match
19514
  return wolfSSL_CTX_check_private_key(wctx->ctx) == SSL_SUCCESS;
19515
}
19516
19517
inline void set_verify_client(ctx_t ctx, bool require) {
19518
  if (!ctx) { return; }
19519
  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19520
  wctx->verify_client = require;
19521
  if (require) {
19522
    wolfSSL_CTX_set_verify(
19523
        wctx->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
19524
        wctx->has_verify_callback ? impl::wolfssl_verify_callback : nullptr);
19525
  } else {
19526
    if (wctx->has_verify_callback) {
19527
      wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
19528
                             impl::wolfssl_verify_callback);
19529
    } else {
19530
      wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_NONE, nullptr);
19531
    }
19532
  }
19533
}
19534
19535
inline session_t create_session(ctx_t ctx, socket_t sock) {
19536
  if (!ctx || sock == INVALID_SOCKET) { return nullptr; }
19537
  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
19538
19539
  auto session = new (std::nothrow) impl::WolfSSLSession();
19540
  if (!session) { return nullptr; }
19541
19542
  session->sock = sock;
19543
  session->ssl = wolfSSL_new(wctx->ctx);
19544
  if (!session->ssl) {
19545
    impl::wolfssl_last_error() =
19546
        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19547
    delete session;
19548
    return nullptr;
19549
  }
19550
19551
  wolfSSL_set_fd(session->ssl, static_cast<int>(sock));
19552
19553
  return static_cast<session_t>(session);
19554
}
19555
19556
inline void free_session(session_t session) {
19557
  if (session) { delete static_cast<impl::WolfSSLSession *>(session); }
19558
}
19559
19560
inline bool set_sni(session_t session, const char *hostname) {
19561
  if (!session || !hostname) { return false; }
19562
  auto wsession = static_cast<impl::WolfSSLSession *>(session);
19563
19564
  int ret = wolfSSL_UseSNI(wsession->ssl, WOLFSSL_SNI_HOST_NAME, hostname,
19565
                           static_cast<word16>(strlen(hostname)));
19566
  if (ret != WOLFSSL_SUCCESS) {
19567
    impl::wolfssl_last_error() =
19568
        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
19569
    return false;
19570
  }
19571
19572
  // Also set hostname for verification
19573
  wolfSSL_check_domain_name(wsession->ssl, hostname);
19574
19575
  wsession->hostname = hostname;
19576
  return true;
19577
}
19578
19579
inline bool set_hostname(session_t session, const char *hostname) {
19580
  // In wolfSSL, set_hostname also sets up hostname verification
19581
  return set_sni(session, hostname);
19582
}
19583
19584
inline TlsError connect(session_t session) {
19585
  TlsError err;
19586
  if (!session) {
19587
    err.code = ErrorCode::Fatal;
19588
    return err;
19589
  }
19590
19591
  auto wsession = static_cast<impl::WolfSSLSession *>(session);
19592
  int ret = wolfSSL_connect(wsession->ssl);
19593
19594
  if (ret == SSL_SUCCESS) {
19595
    err.code = ErrorCode::Success;
19596
  } else {
19597
    int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19598
    err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19599
    err.backend_code = static_cast<uint64_t>(ssl_error);
19600
    impl::wolfssl_last_error() = err.backend_code;
19601
  }
19602
19603
  return err;
19604
}
19605
19606
inline TlsError accept(session_t session) {
19607
  TlsError err;
19608
  if (!session) {
19609
    err.code = ErrorCode::Fatal;
19610
    return err;
19611
  }
19612
19613
  auto wsession = static_cast<impl::WolfSSLSession *>(session);
19614
  int ret = wolfSSL_accept(wsession->ssl);
19615
19616
  if (ret == SSL_SUCCESS) {
19617
    err.code = ErrorCode::Success;
19618
    // Capture SNI from thread-local storage after successful handshake
19619
    wsession->sni_hostname = std::move(impl::wolfssl_pending_sni());
19620
    impl::wolfssl_pending_sni().clear();
19621
  } else {
19622
    int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19623
    err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19624
    err.backend_code = static_cast<uint64_t>(ssl_error);
19625
    impl::wolfssl_last_error() = err.backend_code;
19626
  }
19627
19628
  return err;
19629
}
19630
19631
inline bool connect_nonblocking(session_t session, socket_t sock,
19632
                                time_t timeout_sec, time_t timeout_usec,
19633
                                TlsError *err) {
19634
  if (!session) {
19635
    if (err) { err->code = ErrorCode::Fatal; }
19636
    return false;
19637
  }
19638
19639
  auto wsession = static_cast<impl::WolfSSLSession *>(session);
19640
19641
  // Set socket to non-blocking mode
19642
  detail::set_nonblocking(sock, true);
19643
  auto cleanup =
19644
      detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
19645
19646
  int ret;
19647
  while ((ret = wolfSSL_connect(wsession->ssl)) != SSL_SUCCESS) {
19648
    int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19649
    if (ssl_error == SSL_ERROR_WANT_READ) {
19650
      if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
19651
        continue;
19652
      }
19653
    } else if (ssl_error == SSL_ERROR_WANT_WRITE) {
19654
      if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
19655
        continue;
19656
      }
19657
    }
19658
19659
    // Error or timeout
19660
    if (err) {
19661
      err->code =
19662
          impl::map_wolfssl_error(wsession->ssl, ssl_error, err->sys_errno);
19663
      err->backend_code = static_cast<uint64_t>(ssl_error);
19664
    }
19665
    impl::wolfssl_last_error() = static_cast<uint64_t>(ssl_error);
19666
    return false;
19667
  }
19668
19669
  if (err) { err->code = ErrorCode::Success; }
19670
  return true;
19671
}
19672
19673
inline bool accept_nonblocking(session_t session, socket_t sock,
19674
                               time_t timeout_sec, time_t timeout_usec,
19675
                               TlsError *err) {
19676
  if (!session) {
19677
    if (err) { err->code = ErrorCode::Fatal; }
19678
    return false;
19679
  }
19680
19681
  auto wsession = static_cast<impl::WolfSSLSession *>(session);
19682
19683
  // Set socket to non-blocking mode
19684
  detail::set_nonblocking(sock, true);
19685
  auto cleanup =
19686
      detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
19687
19688
  int ret;
19689
  while ((ret = wolfSSL_accept(wsession->ssl)) != SSL_SUCCESS) {
19690
    int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19691
    if (ssl_error == SSL_ERROR_WANT_READ) {
19692
      if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
19693
        continue;
19694
      }
19695
    } else if (ssl_error == SSL_ERROR_WANT_WRITE) {
19696
      if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
19697
        continue;
19698
      }
19699
    }
19700
19701
    // Error or timeout
19702
    if (err) {
19703
      err->code =
19704
          impl::map_wolfssl_error(wsession->ssl, ssl_error, err->sys_errno);
19705
      err->backend_code = static_cast<uint64_t>(ssl_error);
19706
    }
19707
    impl::wolfssl_last_error() = static_cast<uint64_t>(ssl_error);
19708
    return false;
19709
  }
19710
19711
  if (err) { err->code = ErrorCode::Success; }
19712
19713
  // Capture SNI from thread-local storage after successful handshake
19714
  wsession->sni_hostname = std::move(impl::wolfssl_pending_sni());
19715
  impl::wolfssl_pending_sni().clear();
19716
19717
  return true;
19718
}
19719
19720
inline ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
19721
  if (!session || !buf) {
19722
    err.code = ErrorCode::Fatal;
19723
    return -1;
19724
  }
19725
19726
  auto wsession = static_cast<impl::WolfSSLSession *>(session);
19727
  int ret = wolfSSL_read(wsession->ssl, buf, static_cast<int>(len));
19728
19729
  if (ret > 0) {
19730
    err.code = ErrorCode::Success;
19731
    return static_cast<ssize_t>(ret);
19732
  }
19733
19734
  if (ret == 0) {
19735
    err.code = ErrorCode::PeerClosed;
19736
    return 0;
19737
  }
19738
19739
  int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19740
  err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19741
  err.backend_code = static_cast<uint64_t>(ssl_error);
19742
  impl::wolfssl_last_error() = err.backend_code;
19743
  return -1;
19744
}
19745
19746
inline ssize_t write(session_t session, const void *buf, size_t len,
19747
                     TlsError &err) {
19748
  if (!session || !buf) {
19749
    err.code = ErrorCode::Fatal;
19750
    return -1;
19751
  }
19752
19753
  auto wsession = static_cast<impl::WolfSSLSession *>(session);
19754
  int ret = wolfSSL_write(wsession->ssl, buf, static_cast<int>(len));
19755
19756
  if (ret > 0) {
19757
    err.code = ErrorCode::Success;
19758
    return static_cast<ssize_t>(ret);
19759
  }
19760
19761
  // wolfSSL_write returns 0 when the peer has sent a close_notify.
19762
  // Treat this as an error (return -1) so callers don't spin in a
19763
  // write loop adding zero to the offset.
19764
  if (ret == 0) {
19765
    err.code = ErrorCode::PeerClosed;
19766
    return -1;
19767
  }
19768
19769
  int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19770
  err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
19771
  err.backend_code = static_cast<uint64_t>(ssl_error);
19772
  impl::wolfssl_last_error() = err.backend_code;
19773
  return -1;
19774
}
19775
19776
inline int pending(const_session_t session) {
19777
  if (!session) { return 0; }
19778
  auto wsession =
19779
      static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
19780
  return wolfSSL_pending(wsession->ssl);
19781
}
19782
19783
inline void shutdown(session_t session, bool graceful) {
19784
  if (!session) { return; }
19785
  auto wsession = static_cast<impl::WolfSSLSession *>(session);
19786
19787
  if (graceful) {
19788
    int ret;
19789
    int attempts = 0;
19790
    while ((ret = wolfSSL_shutdown(wsession->ssl)) != SSL_SUCCESS &&
19791
           attempts < 3) {
19792
      int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19793
      if (ssl_error != SSL_ERROR_WANT_READ &&
19794
          ssl_error != SSL_ERROR_WANT_WRITE) {
19795
        break;
19796
      }
19797
      attempts++;
19798
    }
19799
  } else {
19800
    wolfSSL_shutdown(wsession->ssl);
19801
  }
19802
}
19803
19804
inline bool is_peer_closed(session_t session, socket_t sock) {
19805
  if (!session || sock == INVALID_SOCKET) { return true; }
19806
  auto wsession = static_cast<impl::WolfSSLSession *>(session);
19807
19808
  // Check if there's already decrypted data available
19809
  if (wolfSSL_pending(wsession->ssl) > 0) { return false; }
19810
19811
  // Set socket to non-blocking to avoid blocking on read
19812
  detail::set_nonblocking(sock, true);
19813
  auto cleanup =
19814
      detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
19815
19816
  // Peek 1 byte to check connection status without consuming data
19817
  unsigned char buf;
19818
  int ret = wolfSSL_peek(wsession->ssl, &buf, 1);
19819
19820
  // If we got data or WANT_READ (would block), connection is alive
19821
  if (ret > 0) { return false; }
19822
19823
  int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
19824
  if (ssl_error == SSL_ERROR_WANT_READ) { return false; }
19825
19826
  return ssl_error == SSL_ERROR_ZERO_RETURN || ssl_error == SSL_ERROR_SYSCALL ||
19827
         ret == 0;
19828
}
19829
19830
inline cert_t get_peer_cert(const_session_t session) {
19831
  if (!session) { return nullptr; }
19832
  auto wsession =
19833
      static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
19834
19835
  WOLFSSL_X509 *cert = wolfSSL_get_peer_certificate(wsession->ssl);
19836
  return static_cast<cert_t>(cert);
19837
}
19838
19839
inline void free_cert(cert_t cert) {
19840
  if (cert) { wolfSSL_X509_free(static_cast<WOLFSSL_X509 *>(cert)); }
19841
}
19842
19843
inline bool verify_hostname(cert_t cert, const char *hostname) {
19844
  if (!cert || !hostname) { return false; }
19845
  auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19846
  std::string host_str(hostname);
19847
19848
  // Check if hostname is an IP address (IPv4 or IPv6)
19849
  unsigned char ip_bytes[16];
19850
  auto ip_len = impl::parse_ip_address(host_str, ip_bytes);
19851
  auto is_ip = ip_len > 0;
19852
19853
  // Check Subject Alternative Names
19854
  auto *san_names = static_cast<WOLF_STACK_OF(WOLFSSL_GENERAL_NAME) *>(
19855
      wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
19856
19857
  if (san_names) {
19858
    int san_count = wolfSSL_sk_num(san_names);
19859
    for (int i = 0; i < san_count; i++) {
19860
      auto *names =
19861
          static_cast<WOLFSSL_GENERAL_NAME *>(wolfSSL_sk_value(san_names, i));
19862
      if (!names) continue;
19863
19864
      if (!is_ip && names->type == WOLFSSL_GEN_DNS) {
19865
        // DNS name
19866
        unsigned char *dns_name = nullptr;
19867
        int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, names->d.dNSName);
19868
        if (dns_name && dns_len > 0) {
19869
          std::string san_name(reinterpret_cast<char *>(dns_name),
19870
                               static_cast<size_t>(dns_len));
19871
          XFREE(dns_name, nullptr, DYNAMIC_TYPE_OPENSSL);
19872
          if (detail::match_hostname(san_name, host_str)) {
19873
            wolfSSL_sk_free(san_names);
19874
            return true;
19875
          }
19876
        }
19877
      } else if (is_ip && names->type == WOLFSSL_GEN_IPADD) {
19878
        // IP address: only an iPAddress SAN of the same family (4 bytes for
19879
        // IPv4, 16 bytes for IPv6) may authenticate the host.
19880
        unsigned char *ip_data = wolfSSL_ASN1_STRING_data(names->d.iPAddress);
19881
        auto san_ip_len = wolfSSL_ASN1_STRING_length(names->d.iPAddress);
19882
        if (ip_data && san_ip_len == static_cast<int>(ip_len) &&
19883
            memcmp(ip_data, ip_bytes, ip_len) == 0) {
19884
          wolfSSL_sk_free(san_names);
19885
          return true;
19886
        }
19887
      }
19888
    }
19889
    wolfSSL_sk_free(san_names);
19890
  }
19891
19892
  // Fallback: Check Common Name (CN) in subject. Skipped for IP-literal hosts:
19893
  // an IP identity is only valid via an iPAddress SAN, never the CN (RFC 9110;
19894
  // the OpenSSL backend's X509_check_ip behaves the same way).
19895
  auto subject = is_ip ? nullptr : wolfSSL_X509_get_subject_name(x509);
19896
  if (subject) {
19897
    char cn[256] = {};
19898
    int cn_len = wolfSSL_X509_NAME_get_text_by_NID(subject, NID_commonName, cn,
19899
                                                   sizeof(cn));
19900
    if (cn_len > 0) {
19901
      std::string cn_str(cn, static_cast<size_t>(cn_len));
19902
      if (detail::match_hostname(cn_str, host_str)) { return true; }
19903
    }
19904
  }
19905
19906
  return false;
19907
}
19908
19909
inline uint64_t hostname_mismatch_code() {
19910
  return static_cast<uint64_t>(DOMAIN_NAME_MISMATCH);
19911
}
19912
19913
inline long get_verify_result(const_session_t session) {
19914
  if (!session) { return -1; }
19915
  auto wsession =
19916
      static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
19917
  long result = wolfSSL_get_verify_result(wsession->ssl);
19918
  return result;
19919
}
19920
19921
inline std::string get_cert_subject_cn(cert_t cert) {
19922
  if (!cert) return "";
19923
  auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19924
19925
  WOLFSSL_X509_NAME *subject = wolfSSL_X509_get_subject_name(x509);
19926
  if (!subject) return "";
19927
19928
  char cn[256] = {};
19929
  int cn_len = wolfSSL_X509_NAME_get_text_by_NID(subject, NID_commonName, cn,
19930
                                                 sizeof(cn));
19931
  if (cn_len <= 0) return "";
19932
  return std::string(cn, static_cast<size_t>(cn_len));
19933
}
19934
19935
inline std::string get_cert_issuer_name(cert_t cert) {
19936
  if (!cert) return "";
19937
  auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19938
19939
  WOLFSSL_X509_NAME *issuer = wolfSSL_X509_get_issuer_name(x509);
19940
  if (!issuer) return "";
19941
19942
  char *name_str = wolfSSL_X509_NAME_oneline(issuer, nullptr, 0);
19943
  if (!name_str) return "";
19944
19945
  std::string result(name_str);
19946
  XFREE(name_str, nullptr, DYNAMIC_TYPE_OPENSSL);
19947
  return result;
19948
}
19949
19950
inline bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
19951
  sans.clear();
19952
  if (!cert) return false;
19953
  auto x509 = static_cast<WOLFSSL_X509 *>(cert);
19954
19955
  auto *san_names = static_cast<WOLF_STACK_OF(WOLFSSL_GENERAL_NAME) *>(
19956
      wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
19957
  if (!san_names) return true; // No SANs is not an error
19958
19959
  int count = wolfSSL_sk_num(san_names);
19960
  for (int i = 0; i < count; i++) {
19961
    auto *name =
19962
        static_cast<WOLFSSL_GENERAL_NAME *>(wolfSSL_sk_value(san_names, i));
19963
    if (!name) continue;
19964
19965
    SanEntry entry;
19966
    switch (name->type) {
19967
    case WOLFSSL_GEN_DNS: {
19968
      entry.type = SanType::DNS;
19969
      unsigned char *dns_name = nullptr;
19970
      int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, name->d.dNSName);
19971
      if (dns_name && dns_len > 0) {
19972
        entry.value = std::string(reinterpret_cast<char *>(dns_name),
19973
                                  static_cast<size_t>(dns_len));
19974
        XFREE(dns_name, nullptr, DYNAMIC_TYPE_OPENSSL);
19975
      }
19976
      break;
19977
    }
19978
    case WOLFSSL_GEN_IPADD: {
19979
      entry.type = SanType::IP;
19980
      unsigned char *ip_data = wolfSSL_ASN1_STRING_data(name->d.iPAddress);
19981
      int ip_len = wolfSSL_ASN1_STRING_length(name->d.iPAddress);
19982
      if (ip_data && ip_len == 4) {
19983
        char buf[16];
19984
        snprintf(buf, sizeof(buf), "%d.%d.%d.%d", ip_data[0], ip_data[1],
19985
                 ip_data[2], ip_data[3]);
19986
        entry.value = buf;
19987
      } else if (ip_data && ip_len == 16) {
19988
        char buf[64];
19989
        snprintf(buf, sizeof(buf),
19990
                 "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
19991
                 "%02x%02x:%02x%02x:%02x%02x:%02x%02x",
19992
                 ip_data[0], ip_data[1], ip_data[2], ip_data[3], ip_data[4],
19993
                 ip_data[5], ip_data[6], ip_data[7], ip_data[8], ip_data[9],
19994
                 ip_data[10], ip_data[11], ip_data[12], ip_data[13],
19995
                 ip_data[14], ip_data[15]);
19996
        entry.value = buf;
19997
      }
19998
      break;
19999
    }
20000
    case WOLFSSL_GEN_EMAIL:
20001
      entry.type = SanType::EMAIL;
20002
      {
20003
        unsigned char *email = nullptr;
20004
        int email_len = wolfSSL_ASN1_STRING_to_UTF8(&email, name->d.rfc822Name);
20005
        if (email && email_len > 0) {
20006
          entry.value = std::string(reinterpret_cast<char *>(email),
20007
                                    static_cast<size_t>(email_len));
20008
          XFREE(email, nullptr, DYNAMIC_TYPE_OPENSSL);
20009
        }
20010
      }
20011
      break;
20012
    case WOLFSSL_GEN_URI:
20013
      entry.type = SanType::URI;
20014
      {
20015
        unsigned char *uri = nullptr;
20016
        int uri_len = wolfSSL_ASN1_STRING_to_UTF8(
20017
            &uri, name->d.uniformResourceIdentifier);
20018
        if (uri && uri_len > 0) {
20019
          entry.value = std::string(reinterpret_cast<char *>(uri),
20020
                                    static_cast<size_t>(uri_len));
20021
          XFREE(uri, nullptr, DYNAMIC_TYPE_OPENSSL);
20022
        }
20023
      }
20024
      break;
20025
    default: entry.type = SanType::OTHER; break;
20026
    }
20027
20028
    if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
20029
  }
20030
  wolfSSL_sk_free(san_names);
20031
  return true;
20032
}
20033
20034
inline bool get_cert_validity(cert_t cert, time_t &not_before,
20035
                              time_t &not_after) {
20036
  if (!cert) return false;
20037
  auto x509 = static_cast<WOLFSSL_X509 *>(cert);
20038
20039
  const WOLFSSL_ASN1_TIME *nb = wolfSSL_X509_get_notBefore(x509);
20040
  const WOLFSSL_ASN1_TIME *na = wolfSSL_X509_get_notAfter(x509);
20041
20042
  if (!nb || !na) return false;
20043
20044
  // wolfSSL_ASN1_TIME_to_tm is available
20045
  struct tm tm_nb = {}, tm_na = {};
20046
  if (wolfSSL_ASN1_TIME_to_tm(nb, &tm_nb) != WOLFSSL_SUCCESS) return false;
20047
  if (wolfSSL_ASN1_TIME_to_tm(na, &tm_na) != WOLFSSL_SUCCESS) return false;
20048
20049
#ifdef _WIN32
20050
  not_before = _mkgmtime(&tm_nb);
20051
  not_after = _mkgmtime(&tm_na);
20052
#else
20053
  not_before = timegm(&tm_nb);
20054
  not_after = timegm(&tm_na);
20055
#endif
20056
  return true;
20057
}
20058
20059
inline std::string get_cert_serial(cert_t cert) {
20060
  if (!cert) return "";
20061
  auto x509 = static_cast<WOLFSSL_X509 *>(cert);
20062
20063
  WOLFSSL_ASN1_INTEGER *serial_asn1 = wolfSSL_X509_get_serialNumber(x509);
20064
  if (!serial_asn1) return "";
20065
20066
  // Get the serial number data
20067
  int len = serial_asn1->length;
20068
  unsigned char *data = serial_asn1->data;
20069
  if (!data || len <= 0) return "";
20070
20071
  std::string result;
20072
  result.reserve(static_cast<size_t>(len) * 2);
20073
  for (int i = 0; i < len; i++) {
20074
    char hex[3];
20075
    snprintf(hex, sizeof(hex), "%02X", data[i]);
20076
    result += hex;
20077
  }
20078
  return result;
20079
}
20080
20081
inline bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
20082
  if (!cert) return false;
20083
  auto x509 = static_cast<WOLFSSL_X509 *>(cert);
20084
20085
  int der_len = 0;
20086
  const unsigned char *der_data = wolfSSL_X509_get_der(x509, &der_len);
20087
  if (!der_data || der_len <= 0) return false;
20088
20089
  der.assign(der_data, der_data + der_len);
20090
  return true;
20091
}
20092
20093
inline const char *get_sni(const_session_t session) {
20094
  if (!session) return nullptr;
20095
  auto wsession = static_cast<const impl::WolfSSLSession *>(session);
20096
20097
  // For server: return SNI received from client during handshake
20098
  if (!wsession->sni_hostname.empty()) {
20099
    return wsession->sni_hostname.c_str();
20100
  }
20101
20102
  // For client: return the hostname set via set_sni
20103
  if (!wsession->hostname.empty()) { return wsession->hostname.c_str(); }
20104
20105
  return nullptr;
20106
}
20107
20108
inline uint64_t peek_error() {
20109
  return static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
20110
}
20111
20112
inline uint64_t get_error() {
20113
  uint64_t err = impl::wolfssl_last_error();
20114
  impl::wolfssl_last_error() = 0;
20115
  return err;
20116
}
20117
20118
inline std::string error_string(uint64_t code) {
20119
  char buf[256];
20120
  wolfSSL_ERR_error_string(static_cast<unsigned long>(code), buf);
20121
  return std::string(buf);
20122
}
20123
20124
inline ca_store_t create_ca_store(const char *pem, size_t len) {
20125
  if (!pem || len == 0) { return nullptr; }
20126
  // Validate by attempting to load into a temporary ctx
20127
  WOLFSSL_CTX *tmp_ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method());
20128
  if (!tmp_ctx) { return nullptr; }
20129
  int ret = wolfSSL_CTX_load_verify_buffer(
20130
      tmp_ctx, reinterpret_cast<const unsigned char *>(pem),
20131
      static_cast<long>(len), SSL_FILETYPE_PEM);
20132
  wolfSSL_CTX_free(tmp_ctx);
20133
  if (ret != SSL_SUCCESS) { return nullptr; }
20134
  return static_cast<ca_store_t>(
20135
      new impl::WolfSSLCAStore{std::string(pem, len)});
20136
}
20137
20138
inline void free_ca_store(ca_store_t store) {
20139
  delete static_cast<impl::WolfSSLCAStore *>(store);
20140
}
20141
20142
inline bool set_ca_store(ctx_t ctx, ca_store_t store) {
20143
  if (!ctx || !store) { return false; }
20144
  auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
20145
  auto *ca = static_cast<impl::WolfSSLCAStore *>(store);
20146
  int ret = wolfSSL_CTX_load_verify_buffer(
20147
      wctx->ctx, reinterpret_cast<const unsigned char *>(ca->pem_data.data()),
20148
      static_cast<long>(ca->pem_data.size()), SSL_FILETYPE_PEM);
20149
  if (ret == SSL_SUCCESS) { wctx->ca_pem_data_ += ca->pem_data; }
20150
  // This function takes ownership of the store; the PEM data was copied into
20151
  // the context, so release the source
20152
  free_ca_store(store);
20153
  return ret == SSL_SUCCESS;
20154
}
20155
20156
inline size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
20157
  certs.clear();
20158
  if (!ctx) { return 0; }
20159
  auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
20160
  if (wctx->ca_pem_data_.empty()) { return 0; }
20161
20162
  const std::string &pem = wctx->ca_pem_data_;
20163
  const std::string begin_marker = "-----BEGIN CERTIFICATE-----";
20164
  const std::string end_marker = "-----END CERTIFICATE-----";
20165
  size_t pos = 0;
20166
  while ((pos = pem.find(begin_marker, pos)) != std::string::npos) {
20167
    size_t end_pos = pem.find(end_marker, pos);
20168
    if (end_pos == std::string::npos) { break; }
20169
    end_pos += end_marker.size();
20170
    std::string cert_pem = pem.substr(pos, end_pos - pos);
20171
    WOLFSSL_X509 *x509 = wolfSSL_X509_load_certificate_buffer(
20172
        reinterpret_cast<const unsigned char *>(cert_pem.data()),
20173
        static_cast<int>(cert_pem.size()), WOLFSSL_FILETYPE_PEM);
20174
    if (x509) { certs.push_back(static_cast<cert_t>(x509)); }
20175
    pos = end_pos;
20176
  }
20177
  return certs.size();
20178
}
20179
20180
inline std::vector<std::string> get_ca_names(ctx_t ctx) {
20181
  std::vector<std::string> names;
20182
  if (!ctx) { return names; }
20183
  auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
20184
  if (wctx->ca_pem_data_.empty()) { return names; }
20185
20186
  const std::string &pem = wctx->ca_pem_data_;
20187
  const std::string begin_marker = "-----BEGIN CERTIFICATE-----";
20188
  const std::string end_marker = "-----END CERTIFICATE-----";
20189
  size_t pos = 0;
20190
  while ((pos = pem.find(begin_marker, pos)) != std::string::npos) {
20191
    size_t end_pos = pem.find(end_marker, pos);
20192
    if (end_pos == std::string::npos) { break; }
20193
    end_pos += end_marker.size();
20194
    std::string cert_pem = pem.substr(pos, end_pos - pos);
20195
    WOLFSSL_X509 *x509 = wolfSSL_X509_load_certificate_buffer(
20196
        reinterpret_cast<const unsigned char *>(cert_pem.data()),
20197
        static_cast<int>(cert_pem.size()), WOLFSSL_FILETYPE_PEM);
20198
    if (x509) {
20199
      WOLFSSL_X509_NAME *subject = wolfSSL_X509_get_subject_name(x509);
20200
      if (subject) {
20201
        char *name_str = wolfSSL_X509_NAME_oneline(subject, nullptr, 0);
20202
        if (name_str) {
20203
          names.push_back(name_str);
20204
          XFREE(name_str, nullptr, DYNAMIC_TYPE_OPENSSL);
20205
        }
20206
      }
20207
      wolfSSL_X509_free(x509);
20208
    }
20209
    pos = end_pos;
20210
  }
20211
  return names;
20212
}
20213
20214
inline bool update_server_cert(ctx_t ctx, const char *cert_pem,
20215
                               const char *key_pem, const char *password) {
20216
  if (!ctx || !cert_pem || !key_pem) { return false; }
20217
  auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
20218
20219
  // Load new certificate
20220
  int ret = wolfSSL_CTX_use_certificate_buffer(
20221
      wctx->ctx, reinterpret_cast<const unsigned char *>(cert_pem),
20222
      static_cast<long>(strlen(cert_pem)), SSL_FILETYPE_PEM);
20223
  if (ret != SSL_SUCCESS) {
20224
    impl::wolfssl_last_error() =
20225
        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
20226
    return false;
20227
  }
20228
20229
  // Set password if provided
20230
  if (password) { impl::set_wolfssl_password_cb(wctx->ctx, password); }
20231
20232
  // Load new private key
20233
  ret = wolfSSL_CTX_use_PrivateKey_buffer(
20234
      wctx->ctx, reinterpret_cast<const unsigned char *>(key_pem),
20235
      static_cast<long>(strlen(key_pem)), SSL_FILETYPE_PEM);
20236
  if (ret != SSL_SUCCESS) {
20237
    impl::wolfssl_last_error() =
20238
        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
20239
    return false;
20240
  }
20241
20242
  return true;
20243
}
20244
20245
inline bool update_server_client_ca(ctx_t ctx, const char *ca_pem) {
20246
  if (!ctx || !ca_pem) { return false; }
20247
  auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
20248
20249
  int ret = wolfSSL_CTX_load_verify_buffer(
20250
      wctx->ctx, reinterpret_cast<const unsigned char *>(ca_pem),
20251
      static_cast<long>(strlen(ca_pem)), SSL_FILETYPE_PEM);
20252
  if (ret != SSL_SUCCESS) {
20253
    impl::wolfssl_last_error() =
20254
        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
20255
    return false;
20256
  }
20257
  return true;
20258
}
20259
20260
inline bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
20261
  if (!ctx) { return false; }
20262
  auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
20263
20264
  impl::get_verify_callback() = std::move(callback);
20265
  wctx->has_verify_callback = static_cast<bool>(impl::get_verify_callback());
20266
20267
  if (wctx->has_verify_callback) {
20268
    wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
20269
                           impl::wolfssl_verify_callback);
20270
  } else {
20271
    wolfSSL_CTX_set_verify(
20272
        wctx->ctx,
20273
        wctx->verify_client
20274
            ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
20275
            : SSL_VERIFY_NONE,
20276
        nullptr);
20277
  }
20278
  return true;
20279
}
20280
20281
inline long get_verify_error(const_session_t session) {
20282
  if (!session) { return -1; }
20283
  auto *wsession =
20284
      static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
20285
  return wolfSSL_get_verify_result(wsession->ssl);
20286
}
20287
20288
inline std::string verify_error_string(long error_code) {
20289
  if (error_code == 0) { return ""; }
20290
  const char *str =
20291
      wolfSSL_X509_verify_cert_error_string(static_cast<int>(error_code));
20292
  return str ? std::string(str) : std::string();
20293
}
20294
20295
} // namespace tls
20296
20297
#endif // CPPHTTPLIB_WOLFSSL_SUPPORT
20298
20299
// WebSocket implementation
20300
namespace ws {
20301
20302
inline bool WebSocket::send_frame(Opcode op, const char *data, size_t len,
20303
0
                                  bool fin) {
20304
0
  std::lock_guard<std::mutex> lock(write_mutex_);
20305
0
  if (closed_) { return false; }
20306
0
  return detail::write_websocket_frame(strm_, op, data, len, fin, !is_server_);
20307
0
}
20308
20309
0
inline ReadResult WebSocket::read(std::string &msg) {
20310
0
  while (!closed_) {
20311
0
    Opcode opcode;
20312
0
    std::string payload;
20313
0
    bool fin;
20314
0
20315
0
    if (!impl::read_websocket_frame(strm_, opcode, payload, fin, is_server_,
20316
0
                                    CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH)) {
20317
0
      closed_ = true;
20318
0
      return Fail;
20319
0
    }
20320
0
20321
0
    switch (opcode) {
20322
0
    case Opcode::Ping: {
20323
0
      std::lock_guard<std::mutex> lock(write_mutex_);
20324
0
      detail::write_websocket_frame(strm_, Opcode::Pong, payload.data(),
20325
0
                                    payload.size(), true, !is_server_);
20326
0
      continue;
20327
0
    }
20328
0
    case Opcode::Pong: {
20329
0
      std::lock_guard<std::mutex> lock(ping_mutex_);
20330
0
      unacked_pings_ = 0;
20331
0
      continue;
20332
0
    }
20333
0
    case Opcode::Close: {
20334
0
      if (!closed_.exchange(true)) {
20335
0
        // Echo close frame back
20336
0
        std::lock_guard<std::mutex> lock(write_mutex_);
20337
0
        detail::write_websocket_frame(strm_, Opcode::Close, payload.data(),
20338
0
                                      payload.size(), true, !is_server_);
20339
0
      }
20340
0
      return Fail;
20341
0
    }
20342
0
    case Opcode::Text:
20343
0
    case Opcode::Binary: {
20344
0
      auto result = opcode == Opcode::Text ? Text : Binary;
20345
0
      msg = std::move(payload);
20346
0
20347
0
      // Handle fragmentation
20348
0
      if (!fin) {
20349
0
        while (true) {
20350
0
          Opcode cont_opcode;
20351
0
          std::string cont_payload;
20352
0
          bool cont_fin;
20353
0
          if (!impl::read_websocket_frame(
20354
0
                  strm_, cont_opcode, cont_payload, cont_fin, is_server_,
20355
0
                  CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH)) {
20356
0
            closed_ = true;
20357
0
            return Fail;
20358
0
          }
20359
0
          if (cont_opcode == Opcode::Ping) {
20360
0
            std::lock_guard<std::mutex> lock(write_mutex_);
20361
0
            detail::write_websocket_frame(
20362
0
                strm_, Opcode::Pong, cont_payload.data(), cont_payload.size(),
20363
0
                true, !is_server_);
20364
0
            continue;
20365
0
          }
20366
0
          if (cont_opcode == Opcode::Pong) {
20367
0
            std::lock_guard<std::mutex> lock(ping_mutex_);
20368
0
            unacked_pings_ = 0;
20369
0
            continue;
20370
0
          }
20371
0
          if (cont_opcode == Opcode::Close) {
20372
0
            if (!closed_.exchange(true)) {
20373
0
              std::lock_guard<std::mutex> lock(write_mutex_);
20374
0
              detail::write_websocket_frame(
20375
0
                  strm_, Opcode::Close, cont_payload.data(),
20376
0
                  cont_payload.size(), true, !is_server_);
20377
0
            }
20378
0
            return Fail;
20379
0
          }
20380
0
          // RFC 6455: continuation frames must use opcode 0x0
20381
0
          if (cont_opcode != Opcode::Continuation) {
20382
0
            closed_ = true;
20383
0
            return Fail;
20384
0
          }
20385
0
          msg += cont_payload;
20386
0
          if (msg.size() > CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH) {
20387
0
            closed_ = true;
20388
0
            return Fail;
20389
0
          }
20390
0
          if (cont_fin) { break; }
20391
0
        }
20392
0
      }
20393
0
      // RFC 6455 Section 5.6: text frames must contain valid UTF-8
20394
0
      if (result == Text && !impl::is_valid_utf8(msg)) {
20395
0
        close(CloseStatus::InvalidPayload, "invalid UTF-8");
20396
0
        return Fail;
20397
0
      }
20398
0
      return result;
20399
0
    }
20400
0
    default: closed_ = true; return Fail;
20401
0
    }
20402
0
  }
20403
0
  return Fail;
20404
0
}
20405
20406
0
inline bool WebSocket::send(const std::string &data) {
20407
0
  return send_frame(Opcode::Text, data.data(), data.size());
20408
0
}
20409
20410
0
inline bool WebSocket::send(const char *data, size_t len) {
20411
0
  return send_frame(Opcode::Binary, data, len);
20412
0
}
20413
20414
0
inline void WebSocket::close(CloseStatus status, const std::string &reason) {
20415
0
  if (closed_.exchange(true)) { return; }
20416
0
  ping_cv_.notify_all();
20417
0
  std::string payload;
20418
0
  auto code = static_cast<uint16_t>(status);
20419
0
  payload.push_back(static_cast<char>((code >> 8) & 0xFF));
20420
0
  payload.push_back(static_cast<char>(code & 0xFF));
20421
  // RFC 6455 Section 5.5: control frame payload must not exceed 125 bytes
20422
  // Close frame has 2-byte status code, so reason is limited to 123 bytes
20423
0
  payload += reason.substr(0, 123);
20424
0
  {
20425
0
    std::lock_guard<std::mutex> lock(write_mutex_);
20426
0
    detail::write_websocket_frame(strm_, Opcode::Close, payload.data(),
20427
0
                                  payload.size(), true, !is_server_);
20428
0
  }
20429
20430
  // RFC 6455 Section 7.1.1: after sending a Close frame, wait for the peer's
20431
  // Close response before closing the TCP connection. Use a short timeout to
20432
  // avoid hanging if the peer doesn't respond.
20433
0
  strm_.set_read_timeout(CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND, 0);
20434
0
  Opcode op;
20435
0
  std::string resp;
20436
0
  bool fin;
20437
0
  while (impl::read_websocket_frame(strm_, op, resp, fin, is_server_, 125)) {
20438
0
    if (op == Opcode::Close) { break; }
20439
0
  }
20440
0
}
20441
20442
0
inline WebSocket::~WebSocket() {
20443
0
  {
20444
0
    std::lock_guard<std::mutex> lock(ping_mutex_);
20445
0
    closed_ = true;
20446
0
  }
20447
0
  ping_cv_.notify_all();
20448
0
  if (ping_thread_.joinable()) { ping_thread_.join(); }
20449
0
}
20450
20451
0
inline void WebSocket::start_heartbeat() {
20452
0
  if (ping_interval_sec_ == 0) { return; }
20453
0
  ping_thread_ = std::thread([this]() {
20454
0
    std::unique_lock<std::mutex> lock(ping_mutex_);
20455
0
    while (!closed_) {
20456
0
      ping_cv_.wait_for(lock, std::chrono::seconds(ping_interval_sec_));
20457
0
      if (closed_) { break; }
20458
      // If the peer has failed to respond to the previous pings, give up.
20459
      // RFC 6455 does not define a pong-timeout mechanism; this is an
20460
      // opt-in liveness check controlled by max_missed_pongs_.
20461
0
      if (max_missed_pongs_ > 0 && unacked_pings_ >= max_missed_pongs_) {
20462
0
        lock.unlock();
20463
0
        close(CloseStatus::GoingAway, "pong timeout");
20464
0
        return;
20465
0
      }
20466
0
      lock.unlock();
20467
0
      if (!send_frame(Opcode::Ping, nullptr, 0)) {
20468
0
        lock.lock();
20469
0
        closed_ = true;
20470
0
        break;
20471
0
      }
20472
0
      lock.lock();
20473
0
      unacked_pings_++;
20474
0
    }
20475
0
  });
20476
0
}
20477
20478
0
inline const Request &WebSocket::request() const { return req_; }
20479
20480
0
inline bool WebSocket::is_open() const { return !closed_; }
20481
20482
// WebSocketClient implementation
20483
inline WebSocketClient::WebSocketClient(
20484
    const std::string &scheme_host_port_path, const Headers &headers)
20485
    : headers_(headers) {
20486
  detail::UrlComponents uc;
20487
  if (detail::parse_url(scheme_host_port_path, uc) && !uc.scheme.empty() &&
20488
      !uc.host.empty() && !uc.path.empty()) {
20489
    auto &scheme = uc.scheme;
20490
20491
#ifdef CPPHTTPLIB_SSL_ENABLED
20492
    if (scheme != "ws" && scheme != "wss") {
20493
#else
20494
    if (scheme != "ws") {
20495
#endif
20496
#ifndef CPPHTTPLIB_NO_EXCEPTIONS
20497
      std::string msg = "'" + scheme + "' scheme is not supported.";
20498
      throw std::invalid_argument(msg);
20499
#endif
20500
      return;
20501
    }
20502
20503
    auto is_ssl = scheme == "wss";
20504
20505
    host_ = std::move(uc.host);
20506
20507
    port_ = is_ssl ? 443 : 80;
20508
    if (!uc.port.empty() && !detail::parse_port(uc.port, port_)) { return; }
20509
20510
    path_ = std::move(uc.path);
20511
    if (!uc.query.empty()) { path_ += uc.query; }
20512
20513
#ifdef CPPHTTPLIB_SSL_ENABLED
20514
    is_ssl_ = is_ssl;
20515
    if (is_ssl_) {
20516
      // The context lives as long as the client so that CA configuration
20517
      // survives reconnects; sessions are created per connection.
20518
      tls_ctx_ = tls::create_client_context();
20519
      if (!tls_ctx_) { return; }
20520
    }
20521
#else
20522
    if (is_ssl) { return; }
20523
#endif
20524
20525
    is_valid_ = true;
20526
  }
20527
}
20528
20529
inline WebSocketClient::~WebSocketClient() {
20530
  shutdown_and_close();
20531
#ifdef CPPHTTPLIB_SSL_ENABLED
20532
  if (tls_ctx_) {
20533
    tls::free_context(tls_ctx_);
20534
    tls_ctx_ = nullptr;
20535
  }
20536
#endif
20537
}
20538
20539
0
inline bool WebSocketClient::is_valid() const { return is_valid_; }
20540
20541
0
inline void WebSocketClient::shutdown_and_close() {
20542
0
  // Send the close frame while the TLS session is still alive: ws_ holds an
20543
0
  // SSLSocketStream that keeps a raw pointer to tls_session_, so the session
20544
0
  // must outlive ws_->close() and ws_.reset() to avoid a use-after-free.
20545
0
  if (ws_ && ws_->is_open()) { ws_->close(); }
20546
0
  ws_.reset();
20547
0
#ifdef CPPHTTPLIB_SSL_ENABLED
20548
0
  if (is_ssl_) {
20549
0
    if (tls_session_) {
20550
0
      tls::shutdown(tls_session_, true);
20551
0
      tls::free_session(tls_session_);
20552
0
      tls_session_ = nullptr;
20553
0
    }
20554
0
  }
20555
0
#endif
20556
0
  if (sock_ != INVALID_SOCKET) {
20557
0
    detail::shutdown_socket(sock_);
20558
0
    detail::close_socket(sock_);
20559
0
    sock_ = INVALID_SOCKET;
20560
0
  }
20561
0
}
20562
20563
0
inline bool WebSocketClient::create_stream(std::unique_ptr<Stream> &strm) {
20564
0
#ifdef CPPHTTPLIB_SSL_ENABLED
20565
0
  if (is_ssl_) {
20566
0
    if (server_certificate_verification_ && !certs_loaded_) {
20567
0
      uint64_t backend_error = 0;
20568
0
      detail::load_client_ca_config(tls_ctx_, ca_cert_file_path_, std::string(),
20569
0
                                    custom_ca_loaded_, system_ca_mode_,
20570
0
                                    backend_error);
20571
0
      certs_loaded_ = true;
20572
0
    }
20573
0
20574
0
    if (!detail::setup_client_tls_session(host_, tls_ctx_, tls_session_, sock_,
20575
0
                                          server_certificate_verification_,
20576
0
                                          read_timeout_sec_,
20577
0
                                          read_timeout_usec_)) {
20578
0
      return false;
20579
0
    }
20580
0
20581
0
    strm = std::unique_ptr<Stream>(new detail::SSLSocketStream(
20582
0
        sock_, tls_session_, read_timeout_sec_, read_timeout_usec_,
20583
0
        write_timeout_sec_, write_timeout_usec_));
20584
0
    return true;
20585
0
  }
20586
0
#endif
20587
0
  strm = std::unique_ptr<Stream>(
20588
0
      new detail::SocketStream(sock_, read_timeout_sec_, read_timeout_usec_,
20589
0
                               write_timeout_sec_, write_timeout_usec_));
20590
0
  return true;
20591
0
}
20592
20593
0
inline bool WebSocketClient::connect() {
20594
0
  if (!is_valid_) { return false; }
20595
0
  shutdown_and_close();
20596
0
20597
0
  // Check is custom IP specified for host_
20598
0
  std::string ip;
20599
0
  auto it = addr_map_.find(host_);
20600
0
  if (it != addr_map_.end()) { ip = it->second; }
20601
0
20602
0
  Error error;
20603
0
  sock_ = detail::create_client_socket(
20604
0
      host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
20605
0
      socket_options_, connection_timeout_sec_, connection_timeout_usec_,
20606
0
      read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
20607
0
      write_timeout_usec_, interface_, error);
20608
0
20609
0
  if (sock_ == INVALID_SOCKET) { return false; }
20610
0
20611
0
  std::unique_ptr<Stream> strm;
20612
0
  if (!create_stream(strm)) {
20613
0
    shutdown_and_close();
20614
0
    return false;
20615
0
  }
20616
0
20617
0
#ifdef CPPHTTPLIB_SSL_ENABLED
20618
0
  auto is_ssl = is_ssl_;
20619
0
#else
20620
0
  auto is_ssl = false;
20621
0
#endif
20622
0
20623
0
  std::string selected_subprotocol;
20624
0
  if (!detail::perform_websocket_handshake(*strm, host_, port_, is_ssl, path_,
20625
0
                                           headers_, selected_subprotocol)) {
20626
0
    shutdown_and_close();
20627
0
    return false;
20628
0
  }
20629
0
  subprotocol_ = std::move(selected_subprotocol);
20630
0
20631
0
  Request req;
20632
0
  req.method = "GET";
20633
0
  req.path = path_;
20634
0
  ws_ = std::unique_ptr<WebSocket>(new WebSocket(std::move(strm), req, false,
20635
0
                                                 websocket_ping_interval_sec_,
20636
0
                                                 websocket_max_missed_pongs_));
20637
0
  return true;
20638
0
}
20639
20640
0
inline ReadResult WebSocketClient::read(std::string &msg) {
20641
0
  if (!ws_) { return Fail; }
20642
0
  return ws_->read(msg);
20643
0
}
20644
20645
0
inline bool WebSocketClient::send(const std::string &data) {
20646
0
  if (!ws_) { return false; }
20647
0
  return ws_->send(data);
20648
0
}
20649
20650
0
inline bool WebSocketClient::send(const char *data, size_t len) {
20651
0
  if (!ws_) { return false; }
20652
0
  return ws_->send(data, len);
20653
0
}
20654
20655
inline void WebSocketClient::close(CloseStatus status,
20656
0
                                   const std::string &reason) {
20657
0
  if (ws_) { ws_->close(status, reason); }
20658
0
}
20659
20660
0
inline bool WebSocketClient::is_open() const { return ws_ && ws_->is_open(); }
20661
20662
0
inline const std::string &WebSocketClient::subprotocol() const {
20663
0
  return subprotocol_;
20664
0
}
20665
20666
0
inline void WebSocketClient::set_read_timeout(time_t sec, time_t usec) {
20667
0
  read_timeout_sec_ = sec;
20668
0
  read_timeout_usec_ = usec;
20669
0
}
20670
20671
0
inline void WebSocketClient::set_write_timeout(time_t sec, time_t usec) {
20672
0
  write_timeout_sec_ = sec;
20673
0
  write_timeout_usec_ = usec;
20674
0
}
20675
20676
0
inline void WebSocketClient::set_websocket_ping_interval(time_t sec) {
20677
0
  websocket_ping_interval_sec_ = sec;
20678
0
}
20679
20680
0
inline void WebSocketClient::set_websocket_max_missed_pongs(int count) {
20681
0
  websocket_max_missed_pongs_ = count;
20682
0
}
20683
20684
0
inline void WebSocketClient::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
20685
20686
0
inline void WebSocketClient::set_address_family(int family) {
20687
0
  address_family_ = family;
20688
0
}
20689
20690
0
inline void WebSocketClient::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
20691
20692
0
inline void WebSocketClient::set_socket_options(SocketOptions socket_options) {
20693
0
  socket_options_ = std::move(socket_options);
20694
0
}
20695
20696
0
inline void WebSocketClient::set_connection_timeout(time_t sec, time_t usec) {
20697
0
  connection_timeout_sec_ = sec;
20698
0
  connection_timeout_usec_ = usec;
20699
0
}
20700
20701
0
inline void WebSocketClient::set_interface(const std::string &intf) {
20702
0
  interface_ = intf;
20703
0
}
20704
20705
inline void WebSocketClient::set_hostname_addr_map(
20706
0
    std::map<std::string, std::string> addr_map) {
20707
0
  addr_map_ = std::move(addr_map);
20708
0
}
20709
20710
#ifdef CPPHTTPLIB_SSL_ENABLED
20711
20712
inline void WebSocketClient::set_ca_cert_path(const std::string &path) {
20713
  ca_cert_file_path_ = path;
20714
}
20715
20716
inline void WebSocketClient::set_ca_cert_store(tls::ca_store_t store) {
20717
  if (store && tls_ctx_) {
20718
    // set_ca_store takes ownership of store
20719
    tls::set_ca_store(tls_ctx_, store);
20720
    custom_ca_loaded_ = true;
20721
  } else if (store) {
20722
    tls::free_ca_store(store);
20723
  }
20724
}
20725
20726
inline void WebSocketClient::load_ca_cert_store(const char *ca_cert,
20727
                                                std::size_t size) {
20728
  if (tls_ctx_ && ca_cert && size > 0) {
20729
    tls::load_ca_pem(tls_ctx_, ca_cert, size);
20730
    custom_ca_loaded_ = true;
20731
  }
20732
}
20733
20734
inline void
20735
WebSocketClient::enable_server_certificate_verification(bool enabled) {
20736
  server_certificate_verification_ = enabled;
20737
}
20738
20739
inline void WebSocketClient::enable_system_ca(bool enabled) {
20740
  system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled;
20741
}
20742
20743
#endif // CPPHTTPLIB_SSL_ENABLED
20744
20745
} // namespace ws
20746
20747
// ----------------------------------------------------------------------------
20748
20749
} // namespace httplib
20750
20751
#endif // CPPHTTPLIB_HTTPLIB_H