1#include "httplib.h"
2namespace httplib {
3
4
5/*
6 * Implementation that will be part of the .cc file if split into .h + .cc.
7 */
8
9namespace detail {
10
11bool is_hex(char c, int &v) {
12 if (isdigit(c)) {
13 v = c - '0';
14 return true;
15 } else if ('A' <= c && c <= 'F') {
16 v = c - 'A' + 10;
17 return true;
18 } else if ('a' <= c && c <= 'f') {
19 v = c - 'a' + 10;
20 return true;
21 }
22 return false;
23}
24
25bool from_hex_to_i(const std::string &s, size_t i, size_t cnt,
26 int &val) {
27 if (i >= s.size()) { return false; }
28
29 val = 0;
30 for (; cnt; i++, cnt--) {
31 if (!s[i]) { return false; }
32 auto v = 0;
33 if (is_hex(s[i], v)) {
34 val = val * 16 + v;
35 } else {
36 return false;
37 }
38 }
39 return true;
40}
41
42std::string from_i_to_hex(size_t n) {
43 static const auto charset = "0123456789abcdef";
44 std::string ret;
45 do {
46 ret = charset[n & 15] + ret;
47 n >>= 4;
48 } while (n > 0);
49 return ret;
50}
51
52std::string compute_etag(const FileStat &fs) {
53 if (!fs.is_file()) { return std::string(); }
54
55 // If mtime cannot be determined (negative value indicates an error
56 // or sentinel), do not generate an ETag. Returning a neutral / fixed
57 // value like 0 could collide with a real file that legitimately has
58 // mtime == 0 (epoch) and lead to misleading validators.
59 auto mtime_raw = fs.mtime();
60 if (mtime_raw < 0) { return std::string(); }
61
62 auto mtime = static_cast<size_t>(mtime_raw);
63 auto size = fs.size();
64
65 return std::string("W/\"") + from_i_to_hex(mtime) + "-" +
66 from_i_to_hex(size) + "\"";
67}
68
69// Format time_t as HTTP-date (RFC 9110 Section 5.6.7): "Sun, 06 Nov 1994
70// 08:49:37 GMT" This implementation is defensive: it validates `mtime`, checks
71// return values from `gmtime_r`/`gmtime_s`, and ensures `strftime` succeeds.
72std::string file_mtime_to_http_date(time_t mtime) {
73 if (mtime < 0) { return std::string(); }
74
75 struct tm tm_buf;
76#ifdef _WIN32
77 if (gmtime_s(&tm_buf, &mtime) != 0) { return std::string(); }
78#else
79 if (gmtime_r(&mtime, &tm_buf) == nullptr) { return std::string(); }
80#endif
81 char buf[64];
82 if (strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", &tm_buf) == 0) {
83 return std::string();
84 }
85
86 return std::string(buf);
87}
88
89// Parse HTTP-date (RFC 9110 Section 5.6.7) to time_t. Returns -1 on failure.
90time_t parse_http_date(const std::string &date_str) {
91 struct tm tm_buf;
92
93 // Create a classic locale object once for all parsing attempts
94 const std::locale classic_locale = std::locale::classic();
95
96 // Try to parse using std::get_time (C++11, cross-platform)
97 auto try_parse = [&](const char *fmt) -> bool {
98 std::istringstream ss(date_str);
99 ss.imbue(classic_locale);
100
101 memset(&tm_buf, 0, sizeof(tm_buf));
102 ss >> std::get_time(&tm_buf, fmt);
103
104 return !ss.fail();
105 };
106
107 // RFC 9110 preferred format (HTTP-date): "Sun, 06 Nov 1994 08:49:37 GMT"
108 if (!try_parse("%a, %d %b %Y %H:%M:%S")) {
109 // RFC 850 format: "Sunday, 06-Nov-94 08:49:37 GMT"
110 if (!try_parse("%A, %d-%b-%y %H:%M:%S")) {
111 // asctime format: "Sun Nov 6 08:49:37 1994"
112 if (!try_parse("%a %b %d %H:%M:%S %Y")) {
113 return static_cast<time_t>(-1);
114 }
115 }
116 }
117
118#ifdef _WIN32
119 return _mkgmtime(&tm_buf);
120#elif defined _AIX
121 return mktime(&tm_buf);
122#else
123 return timegm(&tm_buf);
124#endif
125}
126
127bool is_weak_etag(const std::string &s) {
128 // Check if the string is a weak ETag (starts with 'W/"')
129 return s.size() > 3 && s[0] == 'W' && s[1] == '/' && s[2] == '"';
130}
131
132bool is_strong_etag(const std::string &s) {
133 // Check if the string is a strong ETag (starts and ends with '"', at least 2
134 // chars)
135 return s.size() >= 2 && s[0] == '"' && s.back() == '"';
136}
137
138size_t to_utf8(int code, char *buff) {
139 if (code < 0x0080) {
140 buff[0] = static_cast<char>(code & 0x7F);
141 return 1;
142 } else if (code < 0x0800) {
143 buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F));
144 buff[1] = static_cast<char>(0x80 | (code & 0x3F));
145 return 2;
146 } else if (code < 0xD800) {
147 buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
148 buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
149 buff[2] = static_cast<char>(0x80 | (code & 0x3F));
150 return 3;
151 } else if (code < 0xE000) { // D800 - DFFF is invalid...
152 return 0;
153 } else if (code < 0x10000) {
154 buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
155 buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
156 buff[2] = static_cast<char>(0x80 | (code & 0x3F));
157 return 3;
158 } else if (code < 0x110000) {
159 buff[0] = static_cast<char>(0xF0 | ((code >> 18) & 0x7));
160 buff[1] = static_cast<char>(0x80 | ((code >> 12) & 0x3F));
161 buff[2] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
162 buff[3] = static_cast<char>(0x80 | (code & 0x3F));
163 return 4;
164 }
165
166 // NOTREACHED
167 return 0;
168}
169
170// NOTE: This code came up with the following stackoverflow post:
171// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c
172std::string base64_encode(const std::string &in) {
173 static const auto lookup =
174 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
175
176 std::string out;
177 out.reserve(in.size());
178
179 auto val = 0;
180 auto valb = -6;
181
182 for (auto c : in) {
183 val = (val << 8) + static_cast<uint8_t>(c);
184 valb += 8;
185 while (valb >= 0) {
186 out.push_back(lookup[(val >> valb) & 0x3F]);
187 valb -= 6;
188 }
189 }
190
191 if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); }
192
193 while (out.size() % 4) {
194 out.push_back('=');
195 }
196
197 return out;
198}
199
200bool is_valid_path(const std::string &path) {
201 size_t level = 0;
202 size_t i = 0;
203
204 // Skip slash
205 while (i < path.size() && path[i] == '/') {
206 i++;
207 }
208
209 while (i < path.size()) {
210 // Read component
211 auto beg = i;
212 while (i < path.size() && path[i] != '/') {
213 if (path[i] == '\0') {
214 return false;
215 } else if (path[i] == '\\') {
216 return false;
217 }
218 i++;
219 }
220
221 auto len = i - beg;
222 assert(len > 0);
223
224 if (!path.compare(beg, len, ".")) {
225 ;
226 } else if (!path.compare(beg, len, "..")) {
227 if (level == 0) { return false; }
228 level--;
229 } else {
230 level++;
231 }
232
233 // Skip slash
234 while (i < path.size() && path[i] == '/') {
235 i++;
236 }
237 }
238
239 return true;
240}
241
242FileStat::FileStat(const std::string &path) {
243#if defined(_WIN32)
244 auto wpath = u8string_to_wstring(path.c_str());
245 ret_ = _wstat(wpath.c_str(), &st_);
246#else
247 ret_ = stat(path.c_str(), &st_);
248#endif
249}
250bool FileStat::is_file() const {
251 return ret_ >= 0 && S_ISREG(st_.st_mode);
252}
253bool FileStat::is_dir() const {
254 return ret_ >= 0 && S_ISDIR(st_.st_mode);
255}
256
257time_t FileStat::mtime() const {
258 return ret_ >= 0 ? static_cast<time_t>(st_.st_mtime)
259 : static_cast<time_t>(-1);
260}
261
262size_t FileStat::size() const {
263 return ret_ >= 0 ? static_cast<size_t>(st_.st_size) : 0;
264}
265
266std::string encode_path(const std::string &s) {
267 std::string result;
268 result.reserve(s.size());
269
270 for (size_t i = 0; s[i]; i++) {
271 switch (s[i]) {
272 case ' ': result += "%20"; break;
273 case '+': result += "%2B"; break;
274 case '\r': result += "%0D"; break;
275 case '\n': result += "%0A"; break;
276 case '\'': result += "%27"; break;
277 case ',': result += "%2C"; break;
278 // case ':': result += "%3A"; break; // ok? probably...
279 case ';': result += "%3B"; break;
280 default:
281 auto c = static_cast<uint8_t>(s[i]);
282 if (c >= 0x80) {
283 result += '%';
284 char hex[4];
285 auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
286 assert(len == 2);
287 result.append(hex, static_cast<size_t>(len));
288 } else {
289 result += s[i];
290 }
291 break;
292 }
293 }
294
295 return result;
296}
297
298std::string file_extension(const std::string &path) {
299 std::smatch m;
300 thread_local auto re = std::regex("\\.([a-zA-Z0-9]+)$");
301 if (std::regex_search(path, m, re)) { return m[1].str(); }
302 return std::string();
303}
304
305bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; }
306
307template <typename T>
308bool parse_header(const char *beg, const char *end, T fn);
309
310template <typename T>
311bool parse_header(const char *beg, const char *end, T fn) {
312 // Skip trailing spaces and tabs.
313 while (beg < end && is_space_or_tab(end[-1])) {
314 end--;
315 }
316
317 auto p = beg;
318 while (p < end && *p != ':') {
319 p++;
320 }
321
322 auto name = std::string(beg, p);
323 if (!detail::fields::is_field_name(name)) { return false; }
324
325 if (p == end) { return false; }
326
327 auto key_end = p;
328
329 if (*p++ != ':') { return false; }
330
331 while (p < end && is_space_or_tab(*p)) {
332 p++;
333 }
334
335 if (p <= end) {
336 auto key_len = key_end - beg;
337 if (!key_len) { return false; }
338
339 auto key = std::string(beg, key_end);
340 auto val = std::string(p, end);
341
342 if (!detail::fields::is_field_value(val)) { return false; }
343
344 if (case_ignore::equal(key, "Location") ||
345 case_ignore::equal(key, "Referer")) {
346 fn(key, val);
347 } else {
348 fn(key, decode_path_component(val));
349 }
350
351 return true;
352 }
353
354 return false;
355}
356
357bool parse_trailers(stream_line_reader &line_reader, Headers &dest,
358 const Headers &src_headers) {
359 // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentions "The chunked
360 // transfer coding is complete when a chunk with a chunk-size of zero is
361 // received, possibly followed by a trailer section, and finally terminated by
362 // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1
363 //
364 // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section
365 // doesn't care for the existence of the final CRLF. In other words, it seems
366 // to be ok whether the final CRLF exists or not in the chunked data.
367 // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3
368 //
369 // According to the reference code in RFC 9112, cpp-httplib now allows
370 // chunked transfer coding data without the final CRLF.
371
372 // RFC 7230 Section 4.1.2 - Headers prohibited in trailers
373 thread_local case_ignore::unordered_set<std::string> prohibited_trailers = {
374 "transfer-encoding",
375 "content-length",
376 "host",
377 "authorization",
378 "www-authenticate",
379 "proxy-authenticate",
380 "proxy-authorization",
381 "cookie",
382 "set-cookie",
383 "cache-control",
384 "expect",
385 "max-forwards",
386 "pragma",
387 "range",
388 "te",
389 "age",
390 "expires",
391 "date",
392 "location",
393 "retry-after",
394 "vary",
395 "warning",
396 "content-encoding",
397 "content-type",
398 "content-range",
399 "trailer"};
400
401 case_ignore::unordered_set<std::string> declared_trailers;
402 auto trailer_header = get_header_value(src_headers, "Trailer", "", 0);
403 if (trailer_header && std::strlen(trailer_header)) {
404 auto len = std::strlen(trailer_header);
405 split(trailer_header, trailer_header + len, ',',
406 [&](const char *b, const char *e) {
407 const char *kbeg = b;
408 const char *kend = e;
409 while (kbeg < kend && (*kbeg == ' ' || *kbeg == '\t')) {
410 ++kbeg;
411 }
412 while (kend > kbeg && (kend[-1] == ' ' || kend[-1] == '\t')) {
413 --kend;
414 }
415 std::string key(kbeg, static_cast<size_t>(kend - kbeg));
416 if (!key.empty() &&
417 prohibited_trailers.find(key) == prohibited_trailers.end()) {
418 declared_trailers.insert(key);
419 }
420 });
421 }
422
423 size_t trailer_header_count = 0;
424 while (strcmp(line_reader.ptr(), "\r\n") != 0) {
425 if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
426 if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
427
428 constexpr auto line_terminator_len = 2;
429 auto line_beg = line_reader.ptr();
430 auto line_end =
431 line_reader.ptr() + line_reader.size() - line_terminator_len;
432
433 if (!parse_header(line_beg, line_end,
434 [&](const std::string &key, const std::string &val) {
435 if (declared_trailers.find(key) !=
436 declared_trailers.end()) {
437 dest.emplace(key, val);
438 trailer_header_count++;
439 }
440 })) {
441 return false;
442 }
443
444 if (!line_reader.getline()) { return false; }
445 }
446
447 return true;
448}
449
450std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left,
451 size_t right) {
452 while (b + left < e && is_space_or_tab(b[left])) {
453 left++;
454 }
455 while (right > 0 && is_space_or_tab(b[right - 1])) {
456 right--;
457 }
458 return std::make_pair(left, right);
459}
460
461std::string trim_copy(const std::string &s) {
462 auto r = trim(s.data(), s.data() + s.size(), 0, s.size());
463 return s.substr(r.first, r.second - r.first);
464}
465
466std::string trim_double_quotes_copy(const std::string &s) {
467 if (s.length() >= 2 && s.front() == '"' && s.back() == '"') {
468 return s.substr(1, s.size() - 2);
469 }
470 return s;
471}
472
473void
474divide(const char *data, std::size_t size, char d,
475 std::function<void(const char *, std::size_t, const char *, std::size_t)>
476 fn) {
477 const auto it = std::find(data, data + size, d);
478 const auto found = static_cast<std::size_t>(it != data + size);
479 const auto lhs_data = data;
480 const auto lhs_size = static_cast<std::size_t>(it - data);
481 const auto rhs_data = it + found;
482 const auto rhs_size = size - lhs_size - found;
483
484 fn(lhs_data, lhs_size, rhs_data, rhs_size);
485}
486
487void
488divide(const std::string &str, char d,
489 std::function<void(const char *, std::size_t, const char *, std::size_t)>
490 fn) {
491 divide(str.data(), str.size(), d, std::move(fn));
492}
493
494void split(const char *b, const char *e, char d,
495 std::function<void(const char *, const char *)> fn) {
496 return split(b, e, d, (std::numeric_limits<size_t>::max)(), std::move(fn));
497}
498
499void split(const char *b, const char *e, char d, size_t m,
500 std::function<void(const char *, const char *)> fn) {
501 size_t i = 0;
502 size_t beg = 0;
503 size_t count = 1;
504
505 while (e ? (b + i < e) : (b[i] != '\0')) {
506 if (b[i] == d && count < m) {
507 auto r = trim(b, e, beg, i);
508 if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
509 beg = i + 1;
510 count++;
511 }
512 i++;
513 }
514
515 if (i) {
516 auto r = trim(b, e, beg, i);
517 if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
518 }
519}
520
521bool split_find(const char *b, const char *e, char d, size_t m,
522 std::function<bool(const char *, const char *)> fn) {
523 size_t i = 0;
524 size_t beg = 0;
525 size_t count = 1;
526
527 while (e ? (b + i < e) : (b[i] != '\0')) {
528 if (b[i] == d && count < m) {
529 auto r = trim(b, e, beg, i);
530 if (r.first < r.second) {
531 auto found = fn(&b[r.first], &b[r.second]);
532 if (found) { return true; }
533 }
534 beg = i + 1;
535 count++;
536 }
537 i++;
538 }
539
540 if (i) {
541 auto r = trim(b, e, beg, i);
542 if (r.first < r.second) {
543 auto found = fn(&b[r.first], &b[r.second]);
544 if (found) { return true; }
545 }
546 }
547
548 return false;
549}
550
551bool split_find(const char *b, const char *e, char d,
552 std::function<bool(const char *, const char *)> fn) {
553 return split_find(b, e, d, (std::numeric_limits<size_t>::max)(),
554 std::move(fn));
555}
556
557stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer,
558 size_t fixed_buffer_size)
559 : strm_(strm), fixed_buffer_(fixed_buffer),
560 fixed_buffer_size_(fixed_buffer_size) {}
561
562const char *stream_line_reader::ptr() const {
563 if (growable_buffer_.empty()) {
564 return fixed_buffer_;
565 } else {
566 return growable_buffer_.data();
567 }
568}
569
570size_t stream_line_reader::size() const {
571 if (growable_buffer_.empty()) {
572 return fixed_buffer_used_size_;
573 } else {
574 return growable_buffer_.size();
575 }
576}
577
578bool stream_line_reader::end_with_crlf() const {
579 auto end = ptr() + size();
580 return size() >= 2 && end[-2] == '\r' && end[-1] == '\n';
581}
582
583bool stream_line_reader::getline() {
584 fixed_buffer_used_size_ = 0;
585 growable_buffer_.clear();
586
587#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
588 char prev_byte = 0;
589#endif
590
591 for (size_t i = 0;; i++) {
592 if (size() >= CPPHTTPLIB_MAX_LINE_LENGTH) {
593 // Treat exceptionally long lines as an error to
594 // prevent infinite loops/memory exhaustion
595 return false;
596 }
597 char byte;
598 auto n = strm_.read(&byte, 1);
599
600 if (n < 0) {
601 return false;
602 } else if (n == 0) {
603 if (i == 0) {
604 return false;
605 } else {
606 break;
607 }
608 }
609
610 append(byte);
611
612#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
613 if (byte == '\n') { break; }
614#else
615 if (prev_byte == '\r' && byte == '\n') { break; }
616 prev_byte = byte;
617#endif
618 }
619
620 return true;
621}
622
623void stream_line_reader::append(char c) {
624 if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) {
625 fixed_buffer_[fixed_buffer_used_size_++] = c;
626 fixed_buffer_[fixed_buffer_used_size_] = '\0';
627 } else {
628 if (growable_buffer_.empty()) {
629 assert(fixed_buffer_[fixed_buffer_used_size_] == '\0');
630 growable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_);
631 }
632 growable_buffer_ += c;
633 }
634}
635
636mmap::mmap(const char *path) { open(path); }
637
638mmap::~mmap() { close(); }
639
640bool mmap::open(const char *path) {
641 close();
642
643#if defined(_WIN32)
644 auto wpath = u8string_to_wstring(path);
645 if (wpath.empty()) { return false; }
646
647 hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ,
648 OPEN_EXISTING, NULL);
649
650 if (hFile_ == INVALID_HANDLE_VALUE) { return false; }
651
652 LARGE_INTEGER size{};
653 if (!::GetFileSizeEx(hFile_, &size)) { return false; }
654 // If the following line doesn't compile due to QuadPart, update Windows SDK.
655 // See:
656 // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721
657 if (static_cast<ULONGLONG>(size.QuadPart) >
658 (std::numeric_limits<decltype(size_)>::max)()) {
659 // `size_t` might be 32-bits, on 32-bits Windows.
660 return false;
661 }
662 size_ = static_cast<size_t>(size.QuadPart);
663
664 hMapping_ =
665 ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL);
666
667 // Special treatment for an empty file...
668 if (hMapping_ == NULL && size_ == 0) {
669 close();
670 is_open_empty_file = true;
671 return true;
672 }
673
674 if (hMapping_ == NULL) {
675 close();
676 return false;
677 }
678
679 addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0);
680
681 if (addr_ == nullptr) {
682 close();
683 return false;
684 }
685#else
686 fd_ = ::open(path, O_RDONLY);
687 if (fd_ == -1) { return false; }
688
689 struct stat sb;
690 if (fstat(fd_, &sb) == -1) {
691 close();
692 return false;
693 }
694 size_ = static_cast<size_t>(sb.st_size);
695
696 addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0);
697
698 // Special treatment for an empty file...
699 if (addr_ == MAP_FAILED && size_ == 0) {
700 close();
701 is_open_empty_file = true;
702 return false;
703 }
704#endif
705
706 return true;
707}
708
709bool mmap::is_open() const {
710 return is_open_empty_file ? true : addr_ != nullptr;
711}
712
713size_t mmap::size() const { return size_; }
714
715const char *mmap::data() const {
716 return is_open_empty_file ? "" : static_cast<const char *>(addr_);
717}
718
719void mmap::close() {
720#if defined(_WIN32)
721 if (addr_) {
722 ::UnmapViewOfFile(addr_);
723 addr_ = nullptr;
724 }
725
726 if (hMapping_) {
727 ::CloseHandle(hMapping_);
728 hMapping_ = NULL;
729 }
730
731 if (hFile_ != INVALID_HANDLE_VALUE) {
732 ::CloseHandle(hFile_);
733 hFile_ = INVALID_HANDLE_VALUE;
734 }
735
736 is_open_empty_file = false;
737#else
738 if (addr_ != nullptr) {
739 munmap(addr_, size_);
740 addr_ = nullptr;
741 }
742
743 if (fd_ != -1) {
744 ::close(fd_);
745 fd_ = -1;
746 }
747#endif
748 size_ = 0;
749}
750int close_socket(socket_t sock) {
751#ifdef _WIN32
752 return closesocket(sock);
753#else
754 return close(sock);
755#endif
756}
757
758template <typename T> inline ssize_t handle_EINTR(T fn) {
759 ssize_t res = 0;
760 while (true) {
761 res = fn();
762 if (res < 0 && errno == EINTR) {
763 std::this_thread::sleep_for(std::chrono::microseconds{1});
764 continue;
765 }
766 break;
767 }
768 return res;
769}
770
771ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) {
772 return handle_EINTR([&]() {
773 return recv(sock,
774#ifdef _WIN32
775 static_cast<char *>(ptr), static_cast<int>(size),
776#else
777 ptr, size,
778#endif
779 flags);
780 });
781}
782
783ssize_t send_socket(socket_t sock, const void *ptr, size_t size,
784 int flags) {
785 return handle_EINTR([&]() {
786 return send(sock,
787#ifdef _WIN32
788 static_cast<const char *>(ptr), static_cast<int>(size),
789#else
790 ptr, size,
791#endif
792 flags);
793 });
794}
795
796int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout) {
797#ifdef _WIN32
798 return ::WSAPoll(fds, nfds, timeout);
799#else
800 return ::poll(fds, nfds, timeout);
801#endif
802}
803
804template <bool Read>
805ssize_t select_impl(socket_t sock, time_t sec, time_t usec) {
806#ifdef __APPLE__
807 if (sock >= FD_SETSIZE) { return -1; }
808
809 fd_set fds, *rfds, *wfds;
810 FD_ZERO(&fds);
811 FD_SET(sock, &fds);
812 rfds = (Read ? &fds : nullptr);
813 wfds = (Read ? nullptr : &fds);
814
815 timeval tv;
816 tv.tv_sec = static_cast<long>(sec);
817 tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
818
819 return handle_EINTR([&]() {
820 return select(static_cast<int>(sock + 1), rfds, wfds, nullptr, &tv);
821 });
822#else
823 struct pollfd pfd;
824 pfd.fd = sock;
825 pfd.events = (Read ? POLLIN : POLLOUT);
826
827 auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
828
829 return handle_EINTR([&]() { return poll_wrapper(&pfd, 1, timeout); });
830#endif
831}
832
833ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
834 return select_impl<true>(sock, sec, usec);
835}
836
837ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
838 return select_impl<false>(sock, sec, usec);
839}
840
841Error wait_until_socket_is_ready(socket_t sock, time_t sec,
842 time_t usec) {
843#ifdef __APPLE__
844 if (sock >= FD_SETSIZE) { return Error::Connection; }
845
846 fd_set fdsr, fdsw;
847 FD_ZERO(&fdsr);
848 FD_ZERO(&fdsw);
849 FD_SET(sock, &fdsr);
850 FD_SET(sock, &fdsw);
851
852 timeval tv;
853 tv.tv_sec = static_cast<long>(sec);
854 tv.tv_usec = static_cast<decltype(tv.tv_usec)>(usec);
855
856 auto ret = handle_EINTR([&]() {
857 return select(static_cast<int>(sock + 1), &fdsr, &fdsw, nullptr, &tv);
858 });
859
860 if (ret == 0) { return Error::ConnectionTimeout; }
861
862 if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) {
863 auto error = 0;
864 socklen_t len = sizeof(error);
865 auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
866 reinterpret_cast<char *>(&error), &len);
867 auto successful = res >= 0 && !error;
868 return successful ? Error::Success : Error::Connection;
869 }
870
871 return Error::Connection;
872#else
873 struct pollfd pfd_read;
874 pfd_read.fd = sock;
875 pfd_read.events = POLLIN | POLLOUT;
876
877 auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
878
879 auto poll_res =
880 handle_EINTR([&]() { return poll_wrapper(&pfd_read, 1, timeout); });
881
882 if (poll_res == 0) { return Error::ConnectionTimeout; }
883
884 if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
885 auto error = 0;
886 socklen_t len = sizeof(error);
887 auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
888 reinterpret_cast<char *>(&error), &len);
889 auto successful = res >= 0 && !error;
890 return successful ? Error::Success : Error::Connection;
891 }
892
893 return Error::Connection;
894#endif
895}
896
897bool is_socket_alive(socket_t sock) {
898 const auto val = detail::select_read(sock, 0, 0);
899 if (val == 0) {
900 return true;
901 } else if (val < 0 && errno == EBADF) {
902 return false;
903 }
904 char buf[1];
905 return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0;
906}
907
908class SocketStream final : public Stream {
909public:
910 SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
911 time_t write_timeout_sec, time_t write_timeout_usec,
912 time_t max_timeout_msec = 0,
913 std::chrono::time_point<std::chrono::steady_clock> start_time =
914 (std::chrono::steady_clock::time_point::min)());
915 ~SocketStream() override;
916
917 bool is_readable() const override;
918 bool wait_readable() const override;
919 bool wait_writable() const override;
920 ssize_t read(char *ptr, size_t size) override;
921 ssize_t write(const char *ptr, size_t size) override;
922 void get_remote_ip_and_port(std::string &ip, int &port) const override;
923 void get_local_ip_and_port(std::string &ip, int &port) const override;
924 socket_t socket() const override;
925 time_t duration() const override;
926
927private:
928 socket_t sock_;
929 time_t read_timeout_sec_;
930 time_t read_timeout_usec_;
931 time_t write_timeout_sec_;
932 time_t write_timeout_usec_;
933 time_t max_timeout_msec_;
934 const std::chrono::time_point<std::chrono::steady_clock> start_time_;
935
936 std::vector<char> read_buff_;
937 size_t read_buff_off_ = 0;
938 size_t read_buff_content_size_ = 0;
939
940 static const size_t read_buff_size_ = 1024l * 4;
941};
942
943#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
944class SSLSocketStream final : public Stream {
945public:
946 SSLSocketStream(
947 socket_t sock, SSL *ssl, time_t read_timeout_sec,
948 time_t read_timeout_usec, time_t write_timeout_sec,
949 time_t write_timeout_usec, time_t max_timeout_msec = 0,
950 std::chrono::time_point<std::chrono::steady_clock> start_time =
951 (std::chrono::steady_clock::time_point::min)());
952 ~SSLSocketStream() override;
953
954 bool is_readable() const override;
955 bool wait_readable() const override;
956 bool wait_writable() const override;
957 ssize_t read(char *ptr, size_t size) override;
958 ssize_t write(const char *ptr, size_t size) override;
959 void get_remote_ip_and_port(std::string &ip, int &port) const override;
960 void get_local_ip_and_port(std::string &ip, int &port) const override;
961 socket_t socket() const override;
962 time_t duration() const override;
963
964private:
965 socket_t sock_;
966 SSL *ssl_;
967 time_t read_timeout_sec_;
968 time_t read_timeout_usec_;
969 time_t write_timeout_sec_;
970 time_t write_timeout_usec_;
971 time_t max_timeout_msec_;
972 const std::chrono::time_point<std::chrono::steady_clock> start_time_;
973};
974#endif
975
976bool keep_alive(const std::atomic<socket_t> &svr_sock, socket_t sock,
977 time_t keep_alive_timeout_sec) {
978 using namespace std::chrono;
979
980 const auto interval_usec =
981 CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND;
982
983 // Avoid expensive `steady_clock::now()` call for the first time
984 if (select_read(sock, 0, interval_usec) > 0) { return true; }
985
986 const auto start = steady_clock::now() - microseconds{interval_usec};
987 const auto timeout = seconds{keep_alive_timeout_sec};
988
989 while (true) {
990 if (svr_sock == INVALID_SOCKET) {
991 break; // Server socket is closed
992 }
993
994 auto val = select_read(sock, 0, interval_usec);
995 if (val < 0) {
996 break; // Ssocket error
997 } else if (val == 0) {
998 if (steady_clock::now() - start > timeout) {
999 break; // Timeout
1000 }
1001 } else {
1002 return true; // Ready for read
1003 }
1004 }
1005
1006 return false;
1007}
1008
1009template <typename T>
1010bool
1011process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
1012 size_t keep_alive_max_count,
1013 time_t keep_alive_timeout_sec, T callback) {
1014 assert(keep_alive_max_count > 0);
1015 auto ret = false;
1016 auto count = keep_alive_max_count;
1017 while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) {
1018 auto close_connection = count == 1;
1019 auto connection_closed = false;
1020 ret = callback(close_connection, connection_closed);
1021 if (!ret || connection_closed) { break; }
1022 count--;
1023 }
1024 return ret;
1025}
1026
1027template <typename T>
1028bool
1029process_server_socket(const std::atomic<socket_t> &svr_sock, socket_t sock,
1030 size_t keep_alive_max_count,
1031 time_t keep_alive_timeout_sec, time_t read_timeout_sec,
1032 time_t read_timeout_usec, time_t write_timeout_sec,
1033 time_t write_timeout_usec, T callback) {
1034 return process_server_socket_core(
1035 svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
1036 [&](bool close_connection, bool &connection_closed) {
1037 SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
1038 write_timeout_sec, write_timeout_usec);
1039 return callback(strm, close_connection, connection_closed);
1040 });
1041}
1042
1043bool process_client_socket(
1044 socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
1045 time_t write_timeout_sec, time_t write_timeout_usec,
1046 time_t max_timeout_msec,
1047 std::chrono::time_point<std::chrono::steady_clock> start_time,
1048 std::function<bool(Stream &)> callback) {
1049 SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
1050 write_timeout_sec, write_timeout_usec, max_timeout_msec,
1051 start_time);
1052 return callback(strm);
1053}
1054
1055int shutdown_socket(socket_t sock) {
1056#ifdef _WIN32
1057 return shutdown(sock, SD_BOTH);
1058#else
1059 return shutdown(sock, SHUT_RDWR);
1060#endif
1061}
1062
1063std::string escape_abstract_namespace_unix_domain(const std::string &s) {
1064 if (s.size() > 1 && s[0] == '\0') {
1065 auto ret = s;
1066 ret[0] = '@';
1067 return ret;
1068 }
1069 return s;
1070}
1071
1072std::string
1073unescape_abstract_namespace_unix_domain(const std::string &s) {
1074 if (s.size() > 1 && s[0] == '@') {
1075 auto ret = s;
1076 ret[0] = '\0';
1077 return ret;
1078 }
1079 return s;
1080}
1081
1082int getaddrinfo_with_timeout(const char *node, const char *service,
1083 const struct addrinfo *hints,
1084 struct addrinfo **res, time_t timeout_sec) {
1085#ifdef CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO
1086 if (timeout_sec <= 0) {
1087 // No timeout specified, use standard getaddrinfo
1088 return getaddrinfo(node, service, hints, res);
1089 }
1090
1091#ifdef _WIN32
1092 // Windows-specific implementation using GetAddrInfoEx with overlapped I/O
1093 OVERLAPPED overlapped = {0};
1094 HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
1095 if (!event) { return EAI_FAIL; }
1096
1097 overlapped.hEvent = event;
1098
1099 PADDRINFOEXW result_addrinfo = nullptr;
1100 HANDLE cancel_handle = nullptr;
1101
1102 ADDRINFOEXW hints_ex = {0};
1103 if (hints) {
1104 hints_ex.ai_flags = hints->ai_flags;
1105 hints_ex.ai_family = hints->ai_family;
1106 hints_ex.ai_socktype = hints->ai_socktype;
1107 hints_ex.ai_protocol = hints->ai_protocol;
1108 }
1109
1110 auto wnode = u8string_to_wstring(node);
1111 auto wservice = u8string_to_wstring(service);
1112
1113 auto ret = ::GetAddrInfoExW(wnode.data(), wservice.data(), NS_DNS, nullptr,
1114 hints ? &hints_ex : nullptr, &result_addrinfo,
1115 nullptr, &overlapped, nullptr, &cancel_handle);
1116
1117 if (ret == WSA_IO_PENDING) {
1118 auto wait_result =
1119 ::WaitForSingleObject(event, static_cast<DWORD>(timeout_sec * 1000));
1120 if (wait_result == WAIT_TIMEOUT) {
1121 if (cancel_handle) { ::GetAddrInfoExCancel(&cancel_handle); }
1122 ::CloseHandle(event);
1123 return EAI_AGAIN;
1124 }
1125
1126 DWORD bytes_returned;
1127 if (!::GetOverlappedResult((HANDLE)INVALID_SOCKET, &overlapped,
1128 &bytes_returned, FALSE)) {
1129 ::CloseHandle(event);
1130 return ::WSAGetLastError();
1131 }
1132 }
1133
1134 ::CloseHandle(event);
1135
1136 if (ret == NO_ERROR || ret == WSA_IO_PENDING) {
1137 *res = reinterpret_cast<struct addrinfo *>(result_addrinfo);
1138 return 0;
1139 }
1140
1141 return ret;
1142#elif TARGET_OS_MAC
1143 if (!node) { return EAI_NONAME; }
1144 // macOS implementation using CFHost API for asynchronous DNS resolution
1145 CFStringRef hostname_ref = CFStringCreateWithCString(
1146 kCFAllocatorDefault, node, kCFStringEncodingUTF8);
1147 if (!hostname_ref) { return EAI_MEMORY; }
1148
1149 CFHostRef host_ref = CFHostCreateWithName(kCFAllocatorDefault, hostname_ref);
1150 CFRelease(hostname_ref);
1151 if (!host_ref) { return EAI_MEMORY; }
1152
1153 // Set up context for callback
1154 struct CFHostContext {
1155 bool completed = false;
1156 bool success = false;
1157 CFArrayRef addresses = nullptr;
1158 std::mutex mutex;
1159 std::condition_variable cv;
1160 } context;
1161
1162 CFHostClientContext client_context;
1163 memset(&client_context, 0, sizeof(client_context));
1164 client_context.info = &context;
1165
1166 // Set callback
1167 auto callback = [](CFHostRef theHost, CFHostInfoType /*typeInfo*/,
1168 const CFStreamError *error, void *info) {
1169 auto ctx = static_cast<CFHostContext *>(info);
1170 std::lock_guard<std::mutex> lock(ctx->mutex);
1171
1172 if (error && error->error != 0) {
1173 ctx->success = false;
1174 } else {
1175 Boolean hasBeenResolved;
1176 ctx->addresses = CFHostGetAddressing(theHost, &hasBeenResolved);
1177 if (ctx->addresses && hasBeenResolved) {
1178 CFRetain(ctx->addresses);
1179 ctx->success = true;
1180 } else {
1181 ctx->success = false;
1182 }
1183 }
1184 ctx->completed = true;
1185 ctx->cv.notify_one();
1186 };
1187
1188 if (!CFHostSetClient(host_ref, callback, &client_context)) {
1189 CFRelease(host_ref);
1190 return EAI_SYSTEM;
1191 }
1192
1193 // Schedule on run loop
1194 CFRunLoopRef run_loop = CFRunLoopGetCurrent();
1195 CFHostScheduleWithRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
1196
1197 // Start resolution
1198 CFStreamError stream_error;
1199 if (!CFHostStartInfoResolution(host_ref, kCFHostAddresses, &stream_error)) {
1200 CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
1201 CFRelease(host_ref);
1202 return EAI_FAIL;
1203 }
1204
1205 // Wait for completion with timeout
1206 auto timeout_time =
1207 std::chrono::steady_clock::now() + std::chrono::seconds(timeout_sec);
1208 bool timed_out = false;
1209
1210 {
1211 std::unique_lock<std::mutex> lock(context.mutex);
1212
1213 while (!context.completed) {
1214 auto now = std::chrono::steady_clock::now();
1215 if (now >= timeout_time) {
1216 timed_out = true;
1217 break;
1218 }
1219
1220 // Run the runloop for a short time
1221 lock.unlock();
1222 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true);
1223 lock.lock();
1224 }
1225 }
1226
1227 // Clean up
1228 CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
1229 CFHostSetClient(host_ref, nullptr, nullptr);
1230
1231 if (timed_out || !context.completed) {
1232 CFHostCancelInfoResolution(host_ref, kCFHostAddresses);
1233 CFRelease(host_ref);
1234 return EAI_AGAIN;
1235 }
1236
1237 if (!context.success || !context.addresses) {
1238 CFRelease(host_ref);
1239 return EAI_NODATA;
1240 }
1241
1242 // Convert CFArray to addrinfo
1243 CFIndex count = CFArrayGetCount(context.addresses);
1244 if (count == 0) {
1245 CFRelease(context.addresses);
1246 CFRelease(host_ref);
1247 return EAI_NODATA;
1248 }
1249
1250 struct addrinfo *result_addrinfo = nullptr;
1251 struct addrinfo **current = &result_addrinfo;
1252
1253 for (CFIndex i = 0; i < count; i++) {
1254 CFDataRef addr_data =
1255 static_cast<CFDataRef>(CFArrayGetValueAtIndex(context.addresses, i));
1256 if (!addr_data) continue;
1257
1258 const struct sockaddr *sockaddr_ptr =
1259 reinterpret_cast<const struct sockaddr *>(CFDataGetBytePtr(addr_data));
1260 socklen_t sockaddr_len = static_cast<socklen_t>(CFDataGetLength(addr_data));
1261
1262 // Allocate addrinfo structure
1263 *current = static_cast<struct addrinfo *>(malloc(sizeof(struct addrinfo)));
1264 if (!*current) {
1265 freeaddrinfo(result_addrinfo);
1266 CFRelease(context.addresses);
1267 CFRelease(host_ref);
1268 return EAI_MEMORY;
1269 }
1270
1271 memset(*current, 0, sizeof(struct addrinfo));
1272
1273 // Set up addrinfo fields
1274 (*current)->ai_family = sockaddr_ptr->sa_family;
1275 (*current)->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM;
1276 (*current)->ai_protocol = hints ? hints->ai_protocol : IPPROTO_TCP;
1277 (*current)->ai_addrlen = sockaddr_len;
1278
1279 // Copy sockaddr
1280 (*current)->ai_addr = static_cast<struct sockaddr *>(malloc(sockaddr_len));
1281 if (!(*current)->ai_addr) {
1282 freeaddrinfo(result_addrinfo);
1283 CFRelease(context.addresses);
1284 CFRelease(host_ref);
1285 return EAI_MEMORY;
1286 }
1287 memcpy((*current)->ai_addr, sockaddr_ptr, sockaddr_len);
1288
1289 // Set port if service is specified
1290 if (service && strlen(service) > 0) {
1291 int port = atoi(service);
1292 if (port > 0) {
1293 if (sockaddr_ptr->sa_family == AF_INET) {
1294 reinterpret_cast<struct sockaddr_in *>((*current)->ai_addr)
1295 ->sin_port = htons(static_cast<uint16_t>(port));
1296 } else if (sockaddr_ptr->sa_family == AF_INET6) {
1297 reinterpret_cast<struct sockaddr_in6 *>((*current)->ai_addr)
1298 ->sin6_port = htons(static_cast<uint16_t>(port));
1299 }
1300 }
1301 }
1302
1303 current = &((*current)->ai_next);
1304 }
1305
1306 CFRelease(context.addresses);
1307 CFRelease(host_ref);
1308
1309 *res = result_addrinfo;
1310 return 0;
1311#elif defined(_GNU_SOURCE) && defined(__GLIBC__) && \
1312 (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2))
1313 // Linux implementation using getaddrinfo_a for asynchronous DNS resolution
1314 struct gaicb request;
1315 struct gaicb *requests[1] = {&request};
1316 struct sigevent sevp;
1317 struct timespec timeout;
1318
1319 // Initialize the request structure
1320 memset(&request, 0, sizeof(request));
1321 request.ar_name = node;
1322 request.ar_service = service;
1323 request.ar_request = hints;
1324
1325 // Set up timeout
1326 timeout.tv_sec = timeout_sec;
1327 timeout.tv_nsec = 0;
1328
1329 // Initialize sigevent structure (not used, but required)
1330 memset(&sevp, 0, sizeof(sevp));
1331 sevp.sigev_notify = SIGEV_NONE;
1332
1333 // Start asynchronous resolution
1334 int start_result = getaddrinfo_a(GAI_NOWAIT, requests, 1, &sevp);
1335 if (start_result != 0) { return start_result; }
1336
1337 // Wait for completion with timeout
1338 int wait_result =
1339 gai_suspend((const struct gaicb *const *)requests, 1, &timeout);
1340
1341 if (wait_result == 0 || wait_result == EAI_ALLDONE) {
1342 // Completed successfully, get the result
1343 int gai_result = gai_error(&request);
1344 if (gai_result == 0) {
1345 *res = request.ar_result;
1346 return 0;
1347 } else {
1348 // Clean up on error
1349 if (request.ar_result) { freeaddrinfo(request.ar_result); }
1350 return gai_result;
1351 }
1352 } else if (wait_result == EAI_AGAIN) {
1353 // Timeout occurred, cancel the request
1354 gai_cancel(&request);
1355 return EAI_AGAIN;
1356 } else {
1357 // Other error occurred
1358 gai_cancel(&request);
1359 return wait_result;
1360 }
1361#else
1362 // Fallback implementation using thread-based timeout for other Unix systems
1363
1364 struct GetAddrInfoState {
1365 ~GetAddrInfoState() {
1366 if (info) { freeaddrinfo(info); }
1367 }
1368
1369 std::mutex mutex;
1370 std::condition_variable result_cv;
1371 bool completed = false;
1372 int result = EAI_SYSTEM;
1373 std::string node;
1374 std::string service;
1375 struct addrinfo hints;
1376 struct addrinfo *info = nullptr;
1377 };
1378
1379 // Allocate on the heap, so the resolver thread can keep using the data.
1380 auto state = std::make_shared<GetAddrInfoState>();
1381 if (node) { state->node = node; }
1382 state->service = service;
1383 state->hints = *hints;
1384
1385 std::thread resolve_thread([state]() {
1386 auto thread_result =
1387 getaddrinfo(state->node.c_str(), state->service.c_str(), &state->hints,
1388 &state->info);
1389
1390 std::lock_guard<std::mutex> lock(state->mutex);
1391 state->result = thread_result;
1392 state->completed = true;
1393 state->result_cv.notify_one();
1394 });
1395
1396 // Wait for completion or timeout
1397 std::unique_lock<std::mutex> lock(state->mutex);
1398 auto finished =
1399 state->result_cv.wait_for(lock, std::chrono::seconds(timeout_sec),
1400 [&] { return state->completed; });
1401
1402 if (finished) {
1403 // Operation completed within timeout
1404 resolve_thread.join();
1405 *res = state->info;
1406 state->info = nullptr; // Pass ownership to caller
1407 return state->result;
1408 } else {
1409 // Timeout occurred
1410 resolve_thread.detach(); // Let the thread finish in background
1411 return EAI_AGAIN; // Return timeout error
1412 }
1413#endif
1414#else
1415 (void)(timeout_sec); // Unused parameter for non-blocking getaddrinfo
1416 return getaddrinfo(node, service, hints, res);
1417#endif
1418}
1419
1420template <typename BindOrConnect>
1421socket_t create_socket(const std::string &host, const std::string &ip, int port,
1422 int address_family, int socket_flags, bool tcp_nodelay,
1423 bool ipv6_v6only, SocketOptions socket_options,
1424 BindOrConnect bind_or_connect, time_t timeout_sec = 0) {
1425 // Get address info
1426 const char *node = nullptr;
1427 struct addrinfo hints;
1428 struct addrinfo *result;
1429
1430 memset(&hints, 0, sizeof(struct addrinfo));
1431 hints.ai_socktype = SOCK_STREAM;
1432 hints.ai_protocol = IPPROTO_IP;
1433
1434 if (!ip.empty()) {
1435 node = ip.c_str();
1436 // Ask getaddrinfo to convert IP in c-string to address
1437 hints.ai_family = AF_UNSPEC;
1438 hints.ai_flags = AI_NUMERICHOST;
1439 } else {
1440 if (!host.empty()) { node = host.c_str(); }
1441 hints.ai_family = address_family;
1442 hints.ai_flags = socket_flags;
1443 }
1444
1445#if !defined(_WIN32) || defined(CPPHTTPLIB_HAVE_AFUNIX_H)
1446 if (hints.ai_family == AF_UNIX) {
1447 const auto addrlen = host.length();
1448 if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; }
1449
1450#ifdef SOCK_CLOEXEC
1451 auto sock = socket(hints.ai_family, hints.ai_socktype | SOCK_CLOEXEC,
1452 hints.ai_protocol);
1453#else
1454 auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
1455#endif
1456
1457 if (sock != INVALID_SOCKET) {
1458 sockaddr_un addr{};
1459 addr.sun_family = AF_UNIX;
1460
1461 auto unescaped_host = unescape_abstract_namespace_unix_domain(host);
1462 std::copy(unescaped_host.begin(), unescaped_host.end(), addr.sun_path);
1463
1464 hints.ai_addr = reinterpret_cast<sockaddr *>(&addr);
1465 hints.ai_addrlen = static_cast<socklen_t>(
1466 sizeof(addr) - sizeof(addr.sun_path) + addrlen);
1467
1468#ifndef SOCK_CLOEXEC
1469#ifndef _WIN32
1470 fcntl(sock, F_SETFD, FD_CLOEXEC);
1471#endif
1472#endif
1473
1474 if (socket_options) { socket_options(sock); }
1475
1476#ifdef _WIN32
1477 // Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so
1478 // remove the option.
1479 detail::set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0);
1480#endif
1481
1482 bool dummy;
1483 if (!bind_or_connect(sock, hints, dummy)) {
1484 close_socket(sock);
1485 sock = INVALID_SOCKET;
1486 }
1487 }
1488 return sock;
1489 }
1490#endif
1491
1492 auto service = std::to_string(port);
1493
1494 if (getaddrinfo_with_timeout(node, service.c_str(), &hints, &result,
1495 timeout_sec)) {
1496#if defined __linux__ && !defined __ANDROID__
1497 res_init();
1498#endif
1499 return INVALID_SOCKET;
1500 }
1501 auto se = detail::scope_exit([&] { freeaddrinfo(result); });
1502
1503 for (auto rp = result; rp; rp = rp->ai_next) {
1504 // Create a socket
1505#ifdef _WIN32
1506 auto sock =
1507 WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0,
1508 WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED);
1509 /**
1510 * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1
1511 * and above the socket creation fails on older Windows Systems.
1512 *
1513 * Let's try to create a socket the old way in this case.
1514 *
1515 * Reference:
1516 * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa
1517 *
1518 * WSA_FLAG_NO_HANDLE_INHERIT:
1519 * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with
1520 * SP1, and later
1521 *
1522 */
1523 if (sock == INVALID_SOCKET) {
1524 sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
1525 }
1526#else
1527
1528#ifdef SOCK_CLOEXEC
1529 auto sock =
1530 socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC, rp->ai_protocol);
1531#else
1532 auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
1533#endif
1534
1535#endif
1536 if (sock == INVALID_SOCKET) { continue; }
1537
1538#if !defined _WIN32 && !defined SOCK_CLOEXEC
1539 if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) {
1540 close_socket(sock);
1541 continue;
1542 }
1543#endif
1544
1545 if (tcp_nodelay) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); }
1546
1547 if (rp->ai_family == AF_INET6) {
1548 set_socket_opt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ipv6_v6only ? 1 : 0);
1549 }
1550
1551 if (socket_options) { socket_options(sock); }
1552
1553 // bind or connect
1554 auto quit = false;
1555 if (bind_or_connect(sock, *rp, quit)) { return sock; }
1556
1557 close_socket(sock);
1558
1559 if (quit) { break; }
1560 }
1561
1562 return INVALID_SOCKET;
1563}
1564
1565void set_nonblocking(socket_t sock, bool nonblocking) {
1566#ifdef _WIN32
1567 auto flags = nonblocking ? 1UL : 0UL;
1568 ioctlsocket(sock, FIONBIO, &flags);
1569#else
1570 auto flags = fcntl(sock, F_GETFL, 0);
1571 fcntl(sock, F_SETFL,
1572 nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK)));
1573#endif
1574}
1575
1576bool is_connection_error() {
1577#ifdef _WIN32
1578 return WSAGetLastError() != WSAEWOULDBLOCK;
1579#else
1580 return errno != EINPROGRESS;
1581#endif
1582}
1583
1584bool bind_ip_address(socket_t sock, const std::string &host) {
1585 struct addrinfo hints;
1586 struct addrinfo *result;
1587
1588 memset(&hints, 0, sizeof(struct addrinfo));
1589 hints.ai_family = AF_UNSPEC;
1590 hints.ai_socktype = SOCK_STREAM;
1591 hints.ai_protocol = 0;
1592
1593 if (getaddrinfo_with_timeout(host.c_str(), "0", &hints, &result, 0)) {
1594 return false;
1595 }
1596
1597 auto se = detail::scope_exit([&] { freeaddrinfo(result); });
1598
1599 auto ret = false;
1600 for (auto rp = result; rp; rp = rp->ai_next) {
1601 const auto &ai = *rp;
1602 if (!::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
1603 ret = true;
1604 break;
1605 }
1606 }
1607
1608 return ret;
1609}
1610
1611#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__
1612#define USE_IF2IP
1613#endif
1614
1615#ifdef USE_IF2IP
1616std::string if2ip(int address_family, const std::string &ifn) {
1617 struct ifaddrs *ifap;
1618 getifaddrs(&ifap);
1619 auto se = detail::scope_exit([&] { freeifaddrs(ifap); });
1620
1621 std::string addr_candidate;
1622 for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
1623 if (ifa->ifa_addr && ifn == ifa->ifa_name &&
1624 (AF_UNSPEC == address_family ||
1625 ifa->ifa_addr->sa_family == address_family)) {
1626 if (ifa->ifa_addr->sa_family == AF_INET) {
1627 auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
1628 char buf[INET_ADDRSTRLEN];
1629 if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
1630 return std::string(buf, INET_ADDRSTRLEN);
1631 }
1632 } else if (ifa->ifa_addr->sa_family == AF_INET6) {
1633 auto sa = reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_addr);
1634 if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) {
1635 char buf[INET6_ADDRSTRLEN] = {};
1636 if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) {
1637 // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL
1638 auto s6_addr_head = sa->sin6_addr.s6_addr[0];
1639 if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) {
1640 addr_candidate = std::string(buf, INET6_ADDRSTRLEN);
1641 } else {
1642 return std::string(buf, INET6_ADDRSTRLEN);
1643 }
1644 }
1645 }
1646 }
1647 }
1648 }
1649 return addr_candidate;
1650}
1651#endif
1652
1653socket_t create_client_socket(
1654 const std::string &host, const std::string &ip, int port,
1655 int address_family, bool tcp_nodelay, bool ipv6_v6only,
1656 SocketOptions socket_options, time_t connection_timeout_sec,
1657 time_t connection_timeout_usec, time_t read_timeout_sec,
1658 time_t read_timeout_usec, time_t write_timeout_sec,
1659 time_t write_timeout_usec, const std::string &intf, Error &error) {
1660 auto sock = create_socket(
1661 host, ip, port, address_family, 0, tcp_nodelay, ipv6_v6only,
1662 std::move(socket_options),
1663 [&](socket_t sock2, struct addrinfo &ai, bool &quit) -> bool {
1664 if (!intf.empty()) {
1665#ifdef USE_IF2IP
1666 auto ip_from_if = if2ip(address_family, intf);
1667 if (ip_from_if.empty()) { ip_from_if = intf; }
1668 if (!bind_ip_address(sock2, ip_from_if)) {
1669 error = Error::BindIPAddress;
1670 return false;
1671 }
1672#endif
1673 }
1674
1675 set_nonblocking(sock2, true);
1676
1677 auto ret =
1678 ::connect(sock2, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
1679
1680 if (ret < 0) {
1681 if (is_connection_error()) {
1682 error = Error::Connection;
1683 return false;
1684 }
1685 error = wait_until_socket_is_ready(sock2, connection_timeout_sec,
1686 connection_timeout_usec);
1687 if (error != Error::Success) {
1688 if (error == Error::ConnectionTimeout) { quit = true; }
1689 return false;
1690 }
1691 }
1692
1693 set_nonblocking(sock2, false);
1694 set_socket_opt_time(sock2, SOL_SOCKET, SO_RCVTIMEO, read_timeout_sec,
1695 read_timeout_usec);
1696 set_socket_opt_time(sock2, SOL_SOCKET, SO_SNDTIMEO, write_timeout_sec,
1697 write_timeout_usec);
1698
1699 error = Error::Success;
1700 return true;
1701 },
1702 connection_timeout_sec); // Pass DNS timeout
1703
1704 if (sock != INVALID_SOCKET) {
1705 error = Error::Success;
1706 } else {
1707 if (error == Error::Success) { error = Error::Connection; }
1708 }
1709
1710 return sock;
1711}
1712
1713bool get_ip_and_port(const struct sockaddr_storage &addr,
1714 socklen_t addr_len, std::string &ip, int &port) {
1715 if (addr.ss_family == AF_INET) {
1716 port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
1717 } else if (addr.ss_family == AF_INET6) {
1718 port =
1719 ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
1720 } else {
1721 return false;
1722 }
1723
1724 std::array<char, NI_MAXHOST> ipstr{};
1725 if (getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len,
1726 ipstr.data(), static_cast<socklen_t>(ipstr.size()), nullptr,
1727 0, NI_NUMERICHOST)) {
1728 return false;
1729 }
1730
1731 ip = ipstr.data();
1732 return true;
1733}
1734
1735void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) {
1736 struct sockaddr_storage addr;
1737 socklen_t addr_len = sizeof(addr);
1738 if (!getsockname(sock, reinterpret_cast<struct sockaddr *>(&addr),
1739 &addr_len)) {
1740 get_ip_and_port(addr, addr_len, ip, port);
1741 }
1742}
1743
1744void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
1745 struct sockaddr_storage addr;
1746 socklen_t addr_len = sizeof(addr);
1747
1748 if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
1749 &addr_len)) {
1750#ifndef _WIN32
1751 if (addr.ss_family == AF_UNIX) {
1752#if defined(__linux__)
1753 struct ucred ucred;
1754 socklen_t len = sizeof(ucred);
1755 if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) {
1756 port = ucred.pid;
1757 }
1758#elif defined(SOL_LOCAL) && defined(SO_PEERPID)
1759 pid_t pid;
1760 socklen_t len = sizeof(pid);
1761 if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) {
1762 port = pid;
1763 }
1764#endif
1765 return;
1766 }
1767#endif
1768 get_ip_and_port(addr, addr_len, ip, port);
1769 }
1770}
1771
1772constexpr unsigned int str2tag_core(const char *s, size_t l,
1773 unsigned int h) {
1774 return (l == 0)
1775 ? h
1776 : str2tag_core(
1777 s + 1, l - 1,
1778 // Unsets the 6 high bits of h, therefore no overflow happens
1779 (((std::numeric_limits<unsigned int>::max)() >> 6) &
1780 h * 33) ^
1781 static_cast<unsigned char>(*s));
1782}
1783
1784unsigned int str2tag(const std::string &s) {
1785 return str2tag_core(s.data(), s.size(), 0);
1786}
1787
1788namespace udl {
1789
1790constexpr unsigned int operator""_t(const char *s, size_t l) {
1791 return str2tag_core(s, l, 0);
1792}
1793
1794} // namespace udl
1795
1796std::string
1797find_content_type(const std::string &path,
1798 const std::map<std::string, std::string> &user_data,
1799 const std::string &default_content_type) {
1800 auto ext = file_extension(path);
1801
1802 auto it = user_data.find(ext);
1803 if (it != user_data.end()) { return it->second; }
1804
1805 using udl::operator""_t;
1806
1807 switch (str2tag(ext)) {
1808 default: return default_content_type;
1809
1810 case "css"_t: return "text/css";
1811 case "csv"_t: return "text/csv";
1812 case "htm"_t:
1813 case "html"_t: return "text/html";
1814 case "js"_t:
1815 case "mjs"_t: return "text/javascript";
1816 case "txt"_t: return "text/plain";
1817 case "vtt"_t: return "text/vtt";
1818
1819 case "apng"_t: return "image/apng";
1820 case "avif"_t: return "image/avif";
1821 case "bmp"_t: return "image/bmp";
1822 case "gif"_t: return "image/gif";
1823 case "png"_t: return "image/png";
1824 case "svg"_t: return "image/svg+xml";
1825 case "webp"_t: return "image/webp";
1826 case "ico"_t: return "image/x-icon";
1827 case "tif"_t: return "image/tiff";
1828 case "tiff"_t: return "image/tiff";
1829 case "jpg"_t:
1830 case "jpeg"_t: return "image/jpeg";
1831
1832 case "mp4"_t: return "video/mp4";
1833 case "mpeg"_t: return "video/mpeg";
1834 case "webm"_t: return "video/webm";
1835
1836 case "mp3"_t: return "audio/mp3";
1837 case "mpga"_t: return "audio/mpeg";
1838 case "weba"_t: return "audio/webm";
1839 case "wav"_t: return "audio/wave";
1840
1841 case "otf"_t: return "font/otf";
1842 case "ttf"_t: return "font/ttf";
1843 case "woff"_t: return "font/woff";
1844 case "woff2"_t: return "font/woff2";
1845
1846 case "7z"_t: return "application/x-7z-compressed";
1847 case "atom"_t: return "application/atom+xml";
1848 case "pdf"_t: return "application/pdf";
1849 case "json"_t: return "application/json";
1850 case "rss"_t: return "application/rss+xml";
1851 case "tar"_t: return "application/x-tar";
1852 case "xht"_t:
1853 case "xhtml"_t: return "application/xhtml+xml";
1854 case "xslt"_t: return "application/xslt+xml";
1855 case "xml"_t: return "application/xml";
1856 case "gz"_t: return "application/gzip";
1857 case "zip"_t: return "application/zip";
1858 case "wasm"_t: return "application/wasm";
1859 }
1860}
1861
1862bool can_compress_content_type(const std::string &content_type) {
1863 using udl::operator""_t;
1864
1865 auto tag = str2tag(content_type);
1866
1867 switch (tag) {
1868 case "image/svg+xml"_t:
1869 case "application/javascript"_t:
1870 case "application/json"_t:
1871 case "application/xml"_t:
1872 case "application/protobuf"_t:
1873 case "application/xhtml+xml"_t: return true;
1874
1875 case "text/event-stream"_t: return false;
1876
1877 default: return !content_type.rfind("text/", 0);
1878 }
1879}
1880
1881EncodingType encoding_type(const Request &req, const Response &res) {
1882 auto ret =
1883 detail::can_compress_content_type(res.get_header_value("Content-Type"));
1884 if (!ret) { return EncodingType::None; }
1885
1886 const auto &s = req.get_header_value("Accept-Encoding");
1887 (void)(s);
1888
1889#ifdef CPPHTTPLIB_BROTLI_SUPPORT
1890 // TODO: 'Accept-Encoding' has br, not br;q=0
1891 ret = s.find("br") != std::string::npos;
1892 if (ret) { return EncodingType::Brotli; }
1893#endif
1894
1895#ifdef CPPHTTPLIB_ZLIB_SUPPORT
1896 // TODO: 'Accept-Encoding' has gzip, not gzip;q=0
1897 ret = s.find("gzip") != std::string::npos;
1898 if (ret) { return EncodingType::Gzip; }
1899#endif
1900
1901#ifdef CPPHTTPLIB_ZSTD_SUPPORT
1902 // TODO: 'Accept-Encoding' has zstd, not zstd;q=0
1903 ret = s.find("zstd") != std::string::npos;
1904 if (ret) { return EncodingType::Zstd; }
1905#endif
1906
1907 return EncodingType::None;
1908}
1909
1910bool nocompressor::compress(const char *data, size_t data_length,
1911 bool /*last*/, Callback callback) {
1912 if (!data_length) { return true; }
1913 return callback(data, data_length);
1914}
1915
1916#ifdef CPPHTTPLIB_ZLIB_SUPPORT
1917gzip_compressor::gzip_compressor() {
1918 std::memset(&strm_, 0, sizeof(strm_));
1919 strm_.zalloc = Z_NULL;
1920 strm_.zfree = Z_NULL;
1921 strm_.opaque = Z_NULL;
1922
1923 is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
1924 Z_DEFAULT_STRATEGY) == Z_OK;
1925}
1926
1927gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); }
1928
1929bool gzip_compressor::compress(const char *data, size_t data_length,
1930 bool last, Callback callback) {
1931 assert(is_valid_);
1932
1933 do {
1934 constexpr size_t max_avail_in =
1935 (std::numeric_limits<decltype(strm_.avail_in)>::max)();
1936
1937 strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
1938 (std::min)(data_length, max_avail_in));
1939 strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
1940
1941 data_length -= strm_.avail_in;
1942 data += strm_.avail_in;
1943
1944 auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH;
1945 auto ret = Z_OK;
1946
1947 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
1948 do {
1949 strm_.avail_out = static_cast<uInt>(buff.size());
1950 strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
1951
1952 ret = deflate(&strm_, flush);
1953 if (ret == Z_STREAM_ERROR) { return false; }
1954
1955 if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
1956 return false;
1957 }
1958 } while (strm_.avail_out == 0);
1959
1960 assert((flush == Z_FINISH && ret == Z_STREAM_END) ||
1961 (flush == Z_NO_FLUSH && ret == Z_OK));
1962 assert(strm_.avail_in == 0);
1963 } while (data_length > 0);
1964
1965 return true;
1966}
1967
1968gzip_decompressor::gzip_decompressor() {
1969 std::memset(&strm_, 0, sizeof(strm_));
1970 strm_.zalloc = Z_NULL;
1971 strm_.zfree = Z_NULL;
1972 strm_.opaque = Z_NULL;
1973
1974 // 15 is the value of wbits, which should be at the maximum possible value
1975 // to ensure that any gzip stream can be decoded. The offset of 32 specifies
1976 // that the stream type should be automatically detected either gzip or
1977 // deflate.
1978 is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
1979}
1980
1981gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); }
1982
1983bool gzip_decompressor::is_valid() const { return is_valid_; }
1984
1985bool gzip_decompressor::decompress(const char *data, size_t data_length,
1986 Callback callback) {
1987 assert(is_valid_);
1988
1989 auto ret = Z_OK;
1990
1991 do {
1992 constexpr size_t max_avail_in =
1993 (std::numeric_limits<decltype(strm_.avail_in)>::max)();
1994
1995 strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
1996 (std::min)(data_length, max_avail_in));
1997 strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
1998
1999 data_length -= strm_.avail_in;
2000 data += strm_.avail_in;
2001
2002 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
2003 while (strm_.avail_in > 0 && ret == Z_OK) {
2004 strm_.avail_out = static_cast<uInt>(buff.size());
2005 strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
2006
2007 ret = inflate(&strm_, Z_NO_FLUSH);
2008
2009 assert(ret != Z_STREAM_ERROR);
2010 switch (ret) {
2011 case Z_NEED_DICT:
2012 case Z_DATA_ERROR:
2013 case Z_MEM_ERROR: inflateEnd(&strm_); return false;
2014 }
2015
2016 if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
2017 return false;
2018 }
2019 }
2020
2021 if (ret != Z_OK && ret != Z_STREAM_END) { return false; }
2022
2023 } while (data_length > 0);
2024
2025 return true;
2026}
2027#endif
2028
2029#ifdef CPPHTTPLIB_BROTLI_SUPPORT
2030brotli_compressor::brotli_compressor() {
2031 state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
2032}
2033
2034brotli_compressor::~brotli_compressor() {
2035 BrotliEncoderDestroyInstance(state_);
2036}
2037
2038bool brotli_compressor::compress(const char *data, size_t data_length,
2039 bool last, Callback callback) {
2040 std::array<uint8_t, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
2041
2042 auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS;
2043 auto available_in = data_length;
2044 auto next_in = reinterpret_cast<const uint8_t *>(data);
2045
2046 for (;;) {
2047 if (last) {
2048 if (BrotliEncoderIsFinished(state_)) { break; }
2049 } else {
2050 if (!available_in) { break; }
2051 }
2052
2053 auto available_out = buff.size();
2054 auto next_out = buff.data();
2055
2056 if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in,
2057 &available_out, &next_out, nullptr)) {
2058 return false;
2059 }
2060
2061 auto output_bytes = buff.size() - available_out;
2062 if (output_bytes) {
2063 callback(reinterpret_cast<const char *>(buff.data()), output_bytes);
2064 }
2065 }
2066
2067 return true;
2068}
2069
2070brotli_decompressor::brotli_decompressor() {
2071 decoder_s = BrotliDecoderCreateInstance(0, 0, 0);
2072 decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
2073 : BROTLI_DECODER_RESULT_ERROR;
2074}
2075
2076brotli_decompressor::~brotli_decompressor() {
2077 if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); }
2078}
2079
2080bool brotli_decompressor::is_valid() const { return decoder_s; }
2081
2082bool brotli_decompressor::decompress(const char *data,
2083 size_t data_length,
2084 Callback callback) {
2085 if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
2086 decoder_r == BROTLI_DECODER_RESULT_ERROR) {
2087 return 0;
2088 }
2089
2090 auto next_in = reinterpret_cast<const uint8_t *>(data);
2091 size_t avail_in = data_length;
2092 size_t total_out;
2093
2094 decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
2095
2096 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
2097 while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
2098 char *next_out = buff.data();
2099 size_t avail_out = buff.size();
2100
2101 decoder_r = BrotliDecoderDecompressStream(
2102 decoder_s, &avail_in, &next_in, &avail_out,
2103 reinterpret_cast<uint8_t **>(&next_out), &total_out);
2104
2105 if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
2106
2107 if (!callback(buff.data(), buff.size() - avail_out)) { return false; }
2108 }
2109
2110 return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
2111 decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
2112}
2113#endif
2114
2115#ifdef CPPHTTPLIB_ZSTD_SUPPORT
2116zstd_compressor::zstd_compressor() {
2117 ctx_ = ZSTD_createCCtx();
2118 ZSTD_CCtx_setParameter(ctx_, ZSTD_c_compressionLevel, ZSTD_fast);
2119}
2120
2121zstd_compressor::~zstd_compressor() { ZSTD_freeCCtx(ctx_); }
2122
2123bool zstd_compressor::compress(const char *data, size_t data_length,
2124 bool last, Callback callback) {
2125 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
2126
2127 ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue;
2128 ZSTD_inBuffer input = {data, data_length, 0};
2129
2130 bool finished;
2131 do {
2132 ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
2133 size_t const remaining = ZSTD_compressStream2(ctx_, &output, &input, mode);
2134
2135 if (ZSTD_isError(remaining)) { return false; }
2136
2137 if (!callback(buff.data(), output.pos)) { return false; }
2138
2139 finished = last ? (remaining == 0) : (input.pos == input.size);
2140
2141 } while (!finished);
2142
2143 return true;
2144}
2145
2146zstd_decompressor::zstd_decompressor() { ctx_ = ZSTD_createDCtx(); }
2147
2148zstd_decompressor::~zstd_decompressor() { ZSTD_freeDCtx(ctx_); }
2149
2150bool zstd_decompressor::is_valid() const { return ctx_ != nullptr; }
2151
2152bool zstd_decompressor::decompress(const char *data, size_t data_length,
2153 Callback callback) {
2154 std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
2155 ZSTD_inBuffer input = {data, data_length, 0};
2156
2157 while (input.pos < input.size) {
2158 ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
2159 size_t const remaining = ZSTD_decompressStream(ctx_, &output, &input);
2160
2161 if (ZSTD_isError(remaining)) { return false; }
2162
2163 if (!callback(buff.data(), output.pos)) { return false; }
2164 }
2165
2166 return true;
2167}
2168#endif
2169
2170std::unique_ptr<decompressor>
2171create_decompressor(const std::string &encoding) {
2172 std::unique_ptr<decompressor> decompressor;
2173
2174 if (encoding == "gzip" || encoding == "deflate") {
2175#ifdef CPPHTTPLIB_ZLIB_SUPPORT
2176 decompressor = detail::make_unique<gzip_decompressor>();
2177#endif
2178 } else if (encoding.find("br") != std::string::npos) {
2179#ifdef CPPHTTPLIB_BROTLI_SUPPORT
2180 decompressor = detail::make_unique<brotli_decompressor>();
2181#endif
2182 } else if (encoding == "zstd" || encoding.find("zstd") != std::string::npos) {
2183#ifdef CPPHTTPLIB_ZSTD_SUPPORT
2184 decompressor = detail::make_unique<zstd_decompressor>();
2185#endif
2186 }
2187
2188 return decompressor;
2189}
2190
2191bool is_prohibited_header_name(const std::string &name) {
2192 using udl::operator""_t;
2193
2194 switch (str2tag(name)) {
2195 case "REMOTE_ADDR"_t:
2196 case "REMOTE_PORT"_t:
2197 case "LOCAL_ADDR"_t:
2198 case "LOCAL_PORT"_t: return true;
2199 default: return false;
2200 }
2201}
2202
2203bool has_header(const Headers &headers, const std::string &key) {
2204 if (is_prohibited_header_name(key)) { return false; }
2205 return headers.find(key) != headers.end();
2206}
2207
2208const char *get_header_value(const Headers &headers,
2209 const std::string &key, const char *def,
2210 size_t id) {
2211 if (is_prohibited_header_name(key)) {
2212#ifndef CPPHTTPLIB_NO_EXCEPTIONS
2213 std::string msg = "Prohibited header name '" + key + "' is specified.";
2214 throw std::invalid_argument(msg);
2215#else
2216 return "";
2217#endif
2218 }
2219
2220 auto rng = headers.equal_range(key);
2221 auto it = rng.first;
2222 std::advance(it, static_cast<ssize_t>(id));
2223 if (it != rng.second) { return it->second.c_str(); }
2224 return def;
2225}
2226
2227bool read_headers(Stream &strm, Headers &headers) {
2228 const auto bufsiz = 2048;
2229 char buf[bufsiz];
2230 stream_line_reader line_reader(strm, buf, bufsiz);
2231
2232 size_t header_count = 0;
2233
2234 for (;;) {
2235 if (!line_reader.getline()) { return false; }
2236
2237 // Check if the line ends with CRLF.
2238 auto line_terminator_len = 2;
2239 if (line_reader.end_with_crlf()) {
2240 // Blank line indicates end of headers.
2241 if (line_reader.size() == 2) { break; }
2242 } else {
2243#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
2244 // Blank line indicates end of headers.
2245 if (line_reader.size() == 1) { break; }
2246 line_terminator_len = 1;
2247#else
2248 continue; // Skip invalid line.
2249#endif
2250 }
2251
2252 if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
2253
2254 // Check header count limit
2255 if (header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
2256
2257 // Exclude line terminator
2258 auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
2259
2260 if (!parse_header(line_reader.ptr(), end,
2261 [&](const std::string &key, const std::string &val) {
2262 headers.emplace(key, val);
2263 })) {
2264 return false;
2265 }
2266
2267 header_count++;
2268 }
2269
2270 return true;
2271}
2272
2273bool read_content_with_length(Stream &strm, size_t len,
2274 DownloadProgress progress,
2275 ContentReceiverWithProgress out) {
2276 char buf[CPPHTTPLIB_RECV_BUFSIZ];
2277
2278 detail::BodyReader br;
2279 br.stream = &strm;
2280 br.content_length = len;
2281 br.chunked = false;
2282 br.bytes_read = 0;
2283 br.last_error = Error::Success;
2284
2285 size_t r = 0;
2286 while (r < len) {
2287 auto read_len = static_cast<size_t>(len - r);
2288 auto to_read = (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ);
2289 auto n = detail::read_body_content(&strm, br, buf, to_read);
2290 if (n <= 0) { return false; }
2291
2292 if (!out(buf, static_cast<size_t>(n), r, len)) { return false; }
2293 r += static_cast<size_t>(n);
2294
2295 if (progress) {
2296 if (!progress(r, len)) { return false; }
2297 }
2298 }
2299
2300 return true;
2301}
2302
2303void skip_content_with_length(Stream &strm, size_t len) {
2304 char buf[CPPHTTPLIB_RECV_BUFSIZ];
2305 size_t r = 0;
2306 while (r < len) {
2307 auto read_len = static_cast<size_t>(len - r);
2308 auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ));
2309 if (n <= 0) { return; }
2310 r += static_cast<size_t>(n);
2311 }
2312}
2313
2314enum class ReadContentResult {
2315 Success, // Successfully read the content
2316 PayloadTooLarge, // The content exceeds the specified payload limit
2317 Error // An error occurred while reading the content
2318};
2319
2320ReadContentResult
2321read_content_without_length(Stream &strm, size_t payload_max_length,
2322 ContentReceiverWithProgress out) {
2323 char buf[CPPHTTPLIB_RECV_BUFSIZ];
2324 size_t r = 0;
2325 for (;;) {
2326 auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
2327 if (n == 0) { return ReadContentResult::Success; }
2328 if (n < 0) { return ReadContentResult::Error; }
2329
2330 // Check if adding this data would exceed the payload limit
2331 if (r > payload_max_length ||
2332 payload_max_length - r < static_cast<size_t>(n)) {
2333 return ReadContentResult::PayloadTooLarge;
2334 }
2335
2336 if (!out(buf, static_cast<size_t>(n), r, 0)) {
2337 return ReadContentResult::Error;
2338 }
2339 r += static_cast<size_t>(n);
2340 }
2341
2342 return ReadContentResult::Success;
2343}
2344
2345template <typename T>
2346ReadContentResult read_content_chunked(Stream &strm, T &x,
2347 size_t payload_max_length,
2348 ContentReceiverWithProgress out) {
2349 detail::ChunkedDecoder dec(strm);
2350
2351 char buf[CPPHTTPLIB_RECV_BUFSIZ];
2352 size_t total_len = 0;
2353
2354 for (;;) {
2355 size_t chunk_offset = 0;
2356 size_t chunk_total = 0;
2357 auto n = dec.read_payload(buf, sizeof(buf), chunk_offset, chunk_total);
2358 if (n < 0) { return ReadContentResult::Error; }
2359
2360 if (n == 0) {
2361 if (!dec.parse_trailers_into(x.trailers, x.headers)) {
2362 return ReadContentResult::Error;
2363 }
2364 return ReadContentResult::Success;
2365 }
2366
2367 if (total_len > payload_max_length ||
2368 payload_max_length - total_len < static_cast<size_t>(n)) {
2369 return ReadContentResult::PayloadTooLarge;
2370 }
2371
2372 if (!out(buf, static_cast<size_t>(n), chunk_offset, chunk_total)) {
2373 return ReadContentResult::Error;
2374 }
2375
2376 total_len += static_cast<size_t>(n);
2377 }
2378}
2379
2380bool is_chunked_transfer_encoding(const Headers &headers) {
2381 return case_ignore::equal(
2382 get_header_value(headers, "Transfer-Encoding", "", 0), "chunked");
2383}
2384
2385template <typename T, typename U>
2386bool prepare_content_receiver(T &x, int &status,
2387 ContentReceiverWithProgress receiver,
2388 bool decompress, U callback) {
2389 if (decompress) {
2390 std::string encoding = x.get_header_value("Content-Encoding");
2391 std::unique_ptr<decompressor> decompressor;
2392
2393 if (!encoding.empty()) {
2394 decompressor = detail::create_decompressor(encoding);
2395 if (!decompressor) {
2396 // Unsupported encoding or no support compiled in
2397 status = StatusCode::UnsupportedMediaType_415;
2398 return false;
2399 }
2400 }
2401
2402 if (decompressor) {
2403 if (decompressor->is_valid()) {
2404 ContentReceiverWithProgress out = [&](const char *buf, size_t n,
2405 size_t off, size_t len) {
2406 return decompressor->decompress(buf, n,
2407 [&](const char *buf2, size_t n2) {
2408 return receiver(buf2, n2, off, len);
2409 });
2410 };
2411 return callback(std::move(out));
2412 } else {
2413 status = StatusCode::InternalServerError_500;
2414 return false;
2415 }
2416 }
2417 }
2418
2419 ContentReceiverWithProgress out = [&](const char *buf, size_t n, size_t off,
2420 size_t len) {
2421 return receiver(buf, n, off, len);
2422 };
2423 return callback(std::move(out));
2424}
2425
2426template <typename T>
2427bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
2428 DownloadProgress progress,
2429 ContentReceiverWithProgress receiver, bool decompress) {
2430 return prepare_content_receiver(
2431 x, status, std::move(receiver), decompress,
2432 [&](const ContentReceiverWithProgress &out) {
2433 auto ret = true;
2434 auto exceed_payload_max_length = false;
2435
2436 if (is_chunked_transfer_encoding(x.headers)) {
2437 auto result = read_content_chunked(strm, x, payload_max_length, out);
2438 if (result == ReadContentResult::Success) {
2439 ret = true;
2440 } else if (result == ReadContentResult::PayloadTooLarge) {
2441 exceed_payload_max_length = true;
2442 ret = false;
2443 } else {
2444 ret = false;
2445 }
2446 } else if (!has_header(x.headers, "Content-Length")) {
2447 auto result =
2448 read_content_without_length(strm, payload_max_length, out);
2449 if (result == ReadContentResult::Success) {
2450 ret = true;
2451 } else if (result == ReadContentResult::PayloadTooLarge) {
2452 exceed_payload_max_length = true;
2453 ret = false;
2454 } else {
2455 ret = false;
2456 }
2457 } else {
2458 auto is_invalid_value = false;
2459 auto len = get_header_value_u64(x.headers, "Content-Length",
2460 (std::numeric_limits<size_t>::max)(),
2461 0, is_invalid_value);
2462
2463 if (is_invalid_value) {
2464 ret = false;
2465 } else if (len > payload_max_length) {
2466 exceed_payload_max_length = true;
2467 skip_content_with_length(strm, len);
2468 ret = false;
2469 } else if (len > 0) {
2470 ret = read_content_with_length(strm, len, std::move(progress), out);
2471 }
2472 }
2473
2474 if (!ret) {
2475 status = exceed_payload_max_length ? StatusCode::PayloadTooLarge_413
2476 : StatusCode::BadRequest_400;
2477 }
2478 return ret;
2479 });
2480}
2481
2482ssize_t write_request_line(Stream &strm, const std::string &method,
2483 const std::string &path) {
2484 std::string s = method;
2485 s += ' ';
2486 s += path;
2487 s += " HTTP/1.1\r\n";
2488 return strm.write(s.data(), s.size());
2489}
2490
2491ssize_t write_response_line(Stream &strm, int status) {
2492 std::string s = "HTTP/1.1 ";
2493 s += std::to_string(status);
2494 s += ' ';
2495 s += httplib::status_message(status);
2496 s += "\r\n";
2497 return strm.write(s.data(), s.size());
2498}
2499
2500ssize_t write_headers(Stream &strm, const Headers &headers) {
2501 ssize_t write_len = 0;
2502 for (const auto &x : headers) {
2503 std::string s;
2504 s = x.first;
2505 s += ": ";
2506 s += x.second;
2507 s += "\r\n";
2508
2509 auto len = strm.write(s.data(), s.size());
2510 if (len < 0) { return len; }
2511 write_len += len;
2512 }
2513 auto len = strm.write("\r\n");
2514 if (len < 0) { return len; }
2515 write_len += len;
2516 return write_len;
2517}
2518
2519bool write_data(Stream &strm, const char *d, size_t l) {
2520 size_t offset = 0;
2521 while (offset < l) {
2522 auto length = strm.write(d + offset, l - offset);
2523 if (length < 0) { return false; }
2524 offset += static_cast<size_t>(length);
2525 }
2526 return true;
2527}
2528
2529template <typename T>
2530bool write_content_with_progress(Stream &strm,
2531 const ContentProvider &content_provider,
2532 size_t offset, size_t length,
2533 T is_shutting_down,
2534 const UploadProgress &upload_progress,
2535 Error &error) {
2536 size_t end_offset = offset + length;
2537 size_t start_offset = offset;
2538 auto ok = true;
2539 DataSink data_sink;
2540
2541 data_sink.write = [&](const char *d, size_t l) -> bool {
2542 if (ok) {
2543 if (write_data(strm, d, l)) {
2544 offset += l;
2545
2546 if (upload_progress && length > 0) {
2547 size_t current_written = offset - start_offset;
2548 if (!upload_progress(current_written, length)) {
2549 ok = false;
2550 return false;
2551 }
2552 }
2553 } else {
2554 ok = false;
2555 }
2556 }
2557 return ok;
2558 };
2559
2560 data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
2561
2562 while (offset < end_offset && !is_shutting_down()) {
2563 if (!strm.wait_writable()) {
2564 error = Error::Write;
2565 return false;
2566 } else if (!content_provider(offset, end_offset - offset, data_sink)) {
2567 error = Error::Canceled;
2568 return false;
2569 } else if (!ok) {
2570 error = Error::Write;
2571 return false;
2572 }
2573 }
2574
2575 error = Error::Success;
2576 return true;
2577}
2578
2579template <typename T>
2580bool write_content(Stream &strm, const ContentProvider &content_provider,
2581 size_t offset, size_t length, T is_shutting_down,
2582 Error &error) {
2583 return write_content_with_progress<T>(strm, content_provider, offset, length,
2584 is_shutting_down, nullptr, error);
2585}
2586
2587template <typename T>
2588bool write_content(Stream &strm, const ContentProvider &content_provider,
2589 size_t offset, size_t length,
2590 const T &is_shutting_down) {
2591 auto error = Error::Success;
2592 return write_content(strm, content_provider, offset, length, is_shutting_down,
2593 error);
2594}
2595
2596template <typename T>
2597bool
2598write_content_without_length(Stream &strm,
2599 const ContentProvider &content_provider,
2600 const T &is_shutting_down) {
2601 size_t offset = 0;
2602 auto data_available = true;
2603 auto ok = true;
2604 DataSink data_sink;
2605
2606 data_sink.write = [&](const char *d, size_t l) -> bool {
2607 if (ok) {
2608 offset += l;
2609 if (!write_data(strm, d, l)) { ok = false; }
2610 }
2611 return ok;
2612 };
2613
2614 data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
2615
2616 data_sink.done = [&](void) { data_available = false; };
2617
2618 while (data_available && !is_shutting_down()) {
2619 if (!strm.wait_writable()) {
2620 return false;
2621 } else if (!content_provider(offset, 0, data_sink)) {
2622 return false;
2623 } else if (!ok) {
2624 return false;
2625 }
2626 }
2627 return true;
2628}
2629
2630template <typename T, typename U>
2631bool
2632write_content_chunked(Stream &strm, const ContentProvider &content_provider,
2633 const T &is_shutting_down, U &compressor, Error &error) {
2634 size_t offset = 0;
2635 auto data_available = true;
2636 auto ok = true;
2637 DataSink data_sink;
2638
2639 data_sink.write = [&](const char *d, size_t l) -> bool {
2640 if (ok) {
2641 data_available = l > 0;
2642 offset += l;
2643
2644 std::string payload;
2645 if (compressor.compress(d, l, false,
2646 [&](const char *data, size_t data_len) {
2647 payload.append(data, data_len);
2648 return true;
2649 })) {
2650 if (!payload.empty()) {
2651 // Emit chunked response header and footer for each chunk
2652 auto chunk =
2653 from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
2654 if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
2655 }
2656 } else {
2657 ok = false;
2658 }
2659 }
2660 return ok;
2661 };
2662
2663 data_sink.is_writable = [&]() -> bool { return strm.wait_writable(); };
2664
2665 auto done_with_trailer = [&](const Headers *trailer) {
2666 if (!ok) { return; }
2667
2668 data_available = false;
2669
2670 std::string payload;
2671 if (!compressor.compress(nullptr, 0, true,
2672 [&](const char *data, size_t data_len) {
2673 payload.append(data, data_len);
2674 return true;
2675 })) {
2676 ok = false;
2677 return;
2678 }
2679
2680 if (!payload.empty()) {
2681 // Emit chunked response header and footer for each chunk
2682 auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
2683 if (!write_data(strm, chunk.data(), chunk.size())) {
2684 ok = false;
2685 return;
2686 }
2687 }
2688
2689 constexpr const char done_marker[] = "0\r\n";
2690 if (!write_data(strm, done_marker, str_len(done_marker))) { ok = false; }
2691
2692 // Trailer
2693 if (trailer) {
2694 for (const auto &kv : *trailer) {
2695 std::string field_line = kv.first + ": " + kv.second + "\r\n";
2696 if (!write_data(strm, field_line.data(), field_line.size())) {
2697 ok = false;
2698 }
2699 }
2700 }
2701
2702 constexpr const char crlf[] = "\r\n";
2703 if (!write_data(strm, crlf, str_len(crlf))) { ok = false; }
2704 };
2705
2706 data_sink.done = [&](void) { done_with_trailer(nullptr); };
2707
2708 data_sink.done_with_trailer = [&](const Headers &trailer) {
2709 done_with_trailer(&trailer);
2710 };
2711
2712 while (data_available && !is_shutting_down()) {
2713 if (!strm.wait_writable()) {
2714 error = Error::Write;
2715 return false;
2716 } else if (!content_provider(offset, 0, data_sink)) {
2717 error = Error::Canceled;
2718 return false;
2719 } else if (!ok) {
2720 error = Error::Write;
2721 return false;
2722 }
2723 }
2724
2725 error = Error::Success;
2726 return true;
2727}
2728
2729template <typename T, typename U>
2730bool write_content_chunked(Stream &strm,
2731 const ContentProvider &content_provider,
2732 const T &is_shutting_down, U &compressor) {
2733 auto error = Error::Success;
2734 return write_content_chunked(strm, content_provider, is_shutting_down,
2735 compressor, error);
2736}
2737
2738template <typename T>
2739bool redirect(T &cli, Request &req, Response &res,
2740 const std::string &path, const std::string &location,
2741 Error &error) {
2742 Request new_req = req;
2743 new_req.path = path;
2744 new_req.redirect_count_ -= 1;
2745
2746 if (res.status == StatusCode::SeeOther_303 &&
2747 (req.method != "GET" && req.method != "HEAD")) {
2748 new_req.method = "GET";
2749 new_req.body.clear();
2750 new_req.headers.clear();
2751 }
2752
2753 Response new_res;
2754
2755 auto ret = cli.send(new_req, new_res, error);
2756 if (ret) {
2757 req = std::move(new_req);
2758 res = std::move(new_res);
2759
2760 if (res.location.empty()) { res.location = location; }
2761 }
2762 return ret;
2763}
2764
2765std::string params_to_query_str(const Params ¶ms) {
2766 std::string query;
2767
2768 for (auto it = params.begin(); it != params.end(); ++it) {
2769 if (it != params.begin()) { query += '&'; }
2770 query += encode_query_component(it->first);
2771 query += '=';
2772 query += encode_query_component(it->second);
2773 }
2774 return query;
2775}
2776
2777void parse_query_text(const char *data, std::size_t size,
2778 Params ¶ms) {
2779 std::set<std::string> cache;
2780 split(data, data + size, '&', [&](const char *b, const char *e) {
2781 std::string kv(b, e);
2782 if (cache.find(kv) != cache.end()) { return; }
2783 cache.insert(std::move(kv));
2784
2785 std::string key;
2786 std::string val;
2787 divide(b, static_cast<std::size_t>(e - b), '=',
2788 [&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data,
2789 std::size_t rhs_size) {
2790 key.assign(lhs_data, lhs_size);
2791 val.assign(rhs_data, rhs_size);
2792 });
2793
2794 if (!key.empty()) {
2795 params.emplace(decode_query_component(key), decode_query_component(val));
2796 }
2797 });
2798}
2799
2800void parse_query_text(const std::string &s, Params ¶ms) {
2801 parse_query_text(s.data(), s.size(), params);
2802}
2803
2804// Normalize a query string by decoding and re-encoding each key/value pair
2805// while preserving the original parameter order. This avoids double-encoding
2806// and ensures consistent encoding without reordering (unlike Params which
2807// uses std::multimap and sorts keys).
2808std::string normalize_query_string(const std::string &query) {
2809 std::string result;
2810 split(query.data(), query.data() + query.size(), '&',
2811 [&](const char *b, const char *e) {
2812 std::string key;
2813 std::string val;
2814 divide(b, static_cast<std::size_t>(e - b), '=',
2815 [&](const char *lhs_data, std::size_t lhs_size,
2816 const char *rhs_data, std::size_t rhs_size) {
2817 key.assign(lhs_data, lhs_size);
2818 val.assign(rhs_data, rhs_size);
2819 });
2820
2821 if (!key.empty()) {
2822 auto dec_key = decode_query_component(key);
2823 auto dec_val = decode_query_component(val);
2824
2825 if (!result.empty()) { result += '&'; }
2826 result += encode_query_component(dec_key);
2827 if (!val.empty() || std::find(b, e, '=') != e) {
2828 result += '=';
2829 result += encode_query_component(dec_val);
2830 }
2831 }
2832 });
2833 return result;
2834}
2835
2836bool parse_multipart_boundary(const std::string &content_type,
2837 std::string &boundary) {
2838 auto boundary_keyword = "boundary=";
2839 auto pos = content_type.find(boundary_keyword);
2840 if (pos == std::string::npos) { return false; }
2841 auto end = content_type.find(';', pos);
2842 auto beg = pos + strlen(boundary_keyword);
2843 boundary = trim_double_quotes_copy(content_type.substr(beg, end - beg));
2844 return !boundary.empty();
2845}
2846
2847void parse_disposition_params(const std::string &s, Params ¶ms) {
2848 std::set<std::string> cache;
2849 split(s.data(), s.data() + s.size(), ';', [&](const char *b, const char *e) {
2850 std::string kv(b, e);
2851 if (cache.find(kv) != cache.end()) { return; }
2852 cache.insert(kv);
2853
2854 std::string key;
2855 std::string val;
2856 split(b, e, '=', [&](const char *b2, const char *e2) {
2857 if (key.empty()) {
2858 key.assign(b2, e2);
2859 } else {
2860 val.assign(b2, e2);
2861 }
2862 });
2863
2864 if (!key.empty()) {
2865 params.emplace(trim_double_quotes_copy((key)),
2866 trim_double_quotes_copy((val)));
2867 }
2868 });
2869}
2870
2871#ifdef CPPHTTPLIB_NO_EXCEPTIONS
2872bool parse_range_header(const std::string &s, Ranges &ranges) {
2873#else
2874bool parse_range_header(const std::string &s, Ranges &ranges) try {
2875#endif
2876 auto is_valid = [](const std::string &str) {
2877 return std::all_of(str.cbegin(), str.cend(),
2878 [](unsigned char c) { return std::isdigit(c); });
2879 };
2880
2881 if (s.size() > 7 && s.compare(0, 6, "bytes=") == 0) {
2882 const auto pos = static_cast<size_t>(6);
2883 const auto len = static_cast<size_t>(s.size() - 6);
2884 auto all_valid_ranges = true;
2885 split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
2886 if (!all_valid_ranges) { return; }
2887
2888 const auto it = std::find(b, e, '-');
2889 if (it == e) {
2890 all_valid_ranges = false;
2891 return;
2892 }
2893
2894 const auto lhs = std::string(b, it);
2895 const auto rhs = std::string(it + 1, e);
2896 if (!is_valid(lhs) || !is_valid(rhs)) {
2897 all_valid_ranges = false;
2898 return;
2899 }
2900
2901 ssize_t first = -1;
2902 if (!lhs.empty()) {
2903 ssize_t v;
2904 auto res = detail::from_chars(lhs.data(), lhs.data() + lhs.size(), v);
2905 if (res.ec == std::errc{}) { first = v; }
2906 }
2907
2908 ssize_t last = -1;
2909 if (!rhs.empty()) {
2910 ssize_t v;
2911 auto res = detail::from_chars(rhs.data(), rhs.data() + rhs.size(), v);
2912 if (res.ec == std::errc{}) { last = v; }
2913 }
2914
2915 if ((first == -1 && last == -1) ||
2916 (first != -1 && last != -1 && first > last)) {
2917 all_valid_ranges = false;
2918 return;
2919 }
2920
2921 ranges.emplace_back(first, last);
2922 });
2923 return all_valid_ranges && !ranges.empty();
2924 }
2925 return false;
2926#ifdef CPPHTTPLIB_NO_EXCEPTIONS
2927}
2928#else
2929} catch (...) { return false; }
2930#endif
2931
2932bool parse_accept_header(const std::string &s,
2933 std::vector<std::string> &content_types) {
2934 content_types.clear();
2935
2936 // Empty string is considered valid (no preference)
2937 if (s.empty()) { return true; }
2938
2939 // Check for invalid patterns: leading/trailing commas or consecutive commas
2940 if (s.front() == ',' || s.back() == ',' ||
2941 s.find(",,") != std::string::npos) {
2942 return false;
2943 }
2944
2945 struct AcceptEntry {
2946 std::string media_type;
2947 double quality;
2948 int order; // Original order in header
2949 };
2950
2951 std::vector<AcceptEntry> entries;
2952 int order = 0;
2953 bool has_invalid_entry = false;
2954
2955 // Split by comma and parse each entry
2956 split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) {
2957 std::string entry(b, e);
2958 entry = trim_copy(entry);
2959
2960 if (entry.empty()) {
2961 has_invalid_entry = true;
2962 return;
2963 }
2964
2965 AcceptEntry accept_entry;
2966 accept_entry.quality = 1.0; // Default quality
2967 accept_entry.order = order++;
2968
2969 // Find q= parameter
2970 auto q_pos = entry.find(";q=");
2971 if (q_pos == std::string::npos) { q_pos = entry.find("; q="); }
2972
2973 if (q_pos != std::string::npos) {
2974 // Extract media type (before q parameter)
2975 accept_entry.media_type = trim_copy(entry.substr(0, q_pos));
2976
2977 // Extract quality value
2978 auto q_start = entry.find('=', q_pos) + 1;
2979 auto q_end = entry.find(';', q_start);
2980 if (q_end == std::string::npos) { q_end = entry.length(); }
2981
2982 std::string quality_str =
2983 trim_copy(entry.substr(q_start, q_end - q_start));
2984 if (quality_str.empty()) {
2985 has_invalid_entry = true;
2986 return;
2987 }
2988
2989 {
2990 double v = 0.0;
2991 auto res = detail::from_chars(
2992 quality_str.data(), quality_str.data() + quality_str.size(), v);
2993 if (res.ec == std::errc{}) {
2994 accept_entry.quality = v;
2995 } else {
2996 has_invalid_entry = true;
2997 return;
2998 }
2999 }
3000 // Check if quality is in valid range [0.0, 1.0]
3001 if (accept_entry.quality < 0.0 || accept_entry.quality > 1.0) {
3002 has_invalid_entry = true;
3003 return;
3004 }
3005 } else {
3006 // No quality parameter, use entire entry as media type
3007 accept_entry.media_type = entry;
3008 }
3009
3010 // Remove additional parameters from media type
3011 auto param_pos = accept_entry.media_type.find(';');
3012 if (param_pos != std::string::npos) {
3013 accept_entry.media_type =
3014 trim_copy(accept_entry.media_type.substr(0, param_pos));
3015 }
3016
3017 // Basic validation of media type format
3018 if (accept_entry.media_type.empty()) {
3019 has_invalid_entry = true;
3020 return;
3021 }
3022
3023 // Check for basic media type format (should contain '/' or be '*')
3024 if (accept_entry.media_type != "*" &&
3025 accept_entry.media_type.find('/') == std::string::npos) {
3026 has_invalid_entry = true;
3027 return;
3028 }
3029
3030 entries.push_back(std::move(accept_entry));
3031 });
3032
3033 // Return false if any invalid entry was found
3034 if (has_invalid_entry) { return false; }
3035
3036 // Sort by quality (descending), then by original order (ascending)
3037 std::sort(entries.begin(), entries.end(),
3038 [](const AcceptEntry &a, const AcceptEntry &b) {
3039 if (a.quality != b.quality) {
3040 return a.quality > b.quality; // Higher quality first
3041 }
3042 return a.order < b.order; // Earlier order first for same quality
3043 });
3044
3045 // Extract sorted media types
3046 content_types.reserve(entries.size());
3047 for (auto &entry : entries) {
3048 content_types.push_back(std::move(entry.media_type));
3049 }
3050
3051 return true;
3052}
3053
3054class FormDataParser {
3055public:
3056 FormDataParser() = default;
3057
3058 void set_boundary(std::string &&boundary) {
3059 boundary_ = std::move(boundary);
3060 dash_boundary_crlf_ = dash_ + boundary_ + crlf_;
3061 crlf_dash_boundary_ = crlf_ + dash_ + boundary_;
3062 }
3063
3064 bool is_valid() const { return is_valid_; }
3065
3066 bool parse(const char *buf, size_t n, const FormDataHeader &header_callback,
3067 const ContentReceiver &content_callback) {
3068
3069 buf_append(buf, n);
3070
3071 while (buf_size() > 0) {
3072 switch (state_) {
3073 case 0: { // Initial boundary
3074 auto pos = buf_find(dash_boundary_crlf_);
3075 if (pos == buf_size()) { return true; }
3076 buf_erase(pos + dash_boundary_crlf_.size());
3077 state_ = 1;
3078 break;
3079 }
3080 case 1: { // New entry
3081 clear_file_info();
3082 state_ = 2;
3083 break;
3084 }
3085 case 2: { // Headers
3086 auto pos = buf_find(crlf_);
3087 if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
3088 while (pos < buf_size()) {
3089 // Empty line
3090 if (pos == 0) {
3091 if (!header_callback(file_)) {
3092 is_valid_ = false;
3093 return false;
3094 }
3095 buf_erase(crlf_.size());
3096 state_ = 3;
3097 break;
3098 }
3099
3100 const auto header = buf_head(pos);
3101
3102 if (!parse_header(header.data(), header.data() + header.size(),
3103 [&](const std::string &, const std::string &) {})) {
3104 is_valid_ = false;
3105 return false;
3106 }
3107
3108 // Parse and emplace space trimmed headers into a map
3109 if (!parse_header(
3110 header.data(), header.data() + header.size(),
3111 [&](const std::string &key, const std::string &val) {
3112 file_.headers.emplace(key, val);
3113 })) {
3114 is_valid_ = false;
3115 return false;
3116 }
3117
3118 constexpr const char header_content_type[] = "Content-Type:";
3119
3120 if (start_with_case_ignore(header, header_content_type)) {
3121 file_.content_type =
3122 trim_copy(header.substr(str_len(header_content_type)));
3123 } else {
3124 thread_local const std::regex re_content_disposition(
3125 R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~",
3126 std::regex_constants::icase);
3127
3128 std::smatch m;
3129 if (std::regex_match(header, m, re_content_disposition)) {
3130 Params params;
3131 parse_disposition_params(m[1], params);
3132
3133 auto it = params.find("name");
3134 if (it != params.end()) {
3135 file_.name = it->second;
3136 } else {
3137 is_valid_ = false;
3138 return false;
3139 }
3140
3141 it = params.find("filename");
3142 if (it != params.end()) { file_.filename = it->second; }
3143
3144 it = params.find("filename*");
3145 if (it != params.end()) {
3146 // Only allow UTF-8 encoding...
3147 thread_local const std::regex re_rfc5987_encoding(
3148 R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase);
3149
3150 std::smatch m2;
3151 if (std::regex_match(it->second, m2, re_rfc5987_encoding)) {
3152 file_.filename = decode_path_component(m2[1]); // override...
3153 } else {
3154 is_valid_ = false;
3155 return false;
3156 }
3157 }
3158 }
3159 }
3160 buf_erase(pos + crlf_.size());
3161 pos = buf_find(crlf_);
3162 }
3163 if (state_ != 3) { return true; }
3164 break;
3165 }
3166 case 3: { // Body
3167 if (crlf_dash_boundary_.size() > buf_size()) { return true; }
3168 auto pos = buf_find(crlf_dash_boundary_);
3169 if (pos < buf_size()) {
3170 if (!content_callback(buf_data(), pos)) {
3171 is_valid_ = false;
3172 return false;
3173 }
3174 buf_erase(pos + crlf_dash_boundary_.size());
3175 state_ = 4;
3176 } else {
3177 auto len = buf_size() - crlf_dash_boundary_.size();
3178 if (len > 0) {
3179 if (!content_callback(buf_data(), len)) {
3180 is_valid_ = false;
3181 return false;
3182 }
3183 buf_erase(len);
3184 }
3185 return true;
3186 }
3187 break;
3188 }
3189 case 4: { // Boundary
3190 if (crlf_.size() > buf_size()) { return true; }
3191 if (buf_start_with(crlf_)) {
3192 buf_erase(crlf_.size());
3193 state_ = 1;
3194 } else {
3195 if (dash_.size() > buf_size()) { return true; }
3196 if (buf_start_with(dash_)) {
3197 buf_erase(dash_.size());
3198 is_valid_ = true;
3199 buf_erase(buf_size()); // Remove epilogue
3200 } else {
3201 return true;
3202 }
3203 }
3204 break;
3205 }
3206 }
3207 }
3208
3209 return true;
3210 }
3211
3212private:
3213 void clear_file_info() {
3214 file_.name.clear();
3215 file_.filename.clear();
3216 file_.content_type.clear();
3217 file_.headers.clear();
3218 }
3219
3220 bool start_with_case_ignore(const std::string &a, const char *b) const {
3221 const auto b_len = strlen(b);
3222 if (a.size() < b_len) { return false; }
3223 for (size_t i = 0; i < b_len; i++) {
3224 if (case_ignore::to_lower(a[i]) != case_ignore::to_lower(b[i])) {
3225 return false;
3226 }
3227 }
3228 return true;
3229 }
3230
3231 const std::string dash_ = "--";
3232 const std::string crlf_ = "\r\n";
3233 std::string boundary_;
3234 std::string dash_boundary_crlf_;
3235 std::string crlf_dash_boundary_;
3236
3237 size_t state_ = 0;
3238 bool is_valid_ = false;
3239 FormData file_;
3240
3241 // Buffer
3242 bool start_with(const std::string &a, size_t spos, size_t epos,
3243 const std::string &b) const {
3244 if (epos - spos < b.size()) { return false; }
3245 for (size_t i = 0; i < b.size(); i++) {
3246 if (a[i + spos] != b[i]) { return false; }
3247 }
3248 return true;
3249 }
3250
3251 size_t buf_size() const { return buf_epos_ - buf_spos_; }
3252
3253 const char *buf_data() const { return &buf_[buf_spos_]; }
3254
3255 std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); }
3256
3257 bool buf_start_with(const std::string &s) const {
3258 return start_with(buf_, buf_spos_, buf_epos_, s);
3259 }
3260
3261 size_t buf_find(const std::string &s) const {
3262 auto c = s.front();
3263
3264 size_t off = buf_spos_;
3265 while (off < buf_epos_) {
3266 auto pos = off;
3267 while (true) {
3268 if (pos == buf_epos_) { return buf_size(); }
3269 if (buf_[pos] == c) { break; }
3270 pos++;
3271 }
3272
3273 auto remaining_size = buf_epos_ - pos;
3274 if (s.size() > remaining_size) { return buf_size(); }
3275
3276 if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; }
3277
3278 off = pos + 1;
3279 }
3280
3281 return buf_size();
3282 }
3283
3284 void buf_append(const char *data, size_t n) {
3285 auto remaining_size = buf_size();
3286 if (remaining_size > 0 && buf_spos_ > 0) {
3287 for (size_t i = 0; i < remaining_size; i++) {
3288 buf_[i] = buf_[buf_spos_ + i];
3289 }
3290 }
3291 buf_spos_ = 0;
3292 buf_epos_ = remaining_size;
3293
3294 if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); }
3295
3296 for (size_t i = 0; i < n; i++) {
3297 buf_[buf_epos_ + i] = data[i];
3298 }
3299 buf_epos_ += n;
3300 }
3301
3302 void buf_erase(size_t size) { buf_spos_ += size; }
3303
3304 std::string buf_;
3305 size_t buf_spos_ = 0;
3306 size_t buf_epos_ = 0;
3307};
3308
3309std::string random_string(size_t length) {
3310 constexpr const char data[] =
3311 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
3312
3313 thread_local auto engine([]() {
3314 // std::random_device might actually be deterministic on some
3315 // platforms, but due to lack of support in the c++ standard library,
3316 // doing better requires either some ugly hacks or breaking portability.
3317 std::random_device seed_gen;
3318 // Request 128 bits of entropy for initialization
3319 std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
3320 return std::mt19937(seed_sequence);
3321 }());
3322
3323 std::string result;
3324 for (size_t i = 0; i < length; i++) {
3325 result += data[engine() % (sizeof(data) - 1)];
3326 }
3327 return result;
3328}
3329
3330std::string make_multipart_data_boundary() {
3331 return "--cpp-httplib-multipart-data-" + detail::random_string(16);
3332}
3333
3334bool is_multipart_boundary_chars_valid(const std::string &boundary) {
3335 auto valid = true;
3336 for (size_t i = 0; i < boundary.size(); i++) {
3337 auto c = boundary[i];
3338 if (!std::isalnum(c) && c != '-' && c != '_') {
3339 valid = false;
3340 break;
3341 }
3342 }
3343 return valid;
3344}
3345
3346template <typename T>
3347std::string
3348serialize_multipart_formdata_item_begin(const T &item,
3349 const std::string &boundary) {
3350 std::string body = "--" + boundary + "\r\n";
3351 body += "Content-Disposition: form-data; name=\"" + item.name + "\"";
3352 if (!item.filename.empty()) {
3353 body += "; filename=\"" + item.filename + "\"";
3354 }
3355 body += "\r\n";
3356 if (!item.content_type.empty()) {
3357 body += "Content-Type: " + item.content_type + "\r\n";
3358 }
3359 body += "\r\n";
3360
3361 return body;
3362}
3363
3364std::string serialize_multipart_formdata_item_end() { return "\r\n"; }
3365
3366std::string
3367serialize_multipart_formdata_finish(const std::string &boundary) {
3368 return "--" + boundary + "--\r\n";
3369}
3370
3371std::string
3372serialize_multipart_formdata_get_content_type(const std::string &boundary) {
3373 return "multipart/form-data; boundary=" + boundary;
3374}
3375
3376std::string
3377serialize_multipart_formdata(const UploadFormDataItems &items,
3378 const std::string &boundary, bool finish = true) {
3379 std::string body;
3380
3381 for (const auto &item : items) {
3382 body += serialize_multipart_formdata_item_begin(item, boundary);
3383 body += item.content + serialize_multipart_formdata_item_end();
3384 }
3385
3386 if (finish) { body += serialize_multipart_formdata_finish(boundary); }
3387
3388 return body;
3389}
3390
3391void coalesce_ranges(Ranges &ranges, size_t content_length) {
3392 if (ranges.size() <= 1) return;
3393
3394 // Sort ranges by start position
3395 std::sort(ranges.begin(), ranges.end(),
3396 [](const Range &a, const Range &b) { return a.first < b.first; });
3397
3398 Ranges coalesced;
3399 coalesced.reserve(ranges.size());
3400
3401 for (auto &r : ranges) {
3402 auto first_pos = r.first;
3403 auto last_pos = r.second;
3404
3405 // Handle special cases like in range_error
3406 if (first_pos == -1 && last_pos == -1) {
3407 first_pos = 0;
3408 last_pos = static_cast<ssize_t>(content_length);
3409 }
3410
3411 if (first_pos == -1) {
3412 first_pos = static_cast<ssize_t>(content_length) - last_pos;
3413 last_pos = static_cast<ssize_t>(content_length) - 1;
3414 }
3415
3416 if (last_pos == -1 || last_pos >= static_cast<ssize_t>(content_length)) {
3417 last_pos = static_cast<ssize_t>(content_length) - 1;
3418 }
3419
3420 // Skip invalid ranges
3421 if (!(0 <= first_pos && first_pos <= last_pos &&
3422 last_pos < static_cast<ssize_t>(content_length))) {
3423 continue;
3424 }
3425
3426 // Coalesce with previous range if overlapping or adjacent (but not
3427 // identical)
3428 if (!coalesced.empty()) {
3429 auto &prev = coalesced.back();
3430 // Check if current range overlaps or is adjacent to previous range
3431 // but don't coalesce identical ranges (allow duplicates)
3432 if (first_pos <= prev.second + 1 &&
3433 !(first_pos == prev.first && last_pos == prev.second)) {
3434 // Extend the previous range
3435 prev.second = (std::max)(prev.second, last_pos);
3436 continue;
3437 }
3438 }
3439
3440 // Add new range
3441 coalesced.emplace_back(first_pos, last_pos);
3442 }
3443
3444 ranges = std::move(coalesced);
3445}
3446
3447bool range_error(Request &req, Response &res) {
3448 if (!req.ranges.empty() && 200 <= res.status && res.status < 300) {
3449 ssize_t content_len = static_cast<ssize_t>(
3450 res.content_length_ ? res.content_length_ : res.body.size());
3451
3452 std::vector<std::pair<ssize_t, ssize_t>> processed_ranges;
3453 size_t overwrapping_count = 0;
3454
3455 // NOTE: The following Range check is based on '14.2. Range' in RFC 9110
3456 // 'HTTP Semantics' to avoid potential denial-of-service attacks.
3457 // https://www.rfc-editor.org/rfc/rfc9110#section-14.2
3458
3459 // Too many ranges
3460 if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return true; }
3461
3462 for (auto &r : req.ranges) {
3463 auto &first_pos = r.first;
3464 auto &last_pos = r.second;
3465
3466 if (first_pos == -1 && last_pos == -1) {
3467 first_pos = 0;
3468 last_pos = content_len;
3469 }
3470
3471 if (first_pos == -1) {
3472 first_pos = content_len - last_pos;
3473 last_pos = content_len - 1;
3474 }
3475
3476 // NOTE: RFC-9110 '14.1.2. Byte Ranges':
3477 // A client can limit the number of bytes requested without knowing the
3478 // size of the selected representation. If the last-pos value is absent,
3479 // or if the value is greater than or equal to the current length of the
3480 // representation data, the byte range is interpreted as the remainder of
3481 // the representation (i.e., the server replaces the value of last-pos
3482 // with a value that is one less than the current length of the selected
3483 // representation).
3484 // https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6
3485 if (last_pos == -1 || last_pos >= content_len) {
3486 last_pos = content_len - 1;
3487 }
3488
3489 // Range must be within content length
3490 if (!(0 <= first_pos && first_pos <= last_pos &&
3491 last_pos <= content_len - 1)) {
3492 return true;
3493 }
3494
3495 // Request must not have more than two overlapping ranges
3496 for (const auto &processed_range : processed_ranges) {
3497 if (!(last_pos < processed_range.first ||
3498 first_pos > processed_range.second)) {
3499 overwrapping_count++;
3500 if (overwrapping_count > 2) { return true; }
3501 break; // Only count once per range
3502 }
3503 }
3504
3505 processed_ranges.emplace_back(first_pos, last_pos);
3506 }
3507
3508 // After validation, coalesce overlapping ranges as per RFC 9110
3509 coalesce_ranges(req.ranges, static_cast<size_t>(content_len));
3510 }
3511
3512 return false;
3513}
3514
3515std::pair<size_t, size_t>
3516get_range_offset_and_length(Range r, size_t content_length) {
3517 assert(r.first != -1 && r.second != -1);
3518 assert(0 <= r.first && r.first < static_cast<ssize_t>(content_length));
3519 assert(r.first <= r.second &&
3520 r.second < static_cast<ssize_t>(content_length));
3521 (void)(content_length);
3522 return std::make_pair(r.first, static_cast<size_t>(r.second - r.first) + 1);
3523}
3524
3525std::string make_content_range_header_field(
3526 const std::pair<size_t, size_t> &offset_and_length, size_t content_length) {
3527 auto st = offset_and_length.first;
3528 auto ed = st + offset_and_length.second - 1;
3529
3530 std::string field = "bytes ";
3531 field += std::to_string(st);
3532 field += '-';
3533 field += std::to_string(ed);
3534 field += '/';
3535 field += std::to_string(content_length);
3536 return field;
3537}
3538
3539template <typename SToken, typename CToken, typename Content>
3540bool process_multipart_ranges_data(const Request &req,
3541 const std::string &boundary,
3542 const std::string &content_type,
3543 size_t content_length, SToken stoken,
3544 CToken ctoken, Content content) {
3545 for (size_t i = 0; i < req.ranges.size(); i++) {
3546 ctoken("--");
3547 stoken(boundary);
3548 ctoken("\r\n");
3549 if (!content_type.empty()) {
3550 ctoken("Content-Type: ");
3551 stoken(content_type);
3552 ctoken("\r\n");
3553 }
3554
3555 auto offset_and_length =
3556 get_range_offset_and_length(req.ranges[i], content_length);
3557
3558 ctoken("Content-Range: ");
3559 stoken(make_content_range_header_field(offset_and_length, content_length));
3560 ctoken("\r\n");
3561 ctoken("\r\n");
3562
3563 if (!content(offset_and_length.first, offset_and_length.second)) {
3564 return false;
3565 }
3566 ctoken("\r\n");
3567 }
3568
3569 ctoken("--");
3570 stoken(boundary);
3571 ctoken("--");
3572
3573 return true;
3574}
3575
3576void make_multipart_ranges_data(const Request &req, Response &res,
3577 const std::string &boundary,
3578 const std::string &content_type,
3579 size_t content_length,
3580 std::string &data) {
3581 process_multipart_ranges_data(
3582 req, boundary, content_type, content_length,
3583 [&](const std::string &token) { data += token; },
3584 [&](const std::string &token) { data += token; },
3585 [&](size_t offset, size_t length) {
3586 assert(offset + length <= content_length);
3587 data += res.body.substr(offset, length);
3588 return true;
3589 });
3590}
3591
3592size_t get_multipart_ranges_data_length(const Request &req,
3593 const std::string &boundary,
3594 const std::string &content_type,
3595 size_t content_length) {
3596 size_t data_length = 0;
3597
3598 process_multipart_ranges_data(
3599 req, boundary, content_type, content_length,
3600 [&](const std::string &token) { data_length += token.size(); },
3601 [&](const std::string &token) { data_length += token.size(); },
3602 [&](size_t /*offset*/, size_t length) {
3603 data_length += length;
3604 return true;
3605 });
3606
3607 return data_length;
3608}
3609
3610template <typename T>
3611bool
3612write_multipart_ranges_data(Stream &strm, const Request &req, Response &res,
3613 const std::string &boundary,
3614 const std::string &content_type,
3615 size_t content_length, const T &is_shutting_down) {
3616 return process_multipart_ranges_data(
3617 req, boundary, content_type, content_length,
3618 [&](const std::string &token) { strm.write(token); },
3619 [&](const std::string &token) { strm.write(token); },
3620 [&](size_t offset, size_t length) {
3621 return write_content(strm, res.content_provider_, offset, length,
3622 is_shutting_down);
3623 });
3624}
3625
3626bool expect_content(const Request &req) {
3627 if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
3628 req.method == "DELETE") {
3629 return true;
3630 }
3631 if (req.has_header("Content-Length") &&
3632 req.get_header_value_u64("Content-Length") > 0) {
3633 return true;
3634 }
3635 if (is_chunked_transfer_encoding(req.headers)) { return true; }
3636 return false;
3637}
3638
3639bool has_crlf(const std::string &s) {
3640 auto p = s.c_str();
3641 while (*p) {
3642 if (*p == '\r' || *p == '\n') { return true; }
3643 p++;
3644 }
3645 return false;
3646}
3647
3648#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
3649std::string message_digest(const std::string &s, const EVP_MD *algo) {
3650 auto context = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(
3651 EVP_MD_CTX_new(), EVP_MD_CTX_free);
3652
3653 unsigned int hash_length = 0;
3654 unsigned char hash[EVP_MAX_MD_SIZE];
3655
3656 EVP_DigestInit_ex(context.get(), algo, nullptr);
3657 EVP_DigestUpdate(context.get(), s.c_str(), s.size());
3658 EVP_DigestFinal_ex(context.get(), hash, &hash_length);
3659
3660 std::stringstream ss;
3661 for (auto i = 0u; i < hash_length; ++i) {
3662 ss << std::hex << std::setw(2) << std::setfill('0')
3663 << static_cast<unsigned int>(hash[i]);
3664 }
3665
3666 return ss.str();
3667}
3668
3669std::string MD5(const std::string &s) {
3670 return message_digest(s, EVP_md5());
3671}
3672
3673std::string SHA_256(const std::string &s) {
3674 return message_digest(s, EVP_sha256());
3675}
3676
3677std::string SHA_512(const std::string &s) {
3678 return message_digest(s, EVP_sha512());
3679}
3680
3681std::pair<std::string, std::string> make_digest_authentication_header(
3682 const Request &req, const std::map<std::string, std::string> &auth,
3683 size_t cnonce_count, const std::string &cnonce, const std::string &username,
3684 const std::string &password, bool is_proxy = false) {
3685 std::string nc;
3686 {
3687 std::stringstream ss;
3688 ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count;
3689 nc = ss.str();
3690 }
3691
3692 std::string qop;
3693 if (auth.find("qop") != auth.end()) {
3694 qop = auth.at("qop");
3695 if (qop.find("auth-int") != std::string::npos) {
3696 qop = "auth-int";
3697 } else if (qop.find("auth") != std::string::npos) {
3698 qop = "auth";
3699 } else {
3700 qop.clear();
3701 }
3702 }
3703
3704 std::string algo = "MD5";
3705 if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
3706
3707 std::string response;
3708 {
3709 auto H = algo == "SHA-256" ? detail::SHA_256
3710 : algo == "SHA-512" ? detail::SHA_512
3711 : detail::MD5;
3712
3713 auto A1 = username + ":" + auth.at("realm") + ":" + password;
3714
3715 auto A2 = req.method + ":" + req.path;
3716 if (qop == "auth-int") { A2 += ":" + H(req.body); }
3717
3718 if (qop.empty()) {
3719 response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2));
3720 } else {
3721 response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
3722 ":" + qop + ":" + H(A2));
3723 }
3724 }
3725
3726 auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : "";
3727
3728 auto field = "Digest username=\"" + username + "\", realm=\"" +
3729 auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
3730 "\", uri=\"" + req.path + "\", algorithm=" + algo +
3731 (qop.empty() ? ", response=\""
3732 : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" +
3733 cnonce + "\", response=\"") +
3734 response + "\"" +
3735 (opaque.empty() ? "" : ", opaque=\"" + opaque + "\"");
3736
3737 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
3738 return std::make_pair(key, field);
3739}
3740
3741bool is_ssl_peer_could_be_closed(SSL *ssl, socket_t sock) {
3742 detail::set_nonblocking(sock, true);
3743 auto se = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
3744
3745 char buf[1];
3746 return !SSL_peek(ssl, buf, 1) &&
3747 SSL_get_error(ssl, 0) == SSL_ERROR_ZERO_RETURN;
3748}
3749
3750#ifdef _WIN32
3751// NOTE: This code came up with the following stackoverflow post:
3752// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store
3753bool load_system_certs_on_windows(X509_STORE *store) {
3754 auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT");
3755 if (!hStore) { return false; }
3756
3757 auto result = false;
3758 PCCERT_CONTEXT pContext = NULL;
3759 while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
3760 nullptr) {
3761 auto encoded_cert =
3762 static_cast<const unsigned char *>(pContext->pbCertEncoded);
3763
3764 auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
3765 if (x509) {
3766 X509_STORE_add_cert(store, x509);
3767 X509_free(x509);
3768 result = true;
3769 }
3770 }
3771
3772 CertFreeCertificateContext(pContext);
3773 CertCloseStore(hStore, 0);
3774
3775 return result;
3776}
3777#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && TARGET_OS_MAC
3778template <typename T>
3779using CFObjectPtr =
3780 std::unique_ptr<typename std::remove_pointer<T>::type, void (*)(CFTypeRef)>;
3781
3782void cf_object_ptr_deleter(CFTypeRef obj) {
3783 if (obj) { CFRelease(obj); }
3784}
3785
3786bool retrieve_certs_from_keychain(CFObjectPtr<CFArrayRef> &certs) {
3787 CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef};
3788 CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll,
3789 kCFBooleanTrue};
3790
3791 CFObjectPtr<CFDictionaryRef> query(
3792 CFDictionaryCreate(nullptr, reinterpret_cast<const void **>(keys), values,
3793 sizeof(keys) / sizeof(keys[0]),
3794 &kCFTypeDictionaryKeyCallBacks,
3795 &kCFTypeDictionaryValueCallBacks),
3796 cf_object_ptr_deleter);
3797
3798 if (!query) { return false; }
3799
3800 CFTypeRef security_items = nullptr;
3801 if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess ||
3802 CFArrayGetTypeID() != CFGetTypeID(security_items)) {
3803 return false;
3804 }
3805
3806 certs.reset(reinterpret_cast<CFArrayRef>(security_items));
3807 return true;
3808}
3809
3810bool retrieve_root_certs_from_keychain(CFObjectPtr<CFArrayRef> &certs) {
3811 CFArrayRef root_security_items = nullptr;
3812 if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) {
3813 return false;
3814 }
3815
3816 certs.reset(root_security_items);
3817 return true;
3818}
3819
3820bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) {
3821 auto result = false;
3822 for (auto i = 0; i < CFArrayGetCount(certs); ++i) {
3823 const auto cert = reinterpret_cast<const __SecCertificate *>(
3824 CFArrayGetValueAtIndex(certs, i));
3825
3826 if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; }
3827
3828 CFDataRef cert_data = nullptr;
3829 if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) !=
3830 errSecSuccess) {
3831 continue;
3832 }
3833
3834 CFObjectPtr<CFDataRef> cert_data_ptr(cert_data, cf_object_ptr_deleter);
3835
3836 auto encoded_cert = static_cast<const unsigned char *>(
3837 CFDataGetBytePtr(cert_data_ptr.get()));
3838
3839 auto x509 =
3840 d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get()));
3841
3842 if (x509) {
3843 X509_STORE_add_cert(store, x509);
3844 X509_free(x509);
3845 result = true;
3846 }
3847 }
3848
3849 return result;
3850}
3851
3852bool load_system_certs_on_macos(X509_STORE *store) {
3853 auto result = false;
3854 CFObjectPtr<CFArrayRef> certs(nullptr, cf_object_ptr_deleter);
3855 if (retrieve_certs_from_keychain(certs) && certs) {
3856 result = add_certs_to_x509_store(certs.get(), store);
3857 }
3858
3859 if (retrieve_root_certs_from_keychain(certs) && certs) {
3860 result = add_certs_to_x509_store(certs.get(), store) || result;
3861 }
3862
3863 return result;
3864}
3865#endif // _WIN32
3866#endif // CPPHTTPLIB_OPENSSL_SUPPORT
3867
3868#ifdef _WIN32
3869class WSInit {
3870public:
3871 WSInit() {
3872 WSADATA wsaData;
3873 if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true;
3874 }
3875
3876 ~WSInit() {
3877 if (is_valid_) WSACleanup();
3878 }
3879
3880 bool is_valid_ = false;
3881};
3882
3883static WSInit wsinit_;
3884#endif
3885
3886bool parse_www_authenticate(const Response &res,
3887 std::map<std::string, std::string> &auth,
3888 bool is_proxy) {
3889 auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
3890 if (res.has_header(auth_key)) {
3891 thread_local auto re =
3892 std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~");
3893 auto s = res.get_header_value(auth_key);
3894 auto pos = s.find(' ');
3895 if (pos != std::string::npos) {
3896 auto type = s.substr(0, pos);
3897 if (type == "Basic") {
3898 return false;
3899 } else if (type == "Digest") {
3900 s = s.substr(pos + 1);
3901 auto beg = std::sregex_iterator(s.begin(), s.end(), re);
3902 for (auto i = beg; i != std::sregex_iterator(); ++i) {
3903 const auto &m = *i;
3904 auto key = s.substr(static_cast<size_t>(m.position(1)),
3905 static_cast<size_t>(m.length(1)));
3906 auto val = m.length(2) > 0
3907 ? s.substr(static_cast<size_t>(m.position(2)),
3908 static_cast<size_t>(m.length(2)))
3909 : s.substr(static_cast<size_t>(m.position(3)),
3910 static_cast<size_t>(m.length(3)));
3911 auth[std::move(key)] = std::move(val);
3912 }
3913 return true;
3914 }
3915 }
3916 }
3917 return false;
3918}
3919
3920class ContentProviderAdapter {
3921public:
3922 explicit ContentProviderAdapter(
3923 ContentProviderWithoutLength &&content_provider)
3924 : content_provider_(std::move(content_provider)) {}
3925
3926 bool operator()(size_t offset, size_t, DataSink &sink) {
3927 return content_provider_(offset, sink);
3928 }
3929
3930private:
3931 ContentProviderWithoutLength content_provider_;
3932};
3933
3934// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
3935namespace fields {
3936
3937bool is_token_char(char c) {
3938 return std::isalnum(c) || c == '!' || c == '#' || c == '$' || c == '%' ||
3939 c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' ||
3940 c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
3941}
3942
3943bool is_token(const std::string &s) {
3944 if (s.empty()) { return false; }
3945 for (auto c : s) {
3946 if (!is_token_char(c)) { return false; }
3947 }
3948 return true;
3949}
3950
3951bool is_field_name(const std::string &s) { return is_token(s); }
3952
3953bool is_vchar(char c) { return c >= 33 && c <= 126; }
3954
3955bool is_obs_text(char c) { return 128 <= static_cast<unsigned char>(c); }
3956
3957bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); }
3958
3959bool is_field_content(const std::string &s) {
3960 if (s.empty()) { return true; }
3961
3962 if (s.size() == 1) {
3963 return is_field_vchar(s[0]);
3964 } else if (s.size() == 2) {
3965 return is_field_vchar(s[0]) && is_field_vchar(s[1]);
3966 } else {
3967 size_t i = 0;
3968
3969 if (!is_field_vchar(s[i])) { return false; }
3970 i++;
3971
3972 while (i < s.size() - 1) {
3973 auto c = s[i++];
3974 if (c == ' ' || c == '\t' || is_field_vchar(c)) {
3975 } else {
3976 return false;
3977 }
3978 }
3979
3980 return is_field_vchar(s[i]);
3981 }
3982}
3983
3984bool is_field_value(const std::string &s) { return is_field_content(s); }
3985
3986} // namespace fields
3987
3988} // namespace detail
3989
3990const char *status_message(int status) {
3991 switch (status) {
3992 case StatusCode::Continue_100: return "Continue";
3993 case StatusCode::SwitchingProtocol_101: return "Switching Protocol";
3994 case StatusCode::Processing_102: return "Processing";
3995 case StatusCode::EarlyHints_103: return "Early Hints";
3996 case StatusCode::OK_200: return "OK";
3997 case StatusCode::Created_201: return "Created";
3998 case StatusCode::Accepted_202: return "Accepted";
3999 case StatusCode::NonAuthoritativeInformation_203:
4000 return "Non-Authoritative Information";
4001 case StatusCode::NoContent_204: return "No Content";
4002 case StatusCode::ResetContent_205: return "Reset Content";
4003 case StatusCode::PartialContent_206: return "Partial Content";
4004 case StatusCode::MultiStatus_207: return "Multi-Status";
4005 case StatusCode::AlreadyReported_208: return "Already Reported";
4006 case StatusCode::IMUsed_226: return "IM Used";
4007 case StatusCode::MultipleChoices_300: return "Multiple Choices";
4008 case StatusCode::MovedPermanently_301: return "Moved Permanently";
4009 case StatusCode::Found_302: return "Found";
4010 case StatusCode::SeeOther_303: return "See Other";
4011 case StatusCode::NotModified_304: return "Not Modified";
4012 case StatusCode::UseProxy_305: return "Use Proxy";
4013 case StatusCode::unused_306: return "unused";
4014 case StatusCode::TemporaryRedirect_307: return "Temporary Redirect";
4015 case StatusCode::PermanentRedirect_308: return "Permanent Redirect";
4016 case StatusCode::BadRequest_400: return "Bad Request";
4017 case StatusCode::Unauthorized_401: return "Unauthorized";
4018 case StatusCode::PaymentRequired_402: return "Payment Required";
4019 case StatusCode::Forbidden_403: return "Forbidden";
4020 case StatusCode::NotFound_404: return "Not Found";
4021 case StatusCode::MethodNotAllowed_405: return "Method Not Allowed";
4022 case StatusCode::NotAcceptable_406: return "Not Acceptable";
4023 case StatusCode::ProxyAuthenticationRequired_407:
4024 return "Proxy Authentication Required";
4025 case StatusCode::RequestTimeout_408: return "Request Timeout";
4026 case StatusCode::Conflict_409: return "Conflict";
4027 case StatusCode::Gone_410: return "Gone";
4028 case StatusCode::LengthRequired_411: return "Length Required";
4029 case StatusCode::PreconditionFailed_412: return "Precondition Failed";
4030 case StatusCode::PayloadTooLarge_413: return "Payload Too Large";
4031 case StatusCode::UriTooLong_414: return "URI Too Long";
4032 case StatusCode::UnsupportedMediaType_415: return "Unsupported Media Type";
4033 case StatusCode::RangeNotSatisfiable_416: return "Range Not Satisfiable";
4034 case StatusCode::ExpectationFailed_417: return "Expectation Failed";
4035 case StatusCode::ImATeapot_418: return "I'm a teapot";
4036 case StatusCode::MisdirectedRequest_421: return "Misdirected Request";
4037 case StatusCode::UnprocessableContent_422: return "Unprocessable Content";
4038 case StatusCode::Locked_423: return "Locked";
4039 case StatusCode::FailedDependency_424: return "Failed Dependency";
4040 case StatusCode::TooEarly_425: return "Too Early";
4041 case StatusCode::UpgradeRequired_426: return "Upgrade Required";
4042 case StatusCode::PreconditionRequired_428: return "Precondition Required";
4043 case StatusCode::TooManyRequests_429: return "Too Many Requests";
4044 case StatusCode::RequestHeaderFieldsTooLarge_431:
4045 return "Request Header Fields Too Large";
4046 case StatusCode::UnavailableForLegalReasons_451:
4047 return "Unavailable For Legal Reasons";
4048 case StatusCode::NotImplemented_501: return "Not Implemented";
4049 case StatusCode::BadGateway_502: return "Bad Gateway";
4050 case StatusCode::ServiceUnavailable_503: return "Service Unavailable";
4051 case StatusCode::GatewayTimeout_504: return "Gateway Timeout";
4052 case StatusCode::HttpVersionNotSupported_505:
4053 return "HTTP Version Not Supported";
4054 case StatusCode::VariantAlsoNegotiates_506: return "Variant Also Negotiates";
4055 case StatusCode::InsufficientStorage_507: return "Insufficient Storage";
4056 case StatusCode::LoopDetected_508: return "Loop Detected";
4057 case StatusCode::NotExtended_510: return "Not Extended";
4058 case StatusCode::NetworkAuthenticationRequired_511:
4059 return "Network Authentication Required";
4060
4061 default:
4062 case StatusCode::InternalServerError_500: return "Internal Server Error";
4063 }
4064}
4065
4066std::string to_string(const Error error) {
4067 switch (error) {
4068 case Error::Success: return "Success (no error)";
4069 case Error::Unknown: return "Unknown";
4070 case Error::Connection: return "Could not establish connection";
4071 case Error::BindIPAddress: return "Failed to bind IP address";
4072 case Error::Read: return "Failed to read connection";
4073 case Error::Write: return "Failed to write connection";
4074 case Error::ExceedRedirectCount: return "Maximum redirect count exceeded";
4075 case Error::Canceled: return "Connection handling canceled";
4076 case Error::SSLConnection: return "SSL connection failed";
4077 case Error::SSLLoadingCerts: return "SSL certificate loading failed";
4078 case Error::SSLServerVerification: return "SSL server verification failed";
4079 case Error::SSLServerHostnameVerification:
4080 return "SSL server hostname verification failed";
4081 case Error::UnsupportedMultipartBoundaryChars:
4082 return "Unsupported HTTP multipart boundary characters";
4083 case Error::Compression: return "Compression failed";
4084 case Error::ConnectionTimeout: return "Connection timed out";
4085 case Error::ProxyConnection: return "Proxy connection failed";
4086 case Error::ConnectionClosed: return "Connection closed by server";
4087 case Error::Timeout: return "Read timeout";
4088 case Error::ResourceExhaustion: return "Resource exhaustion";
4089 case Error::TooManyFormDataFiles: return "Too many form data files";
4090 case Error::ExceedMaxPayloadSize: return "Exceeded maximum payload size";
4091 case Error::ExceedUriMaxLength: return "Exceeded maximum URI length";
4092 case Error::ExceedMaxSocketDescriptorCount:
4093 return "Exceeded maximum socket descriptor count";
4094 case Error::InvalidRequestLine: return "Invalid request line";
4095 case Error::InvalidHTTPMethod: return "Invalid HTTP method";
4096 case Error::InvalidHTTPVersion: return "Invalid HTTP version";
4097 case Error::InvalidHeaders: return "Invalid headers";
4098 case Error::MultipartParsing: return "Multipart parsing failed";
4099 case Error::OpenFile: return "Failed to open file";
4100 case Error::Listen: return "Failed to listen on socket";
4101 case Error::GetSockName: return "Failed to get socket name";
4102 case Error::UnsupportedAddressFamily: return "Unsupported address family";
4103 case Error::HTTPParsing: return "HTTP parsing failed";
4104 case Error::InvalidRangeHeader: return "Invalid Range header";
4105 default: break;
4106 }
4107
4108 return "Invalid";
4109}
4110
4111std::ostream &operator<<(std::ostream &os, const Error &obj) {
4112 os << to_string(obj);
4113 os << " (" << static_cast<std::underlying_type<Error>::type>(obj) << ')';
4114 return os;
4115}
4116
4117std::string hosted_at(const std::string &hostname) {
4118 std::vector<std::string> addrs;
4119 hosted_at(hostname, addrs);
4120 if (addrs.empty()) { return std::string(); }
4121 return addrs[0];
4122}
4123
4124void hosted_at(const std::string &hostname,
4125 std::vector<std::string> &addrs) {
4126 struct addrinfo hints;
4127 struct addrinfo *result;
4128
4129 memset(&hints, 0, sizeof(struct addrinfo));
4130 hints.ai_family = AF_UNSPEC;
4131 hints.ai_socktype = SOCK_STREAM;
4132 hints.ai_protocol = 0;
4133
4134 if (detail::getaddrinfo_with_timeout(hostname.c_str(), nullptr, &hints,
4135 &result, 0)) {
4136#if defined __linux__ && !defined __ANDROID__
4137 res_init();
4138#endif
4139 return;
4140 }
4141 auto se = detail::scope_exit([&] { freeaddrinfo(result); });
4142
4143 for (auto rp = result; rp; rp = rp->ai_next) {
4144 const auto &addr =
4145 *reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
4146 std::string ip;
4147 auto dummy = -1;
4148 if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip,
4149 dummy)) {
4150 addrs.emplace_back(std::move(ip));
4151 }
4152 }
4153}
4154
4155std::string encode_uri_component(const std::string &value) {
4156 std::ostringstream escaped;
4157 escaped.fill('0');
4158 escaped << std::hex;
4159
4160 for (auto c : value) {
4161 if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
4162 c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
4163 c == ')') {
4164 escaped << c;
4165 } else {
4166 escaped << std::uppercase;
4167 escaped << '%' << std::setw(2)
4168 << static_cast<int>(static_cast<unsigned char>(c));
4169 escaped << std::nouppercase;
4170 }
4171 }
4172
4173 return escaped.str();
4174}
4175
4176std::string encode_uri(const std::string &value) {
4177 std::ostringstream escaped;
4178 escaped.fill('0');
4179 escaped << std::hex;
4180
4181 for (auto c : value) {
4182 if (std::isalnum(static_cast<uint8_t>(c)) || c == '-' || c == '_' ||
4183 c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' ||
4184 c == ')' || c == ';' || c == '/' || c == '?' || c == ':' || c == '@' ||
4185 c == '&' || c == '=' || c == '+' || c == '$' || c == ',' || c == '#') {
4186 escaped << c;
4187 } else {
4188 escaped << std::uppercase;
4189 escaped << '%' << std::setw(2)
4190 << static_cast<int>(static_cast<unsigned char>(c));
4191 escaped << std::nouppercase;
4192 }
4193 }
4194
4195 return escaped.str();
4196}
4197
4198std::string decode_uri_component(const std::string &value) {
4199 std::string result;
4200
4201 for (size_t i = 0; i < value.size(); i++) {
4202 if (value[i] == '%' && i + 2 < value.size()) {
4203 auto val = 0;
4204 if (detail::from_hex_to_i(value, i + 1, 2, val)) {
4205 result += static_cast<char>(val);
4206 i += 2;
4207 } else {
4208 result += value[i];
4209 }
4210 } else {
4211 result += value[i];
4212 }
4213 }
4214
4215 return result;
4216}
4217
4218std::string decode_uri(const std::string &value) {
4219 std::string result;
4220
4221 for (size_t i = 0; i < value.size(); i++) {
4222 if (value[i] == '%' && i + 2 < value.size()) {
4223 auto val = 0;
4224 if (detail::from_hex_to_i(value, i + 1, 2, val)) {
4225 result += static_cast<char>(val);
4226 i += 2;
4227 } else {
4228 result += value[i];
4229 }
4230 } else {
4231 result += value[i];
4232 }
4233 }
4234
4235 return result;
4236}
4237
4238std::string encode_path_component(const std::string &component) {
4239 std::string result;
4240 result.reserve(component.size() * 3);
4241
4242 for (size_t i = 0; i < component.size(); i++) {
4243 auto c = static_cast<unsigned char>(component[i]);
4244
4245 // Unreserved characters per RFC 3986: ALPHA / DIGIT / "-" / "." / "_" / "~"
4246 if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
4247 result += static_cast<char>(c);
4248 }
4249 // Path-safe sub-delimiters: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" /
4250 // "," / ";" / "="
4251 else if (c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' ||
4252 c == ')' || c == '*' || c == '+' || c == ',' || c == ';' ||
4253 c == '=') {
4254 result += static_cast<char>(c);
4255 }
4256 // Colon is allowed in path segments except first segment
4257 else if (c == ':') {
4258 result += static_cast<char>(c);
4259 }
4260 // @ is allowed in path
4261 else if (c == '@') {
4262 result += static_cast<char>(c);
4263 } else {
4264 result += '%';
4265 char hex[3];
4266 snprintf(hex, sizeof(hex), "%02X", c);
4267 result.append(hex, 2);
4268 }
4269 }
4270 return result;
4271}
4272
4273std::string decode_path_component(const std::string &component) {
4274 std::string result;
4275 result.reserve(component.size());
4276
4277 for (size_t i = 0; i < component.size(); i++) {
4278 if (component[i] == '%' && i + 1 < component.size()) {
4279 if (component[i + 1] == 'u') {
4280 // Unicode %uXXXX encoding
4281 auto val = 0;
4282 if (detail::from_hex_to_i(component, i + 2, 4, val)) {
4283 // 4 digits Unicode codes
4284 char buff[4];
4285 size_t len = detail::to_utf8(val, buff);
4286 if (len > 0) { result.append(buff, len); }
4287 i += 5; // 'u0000'
4288 } else {
4289 result += component[i];
4290 }
4291 } else {
4292 // Standard %XX encoding
4293 auto val = 0;
4294 if (detail::from_hex_to_i(component, i + 1, 2, val)) {
4295 // 2 digits hex codes
4296 result += static_cast<char>(val);
4297 i += 2; // 'XX'
4298 } else {
4299 result += component[i];
4300 }
4301 }
4302 } else {
4303 result += component[i];
4304 }
4305 }
4306 return result;
4307}
4308
4309std::string encode_query_component(const std::string &component,
4310 bool space_as_plus) {
4311 std::string result;
4312 result.reserve(component.size() * 3);
4313
4314 for (size_t i = 0; i < component.size(); i++) {
4315 auto c = static_cast<unsigned char>(component[i]);
4316
4317 // Unreserved characters per RFC 3986
4318 if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
4319 result += static_cast<char>(c);
4320 }
4321 // Space handling
4322 else if (c == ' ') {
4323 if (space_as_plus) {
4324 result += '+';
4325 } else {
4326 result += "%20";
4327 }
4328 }
4329 // Plus sign handling
4330 else if (c == '+') {
4331 if (space_as_plus) {
4332 result += "%2B";
4333 } else {
4334 result += static_cast<char>(c);
4335 }
4336 }
4337 // Query-safe sub-delimiters (excluding & and = which are query delimiters)
4338 else if (c == '!' || c == '$' || c == '\'' || c == '(' || c == ')' ||
4339 c == '*' || c == ',' || c == ';') {
4340 result += static_cast<char>(c);
4341 }
4342 // Colon and @ are allowed in query
4343 else if (c == ':' || c == '@') {
4344 result += static_cast<char>(c);
4345 }
4346 // Forward slash is allowed in query values
4347 else if (c == '/') {
4348 result += static_cast<char>(c);
4349 }
4350 // Question mark is allowed in query values (after first ?)
4351 else if (c == '?') {
4352 result += static_cast<char>(c);
4353 } else {
4354 result += '%';
4355 char hex[3];
4356 snprintf(hex, sizeof(hex), "%02X", c);
4357 result.append(hex, 2);
4358 }
4359 }
4360 return result;
4361}
4362
4363std::string decode_query_component(const std::string &component,
4364 bool plus_as_space) {
4365 std::string result;
4366 result.reserve(component.size());
4367
4368 for (size_t i = 0; i < component.size(); i++) {
4369 if (component[i] == '%' && i + 2 < component.size()) {
4370 std::string hex = component.substr(i + 1, 2);
4371 char *end;
4372 unsigned long value = std::strtoul(hex.c_str(), &end, 16);
4373 if (end == hex.c_str() + 2) {
4374 result += static_cast<char>(value);
4375 i += 2;
4376 } else {
4377 result += component[i];
4378 }
4379 } else if (component[i] == '+' && plus_as_space) {
4380 result += ' '; // + becomes space in form-urlencoded
4381 } else {
4382 result += component[i];
4383 }
4384 }
4385 return result;
4386}
4387
4388std::string append_query_params(const std::string &path,
4389 const Params ¶ms) {
4390 std::string path_with_query = path;
4391 thread_local const std::regex re("[^?]+\\?.*");
4392 auto delm = std::regex_match(path, re) ? '&' : '?';
4393 path_with_query += delm + detail::params_to_query_str(params);
4394 return path_with_query;
4395}
4396
4397// Header utilities
4398std::pair<std::string, std::string>
4399make_range_header(const Ranges &ranges) {
4400 std::string field = "bytes=";
4401 auto i = 0;
4402 for (const auto &r : ranges) {
4403 if (i != 0) { field += ", "; }
4404 if (r.first != -1) { field += std::to_string(r.first); }
4405 field += '-';
4406 if (r.second != -1) { field += std::to_string(r.second); }
4407 i++;
4408 }
4409 return std::make_pair("Range", std::move(field));
4410}
4411
4412std::pair<std::string, std::string>
4413make_basic_authentication_header(const std::string &username,
4414 const std::string &password, bool is_proxy) {
4415 auto field = "Basic " + detail::base64_encode(username + ":" + password);
4416 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
4417 return std::make_pair(key, std::move(field));
4418}
4419
4420std::pair<std::string, std::string>
4421make_bearer_token_authentication_header(const std::string &token,
4422 bool is_proxy = false) {
4423 auto field = "Bearer " + token;
4424 auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
4425 return std::make_pair(key, std::move(field));
4426}
4427
4428// Request implementation
4429bool Request::has_header(const std::string &key) const {
4430 return detail::has_header(headers, key);
4431}
4432
4433std::string Request::get_header_value(const std::string &key,
4434 const char *def, size_t id) const {
4435 return detail::get_header_value(headers, key, def, id);
4436}
4437
4438size_t Request::get_header_value_count(const std::string &key) const {
4439 auto r = headers.equal_range(key);
4440 return static_cast<size_t>(std::distance(r.first, r.second));
4441}
4442
4443void Request::set_header(const std::string &key,
4444 const std::string &val) {
4445 if (detail::fields::is_field_name(key) &&
4446 detail::fields::is_field_value(val)) {
4447 headers.emplace(key, val);
4448 }
4449}
4450
4451bool Request::has_trailer(const std::string &key) const {
4452 return trailers.find(key) != trailers.end();
4453}
4454
4455std::string Request::get_trailer_value(const std::string &key,
4456 size_t id) const {
4457 auto rng = trailers.equal_range(key);
4458 auto it = rng.first;
4459 std::advance(it, static_cast<ssize_t>(id));
4460 if (it != rng.second) { return it->second; }
4461 return std::string();
4462}
4463
4464size_t Request::get_trailer_value_count(const std::string &key) const {
4465 auto r = trailers.equal_range(key);
4466 return static_cast<size_t>(std::distance(r.first, r.second));
4467}
4468
4469bool Request::has_param(const std::string &key) const {
4470 return params.find(key) != params.end();
4471}
4472
4473std::string Request::get_param_value(const std::string &key,
4474 size_t id) const {
4475 auto rng = params.equal_range(key);
4476 auto it = rng.first;
4477 std::advance(it, static_cast<ssize_t>(id));
4478 if (it != rng.second) { return it->second; }
4479 return std::string();
4480}
4481
4482size_t Request::get_param_value_count(const std::string &key) const {
4483 auto r = params.equal_range(key);
4484 return static_cast<size_t>(std::distance(r.first, r.second));
4485}
4486
4487bool Request::is_multipart_form_data() const {
4488 const auto &content_type = get_header_value("Content-Type");
4489 return !content_type.rfind("multipart/form-data", 0);
4490}
4491
4492// Multipart FormData implementation
4493std::string MultipartFormData::get_field(const std::string &key,
4494 size_t id) const {
4495 auto rng = fields.equal_range(key);
4496 auto it = rng.first;
4497 std::advance(it, static_cast<ssize_t>(id));
4498 if (it != rng.second) { return it->second.content; }
4499 return std::string();
4500}
4501
4502std::vector<std::string>
4503MultipartFormData::get_fields(const std::string &key) const {
4504 std::vector<std::string> values;
4505 auto rng = fields.equal_range(key);
4506 for (auto it = rng.first; it != rng.second; it++) {
4507 values.push_back(it->second.content);
4508 }
4509 return values;
4510}
4511
4512bool MultipartFormData::has_field(const std::string &key) const {
4513 return fields.find(key) != fields.end();
4514}
4515
4516size_t MultipartFormData::get_field_count(const std::string &key) const {
4517 auto r = fields.equal_range(key);
4518 return static_cast<size_t>(std::distance(r.first, r.second));
4519}
4520
4521FormData MultipartFormData::get_file(const std::string &key,
4522 size_t id) const {
4523 auto rng = files.equal_range(key);
4524 auto it = rng.first;
4525 std::advance(it, static_cast<ssize_t>(id));
4526 if (it != rng.second) { return it->second; }
4527 return FormData();
4528}
4529
4530std::vector<FormData>
4531MultipartFormData::get_files(const std::string &key) const {
4532 std::vector<FormData> values;
4533 auto rng = files.equal_range(key);
4534 for (auto it = rng.first; it != rng.second; it++) {
4535 values.push_back(it->second);
4536 }
4537 return values;
4538}
4539
4540bool MultipartFormData::has_file(const std::string &key) const {
4541 return files.find(key) != files.end();
4542}
4543
4544size_t MultipartFormData::get_file_count(const std::string &key) const {
4545 auto r = files.equal_range(key);
4546 return static_cast<size_t>(std::distance(r.first, r.second));
4547}
4548
4549// Response implementation
4550bool Response::has_header(const std::string &key) const {
4551 return headers.find(key) != headers.end();
4552}
4553
4554std::string Response::get_header_value(const std::string &key,
4555 const char *def,
4556 size_t id) const {
4557 return detail::get_header_value(headers, key, def, id);
4558}
4559
4560size_t Response::get_header_value_count(const std::string &key) const {
4561 auto r = headers.equal_range(key);
4562 return static_cast<size_t>(std::distance(r.first, r.second));
4563}
4564
4565void Response::set_header(const std::string &key,
4566 const std::string &val) {
4567 if (detail::fields::is_field_name(key) &&
4568 detail::fields::is_field_value(val)) {
4569 headers.emplace(key, val);
4570 }
4571}
4572bool Response::has_trailer(const std::string &key) const {
4573 return trailers.find(key) != trailers.end();
4574}
4575
4576std::string Response::get_trailer_value(const std::string &key,
4577 size_t id) const {
4578 auto rng = trailers.equal_range(key);
4579 auto it = rng.first;
4580 std::advance(it, static_cast<ssize_t>(id));
4581 if (it != rng.second) { return it->second; }
4582 return std::string();
4583}
4584
4585size_t Response::get_trailer_value_count(const std::string &key) const {
4586 auto r = trailers.equal_range(key);
4587 return static_cast<size_t>(std::distance(r.first, r.second));
4588}
4589
4590void Response::set_redirect(const std::string &url, int stat) {
4591 if (detail::fields::is_field_value(url)) {
4592 set_header("Location", url);
4593 if (300 <= stat && stat < 400) {
4594 this->status = stat;
4595 } else {
4596 this->status = StatusCode::Found_302;
4597 }
4598 }
4599}
4600
4601void Response::set_content(const char *s, size_t n,
4602 const std::string &content_type) {
4603 body.assign(s, n);
4604
4605 auto rng = headers.equal_range("Content-Type");
4606 headers.erase(rng.first, rng.second);
4607 set_header("Content-Type", content_type);
4608}
4609
4610void Response::set_content(const std::string &s,
4611 const std::string &content_type) {
4612 set_content(s.data(), s.size(), content_type);
4613}
4614
4615void Response::set_content(std::string &&s,
4616 const std::string &content_type) {
4617 body = std::move(s);
4618
4619 auto rng = headers.equal_range("Content-Type");
4620 headers.erase(rng.first, rng.second);
4621 set_header("Content-Type", content_type);
4622}
4623
4624void Response::set_content_provider(
4625 size_t in_length, const std::string &content_type, ContentProvider provider,
4626 ContentProviderResourceReleaser resource_releaser) {
4627 set_header("Content-Type", content_type);
4628 content_length_ = in_length;
4629 if (in_length > 0) { content_provider_ = std::move(provider); }
4630 content_provider_resource_releaser_ = std::move(resource_releaser);
4631 is_chunked_content_provider_ = false;
4632}
4633
4634void Response::set_content_provider(
4635 const std::string &content_type, ContentProviderWithoutLength provider,
4636 ContentProviderResourceReleaser resource_releaser) {
4637 set_header("Content-Type", content_type);
4638 content_length_ = 0;
4639 content_provider_ = detail::ContentProviderAdapter(std::move(provider));
4640 content_provider_resource_releaser_ = std::move(resource_releaser);
4641 is_chunked_content_provider_ = false;
4642}
4643
4644void Response::set_chunked_content_provider(
4645 const std::string &content_type, ContentProviderWithoutLength provider,
4646 ContentProviderResourceReleaser resource_releaser) {
4647 set_header("Content-Type", content_type);
4648 content_length_ = 0;
4649 content_provider_ = detail::ContentProviderAdapter(std::move(provider));
4650 content_provider_resource_releaser_ = std::move(resource_releaser);
4651 is_chunked_content_provider_ = true;
4652}
4653
4654void Response::set_file_content(const std::string &path,
4655 const std::string &content_type) {
4656 file_content_path_ = path;
4657 file_content_content_type_ = content_type;
4658}
4659
4660void Response::set_file_content(const std::string &path) {
4661 file_content_path_ = path;
4662}
4663
4664// Result implementation
4665bool Result::has_request_header(const std::string &key) const {
4666 return request_headers_.find(key) != request_headers_.end();
4667}
4668
4669std::string Result::get_request_header_value(const std::string &key,
4670 const char *def,
4671 size_t id) const {
4672 return detail::get_header_value(request_headers_, key, def, id);
4673}
4674
4675size_t
4676Result::get_request_header_value_count(const std::string &key) const {
4677 auto r = request_headers_.equal_range(key);
4678 return static_cast<size_t>(std::distance(r.first, r.second));
4679}
4680
4681// Stream implementation
4682ssize_t Stream::write(const char *ptr) {
4683 return write(ptr, strlen(ptr));
4684}
4685
4686ssize_t Stream::write(const std::string &s) {
4687 return write(s.data(), s.size());
4688}
4689
4690// BodyReader implementation
4691ssize_t detail::BodyReader::read(char *buf, size_t len) {
4692 if (!stream) {
4693 last_error = Error::Connection;
4694 return -1;
4695 }
4696 if (eof) { return 0; }
4697
4698 if (!chunked) {
4699 // Content-Length based reading
4700 if (bytes_read >= content_length) {
4701 eof = true;
4702 return 0;
4703 }
4704
4705 auto remaining = content_length - bytes_read;
4706 auto to_read = (std::min)(len, remaining);
4707 auto n = stream->read(buf, to_read);
4708
4709 if (n < 0) {
4710 last_error = stream->get_error();
4711 if (last_error == Error::Success) { last_error = Error::Read; }
4712 eof = true;
4713 return n;
4714 }
4715 if (n == 0) {
4716 // Unexpected EOF before content_length
4717 last_error = stream->get_error();
4718 if (last_error == Error::Success) { last_error = Error::Read; }
4719 eof = true;
4720 return 0;
4721 }
4722
4723 bytes_read += static_cast<size_t>(n);
4724 if (bytes_read >= content_length) { eof = true; }
4725 return n;
4726 }
4727
4728 // Chunked transfer encoding: delegate to shared decoder instance.
4729 if (!chunked_decoder) { chunked_decoder.reset(new ChunkedDecoder(*stream)); }
4730
4731 size_t chunk_offset = 0;
4732 size_t chunk_total = 0;
4733 auto n = chunked_decoder->read_payload(buf, len, chunk_offset, chunk_total);
4734 if (n < 0) {
4735 last_error = stream->get_error();
4736 if (last_error == Error::Success) { last_error = Error::Read; }
4737 eof = true;
4738 return n;
4739 }
4740
4741 if (n == 0) {
4742 // Final chunk observed. Leave trailer parsing to the caller (StreamHandle).
4743 eof = true;
4744 return 0;
4745 }
4746
4747 bytes_read += static_cast<size_t>(n);
4748 return n;
4749}
4750
4751namespace detail {
4752
4753void calc_actual_timeout(time_t max_timeout_msec, time_t duration_msec,
4754 time_t timeout_sec, time_t timeout_usec,
4755 time_t &actual_timeout_sec,
4756 time_t &actual_timeout_usec) {
4757 auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000);
4758
4759 auto actual_timeout_msec =
4760 (std::min)(max_timeout_msec - duration_msec, timeout_msec);
4761
4762 if (actual_timeout_msec < 0) { actual_timeout_msec = 0; }
4763
4764 actual_timeout_sec = actual_timeout_msec / 1000;
4765 actual_timeout_usec = (actual_timeout_msec % 1000) * 1000;
4766}
4767
4768// Socket stream implementation
4769SocketStream::SocketStream(
4770 socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
4771 time_t write_timeout_sec, time_t write_timeout_usec,
4772 time_t max_timeout_msec,
4773 std::chrono::time_point<std::chrono::steady_clock> start_time)
4774 : sock_(sock), read_timeout_sec_(read_timeout_sec),
4775 read_timeout_usec_(read_timeout_usec),
4776 write_timeout_sec_(write_timeout_sec),
4777 write_timeout_usec_(write_timeout_usec),
4778 max_timeout_msec_(max_timeout_msec), start_time_(start_time),
4779 read_buff_(read_buff_size_, 0) {}
4780
4781SocketStream::~SocketStream() = default;
4782
4783bool SocketStream::is_readable() const {
4784 return read_buff_off_ < read_buff_content_size_;
4785}
4786
4787bool SocketStream::wait_readable() const {
4788 if (max_timeout_msec_ <= 0) {
4789 return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
4790 }
4791
4792 time_t read_timeout_sec;
4793 time_t read_timeout_usec;
4794 calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
4795 read_timeout_usec_, read_timeout_sec, read_timeout_usec);
4796
4797 return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
4798}
4799
4800bool SocketStream::wait_writable() const {
4801 return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
4802 is_socket_alive(sock_);
4803}
4804
4805ssize_t SocketStream::read(char *ptr, size_t size) {
4806#ifdef _WIN32
4807 size =
4808 (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
4809#else
4810 size = (std::min)(size,
4811 static_cast<size_t>((std::numeric_limits<ssize_t>::max)()));
4812#endif
4813
4814 if (read_buff_off_ < read_buff_content_size_) {
4815 auto remaining_size = read_buff_content_size_ - read_buff_off_;
4816 if (size <= remaining_size) {
4817 memcpy(ptr, read_buff_.data() + read_buff_off_, size);
4818 read_buff_off_ += size;
4819 return static_cast<ssize_t>(size);
4820 } else {
4821 memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size);
4822 read_buff_off_ += remaining_size;
4823 return static_cast<ssize_t>(remaining_size);
4824 }
4825 }
4826
4827 if (!wait_readable()) {
4828 error_ = Error::Timeout;
4829 return -1;
4830 }
4831
4832 read_buff_off_ = 0;
4833 read_buff_content_size_ = 0;
4834
4835 if (size < read_buff_size_) {
4836 auto n = read_socket(sock_, read_buff_.data(), read_buff_size_,
4837 CPPHTTPLIB_RECV_FLAGS);
4838 if (n <= 0) {
4839 if (n == 0) {
4840 error_ = Error::ConnectionClosed;
4841 } else {
4842 error_ = Error::Read;
4843 }
4844 return n;
4845 } else if (n <= static_cast<ssize_t>(size)) {
4846 memcpy(ptr, read_buff_.data(), static_cast<size_t>(n));
4847 return n;
4848 } else {
4849 memcpy(ptr, read_buff_.data(), size);
4850 read_buff_off_ = size;
4851 read_buff_content_size_ = static_cast<size_t>(n);
4852 return static_cast<ssize_t>(size);
4853 }
4854 } else {
4855 auto n = read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS);
4856 if (n <= 0) {
4857 if (n == 0) {
4858 error_ = Error::ConnectionClosed;
4859 } else {
4860 error_ = Error::Read;
4861 }
4862 }
4863 return n;
4864 }
4865}
4866
4867ssize_t SocketStream::write(const char *ptr, size_t size) {
4868 if (!wait_writable()) { return -1; }
4869
4870#if defined(_WIN32) && !defined(_WIN64)
4871 size =
4872 (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
4873#endif
4874
4875 return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS);
4876}
4877
4878void SocketStream::get_remote_ip_and_port(std::string &ip,
4879 int &port) const {
4880 return detail::get_remote_ip_and_port(sock_, ip, port);
4881}
4882
4883void SocketStream::get_local_ip_and_port(std::string &ip,
4884 int &port) const {
4885 return detail::get_local_ip_and_port(sock_, ip, port);
4886}
4887
4888socket_t SocketStream::socket() const { return sock_; }
4889
4890time_t SocketStream::duration() const {
4891 return std::chrono::duration_cast<std::chrono::milliseconds>(
4892 std::chrono::steady_clock::now() - start_time_)
4893 .count();
4894}
4895
4896// Buffer stream implementation
4897bool BufferStream::is_readable() const { return true; }
4898
4899bool BufferStream::wait_readable() const { return true; }
4900
4901bool BufferStream::wait_writable() const { return true; }
4902
4903ssize_t BufferStream::read(char *ptr, size_t size) {
4904#if defined(_MSC_VER) && _MSC_VER < 1910
4905 auto len_read = buffer._Copy_s(ptr, size, size, position);
4906#else
4907 auto len_read = buffer.copy(ptr, size, position);
4908#endif
4909 position += static_cast<size_t>(len_read);
4910 return static_cast<ssize_t>(len_read);
4911}
4912
4913ssize_t BufferStream::write(const char *ptr, size_t size) {
4914 buffer.append(ptr, size);
4915 return static_cast<ssize_t>(size);
4916}
4917
4918void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
4919 int & /*port*/) const {}
4920
4921void BufferStream::get_local_ip_and_port(std::string & /*ip*/,
4922 int & /*port*/) const {}
4923
4924socket_t BufferStream::socket() const { return 0; }
4925
4926time_t BufferStream::duration() const { return 0; }
4927
4928const std::string &BufferStream::get_buffer() const { return buffer; }
4929
4930PathParamsMatcher::PathParamsMatcher(const std::string &pattern)
4931 : MatcherBase(pattern) {
4932 constexpr const char marker[] = "/:";
4933
4934 // One past the last ending position of a path param substring
4935 std::size_t last_param_end = 0;
4936
4937#ifndef CPPHTTPLIB_NO_EXCEPTIONS
4938 // Needed to ensure that parameter names are unique during matcher
4939 // construction
4940 // If exceptions are disabled, only last duplicate path
4941 // parameter will be set
4942 std::unordered_set<std::string> param_name_set;
4943#endif
4944
4945 while (true) {
4946 const auto marker_pos = pattern.find(
4947 marker, last_param_end == 0 ? last_param_end : last_param_end - 1);
4948 if (marker_pos == std::string::npos) { break; }
4949
4950 static_fragments_.push_back(
4951 pattern.substr(last_param_end, marker_pos - last_param_end + 1));
4952
4953 const auto param_name_start = marker_pos + str_len(marker);
4954
4955 auto sep_pos = pattern.find(separator, param_name_start);
4956 if (sep_pos == std::string::npos) { sep_pos = pattern.length(); }
4957
4958 auto param_name =
4959 pattern.substr(param_name_start, sep_pos - param_name_start);
4960
4961#ifndef CPPHTTPLIB_NO_EXCEPTIONS
4962 if (param_name_set.find(param_name) != param_name_set.cend()) {
4963 std::string msg = "Encountered path parameter '" + param_name +
4964 "' multiple times in route pattern '" + pattern + "'.";
4965 throw std::invalid_argument(msg);
4966 }
4967#endif
4968
4969 param_names_.push_back(std::move(param_name));
4970
4971 last_param_end = sep_pos + 1;
4972 }
4973
4974 if (last_param_end < pattern.length()) {
4975 static_fragments_.push_back(pattern.substr(last_param_end));
4976 }
4977}
4978
4979bool PathParamsMatcher::match(Request &request) const {
4980 request.matches = std::smatch();
4981 request.path_params.clear();
4982 request.path_params.reserve(param_names_.size());
4983
4984 // One past the position at which the path matched the pattern last time
4985 std::size_t starting_pos = 0;
4986 for (size_t i = 0; i < static_fragments_.size(); ++i) {
4987 const auto &fragment = static_fragments_[i];
4988
4989 if (starting_pos + fragment.length() > request.path.length()) {
4990 return false;
4991 }
4992
4993 // Avoid unnecessary allocation by using strncmp instead of substr +
4994 // comparison
4995 if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(),
4996 fragment.length()) != 0) {
4997 return false;
4998 }
4999
5000 starting_pos += fragment.length();
5001
5002 // Should only happen when we have a static fragment after a param
5003 // Example: '/users/:id/subscriptions'
5004 // The 'subscriptions' fragment here does not have a corresponding param
5005 if (i >= param_names_.size()) { continue; }
5006
5007 auto sep_pos = request.path.find(separator, starting_pos);
5008 if (sep_pos == std::string::npos) { sep_pos = request.path.length(); }
5009
5010 const auto ¶m_name = param_names_[i];
5011
5012 request.path_params.emplace(
5013 param_name, request.path.substr(starting_pos, sep_pos - starting_pos));
5014
5015 // Mark everything up to '/' as matched
5016 starting_pos = sep_pos + 1;
5017 }
5018 // Returns false if the path is longer than the pattern
5019 return starting_pos >= request.path.length();
5020}
5021
5022bool RegexMatcher::match(Request &request) const {
5023 request.path_params.clear();
5024 return std::regex_match(request.path, request.matches, regex_);
5025}
5026
5027// Enclose IPv6 address in brackets if needed
5028std::string prepare_host_string(const std::string &host) {
5029 // Enclose IPv6 address in brackets (but not if already enclosed)
5030 if (host.find(':') == std::string::npos ||
5031 (!host.empty() && host[0] == '[')) {
5032 // IPv4, hostname, or already bracketed IPv6
5033 return host;
5034 } else {
5035 // IPv6 address without brackets
5036 return "[" + host + "]";
5037 }
5038}
5039
5040std::string make_host_and_port_string(const std::string &host, int port,
5041 bool is_ssl) {
5042 auto result = prepare_host_string(host);
5043
5044 // Append port if not default
5045 if ((!is_ssl && port == 80) || (is_ssl && port == 443)) {
5046 ; // do nothing
5047 } else {
5048 result += ":" + std::to_string(port);
5049 }
5050
5051 return result;
5052}
5053
5054// Create "host:port" string always including port number (for CONNECT method)
5055std::string
5056make_host_and_port_string_always_port(const std::string &host, int port) {
5057 return prepare_host_string(host) + ":" + std::to_string(port);
5058}
5059
5060template <typename T>
5061bool check_and_write_headers(Stream &strm, Headers &headers,
5062 T header_writer, Error &error) {
5063 for (const auto &h : headers) {
5064 if (!detail::fields::is_field_name(h.first) ||
5065 !detail::fields::is_field_value(h.second)) {
5066 error = Error::InvalidHeaders;
5067 return false;
5068 }
5069 }
5070 if (header_writer(strm, headers) <= 0) {
5071 error = Error::Write;
5072 return false;
5073 }
5074 return true;
5075}
5076
5077} // namespace detail
5078
5079// HTTP server implementation
5080Server::Server()
5081 : new_task_queue(
5082 [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) {
5083#ifndef _WIN32
5084 signal(SIGPIPE, SIG_IGN);
5085#endif
5086}
5087
5088Server::~Server() = default;
5089
5090std::unique_ptr<detail::MatcherBase>
5091Server::make_matcher(const std::string &pattern) {
5092 if (pattern.find("/:") != std::string::npos) {
5093 return detail::make_unique<detail::PathParamsMatcher>(pattern);
5094 } else {
5095 return detail::make_unique<detail::RegexMatcher>(pattern);
5096 }
5097}
5098
5099Server &Server::Get(const std::string &pattern, Handler handler) {
5100 get_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
5101 return *this;
5102}
5103
5104Server &Server::Post(const std::string &pattern, Handler handler) {
5105 post_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
5106 return *this;
5107}
5108
5109Server &Server::Post(const std::string &pattern,
5110 HandlerWithContentReader handler) {
5111 post_handlers_for_content_reader_.emplace_back(make_matcher(pattern),
5112 std::move(handler));
5113 return *this;
5114}
5115
5116Server &Server::Put(const std::string &pattern, Handler handler) {
5117 put_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
5118 return *this;
5119}
5120
5121Server &Server::Put(const std::string &pattern,
5122 HandlerWithContentReader handler) {
5123 put_handlers_for_content_reader_.emplace_back(make_matcher(pattern),
5124 std::move(handler));
5125 return *this;
5126}
5127
5128Server &Server::Patch(const std::string &pattern, Handler handler) {
5129 patch_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
5130 return *this;
5131}
5132
5133Server &Server::Patch(const std::string &pattern,
5134 HandlerWithContentReader handler) {
5135 patch_handlers_for_content_reader_.emplace_back(make_matcher(pattern),
5136 std::move(handler));
5137 return *this;
5138}
5139
5140Server &Server::Delete(const std::string &pattern, Handler handler) {
5141 delete_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
5142 return *this;
5143}
5144
5145Server &Server::Delete(const std::string &pattern,
5146 HandlerWithContentReader handler) {
5147 delete_handlers_for_content_reader_.emplace_back(make_matcher(pattern),
5148 std::move(handler));
5149 return *this;
5150}
5151
5152Server &Server::Options(const std::string &pattern, Handler handler) {
5153 options_handlers_.emplace_back(make_matcher(pattern), std::move(handler));
5154 return *this;
5155}
5156
5157bool Server::set_base_dir(const std::string &dir,
5158 const std::string &mount_point) {
5159 return set_mount_point(mount_point, dir);
5160}
5161
5162bool Server::set_mount_point(const std::string &mount_point,
5163 const std::string &dir, Headers headers) {
5164 detail::FileStat stat(dir);
5165 if (stat.is_dir()) {
5166 std::string mnt = !mount_point.empty() ? mount_point : "/";
5167 if (!mnt.empty() && mnt[0] == '/') {
5168 base_dirs_.push_back({std::move(mnt), dir, std::move(headers)});
5169 return true;
5170 }
5171 }
5172 return false;
5173}
5174
5175bool Server::remove_mount_point(const std::string &mount_point) {
5176 for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
5177 if (it->mount_point == mount_point) {
5178 base_dirs_.erase(it);
5179 return true;
5180 }
5181 }
5182 return false;
5183}
5184
5185Server &
5186Server::set_file_extension_and_mimetype_mapping(const std::string &ext,
5187 const std::string &mime) {
5188 file_extension_and_mimetype_map_[ext] = mime;
5189 return *this;
5190}
5191
5192Server &Server::set_default_file_mimetype(const std::string &mime) {
5193 default_file_mimetype_ = mime;
5194 return *this;
5195}
5196
5197Server &Server::set_file_request_handler(Handler handler) {
5198 file_request_handler_ = std::move(handler);
5199 return *this;
5200}
5201
5202Server &Server::set_error_handler_core(HandlerWithResponse handler,
5203 std::true_type) {
5204 error_handler_ = std::move(handler);
5205 return *this;
5206}
5207
5208Server &Server::set_error_handler_core(Handler handler,
5209 std::false_type) {
5210 error_handler_ = [handler](const Request &req, Response &res) {
5211 handler(req, res);
5212 return HandlerResponse::Handled;
5213 };
5214 return *this;
5215}
5216
5217Server &Server::set_exception_handler(ExceptionHandler handler) {
5218 exception_handler_ = std::move(handler);
5219 return *this;
5220}
5221
5222Server &Server::set_pre_routing_handler(HandlerWithResponse handler) {
5223 pre_routing_handler_ = std::move(handler);
5224 return *this;
5225}
5226
5227Server &Server::set_post_routing_handler(Handler handler) {
5228 post_routing_handler_ = std::move(handler);
5229 return *this;
5230}
5231
5232Server &Server::set_pre_request_handler(HandlerWithResponse handler) {
5233 pre_request_handler_ = std::move(handler);
5234 return *this;
5235}
5236
5237Server &Server::set_logger(Logger logger) {
5238 logger_ = std::move(logger);
5239 return *this;
5240}
5241
5242Server &Server::set_error_logger(ErrorLogger error_logger) {
5243 error_logger_ = std::move(error_logger);
5244 return *this;
5245}
5246
5247Server &Server::set_pre_compression_logger(Logger logger) {
5248 pre_compression_logger_ = std::move(logger);
5249 return *this;
5250}
5251
5252Server &
5253Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
5254 expect_100_continue_handler_ = std::move(handler);
5255 return *this;
5256}
5257
5258Server &Server::set_address_family(int family) {
5259 address_family_ = family;
5260 return *this;
5261}
5262
5263Server &Server::set_tcp_nodelay(bool on) {
5264 tcp_nodelay_ = on;
5265 return *this;
5266}
5267
5268Server &Server::set_ipv6_v6only(bool on) {
5269 ipv6_v6only_ = on;
5270 return *this;
5271}
5272
5273Server &Server::set_socket_options(SocketOptions socket_options) {
5274 socket_options_ = std::move(socket_options);
5275 return *this;
5276}
5277
5278Server &Server::set_default_headers(Headers headers) {
5279 default_headers_ = std::move(headers);
5280 return *this;
5281}
5282
5283Server &Server::set_header_writer(
5284 std::function<ssize_t(Stream &, Headers &)> const &writer) {
5285 header_writer_ = writer;
5286 return *this;
5287}
5288
5289Server &
5290Server::set_trusted_proxies(const std::vector<std::string> &proxies) {
5291 trusted_proxies_ = proxies;
5292 return *this;
5293}
5294
5295Server &Server::set_keep_alive_max_count(size_t count) {
5296 keep_alive_max_count_ = count;
5297 return *this;
5298}
5299
5300Server &Server::set_keep_alive_timeout(time_t sec) {
5301 keep_alive_timeout_sec_ = sec;
5302 return *this;
5303}
5304
5305Server &Server::set_read_timeout(time_t sec, time_t usec) {
5306 read_timeout_sec_ = sec;
5307 read_timeout_usec_ = usec;
5308 return *this;
5309}
5310
5311Server &Server::set_write_timeout(time_t sec, time_t usec) {
5312 write_timeout_sec_ = sec;
5313 write_timeout_usec_ = usec;
5314 return *this;
5315}
5316
5317Server &Server::set_idle_interval(time_t sec, time_t usec) {
5318 idle_interval_sec_ = sec;
5319 idle_interval_usec_ = usec;
5320 return *this;
5321}
5322
5323Server &Server::set_payload_max_length(size_t length) {
5324 payload_max_length_ = length;
5325 return *this;
5326}
5327
5328bool Server::bind_to_port(const std::string &host, int port,
5329 int socket_flags) {
5330 auto ret = bind_internal(host, port, socket_flags);
5331 if (ret == -1) { is_decommissioned = true; }
5332 return ret >= 0;
5333}
5334int Server::bind_to_any_port(const std::string &host, int socket_flags) {
5335 auto ret = bind_internal(host, 0, socket_flags);
5336 if (ret == -1) { is_decommissioned = true; }
5337 return ret;
5338}
5339
5340bool Server::listen_after_bind() { return listen_internal(); }
5341
5342bool Server::listen(const std::string &host, int port,
5343 int socket_flags) {
5344 return bind_to_port(host, port, socket_flags) && listen_internal();
5345}
5346
5347bool Server::is_running() const { return is_running_; }
5348
5349void Server::wait_until_ready() const {
5350 while (!is_running_ && !is_decommissioned) {
5351 std::this_thread::sleep_for(std::chrono::milliseconds{1});
5352 }
5353}
5354
5355void Server::stop() {
5356 if (is_running_) {
5357 assert(svr_sock_ != INVALID_SOCKET);
5358 std::atomic<socket_t> sock(svr_sock_.exchange(INVALID_SOCKET));
5359 detail::shutdown_socket(sock);
5360 detail::close_socket(sock);
5361 }
5362 is_decommissioned = false;
5363}
5364
5365void Server::decommission() { is_decommissioned = true; }
5366
5367bool Server::parse_request_line(const char *s, Request &req) const {
5368 auto len = strlen(s);
5369 if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; }
5370 len -= 2;
5371
5372 {
5373 size_t count = 0;
5374
5375 detail::split(s, s + len, ' ', [&](const char *b, const char *e) {
5376 switch (count) {
5377 case 0: req.method = std::string(b, e); break;
5378 case 1: req.target = std::string(b, e); break;
5379 case 2: req.version = std::string(b, e); break;
5380 default: break;
5381 }
5382 count++;
5383 });
5384
5385 if (count != 3) { return false; }
5386 }
5387
5388 thread_local const std::set<std::string> methods{
5389 "GET", "HEAD", "POST", "PUT", "DELETE",
5390 "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"};
5391
5392 if (methods.find(req.method) == methods.end()) {
5393 output_error_log(Error::InvalidHTTPMethod, &req);
5394 return false;
5395 }
5396
5397 if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") {
5398 output_error_log(Error::InvalidHTTPVersion, &req);
5399 return false;
5400 }
5401
5402 {
5403 // Skip URL fragment
5404 for (size_t i = 0; i < req.target.size(); i++) {
5405 if (req.target[i] == '#') {
5406 req.target.erase(i);
5407 break;
5408 }
5409 }
5410
5411 detail::divide(req.target, '?',
5412 [&](const char *lhs_data, std::size_t lhs_size,
5413 const char *rhs_data, std::size_t rhs_size) {
5414 req.path =
5415 decode_path_component(std::string(lhs_data, lhs_size));
5416 detail::parse_query_text(rhs_data, rhs_size, req.params);
5417 });
5418 }
5419
5420 return true;
5421}
5422
5423bool Server::write_response(Stream &strm, bool close_connection,
5424 Request &req, Response &res) {
5425 // NOTE: `req.ranges` should be empty, otherwise it will be applied
5426 // incorrectly to the error content.
5427 req.ranges.clear();
5428 return write_response_core(strm, close_connection, req, res, false);
5429}
5430
5431bool Server::write_response_with_content(Stream &strm,
5432 bool close_connection,
5433 const Request &req,
5434 Response &res) {
5435 return write_response_core(strm, close_connection, req, res, true);
5436}
5437
5438bool Server::write_response_core(Stream &strm, bool close_connection,
5439 const Request &req, Response &res,
5440 bool need_apply_ranges) {
5441 assert(res.status != -1);
5442
5443 if (400 <= res.status && error_handler_ &&
5444 error_handler_(req, res) == HandlerResponse::Handled) {
5445 need_apply_ranges = true;
5446 }
5447
5448 std::string content_type;
5449 std::string boundary;
5450 if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); }
5451
5452 // Prepare additional headers
5453 if (close_connection || req.get_header_value("Connection") == "close" ||
5454 400 <= res.status) { // Don't leave connections open after errors
5455 res.set_header("Connection", "close");
5456 } else {
5457 std::string s = "timeout=";
5458 s += std::to_string(keep_alive_timeout_sec_);
5459 s += ", max=";
5460 s += std::to_string(keep_alive_max_count_);
5461 res.set_header("Keep-Alive", s);
5462 }
5463
5464 if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) &&
5465 !res.has_header("Content-Type")) {
5466 res.set_header("Content-Type", "text/plain");
5467 }
5468
5469 if (res.body.empty() && !res.content_length_ && !res.content_provider_ &&
5470 !res.has_header("Content-Length")) {
5471 res.set_header("Content-Length", "0");
5472 }
5473
5474 if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) {
5475 res.set_header("Accept-Ranges", "bytes");
5476 }
5477
5478 if (post_routing_handler_) { post_routing_handler_(req, res); }
5479
5480 // Response line and headers
5481 {
5482 detail::BufferStream bstrm;
5483 if (!detail::write_response_line(bstrm, res.status)) { return false; }
5484 if (header_writer_(bstrm, res.headers) <= 0) { return false; }
5485
5486 // Flush buffer
5487 auto &data = bstrm.get_buffer();
5488 detail::write_data(strm, data.data(), data.size());
5489 }
5490
5491 // Body
5492 auto ret = true;
5493 if (req.method != "HEAD") {
5494 if (!res.body.empty()) {
5495 if (!detail::write_data(strm, res.body.data(), res.body.size())) {
5496 ret = false;
5497 }
5498 } else if (res.content_provider_) {
5499 if (write_content_with_provider(strm, req, res, boundary, content_type)) {
5500 res.content_provider_success_ = true;
5501 } else {
5502 ret = false;
5503 }
5504 }
5505 }
5506
5507 // Log
5508 output_log(req, res);
5509
5510 return ret;
5511}
5512
5513bool
5514Server::write_content_with_provider(Stream &strm, const Request &req,
5515 Response &res, const std::string &boundary,
5516 const std::string &content_type) {
5517 auto is_shutting_down = [this]() {
5518 return this->svr_sock_ == INVALID_SOCKET;
5519 };
5520
5521 if (res.content_length_ > 0) {
5522 if (req.ranges.empty()) {
5523 return detail::write_content(strm, res.content_provider_, 0,
5524 res.content_length_, is_shutting_down);
5525 } else if (req.ranges.size() == 1) {
5526 auto offset_and_length = detail::get_range_offset_and_length(
5527 req.ranges[0], res.content_length_);
5528
5529 return detail::write_content(strm, res.content_provider_,
5530 offset_and_length.first,
5531 offset_and_length.second, is_shutting_down);
5532 } else {
5533 return detail::write_multipart_ranges_data(
5534 strm, req, res, boundary, content_type, res.content_length_,
5535 is_shutting_down);
5536 }
5537 } else {
5538 if (res.is_chunked_content_provider_) {
5539 auto type = detail::encoding_type(req, res);
5540
5541 std::unique_ptr<detail::compressor> compressor;
5542 if (type == detail::EncodingType::Gzip) {
5543#ifdef CPPHTTPLIB_ZLIB_SUPPORT
5544 compressor = detail::make_unique<detail::gzip_compressor>();
5545#endif
5546 } else if (type == detail::EncodingType::Brotli) {
5547#ifdef CPPHTTPLIB_BROTLI_SUPPORT
5548 compressor = detail::make_unique<detail::brotli_compressor>();
5549#endif
5550 } else if (type == detail::EncodingType::Zstd) {
5551#ifdef CPPHTTPLIB_ZSTD_SUPPORT
5552 compressor = detail::make_unique<detail::zstd_compressor>();
5553#endif
5554 } else {
5555 compressor = detail::make_unique<detail::nocompressor>();
5556 }
5557 assert(compressor != nullptr);
5558
5559 return detail::write_content_chunked(strm, res.content_provider_,
5560 is_shutting_down, *compressor);
5561 } else {
5562 return detail::write_content_without_length(strm, res.content_provider_,
5563 is_shutting_down);
5564 }
5565 }
5566}
5567
5568bool Server::read_content(Stream &strm, Request &req, Response &res) {
5569 FormFields::iterator cur_field;
5570 FormFiles::iterator cur_file;
5571 auto is_text_field = false;
5572 size_t count = 0;
5573 if (read_content_core(
5574 strm, req, res,
5575 // Regular
5576 [&](const char *buf, size_t n) {
5577 // Prevent arithmetic overflow when checking sizes.
5578 // Avoid computing (req.body.size() + n) directly because
5579 // adding two unsigned `size_t` values can wrap around and
5580 // produce a small result instead of indicating overflow.
5581 // Instead, check using subtraction: ensure `n` does not
5582 // exceed the remaining capacity `max_size() - size()`.
5583 if (req.body.size() >= req.body.max_size() ||
5584 n > req.body.max_size() - req.body.size()) {
5585 return false;
5586 }
5587
5588 // Limit decompressed body size to payload_max_length_ to protect
5589 // against "zip bomb" attacks where a small compressed payload
5590 // decompresses to a massive size.
5591 if (payload_max_length_ > 0 &&
5592 (req.body.size() >= payload_max_length_ ||
5593 n > payload_max_length_ - req.body.size())) {
5594 return false;
5595 }
5596
5597 req.body.append(buf, n);
5598 return true;
5599 },
5600 // Multipart FormData
5601 [&](const FormData &file) {
5602 if (count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) {
5603 output_error_log(Error::TooManyFormDataFiles, &req);
5604 return false;
5605 }
5606
5607 if (file.filename.empty()) {
5608 cur_field = req.form.fields.emplace(
5609 file.name, FormField{file.name, file.content, file.headers});
5610 is_text_field = true;
5611 } else {
5612 cur_file = req.form.files.emplace(file.name, file);
5613 is_text_field = false;
5614 }
5615 return true;
5616 },
5617 [&](const char *buf, size_t n) {
5618 if (is_text_field) {
5619 auto &content = cur_field->second.content;
5620 if (content.size() + n > content.max_size()) { return false; }
5621 content.append(buf, n);
5622 } else {
5623 auto &content = cur_file->second.content;
5624 if (content.size() + n > content.max_size()) { return false; }
5625 content.append(buf, n);
5626 }
5627 return true;
5628 })) {
5629 const auto &content_type = req.get_header_value("Content-Type");
5630 if (!content_type.find("application/x-www-form-urlencoded")) {
5631 if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
5632 res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414?
5633 output_error_log(Error::ExceedMaxPayloadSize, &req);
5634 return false;
5635 }
5636 detail::parse_query_text(req.body, req.params);
5637 }
5638 return true;
5639 }
5640 return false;
5641}
5642
5643bool Server::read_content_with_content_receiver(
5644 Stream &strm, Request &req, Response &res, ContentReceiver receiver,
5645 FormDataHeader multipart_header, ContentReceiver multipart_receiver) {
5646 return read_content_core(strm, req, res, std::move(receiver),
5647 std::move(multipart_header),
5648 std::move(multipart_receiver));
5649}
5650
5651bool Server::read_content_core(
5652 Stream &strm, Request &req, Response &res, ContentReceiver receiver,
5653 FormDataHeader multipart_header, ContentReceiver multipart_receiver) const {
5654 detail::FormDataParser multipart_form_data_parser;
5655 ContentReceiverWithProgress out;
5656
5657 if (req.is_multipart_form_data()) {
5658 const auto &content_type = req.get_header_value("Content-Type");
5659 std::string boundary;
5660 if (!detail::parse_multipart_boundary(content_type, boundary)) {
5661 res.status = StatusCode::BadRequest_400;
5662 output_error_log(Error::MultipartParsing, &req);
5663 return false;
5664 }
5665
5666 multipart_form_data_parser.set_boundary(std::move(boundary));
5667 out = [&](const char *buf, size_t n, size_t /*off*/, size_t /*len*/) {
5668 return multipart_form_data_parser.parse(buf, n, multipart_header,
5669 multipart_receiver);
5670 };
5671 } else {
5672 out = [receiver](const char *buf, size_t n, size_t /*off*/,
5673 size_t /*len*/) { return receiver(buf, n); };
5674 }
5675
5676 // RFC 7230 Section 3.3.3: If this is a request message and none of the above
5677 // are true (no Transfer-Encoding and no Content-Length), then the message
5678 // body length is zero (no message body is present).
5679 //
5680 // For non-SSL builds, peek into the socket to detect clients that send a
5681 // body without a Content-Length header (raw HTTP over TCP). If there is
5682 // pending data that exceeds the configured payload limit, treat this as an
5683 // oversized request and fail early (causing connection close). For SSL
5684 // builds we cannot reliably peek the decrypted application bytes, so keep
5685 // the original behaviour.
5686#if !defined(CPPHTTPLIB_OPENSSL_SUPPORT)
5687 if (!req.has_header("Content-Length") &&
5688 !detail::is_chunked_transfer_encoding(req.headers)) {
5689 // Only peek if payload_max_length is set to a finite value
5690 if (payload_max_length_ > 0 &&
5691 payload_max_length_ < (std::numeric_limits<size_t>::max)()) {
5692 socket_t s = strm.socket();
5693 if (s != INVALID_SOCKET) {
5694 // Peek to check if there is any pending data
5695 char peekbuf[1];
5696 ssize_t n = ::recv(s, peekbuf, 1, MSG_PEEK);
5697 if (n > 0) {
5698 // There is data, so read it with payload limit enforcement
5699 auto result = detail::read_content_without_length(
5700 strm, payload_max_length_, out);
5701 if (result == detail::ReadContentResult::PayloadTooLarge) {
5702 res.status = StatusCode::PayloadTooLarge_413;
5703 return false;
5704 } else if (result != detail::ReadContentResult::Success) {
5705 return false;
5706 }
5707 return true;
5708 }
5709 }
5710 }
5711 return true;
5712 }
5713#else
5714 if (!req.has_header("Content-Length") &&
5715 !detail::is_chunked_transfer_encoding(req.headers)) {
5716 return true;
5717 }
5718#endif
5719
5720 if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr,
5721 out, true)) {
5722 return false;
5723 }
5724
5725 if (req.is_multipart_form_data()) {
5726 if (!multipart_form_data_parser.is_valid()) {
5727 res.status = StatusCode::BadRequest_400;
5728 output_error_log(Error::MultipartParsing, &req);
5729 return false;
5730 }
5731 }
5732
5733 return true;
5734}
5735
5736bool Server::handle_file_request(Request &req, Response &res) {
5737 for (const auto &entry : base_dirs_) {
5738 // Prefix match
5739 if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) {
5740 std::string sub_path = "/" + req.path.substr(entry.mount_point.size());
5741 if (detail::is_valid_path(sub_path)) {
5742 auto path = entry.base_dir + sub_path;
5743 if (path.back() == '/') { path += "index.html"; }
5744
5745 detail::FileStat stat(path);
5746
5747 if (stat.is_dir()) {
5748 res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301);
5749 return true;
5750 }
5751
5752 if (stat.is_file()) {
5753 for (const auto &kv : entry.headers) {
5754 res.set_header(kv.first, kv.second);
5755 }
5756
5757 auto etag = detail::compute_etag(stat);
5758 if (!etag.empty()) { res.set_header("ETag", etag); }
5759
5760 auto mtime = stat.mtime();
5761
5762 auto last_modified = detail::file_mtime_to_http_date(mtime);
5763 if (!last_modified.empty()) {
5764 res.set_header("Last-Modified", last_modified);
5765 }
5766
5767 if (check_if_not_modified(req, res, etag, mtime)) { return true; }
5768
5769 check_if_range(req, etag, mtime);
5770
5771 auto mm = std::make_shared<detail::mmap>(path.c_str());
5772 if (!mm->is_open()) {
5773 output_error_log(Error::OpenFile, &req);
5774 return false;
5775 }
5776
5777 res.set_content_provider(
5778 mm->size(),
5779 detail::find_content_type(path, file_extension_and_mimetype_map_,
5780 default_file_mimetype_),
5781 [mm](size_t offset, size_t length, DataSink &sink) -> bool {
5782 sink.write(mm->data() + offset, length);
5783 return true;
5784 });
5785
5786 if (req.method != "HEAD" && file_request_handler_) {
5787 file_request_handler_(req, res);
5788 }
5789
5790 return true;
5791 } else {
5792 output_error_log(Error::OpenFile, &req);
5793 }
5794 }
5795 }
5796 }
5797 return false;
5798}
5799
5800bool Server::check_if_not_modified(const Request &req, Response &res,
5801 const std::string &etag,
5802 time_t mtime) const {
5803 // Handle conditional GET:
5804 // 1. If-None-Match takes precedence (RFC 9110 Section 13.1.2)
5805 // 2. If-Modified-Since is checked only when If-None-Match is absent
5806 if (req.has_header("If-None-Match")) {
5807 if (!etag.empty()) {
5808 auto val = req.get_header_value("If-None-Match");
5809
5810 // NOTE: We use exact string matching here. This works correctly
5811 // because our server always generates weak ETags (W/"..."), and
5812 // clients typically send back the same ETag they received.
5813 // RFC 9110 Section 8.8.3.2 allows weak comparison for
5814 // If-None-Match, where W/"x" and "x" would match, but this
5815 // simplified implementation requires exact matches.
5816 auto ret = detail::split_find(val.data(), val.data() + val.size(), ',',
5817 [&](const char *b, const char *e) {
5818 return std::equal(b, e, "*") ||
5819 std::equal(b, e, etag.begin());
5820 });
5821
5822 if (ret) {
5823 res.status = StatusCode::NotModified_304;
5824 return true;
5825 }
5826 }
5827 } else if (req.has_header("If-Modified-Since")) {
5828 auto val = req.get_header_value("If-Modified-Since");
5829 auto t = detail::parse_http_date(val);
5830
5831 if (t != static_cast<time_t>(-1) && mtime <= t) {
5832 res.status = StatusCode::NotModified_304;
5833 return true;
5834 }
5835 }
5836 return false;
5837}
5838
5839bool Server::check_if_range(Request &req, const std::string &etag,
5840 time_t mtime) const {
5841 // Handle If-Range for partial content requests (RFC 9110
5842 // Section 13.1.5). If-Range is only evaluated when Range header is
5843 // present. If the validator matches, serve partial content; otherwise
5844 // serve full content.
5845 if (!req.ranges.empty() && req.has_header("If-Range")) {
5846 auto val = req.get_header_value("If-Range");
5847
5848 auto is_valid_range = [&]() {
5849 if (detail::is_strong_etag(val)) {
5850 // RFC 9110 Section 13.1.5: If-Range requires strong ETag
5851 // comparison.
5852 return (!etag.empty() && val == etag);
5853 } else if (detail::is_weak_etag(val)) {
5854 // Weak ETags are not valid for If-Range (RFC 9110 Section 13.1.5)
5855 return false;
5856 } else {
5857 // HTTP-date comparison
5858 auto t = detail::parse_http_date(val);
5859 return (t != static_cast<time_t>(-1) && mtime <= t);
5860 }
5861 };
5862
5863 if (!is_valid_range()) {
5864 // Validator doesn't match: ignore Range and serve full content
5865 req.ranges.clear();
5866 return false;
5867 }
5868 }
5869
5870 return true;
5871}
5872
5873socket_t
5874Server::create_server_socket(const std::string &host, int port,
5875 int socket_flags,
5876 SocketOptions socket_options) const {
5877 return detail::create_socket(
5878 host, std::string(), port, address_family_, socket_flags, tcp_nodelay_,
5879 ipv6_v6only_, std::move(socket_options),
5880 [&](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool {
5881 if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
5882 output_error_log(Error::BindIPAddress, nullptr);
5883 return false;
5884 }
5885 if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) {
5886 output_error_log(Error::Listen, nullptr);
5887 return false;
5888 }
5889 return true;
5890 });
5891}
5892
5893int Server::bind_internal(const std::string &host, int port,
5894 int socket_flags) {
5895 if (is_decommissioned) { return -1; }
5896
5897 if (!is_valid()) { return -1; }
5898
5899 svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
5900 if (svr_sock_ == INVALID_SOCKET) { return -1; }
5901
5902 if (port == 0) {
5903 struct sockaddr_storage addr;
5904 socklen_t addr_len = sizeof(addr);
5905 if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr),
5906 &addr_len) == -1) {
5907 output_error_log(Error::GetSockName, nullptr);
5908 return -1;
5909 }
5910 if (addr.ss_family == AF_INET) {
5911 return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
5912 } else if (addr.ss_family == AF_INET6) {
5913 return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
5914 } else {
5915 output_error_log(Error::UnsupportedAddressFamily, nullptr);
5916 return -1;
5917 }
5918 } else {
5919 return port;
5920 }
5921}
5922
5923bool Server::listen_internal() {
5924 if (is_decommissioned) { return false; }
5925
5926 auto ret = true;
5927 is_running_ = true;
5928 auto se = detail::scope_exit([&]() { is_running_ = false; });
5929
5930 {
5931 std::unique_ptr<TaskQueue> task_queue(new_task_queue());
5932
5933 while (svr_sock_ != INVALID_SOCKET) {
5934#ifndef _WIN32
5935 if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) {
5936#endif
5937 auto val = detail::select_read(svr_sock_, idle_interval_sec_,
5938 idle_interval_usec_);
5939 if (val == 0) { // Timeout
5940 task_queue->on_idle();
5941 continue;
5942 }
5943#ifndef _WIN32
5944 }
5945#endif
5946
5947#if defined _WIN32
5948 // sockets connected via WASAccept inherit flags NO_HANDLE_INHERIT,
5949 // OVERLAPPED
5950 socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0);
5951#elif defined SOCK_CLOEXEC
5952 socket_t sock = accept4(svr_sock_, nullptr, nullptr, SOCK_CLOEXEC);
5953#else
5954 socket_t sock = accept(svr_sock_, nullptr, nullptr);
5955#endif
5956
5957 if (sock == INVALID_SOCKET) {
5958 if (errno == EMFILE) {
5959 // The per-process limit of open file descriptors has been reached.
5960 // Try to accept new connections after a short sleep.
5961 std::this_thread::sleep_for(std::chrono::microseconds{1});
5962 continue;
5963 } else if (errno == EINTR || errno == EAGAIN) {
5964 continue;
5965 }
5966 if (svr_sock_ != INVALID_SOCKET) {
5967 detail::close_socket(svr_sock_);
5968 ret = false;
5969 output_error_log(Error::Connection, nullptr);
5970 } else {
5971 ; // The server socket was closed by user.
5972 }
5973 break;
5974 }
5975
5976 detail::set_socket_opt_time(sock, SOL_SOCKET, SO_RCVTIMEO,
5977 read_timeout_sec_, read_timeout_usec_);
5978 detail::set_socket_opt_time(sock, SOL_SOCKET, SO_SNDTIMEO,
5979 write_timeout_sec_, write_timeout_usec_);
5980
5981 if (!task_queue->enqueue(
5982 [this, sock]() { process_and_close_socket(sock); })) {
5983 output_error_log(Error::ResourceExhaustion, nullptr);
5984 detail::shutdown_socket(sock);
5985 detail::close_socket(sock);
5986 }
5987 }
5988
5989 task_queue->shutdown();
5990 }
5991
5992 is_decommissioned = !ret;
5993 return ret;
5994}
5995
5996bool Server::routing(Request &req, Response &res, Stream &strm) {
5997 if (pre_routing_handler_ &&
5998 pre_routing_handler_(req, res) == HandlerResponse::Handled) {
5999 return true;
6000 }
6001
6002 // File handler
6003 if ((req.method == "GET" || req.method == "HEAD") &&
6004 handle_file_request(req, res)) {
6005 return true;
6006 }
6007
6008 if (detail::expect_content(req)) {
6009 // Content reader handler
6010 {
6011 ContentReader reader(
6012 [&](ContentReceiver receiver) {
6013 auto result = read_content_with_content_receiver(
6014 strm, req, res, std::move(receiver), nullptr, nullptr);
6015 if (!result) { output_error_log(Error::Read, &req); }
6016 return result;
6017 },
6018 [&](FormDataHeader header, ContentReceiver receiver) {
6019 auto result = read_content_with_content_receiver(
6020 strm, req, res, nullptr, std::move(header),
6021 std::move(receiver));
6022 if (!result) { output_error_log(Error::Read, &req); }
6023 return result;
6024 });
6025
6026 if (req.method == "POST") {
6027 if (dispatch_request_for_content_reader(
6028 req, res, std::move(reader),
6029 post_handlers_for_content_reader_)) {
6030 return true;
6031 }
6032 } else if (req.method == "PUT") {
6033 if (dispatch_request_for_content_reader(
6034 req, res, std::move(reader),
6035 put_handlers_for_content_reader_)) {
6036 return true;
6037 }
6038 } else if (req.method == "PATCH") {
6039 if (dispatch_request_for_content_reader(
6040 req, res, std::move(reader),
6041 patch_handlers_for_content_reader_)) {
6042 return true;
6043 }
6044 } else if (req.method == "DELETE") {
6045 if (dispatch_request_for_content_reader(
6046 req, res, std::move(reader),
6047 delete_handlers_for_content_reader_)) {
6048 return true;
6049 }
6050 }
6051 }
6052
6053 // Read content into `req.body`
6054 if (!read_content(strm, req, res)) {
6055 output_error_log(Error::Read, &req);
6056 return false;
6057 }
6058 }
6059
6060 // Regular handler
6061 if (req.method == "GET" || req.method == "HEAD") {
6062 return dispatch_request(req, res, get_handlers_);
6063 } else if (req.method == "POST") {
6064 return dispatch_request(req, res, post_handlers_);
6065 } else if (req.method == "PUT") {
6066 return dispatch_request(req, res, put_handlers_);
6067 } else if (req.method == "DELETE") {
6068 return dispatch_request(req, res, delete_handlers_);
6069 } else if (req.method == "OPTIONS") {
6070 return dispatch_request(req, res, options_handlers_);
6071 } else if (req.method == "PATCH") {
6072 return dispatch_request(req, res, patch_handlers_);
6073 }
6074
6075 res.status = StatusCode::BadRequest_400;
6076 return false;
6077}
6078
6079bool Server::dispatch_request(Request &req, Response &res,
6080 const Handlers &handlers) const {
6081 for (const auto &x : handlers) {
6082 const auto &matcher = x.first;
6083 const auto &handler = x.second;
6084
6085 if (matcher->match(req)) {
6086 req.matched_route = matcher->pattern();
6087 if (!pre_request_handler_ ||
6088 pre_request_handler_(req, res) != HandlerResponse::Handled) {
6089 handler(req, res);
6090 }
6091 return true;
6092 }
6093 }
6094 return false;
6095}
6096
6097void Server::apply_ranges(const Request &req, Response &res,
6098 std::string &content_type,
6099 std::string &boundary) const {
6100 if (req.ranges.size() > 1 && res.status == StatusCode::PartialContent_206) {
6101 auto it = res.headers.find("Content-Type");
6102 if (it != res.headers.end()) {
6103 content_type = it->second;
6104 res.headers.erase(it);
6105 }
6106
6107 boundary = detail::make_multipart_data_boundary();
6108
6109 res.set_header("Content-Type",
6110 "multipart/byteranges; boundary=" + boundary);
6111 }
6112
6113 auto type = detail::encoding_type(req, res);
6114
6115 if (res.body.empty()) {
6116 if (res.content_length_ > 0) {
6117 size_t length = 0;
6118 if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
6119 length = res.content_length_;
6120 } else if (req.ranges.size() == 1) {
6121 auto offset_and_length = detail::get_range_offset_and_length(
6122 req.ranges[0], res.content_length_);
6123
6124 length = offset_and_length.second;
6125
6126 auto content_range = detail::make_content_range_header_field(
6127 offset_and_length, res.content_length_);
6128 res.set_header("Content-Range", content_range);
6129 } else {
6130 length = detail::get_multipart_ranges_data_length(
6131 req, boundary, content_type, res.content_length_);
6132 }
6133 res.set_header("Content-Length", std::to_string(length));
6134 } else {
6135 if (res.content_provider_) {
6136 if (res.is_chunked_content_provider_) {
6137 res.set_header("Transfer-Encoding", "chunked");
6138 if (type == detail::EncodingType::Gzip) {
6139 res.set_header("Content-Encoding", "gzip");
6140 res.set_header("Vary", "Accept-Encoding");
6141 } else if (type == detail::EncodingType::Brotli) {
6142 res.set_header("Content-Encoding", "br");
6143 res.set_header("Vary", "Accept-Encoding");
6144 } else if (type == detail::EncodingType::Zstd) {
6145 res.set_header("Content-Encoding", "zstd");
6146 res.set_header("Vary", "Accept-Encoding");
6147 }
6148 }
6149 }
6150 }
6151 } else {
6152 if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
6153 ;
6154 } else if (req.ranges.size() == 1) {
6155 auto offset_and_length =
6156 detail::get_range_offset_and_length(req.ranges[0], res.body.size());
6157 auto offset = offset_and_length.first;
6158 auto length = offset_and_length.second;
6159
6160 auto content_range = detail::make_content_range_header_field(
6161 offset_and_length, res.body.size());
6162 res.set_header("Content-Range", content_range);
6163
6164 assert(offset + length <= res.body.size());
6165 res.body = res.body.substr(offset, length);
6166 } else {
6167 std::string data;
6168 detail::make_multipart_ranges_data(req, res, boundary, content_type,
6169 res.body.size(), data);
6170 res.body.swap(data);
6171 }
6172
6173 if (type != detail::EncodingType::None) {
6174 output_pre_compression_log(req, res);
6175
6176 std::unique_ptr<detail::compressor> compressor;
6177 std::string content_encoding;
6178
6179 if (type == detail::EncodingType::Gzip) {
6180#ifdef CPPHTTPLIB_ZLIB_SUPPORT
6181 compressor = detail::make_unique<detail::gzip_compressor>();
6182 content_encoding = "gzip";
6183#endif
6184 } else if (type == detail::EncodingType::Brotli) {
6185#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6186 compressor = detail::make_unique<detail::brotli_compressor>();
6187 content_encoding = "br";
6188#endif
6189 } else if (type == detail::EncodingType::Zstd) {
6190#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6191 compressor = detail::make_unique<detail::zstd_compressor>();
6192 content_encoding = "zstd";
6193#endif
6194 }
6195
6196 if (compressor) {
6197 std::string compressed;
6198 if (compressor->compress(res.body.data(), res.body.size(), true,
6199 [&](const char *data, size_t data_len) {
6200 compressed.append(data, data_len);
6201 return true;
6202 })) {
6203 res.body.swap(compressed);
6204 res.set_header("Content-Encoding", content_encoding);
6205 res.set_header("Vary", "Accept-Encoding");
6206 }
6207 }
6208 }
6209
6210 auto length = std::to_string(res.body.size());
6211 res.set_header("Content-Length", length);
6212 }
6213}
6214
6215bool Server::dispatch_request_for_content_reader(
6216 Request &req, Response &res, ContentReader content_reader,
6217 const HandlersForContentReader &handlers) const {
6218 for (const auto &x : handlers) {
6219 const auto &matcher = x.first;
6220 const auto &handler = x.second;
6221
6222 if (matcher->match(req)) {
6223 req.matched_route = matcher->pattern();
6224 if (!pre_request_handler_ ||
6225 pre_request_handler_(req, res) != HandlerResponse::Handled) {
6226 handler(req, res, content_reader);
6227 }
6228 return true;
6229 }
6230 }
6231 return false;
6232}
6233
6234std::string
6235get_client_ip(const std::string &x_forwarded_for,
6236 const std::vector<std::string> &trusted_proxies) {
6237 // X-Forwarded-For is a comma-separated list per RFC 7239
6238 std::vector<std::string> ip_list;
6239 detail::split(x_forwarded_for.data(),
6240 x_forwarded_for.data() + x_forwarded_for.size(), ',',
6241 [&](const char *b, const char *e) {
6242 auto r = detail::trim(b, e, 0, static_cast<size_t>(e - b));
6243 ip_list.emplace_back(std::string(b + r.first, b + r.second));
6244 });
6245
6246 for (size_t i = 0; i < ip_list.size(); ++i) {
6247 auto ip = ip_list[i];
6248
6249 auto is_trusted_proxy =
6250 std::any_of(trusted_proxies.begin(), trusted_proxies.end(),
6251 [&](const std::string &proxy) { return ip == proxy; });
6252
6253 if (is_trusted_proxy) {
6254 if (i == 0) {
6255 // If the trusted proxy is the first IP, there's no preceding client IP
6256 return ip;
6257 } else {
6258 // Return the IP immediately before the trusted proxy
6259 return ip_list[i - 1];
6260 }
6261 }
6262 }
6263
6264 // If no trusted proxy is found, return the first IP in the list
6265 return ip_list.front();
6266}
6267
6268bool
6269Server::process_request(Stream &strm, const std::string &remote_addr,
6270 int remote_port, const std::string &local_addr,
6271 int local_port, bool close_connection,
6272 bool &connection_closed,
6273 const std::function<void(Request &)> &setup_request) {
6274 std::array<char, 2048> buf{};
6275
6276 detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
6277
6278 // Connection has been closed on client
6279 if (!line_reader.getline()) { return false; }
6280
6281 Request req;
6282 req.start_time_ = std::chrono::steady_clock::now();
6283 req.remote_addr = remote_addr;
6284 req.remote_port = remote_port;
6285 req.local_addr = local_addr;
6286 req.local_port = local_port;
6287
6288 Response res;
6289 res.version = "HTTP/1.1";
6290 res.headers = default_headers_;
6291
6292#ifdef __APPLE__
6293 // Socket file descriptor exceeded FD_SETSIZE...
6294 if (strm.socket() >= FD_SETSIZE) {
6295 Headers dummy;
6296 detail::read_headers(strm, dummy);
6297 res.status = StatusCode::InternalServerError_500;
6298 output_error_log(Error::ExceedMaxSocketDescriptorCount, &req);
6299 return write_response(strm, close_connection, req, res);
6300 }
6301#endif
6302
6303 // Request line and headers
6304 if (!parse_request_line(line_reader.ptr(), req)) {
6305 res.status = StatusCode::BadRequest_400;
6306 output_error_log(Error::InvalidRequestLine, &req);
6307 return write_response(strm, close_connection, req, res);
6308 }
6309
6310 // Request headers
6311 if (!detail::read_headers(strm, req.headers)) {
6312 res.status = StatusCode::BadRequest_400;
6313 output_error_log(Error::InvalidHeaders, &req);
6314 return write_response(strm, close_connection, req, res);
6315 }
6316
6317 // Check if the request URI doesn't exceed the limit
6318 if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
6319 res.status = StatusCode::UriTooLong_414;
6320 output_error_log(Error::ExceedUriMaxLength, &req);
6321 return write_response(strm, close_connection, req, res);
6322 }
6323
6324 if (req.get_header_value("Connection") == "close") {
6325 connection_closed = true;
6326 }
6327
6328 if (req.version == "HTTP/1.0" &&
6329 req.get_header_value("Connection") != "Keep-Alive") {
6330 connection_closed = true;
6331 }
6332
6333 if (!trusted_proxies_.empty() && req.has_header("X-Forwarded-For")) {
6334 auto x_forwarded_for = req.get_header_value("X-Forwarded-For");
6335 req.remote_addr = get_client_ip(x_forwarded_for, trusted_proxies_);
6336 } else {
6337 req.remote_addr = remote_addr;
6338 }
6339 req.remote_port = remote_port;
6340
6341 req.local_addr = local_addr;
6342 req.local_port = local_port;
6343
6344 if (req.has_header("Accept")) {
6345 const auto &accept_header = req.get_header_value("Accept");
6346 if (!detail::parse_accept_header(accept_header, req.accept_content_types)) {
6347 res.status = StatusCode::BadRequest_400;
6348 output_error_log(Error::HTTPParsing, &req);
6349 return write_response(strm, close_connection, req, res);
6350 }
6351 }
6352
6353 if (req.has_header("Range")) {
6354 const auto &range_header_value = req.get_header_value("Range");
6355 if (!detail::parse_range_header(range_header_value, req.ranges)) {
6356 res.status = StatusCode::RangeNotSatisfiable_416;
6357 output_error_log(Error::InvalidRangeHeader, &req);
6358 return write_response(strm, close_connection, req, res);
6359 }
6360 }
6361
6362 if (setup_request) { setup_request(req); }
6363
6364 if (req.get_header_value("Expect") == "100-continue") {
6365 int status = StatusCode::Continue_100;
6366 if (expect_100_continue_handler_) {
6367 status = expect_100_continue_handler_(req, res);
6368 }
6369 switch (status) {
6370 case StatusCode::Continue_100:
6371 case StatusCode::ExpectationFailed_417:
6372 detail::write_response_line(strm, status);
6373 strm.write("\r\n");
6374 break;
6375 default:
6376 connection_closed = true;
6377 return write_response(strm, true, req, res);
6378 }
6379 }
6380
6381 // Setup `is_connection_closed` method
6382 auto sock = strm.socket();
6383 req.is_connection_closed = [sock]() {
6384 return !detail::is_socket_alive(sock);
6385 };
6386
6387 // Routing
6388 auto routed = false;
6389#ifdef CPPHTTPLIB_NO_EXCEPTIONS
6390 routed = routing(req, res, strm);
6391#else
6392 try {
6393 routed = routing(req, res, strm);
6394 } catch (std::exception &e) {
6395 if (exception_handler_) {
6396 auto ep = std::current_exception();
6397 exception_handler_(req, res, ep);
6398 routed = true;
6399 } else {
6400 res.status = StatusCode::InternalServerError_500;
6401 std::string val;
6402 auto s = e.what();
6403 for (size_t i = 0; s[i]; i++) {
6404 switch (s[i]) {
6405 case '\r': val += "\\r"; break;
6406 case '\n': val += "\\n"; break;
6407 default: val += s[i]; break;
6408 }
6409 }
6410 res.set_header("EXCEPTION_WHAT", val);
6411 }
6412 } catch (...) {
6413 if (exception_handler_) {
6414 auto ep = std::current_exception();
6415 exception_handler_(req, res, ep);
6416 routed = true;
6417 } else {
6418 res.status = StatusCode::InternalServerError_500;
6419 res.set_header("EXCEPTION_WHAT", "UNKNOWN");
6420 }
6421 }
6422#endif
6423 if (routed) {
6424 if (res.status == -1) {
6425 res.status = req.ranges.empty() ? StatusCode::OK_200
6426 : StatusCode::PartialContent_206;
6427 }
6428
6429 // Serve file content by using a content provider
6430 if (!res.file_content_path_.empty()) {
6431 const auto &path = res.file_content_path_;
6432 auto mm = std::make_shared<detail::mmap>(path.c_str());
6433 if (!mm->is_open()) {
6434 res.body.clear();
6435 res.content_length_ = 0;
6436 res.content_provider_ = nullptr;
6437 res.status = StatusCode::NotFound_404;
6438 output_error_log(Error::OpenFile, &req);
6439 return write_response(strm, close_connection, req, res);
6440 }
6441
6442 auto content_type = res.file_content_content_type_;
6443 if (content_type.empty()) {
6444 content_type = detail::find_content_type(
6445 path, file_extension_and_mimetype_map_, default_file_mimetype_);
6446 }
6447
6448 res.set_content_provider(
6449 mm->size(), content_type,
6450 [mm](size_t offset, size_t length, DataSink &sink) -> bool {
6451 sink.write(mm->data() + offset, length);
6452 return true;
6453 });
6454 }
6455
6456 if (detail::range_error(req, res)) {
6457 res.body.clear();
6458 res.content_length_ = 0;
6459 res.content_provider_ = nullptr;
6460 res.status = StatusCode::RangeNotSatisfiable_416;
6461 return write_response(strm, close_connection, req, res);
6462 }
6463
6464 return write_response_with_content(strm, close_connection, req, res);
6465 } else {
6466 if (res.status == -1) { res.status = StatusCode::NotFound_404; }
6467
6468 return write_response(strm, close_connection, req, res);
6469 }
6470}
6471
6472bool Server::is_valid() const { return true; }
6473
6474bool Server::process_and_close_socket(socket_t sock) {
6475 std::string remote_addr;
6476 int remote_port = 0;
6477 detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
6478
6479 std::string local_addr;
6480 int local_port = 0;
6481 detail::get_local_ip_and_port(sock, local_addr, local_port);
6482
6483 auto ret = detail::process_server_socket(
6484 svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
6485 read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
6486 write_timeout_usec_,
6487 [&](Stream &strm, bool close_connection, bool &connection_closed) {
6488 return process_request(strm, remote_addr, remote_port, local_addr,
6489 local_port, close_connection, connection_closed,
6490 nullptr);
6491 });
6492
6493 detail::shutdown_socket(sock);
6494 detail::close_socket(sock);
6495 return ret;
6496}
6497
6498void Server::output_log(const Request &req, const Response &res) const {
6499 if (logger_) {
6500 std::lock_guard<std::mutex> guard(logger_mutex_);
6501 logger_(req, res);
6502 }
6503}
6504
6505void Server::output_pre_compression_log(const Request &req,
6506 const Response &res) const {
6507 if (pre_compression_logger_) {
6508 std::lock_guard<std::mutex> guard(logger_mutex_);
6509 pre_compression_logger_(req, res);
6510 }
6511}
6512
6513void Server::output_error_log(const Error &err,
6514 const Request *req) const {
6515 if (error_logger_) {
6516 std::lock_guard<std::mutex> guard(logger_mutex_);
6517 error_logger_(err, req);
6518 }
6519}
6520
6521// HTTP client implementation
6522ClientImpl::ClientImpl(const std::string &host)
6523 : ClientImpl(host, 80, std::string(), std::string()) {}
6524
6525ClientImpl::ClientImpl(const std::string &host, int port)
6526 : ClientImpl(host, port, std::string(), std::string()) {}
6527
6528ClientImpl::ClientImpl(const std::string &host, int port,
6529 const std::string &client_cert_path,
6530 const std::string &client_key_path)
6531 : host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port),
6532 client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
6533
6534ClientImpl::~ClientImpl() {
6535 // Wait until all the requests in flight are handled.
6536 size_t retry_count = 10;
6537 while (retry_count-- > 0) {
6538 {
6539 std::lock_guard<std::mutex> guard(socket_mutex_);
6540 if (socket_requests_in_flight_ == 0) { break; }
6541 }
6542 std::this_thread::sleep_for(std::chrono::milliseconds{1});
6543 }
6544
6545 std::lock_guard<std::mutex> guard(socket_mutex_);
6546 shutdown_socket(socket_);
6547 close_socket(socket_);
6548}
6549
6550bool ClientImpl::is_valid() const { return true; }
6551
6552void ClientImpl::copy_settings(const ClientImpl &rhs) {
6553 client_cert_path_ = rhs.client_cert_path_;
6554 client_key_path_ = rhs.client_key_path_;
6555 connection_timeout_sec_ = rhs.connection_timeout_sec_;
6556 read_timeout_sec_ = rhs.read_timeout_sec_;
6557 read_timeout_usec_ = rhs.read_timeout_usec_;
6558 write_timeout_sec_ = rhs.write_timeout_sec_;
6559 write_timeout_usec_ = rhs.write_timeout_usec_;
6560 max_timeout_msec_ = rhs.max_timeout_msec_;
6561 basic_auth_username_ = rhs.basic_auth_username_;
6562 basic_auth_password_ = rhs.basic_auth_password_;
6563 bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
6564#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6565 digest_auth_username_ = rhs.digest_auth_username_;
6566 digest_auth_password_ = rhs.digest_auth_password_;
6567#endif
6568 keep_alive_ = rhs.keep_alive_;
6569 follow_location_ = rhs.follow_location_;
6570 path_encode_ = rhs.path_encode_;
6571 address_family_ = rhs.address_family_;
6572 tcp_nodelay_ = rhs.tcp_nodelay_;
6573 ipv6_v6only_ = rhs.ipv6_v6only_;
6574 socket_options_ = rhs.socket_options_;
6575 compress_ = rhs.compress_;
6576 decompress_ = rhs.decompress_;
6577 interface_ = rhs.interface_;
6578 proxy_host_ = rhs.proxy_host_;
6579 proxy_port_ = rhs.proxy_port_;
6580 proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
6581 proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
6582 proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
6583#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6584 proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
6585 proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
6586#endif
6587#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6588 ca_cert_file_path_ = rhs.ca_cert_file_path_;
6589 ca_cert_dir_path_ = rhs.ca_cert_dir_path_;
6590 ca_cert_store_ = rhs.ca_cert_store_;
6591#endif
6592#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6593 server_certificate_verification_ = rhs.server_certificate_verification_;
6594 server_hostname_verification_ = rhs.server_hostname_verification_;
6595 server_certificate_verifier_ = rhs.server_certificate_verifier_;
6596#endif
6597 logger_ = rhs.logger_;
6598 error_logger_ = rhs.error_logger_;
6599}
6600
6601socket_t ClientImpl::create_client_socket(Error &error) const {
6602 if (!proxy_host_.empty() && proxy_port_ != -1) {
6603 return detail::create_client_socket(
6604 proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_,
6605 ipv6_v6only_, socket_options_, connection_timeout_sec_,
6606 connection_timeout_usec_, read_timeout_sec_, read_timeout_usec_,
6607 write_timeout_sec_, write_timeout_usec_, interface_, error);
6608 }
6609
6610 // Check is custom IP specified for host_
6611 std::string ip;
6612 auto it = addr_map_.find(host_);
6613 if (it != addr_map_.end()) { ip = it->second; }
6614
6615 return detail::create_client_socket(
6616 host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
6617 socket_options_, connection_timeout_sec_, connection_timeout_usec_,
6618 read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
6619 write_timeout_usec_, interface_, error);
6620}
6621
6622bool ClientImpl::create_and_connect_socket(Socket &socket,
6623 Error &error) {
6624 auto sock = create_client_socket(error);
6625 if (sock == INVALID_SOCKET) { return false; }
6626 socket.sock = sock;
6627 return true;
6628}
6629
6630bool ClientImpl::ensure_socket_connection(Socket &socket, Error &error) {
6631 return create_and_connect_socket(socket, error);
6632}
6633
6634#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6635bool SSLClient::ensure_socket_connection(Socket &socket, Error &error) {
6636 if (!ClientImpl::ensure_socket_connection(socket, error)) { return false; }
6637
6638 if (!proxy_host_.empty() && proxy_port_ != -1) { return true; }
6639
6640 if (!initialize_ssl(socket, error)) {
6641 shutdown_socket(socket);
6642 close_socket(socket);
6643 return false;
6644 }
6645
6646 return true;
6647}
6648#endif
6649
6650void ClientImpl::shutdown_ssl(Socket & /*socket*/,
6651 bool /*shutdown_gracefully*/) {
6652 // If there are any requests in flight from threads other than us, then it's
6653 // a thread-unsafe race because individual ssl* objects are not thread-safe.
6654 assert(socket_requests_in_flight_ == 0 ||
6655 socket_requests_are_from_thread_ == std::this_thread::get_id());
6656}
6657
6658void ClientImpl::shutdown_socket(Socket &socket) const {
6659 if (socket.sock == INVALID_SOCKET) { return; }
6660 detail::shutdown_socket(socket.sock);
6661}
6662
6663void ClientImpl::close_socket(Socket &socket) {
6664 // If there are requests in flight in another thread, usually closing
6665 // the socket will be fine and they will simply receive an error when
6666 // using the closed socket, but it is still a bug since rarely the OS
6667 // may reassign the socket id to be used for a new socket, and then
6668 // suddenly they will be operating on a live socket that is different
6669 // than the one they intended!
6670 assert(socket_requests_in_flight_ == 0 ||
6671 socket_requests_are_from_thread_ == std::this_thread::get_id());
6672
6673 // It is also a bug if this happens while SSL is still active
6674#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6675 assert(socket.ssl == nullptr);
6676#endif
6677 if (socket.sock == INVALID_SOCKET) { return; }
6678 detail::close_socket(socket.sock);
6679 socket.sock = INVALID_SOCKET;
6680}
6681
6682bool ClientImpl::read_response_line(Stream &strm, const Request &req,
6683 Response &res,
6684 bool skip_100_continue) const {
6685 std::array<char, 2048> buf{};
6686
6687 detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
6688
6689 if (!line_reader.getline()) { return false; }
6690
6691#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
6692 thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n");
6693#else
6694 thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n");
6695#endif
6696
6697 std::cmatch m;
6698 if (!std::regex_match(line_reader.ptr(), m, re)) {
6699 return req.method == "CONNECT";
6700 }
6701 res.version = std::string(m[1]);
6702 res.status = std::stoi(std::string(m[2]));
6703 res.reason = std::string(m[3]);
6704
6705 // Ignore '100 Continue' (only when not using Expect: 100-continue explicitly)
6706 while (skip_100_continue && res.status == StatusCode::Continue_100) {
6707 if (!line_reader.getline()) { return false; } // CRLF
6708 if (!line_reader.getline()) { return false; } // next response line
6709
6710 if (!std::regex_match(line_reader.ptr(), m, re)) { return false; }
6711 res.version = std::string(m[1]);
6712 res.status = std::stoi(std::string(m[2]));
6713 res.reason = std::string(m[3]);
6714 }
6715
6716 return true;
6717}
6718
6719bool ClientImpl::send(Request &req, Response &res, Error &error) {
6720 std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
6721 auto ret = send_(req, res, error);
6722 if (error == Error::SSLPeerCouldBeClosed_) {
6723 assert(!ret);
6724 ret = send_(req, res, error);
6725 }
6726 return ret;
6727}
6728
6729bool ClientImpl::send_(Request &req, Response &res, Error &error) {
6730 {
6731 std::lock_guard<std::mutex> guard(socket_mutex_);
6732
6733 // Set this to false immediately - if it ever gets set to true by the end
6734 // of the request, we know another thread instructed us to close the
6735 // socket.
6736 socket_should_be_closed_when_request_is_done_ = false;
6737
6738 auto is_alive = false;
6739 if (socket_.is_open()) {
6740 is_alive = detail::is_socket_alive(socket_.sock);
6741
6742#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6743 if (is_alive && is_ssl()) {
6744 if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) {
6745 is_alive = false;
6746 }
6747 }
6748#endif
6749
6750 if (!is_alive) {
6751 // Attempt to avoid sigpipe by shutting down non-gracefully if it
6752 // seems like the other side has already closed the connection Also,
6753 // there cannot be any requests in flight from other threads since we
6754 // locked request_mutex_, so safe to close everything immediately
6755 const bool shutdown_gracefully = false;
6756 shutdown_ssl(socket_, shutdown_gracefully);
6757 shutdown_socket(socket_);
6758 close_socket(socket_);
6759 }
6760 }
6761
6762 if (!is_alive) {
6763 if (!ensure_socket_connection(socket_, error)) {
6764 output_error_log(error, &req);
6765 return false;
6766 }
6767
6768#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6769 // TODO: refactoring
6770 if (is_ssl()) {
6771 auto &scli = static_cast<SSLClient &>(*this);
6772 if (!proxy_host_.empty() && proxy_port_ != -1) {
6773 auto success = false;
6774 if (!scli.connect_with_proxy(socket_, req.start_time_, res, success,
6775 error)) {
6776 if (!success) { output_error_log(error, &req); }
6777 return success;
6778 }
6779 }
6780
6781 if (!proxy_host_.empty() && proxy_port_ != -1) {
6782 if (!scli.initialize_ssl(socket_, error)) {
6783 output_error_log(error, &req);
6784 return false;
6785 }
6786 }
6787 }
6788#endif
6789 }
6790
6791 // Mark the current socket as being in use so that it cannot be closed by
6792 // anyone else while this request is ongoing, even though we will be
6793 // releasing the mutex.
6794 if (socket_requests_in_flight_ > 1) {
6795 assert(socket_requests_are_from_thread_ == std::this_thread::get_id());
6796 }
6797 socket_requests_in_flight_ += 1;
6798 socket_requests_are_from_thread_ = std::this_thread::get_id();
6799 }
6800
6801 for (const auto &header : default_headers_) {
6802 if (req.headers.find(header.first) == req.headers.end()) {
6803 req.headers.insert(header);
6804 }
6805 }
6806
6807 auto ret = false;
6808 auto close_connection = !keep_alive_;
6809
6810 auto se = detail::scope_exit([&]() {
6811 // Briefly lock mutex in order to mark that a request is no longer ongoing
6812 std::lock_guard<std::mutex> guard(socket_mutex_);
6813 socket_requests_in_flight_ -= 1;
6814 if (socket_requests_in_flight_ <= 0) {
6815 assert(socket_requests_in_flight_ == 0);
6816 socket_requests_are_from_thread_ = std::thread::id();
6817 }
6818
6819 if (socket_should_be_closed_when_request_is_done_ || close_connection ||
6820 !ret) {
6821 shutdown_ssl(socket_, true);
6822 shutdown_socket(socket_);
6823 close_socket(socket_);
6824 }
6825 });
6826
6827 ret = process_socket(socket_, req.start_time_, [&](Stream &strm) {
6828 return handle_request(strm, req, res, close_connection, error);
6829 });
6830
6831 if (!ret) {
6832 if (error == Error::Success) {
6833 error = Error::Unknown;
6834 output_error_log(error, &req);
6835 }
6836 }
6837
6838 return ret;
6839}
6840
6841Result ClientImpl::send(const Request &req) {
6842 auto req2 = req;
6843 return send_(std::move(req2));
6844}
6845
6846Result ClientImpl::send_(Request &&req) {
6847 auto res = detail::make_unique<Response>();
6848 auto error = Error::Success;
6849 auto ret = send(req, *res, error);
6850#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6851 return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers),
6852 last_ssl_error_, last_openssl_error_};
6853#else
6854 return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)};
6855#endif
6856}
6857
6858void ClientImpl::prepare_default_headers(Request &r, bool for_stream,
6859 const std::string &ct) {
6860 (void)for_stream;
6861 for (const auto &header : default_headers_) {
6862 if (!r.has_header(header.first)) { r.headers.insert(header); }
6863 }
6864
6865 if (!r.has_header("Host")) {
6866 if (address_family_ == AF_UNIX) {
6867 r.headers.emplace("Host", "localhost");
6868 } else {
6869 r.headers.emplace(
6870 "Host", detail::make_host_and_port_string(host_, port_, is_ssl()));
6871 }
6872 }
6873
6874 if (!r.has_header("Accept")) { r.headers.emplace("Accept", "*/*"); }
6875
6876 if (!r.content_receiver) {
6877 if (!r.has_header("Accept-Encoding")) {
6878 std::string accept_encoding;
6879#ifdef CPPHTTPLIB_BROTLI_SUPPORT
6880 accept_encoding = "br";
6881#endif
6882#ifdef CPPHTTPLIB_ZLIB_SUPPORT
6883 if (!accept_encoding.empty()) { accept_encoding += ", "; }
6884 accept_encoding += "gzip, deflate";
6885#endif
6886#ifdef CPPHTTPLIB_ZSTD_SUPPORT
6887 if (!accept_encoding.empty()) { accept_encoding += ", "; }
6888 accept_encoding += "zstd";
6889#endif
6890 r.set_header("Accept-Encoding", accept_encoding);
6891 }
6892
6893#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
6894 if (!r.has_header("User-Agent")) {
6895 auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION;
6896 r.set_header("User-Agent", agent);
6897 }
6898#endif
6899 }
6900
6901 if (!r.body.empty()) {
6902 if (!ct.empty() && !r.has_header("Content-Type")) {
6903 r.headers.emplace("Content-Type", ct);
6904 }
6905 if (!r.has_header("Content-Length")) {
6906 r.headers.emplace("Content-Length", std::to_string(r.body.size()));
6907 }
6908 }
6909}
6910
6911ClientImpl::StreamHandle
6912ClientImpl::open_stream(const std::string &method, const std::string &path,
6913 const Params ¶ms, const Headers &headers,
6914 const std::string &body,
6915 const std::string &content_type) {
6916 StreamHandle handle;
6917 handle.response = detail::make_unique<Response>();
6918 handle.error = Error::Success;
6919
6920 auto query_path = params.empty() ? path : append_query_params(path, params);
6921 handle.connection_ = detail::make_unique<ClientConnection>();
6922
6923 {
6924 std::lock_guard<std::mutex> guard(socket_mutex_);
6925
6926 auto is_alive = false;
6927 if (socket_.is_open()) {
6928 is_alive = detail::is_socket_alive(socket_.sock);
6929#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6930 if (is_alive && is_ssl()) {
6931 if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) {
6932 is_alive = false;
6933 }
6934 }
6935#endif
6936 if (!is_alive) {
6937 shutdown_ssl(socket_, false);
6938 shutdown_socket(socket_);
6939 close_socket(socket_);
6940 }
6941 }
6942
6943 if (!is_alive) {
6944 if (!ensure_socket_connection(socket_, handle.error)) {
6945 handle.response.reset();
6946 return handle;
6947 }
6948
6949#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6950 if (is_ssl()) {
6951 auto &scli = static_cast<SSLClient &>(*this);
6952 if (!proxy_host_.empty() && proxy_port_ != -1) {
6953 if (!scli.initialize_ssl(socket_, handle.error)) {
6954 handle.response.reset();
6955 return handle;
6956 }
6957 }
6958 }
6959#endif
6960 }
6961
6962 transfer_socket_ownership_to_handle(handle);
6963 }
6964
6965#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
6966 if (is_ssl() && handle.connection_->ssl) {
6967 handle.socket_stream_ = detail::make_unique<detail::SSLSocketStream>(
6968 handle.connection_->sock, handle.connection_->ssl, read_timeout_sec_,
6969 read_timeout_usec_, write_timeout_sec_, write_timeout_usec_);
6970 } else {
6971 handle.socket_stream_ = detail::make_unique<detail::SocketStream>(
6972 handle.connection_->sock, read_timeout_sec_, read_timeout_usec_,
6973 write_timeout_sec_, write_timeout_usec_);
6974 }
6975#else
6976 handle.socket_stream_ = detail::make_unique<detail::SocketStream>(
6977 handle.connection_->sock, read_timeout_sec_, read_timeout_usec_,
6978 write_timeout_sec_, write_timeout_usec_);
6979#endif
6980 handle.stream_ = handle.socket_stream_.get();
6981
6982 Request req;
6983 req.method = method;
6984 req.path = query_path;
6985 req.headers = headers;
6986 req.body = body;
6987
6988 prepare_default_headers(req, true, content_type);
6989
6990 auto &strm = *handle.stream_;
6991 if (detail::write_request_line(strm, req.method, req.path) < 0) {
6992 handle.error = Error::Write;
6993 handle.response.reset();
6994 return handle;
6995 }
6996
6997 if (!detail::check_and_write_headers(strm, req.headers, header_writer_,
6998 handle.error)) {
6999 handle.response.reset();
7000 return handle;
7001 }
7002
7003 if (!body.empty()) {
7004 if (strm.write(body.data(), body.size()) < 0) {
7005 handle.error = Error::Write;
7006 handle.response.reset();
7007 return handle;
7008 }
7009 }
7010
7011 if (!read_response_line(strm, req, *handle.response) ||
7012 !detail::read_headers(strm, handle.response->headers)) {
7013 handle.error = Error::Read;
7014 handle.response.reset();
7015 return handle;
7016 }
7017
7018 handle.body_reader_.stream = handle.stream_;
7019
7020 auto content_length_str = handle.response->get_header_value("Content-Length");
7021 if (!content_length_str.empty()) {
7022 handle.body_reader_.content_length =
7023 static_cast<size_t>(std::stoull(content_length_str));
7024 }
7025
7026 auto transfer_encoding =
7027 handle.response->get_header_value("Transfer-Encoding");
7028 handle.body_reader_.chunked = (transfer_encoding == "chunked");
7029
7030 auto content_encoding = handle.response->get_header_value("Content-Encoding");
7031 if (!content_encoding.empty()) {
7032 handle.decompressor_ = detail::create_decompressor(content_encoding);
7033 }
7034
7035 return handle;
7036}
7037
7038ssize_t ClientImpl::StreamHandle::read(char *buf, size_t len) {
7039 if (!is_valid() || !response) { return -1; }
7040
7041 if (decompressor_) { return read_with_decompression(buf, len); }
7042 auto n = detail::read_body_content(stream_, body_reader_, buf, len);
7043
7044 if (n <= 0 && body_reader_.chunked && !trailers_parsed_ && stream_) {
7045 trailers_parsed_ = true;
7046 if (body_reader_.chunked_decoder) {
7047 if (!body_reader_.chunked_decoder->parse_trailers_into(
7048 response->trailers, response->headers)) {
7049 return n;
7050 }
7051 } else {
7052 detail::ChunkedDecoder dec(*stream_);
7053 if (!dec.parse_trailers_into(response->trailers, response->headers)) {
7054 return n;
7055 }
7056 }
7057 }
7058
7059 return n;
7060}
7061
7062ssize_t ClientImpl::StreamHandle::read_with_decompression(char *buf,
7063 size_t len) {
7064 if (decompress_offset_ < decompress_buffer_.size()) {
7065 auto available = decompress_buffer_.size() - decompress_offset_;
7066 auto to_copy = (std::min)(len, available);
7067 std::memcpy(buf, decompress_buffer_.data() + decompress_offset_, to_copy);
7068 decompress_offset_ += to_copy;
7069 return static_cast<ssize_t>(to_copy);
7070 }
7071
7072 decompress_buffer_.clear();
7073 decompress_offset_ = 0;
7074
7075 constexpr size_t kDecompressionBufferSize = 8192;
7076 char compressed_buf[kDecompressionBufferSize];
7077
7078 while (true) {
7079 auto n = detail::read_body_content(stream_, body_reader_, compressed_buf,
7080 sizeof(compressed_buf));
7081
7082 if (n <= 0) { return n; }
7083
7084 bool decompress_ok =
7085 decompressor_->decompress(compressed_buf, static_cast<size_t>(n),
7086 [this](const char *data, size_t data_len) {
7087 decompress_buffer_.append(data, data_len);
7088 return true;
7089 });
7090
7091 if (!decompress_ok) {
7092 body_reader_.last_error = Error::Read;
7093 return -1;
7094 }
7095
7096 if (!decompress_buffer_.empty()) { break; }
7097 }
7098
7099 auto to_copy = (std::min)(len, decompress_buffer_.size());
7100 std::memcpy(buf, decompress_buffer_.data(), to_copy);
7101 decompress_offset_ = to_copy;
7102 return static_cast<ssize_t>(to_copy);
7103}
7104
7105void ClientImpl::StreamHandle::parse_trailers_if_needed() {
7106 if (!response || !stream_ || !body_reader_.chunked || trailers_parsed_) {
7107 return;
7108 }
7109
7110 trailers_parsed_ = true;
7111
7112 const auto bufsiz = 128;
7113 char line_buf[bufsiz];
7114 detail::stream_line_reader line_reader(*stream_, line_buf, bufsiz);
7115
7116 if (!line_reader.getline()) { return; }
7117
7118 if (!detail::parse_trailers(line_reader, response->trailers,
7119 response->headers)) {
7120 return;
7121 }
7122}
7123
7124// Inline method implementations for `ChunkedDecoder`.
7125namespace detail {
7126
7127ChunkedDecoder::ChunkedDecoder(Stream &s) : strm(s) {}
7128
7129ssize_t ChunkedDecoder::read_payload(char *buf, size_t len,
7130 size_t &out_chunk_offset,
7131 size_t &out_chunk_total) {
7132 if (finished) { return 0; }
7133
7134 if (chunk_remaining == 0) {
7135 stream_line_reader lr(strm, line_buf, sizeof(line_buf));
7136 if (!lr.getline()) { return -1; }
7137
7138 char *endptr = nullptr;
7139 unsigned long chunk_len = std::strtoul(lr.ptr(), &endptr, 16);
7140 if (endptr == lr.ptr()) { return -1; }
7141 if (chunk_len == ULONG_MAX) { return -1; }
7142
7143 if (chunk_len == 0) {
7144 chunk_remaining = 0;
7145 finished = true;
7146 out_chunk_offset = 0;
7147 out_chunk_total = 0;
7148 return 0;
7149 }
7150
7151 chunk_remaining = static_cast<size_t>(chunk_len);
7152 last_chunk_total = chunk_remaining;
7153 last_chunk_offset = 0;
7154 }
7155
7156 auto to_read = (std::min)(chunk_remaining, len);
7157 auto n = strm.read(buf, to_read);
7158 if (n <= 0) { return -1; }
7159
7160 auto offset_before = last_chunk_offset;
7161 last_chunk_offset += static_cast<size_t>(n);
7162 chunk_remaining -= static_cast<size_t>(n);
7163
7164 out_chunk_offset = offset_before;
7165 out_chunk_total = last_chunk_total;
7166
7167 if (chunk_remaining == 0) {
7168 stream_line_reader lr(strm, line_buf, sizeof(line_buf));
7169 if (!lr.getline()) { return -1; }
7170 if (std::strcmp(lr.ptr(), "\r\n") != 0) { return -1; }
7171 }
7172
7173 return n;
7174}
7175
7176bool ChunkedDecoder::parse_trailers_into(Headers &dest,
7177 const Headers &src_headers) {
7178 stream_line_reader lr(strm, line_buf, sizeof(line_buf));
7179 if (!lr.getline()) { return false; }
7180 return parse_trailers(lr, dest, src_headers);
7181}
7182
7183} // namespace detail
7184
7185void
7186ClientImpl::transfer_socket_ownership_to_handle(StreamHandle &handle) {
7187 handle.connection_->sock = socket_.sock;
7188#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
7189 handle.connection_->ssl = socket_.ssl;
7190 socket_.ssl = nullptr;
7191#endif
7192 socket_.sock = INVALID_SOCKET;
7193}
7194
7195bool ClientImpl::handle_request(Stream &strm, Request &req,
7196 Response &res, bool close_connection,
7197 Error &error) {
7198 if (req.path.empty()) {
7199 error = Error::Connection;
7200 output_error_log(error, &req);
7201 return false;
7202 }
7203
7204 auto req_save = req;
7205
7206 bool ret;
7207
7208 if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) {
7209 auto req2 = req;
7210 req2.path = "http://" +
7211 detail::make_host_and_port_string(host_, port_, false) +
7212 req.path;
7213 ret = process_request(strm, req2, res, close_connection, error);
7214 req = std::move(req2);
7215 req.path = req_save.path;
7216 } else {
7217 ret = process_request(strm, req, res, close_connection, error);
7218 }
7219
7220 if (!ret) { return false; }
7221
7222 if (res.get_header_value("Connection") == "close" ||
7223 (res.version == "HTTP/1.0" && res.reason != "Connection established")) {
7224 // TODO this requires a not-entirely-obvious chain of calls to be correct
7225 // for this to be safe.
7226
7227 // This is safe to call because handle_request is only called by send_
7228 // which locks the request mutex during the process. It would be a bug
7229 // to call it from a different thread since it's a thread-safety issue
7230 // to do these things to the socket if another thread is using the socket.
7231 std::lock_guard<std::mutex> guard(socket_mutex_);
7232 shutdown_ssl(socket_, true);
7233 shutdown_socket(socket_);
7234 close_socket(socket_);
7235 }
7236
7237 if (300 < res.status && res.status < 400 && follow_location_) {
7238 req = std::move(req_save);
7239 ret = redirect(req, res, error);
7240 }
7241
7242#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
7243 if ((res.status == StatusCode::Unauthorized_401 ||
7244 res.status == StatusCode::ProxyAuthenticationRequired_407) &&
7245 req.authorization_count_ < 5) {
7246 auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407;
7247 const auto &username =
7248 is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
7249 const auto &password =
7250 is_proxy ? proxy_digest_auth_password_ : digest_auth_password_;
7251
7252 if (!username.empty() && !password.empty()) {
7253 std::map<std::string, std::string> auth;
7254 if (detail::parse_www_authenticate(res, auth, is_proxy)) {
7255 Request new_req = req;
7256 new_req.authorization_count_ += 1;
7257 new_req.headers.erase(is_proxy ? "Proxy-Authorization"
7258 : "Authorization");
7259 new_req.headers.insert(detail::make_digest_authentication_header(
7260 req, auth, new_req.authorization_count_, detail::random_string(10),
7261 username, password, is_proxy));
7262
7263 Response new_res;
7264
7265 ret = send(new_req, new_res, error);
7266 if (ret) { res = std::move(new_res); }
7267 }
7268 }
7269 }
7270#endif
7271
7272 return ret;
7273}
7274
7275bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
7276 if (req.redirect_count_ == 0) {
7277 error = Error::ExceedRedirectCount;
7278 output_error_log(error, &req);
7279 return false;
7280 }
7281
7282 auto location = res.get_header_value("location");
7283 if (location.empty()) { return false; }
7284
7285 thread_local const std::regex re(
7286 R"((?:(https?):)?(?://(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)");
7287
7288 std::smatch m;
7289 if (!std::regex_match(location, m, re)) { return false; }
7290
7291 auto scheme = is_ssl() ? "https" : "http";
7292
7293 auto next_scheme = m[1].str();
7294 auto next_host = m[2].str();
7295 if (next_host.empty()) { next_host = m[3].str(); }
7296 auto port_str = m[4].str();
7297 auto next_path = m[5].str();
7298 auto next_query = m[6].str();
7299
7300 auto next_port = port_;
7301 if (!port_str.empty()) {
7302 next_port = std::stoi(port_str);
7303 } else if (!next_scheme.empty()) {
7304 next_port = next_scheme == "https" ? 443 : 80;
7305 }
7306
7307 if (next_scheme.empty()) { next_scheme = scheme; }
7308 if (next_host.empty()) { next_host = host_; }
7309 if (next_path.empty()) { next_path = "/"; }
7310
7311 auto path = decode_query_component(next_path, true) + next_query;
7312
7313 // Same host redirect - use current client
7314 if (next_scheme == scheme && next_host == host_ && next_port == port_) {
7315 return detail::redirect(*this, req, res, path, location, error);
7316 }
7317
7318 // Cross-host/scheme redirect - create new client with robust setup
7319 return create_redirect_client(next_scheme, next_host, next_port, req, res,
7320 path, location, error);
7321}
7322
7323// New method for robust redirect client creation
7324bool ClientImpl::create_redirect_client(
7325 const std::string &scheme, const std::string &host, int port, Request &req,
7326 Response &res, const std::string &path, const std::string &location,
7327 Error &error) {
7328 // Determine if we need SSL
7329 auto need_ssl = (scheme == "https");
7330
7331 // Clean up request headers that are host/client specific
7332 // Remove headers that should not be carried over to new host
7333 auto headers_to_remove =
7334 std::vector<std::string>{"Host", "Proxy-Authorization", "Authorization"};
7335
7336 for (const auto &header_name : headers_to_remove) {
7337 auto it = req.headers.find(header_name);
7338 while (it != req.headers.end()) {
7339 it = req.headers.erase(it);
7340 it = req.headers.find(header_name);
7341 }
7342 }
7343
7344 // Create appropriate client type and handle redirect
7345 if (need_ssl) {
7346#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
7347 // Create SSL client for HTTPS redirect
7348 SSLClient redirect_client(host, port);
7349
7350 // Setup basic client configuration first
7351 setup_redirect_client(redirect_client);
7352
7353 // SSL-specific configuration for proxy environments
7354 if (!proxy_host_.empty() && proxy_port_ != -1) {
7355 // Critical: Disable SSL verification for proxy environments
7356 redirect_client.enable_server_certificate_verification(false);
7357 redirect_client.enable_server_hostname_verification(false);
7358 } else {
7359 // For direct SSL connections, copy SSL verification settings
7360 redirect_client.enable_server_certificate_verification(
7361 server_certificate_verification_);
7362 redirect_client.enable_server_hostname_verification(
7363 server_hostname_verification_);
7364 }
7365
7366 // Handle CA certificate store and paths if available
7367 if (ca_cert_store_ && X509_STORE_up_ref(ca_cert_store_)) {
7368 redirect_client.set_ca_cert_store(ca_cert_store_);
7369 }
7370 if (!ca_cert_file_path_.empty()) {
7371 redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_);
7372 }
7373
7374 // Client certificates are set through constructor for SSLClient
7375 // NOTE: SSLClient constructor already takes client_cert_path and
7376 // client_key_path so we need to create it properly if client certs are
7377 // needed
7378
7379 // Execute the redirect
7380 return detail::redirect(redirect_client, req, res, path, location, error);
7381#else
7382 // SSL not supported - set appropriate error
7383 error = Error::SSLConnection;
7384 output_error_log(error, &req);
7385 return false;
7386#endif
7387 } else {
7388 // HTTP redirect
7389 ClientImpl redirect_client(host, port);
7390
7391 // Setup client with robust configuration
7392 setup_redirect_client(redirect_client);
7393
7394 // Execute the redirect
7395 return detail::redirect(redirect_client, req, res, path, location, error);
7396 }
7397}
7398
7399// New method for robust client setup (based on basic_manual_redirect.cpp
7400// logic)
7401template <typename ClientType>
7402void ClientImpl::setup_redirect_client(ClientType &client) {
7403 // Copy basic settings first
7404 client.set_connection_timeout(connection_timeout_sec_);
7405 client.set_read_timeout(read_timeout_sec_, read_timeout_usec_);
7406 client.set_write_timeout(write_timeout_sec_, write_timeout_usec_);
7407 client.set_keep_alive(keep_alive_);
7408 client.set_follow_location(
7409 true); // Enable redirects to handle multi-step redirects
7410 client.set_path_encode(path_encode_);
7411 client.set_compress(compress_);
7412 client.set_decompress(decompress_);
7413
7414 // Copy authentication settings BEFORE proxy setup
7415 if (!basic_auth_username_.empty()) {
7416 client.set_basic_auth(basic_auth_username_, basic_auth_password_);
7417 }
7418 if (!bearer_token_auth_token_.empty()) {
7419 client.set_bearer_token_auth(bearer_token_auth_token_);
7420 }
7421#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
7422 if (!digest_auth_username_.empty()) {
7423 client.set_digest_auth(digest_auth_username_, digest_auth_password_);
7424 }
7425#endif
7426
7427 // Setup proxy configuration (CRITICAL ORDER - proxy must be set
7428 // before proxy auth)
7429 if (!proxy_host_.empty() && proxy_port_ != -1) {
7430 // First set proxy host and port
7431 client.set_proxy(proxy_host_, proxy_port_);
7432
7433 // Then set proxy authentication (order matters!)
7434 if (!proxy_basic_auth_username_.empty()) {
7435 client.set_proxy_basic_auth(proxy_basic_auth_username_,
7436 proxy_basic_auth_password_);
7437 }
7438 if (!proxy_bearer_token_auth_token_.empty()) {
7439 client.set_proxy_bearer_token_auth(proxy_bearer_token_auth_token_);
7440 }
7441#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
7442 if (!proxy_digest_auth_username_.empty()) {
7443 client.set_proxy_digest_auth(proxy_digest_auth_username_,
7444 proxy_digest_auth_password_);
7445 }
7446#endif
7447 }
7448
7449 // Copy network and socket settings
7450 client.set_address_family(address_family_);
7451 client.set_tcp_nodelay(tcp_nodelay_);
7452 client.set_ipv6_v6only(ipv6_v6only_);
7453 if (socket_options_) { client.set_socket_options(socket_options_); }
7454 if (!interface_.empty()) { client.set_interface(interface_); }
7455
7456 // Copy logging and headers
7457 if (logger_) { client.set_logger(logger_); }
7458 if (error_logger_) { client.set_error_logger(error_logger_); }
7459
7460 // NOTE: DO NOT copy default_headers_ as they may contain stale Host headers
7461 // Each new client should generate its own headers based on its target host
7462}
7463
7464bool ClientImpl::write_content_with_provider(Stream &strm,
7465 const Request &req,
7466 Error &error) const {
7467 auto is_shutting_down = []() { return false; };
7468
7469 if (req.is_chunked_content_provider_) {
7470 // TODO: Brotli support
7471 std::unique_ptr<detail::compressor> compressor;
7472#ifdef CPPHTTPLIB_ZLIB_SUPPORT
7473 if (compress_) {
7474 compressor = detail::make_unique<detail::gzip_compressor>();
7475 } else
7476#endif
7477 {
7478 compressor = detail::make_unique<detail::nocompressor>();
7479 }
7480
7481 return detail::write_content_chunked(strm, req.content_provider_,
7482 is_shutting_down, *compressor, error);
7483 } else {
7484 return detail::write_content_with_progress(
7485 strm, req.content_provider_, 0, req.content_length_, is_shutting_down,
7486 req.upload_progress, error);
7487 }
7488}
7489
7490bool ClientImpl::write_request(Stream &strm, Request &req,
7491 bool close_connection, Error &error,
7492 bool skip_body) {
7493 // Prepare additional headers
7494 if (close_connection) {
7495 if (!req.has_header("Connection")) {
7496 req.set_header("Connection", "close");
7497 }
7498 }
7499
7500 std::string ct_for_defaults;
7501 if (!req.has_header("Content-Type") && !req.body.empty()) {
7502 ct_for_defaults = "text/plain";
7503 }
7504 prepare_default_headers(req, false, ct_for_defaults);
7505
7506 if (req.body.empty()) {
7507 if (req.content_provider_) {
7508 if (!req.is_chunked_content_provider_) {
7509 if (!req.has_header("Content-Length")) {
7510 auto length = std::to_string(req.content_length_);
7511 req.set_header("Content-Length", length);
7512 }
7513 }
7514 } else {
7515 if (req.method == "POST" || req.method == "PUT" ||
7516 req.method == "PATCH") {
7517 req.set_header("Content-Length", "0");
7518 }
7519 }
7520 }
7521
7522 if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) {
7523 if (!req.has_header("Authorization")) {
7524 req.headers.insert(make_basic_authentication_header(
7525 basic_auth_username_, basic_auth_password_, false));
7526 }
7527 }
7528
7529 if (!proxy_basic_auth_username_.empty() &&
7530 !proxy_basic_auth_password_.empty()) {
7531 if (!req.has_header("Proxy-Authorization")) {
7532 req.headers.insert(make_basic_authentication_header(
7533 proxy_basic_auth_username_, proxy_basic_auth_password_, true));
7534 }
7535 }
7536
7537 if (!bearer_token_auth_token_.empty()) {
7538 if (!req.has_header("Authorization")) {
7539 req.headers.insert(make_bearer_token_authentication_header(
7540 bearer_token_auth_token_, false));
7541 }
7542 }
7543
7544 if (!proxy_bearer_token_auth_token_.empty()) {
7545 if (!req.has_header("Proxy-Authorization")) {
7546 req.headers.insert(make_bearer_token_authentication_header(
7547 proxy_bearer_token_auth_token_, true));
7548 }
7549 }
7550
7551 // Request line and headers
7552 {
7553 detail::BufferStream bstrm;
7554
7555 // Extract path and query from req.path
7556 std::string path_part, query_part;
7557 auto query_pos = req.path.find('?');
7558 if (query_pos != std::string::npos) {
7559 path_part = req.path.substr(0, query_pos);
7560 query_part = req.path.substr(query_pos + 1);
7561 } else {
7562 path_part = req.path;
7563 query_part = "";
7564 }
7565
7566 // Encode path part. If the original `req.path` already contained a
7567 // query component, preserve its raw query string (including parameter
7568 // order) instead of reparsing and reassembling it which may reorder
7569 // parameters due to container ordering (e.g. `Params` uses
7570 // `std::multimap`). When there is no query in `req.path`, fall back to
7571 // building a query from `req.params` so existing callers that pass
7572 // `Params` continue to work.
7573 auto path_with_query =
7574 path_encode_ ? detail::encode_path(path_part) : path_part;
7575
7576 if (!query_part.empty()) {
7577 // Normalize the query string (decode then re-encode) while preserving
7578 // the original parameter order.
7579 auto normalized = detail::normalize_query_string(query_part);
7580 if (!normalized.empty()) { path_with_query += '?' + normalized; }
7581
7582 // Still populate req.params for handlers/users who read them.
7583 detail::parse_query_text(query_part, req.params);
7584 } else {
7585 // No query in path; parse any query_part (empty) and append params
7586 // from `req.params` when present (preserves prior behavior for
7587 // callers who provide Params separately).
7588 detail::parse_query_text(query_part, req.params);
7589 if (!req.params.empty()) {
7590 path_with_query = append_query_params(path_with_query, req.params);
7591 }
7592 }
7593
7594 // Write request line and headers
7595 detail::write_request_line(bstrm, req.method, path_with_query);
7596 if (!detail::check_and_write_headers(bstrm, req.headers, header_writer_,
7597 error)) {
7598 output_error_log(error, &req);
7599 return false;
7600 }
7601
7602 // Flush buffer
7603 auto &data = bstrm.get_buffer();
7604 if (!detail::write_data(strm, data.data(), data.size())) {
7605 error = Error::Write;
7606 output_error_log(error, &req);
7607 return false;
7608 }
7609 }
7610
7611 // After sending request line and headers, wait briefly for an early server
7612 // response (e.g. 4xx) and avoid sending a potentially large request body
7613 // unnecessarily. This workaround is only enabled on Windows because Unix
7614 // platforms surface write errors (EPIPE) earlier; on Windows kernel send
7615 // buffering can accept large writes even when the peer already responded.
7616 // Check the stream first (which covers SSL via `is_readable()`), then
7617 // fall back to select on the socket. Only perform the wait for very large
7618 // request bodies to avoid interfering with normal small requests and
7619 // reduce side-effects. Poll briefly (up to 50ms as default) for an early
7620 // response. Skip this check when using Expect: 100-continue, as the protocol
7621 // handles early responses properly.
7622#if defined(_WIN32)
7623 if (!skip_body &&
7624 req.body.size() > CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD &&
7625 req.path.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
7626 auto start = std::chrono::high_resolution_clock::now();
7627
7628 for (;;) {
7629 // Prefer socket-level readiness to avoid SSL_pending() false-positives
7630 // from SSL internals. If the underlying socket is readable, assume an
7631 // early response may be present.
7632 auto sock = strm.socket();
7633 if (sock != INVALID_SOCKET && detail::select_read(sock, 0, 0) > 0) {
7634 return false;
7635 }
7636
7637 // Fallback to stream-level check for non-socket streams or when the
7638 // socket isn't reporting readable. Avoid using `is_readable()` for
7639 // SSL, since `SSL_pending()` may report buffered records that do not
7640 // indicate a complete application-level response yet.
7641 if (!is_ssl() && strm.is_readable()) { return false; }
7642
7643 auto now = std::chrono::high_resolution_clock::now();
7644 auto elapsed =
7645 std::chrono::duration_cast<std::chrono::milliseconds>(now - start)
7646 .count();
7647 if (elapsed >= CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND) {
7648 break;
7649 }
7650
7651 std::this_thread::sleep_for(std::chrono::milliseconds(1));
7652 }
7653 }
7654#endif
7655
7656 // Body
7657 if (skip_body) { return true; }
7658
7659 return write_request_body(strm, req, error);
7660}
7661
7662bool ClientImpl::write_request_body(Stream &strm, Request &req,
7663 Error &error) {
7664 if (req.body.empty()) {
7665 return write_content_with_provider(strm, req, error);
7666 }
7667
7668 if (req.upload_progress) {
7669 auto body_size = req.body.size();
7670 size_t written = 0;
7671 auto data = req.body.data();
7672
7673 while (written < body_size) {
7674 size_t to_write = (std::min)(CPPHTTPLIB_SEND_BUFSIZ, body_size - written);
7675 if (!detail::write_data(strm, data + written, to_write)) {
7676 error = Error::Write;
7677 output_error_log(error, &req);
7678 return false;
7679 }
7680 written += to_write;
7681
7682 if (!req.upload_progress(written, body_size)) {
7683 error = Error::Canceled;
7684 output_error_log(error, &req);
7685 return false;
7686 }
7687 }
7688 } else {
7689 if (!detail::write_data(strm, req.body.data(), req.body.size())) {
7690 error = Error::Write;
7691 output_error_log(error, &req);
7692 return false;
7693 }
7694 }
7695
7696 return true;
7697}
7698
7699std::unique_ptr<Response>
7700ClientImpl::send_with_content_provider_and_receiver(
7701 Request &req, const char *body, size_t content_length,
7702 ContentProvider content_provider,
7703 ContentProviderWithoutLength content_provider_without_length,
7704 const std::string &content_type, ContentReceiver content_receiver,
7705 Error &error) {
7706 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
7707
7708#ifdef CPPHTTPLIB_ZLIB_SUPPORT
7709 if (compress_) { req.set_header("Content-Encoding", "gzip"); }
7710#endif
7711
7712#ifdef CPPHTTPLIB_ZLIB_SUPPORT
7713 if (compress_ && !content_provider_without_length) {
7714 // TODO: Brotli support
7715 detail::gzip_compressor compressor;
7716
7717 if (content_provider) {
7718 auto ok = true;
7719 size_t offset = 0;
7720 DataSink data_sink;
7721
7722 data_sink.write = [&](const char *data, size_t data_len) -> bool {
7723 if (ok) {
7724 auto last = offset + data_len == content_length;
7725
7726 auto ret = compressor.compress(
7727 data, data_len, last,
7728 [&](const char *compressed_data, size_t compressed_data_len) {
7729 req.body.append(compressed_data, compressed_data_len);
7730 return true;
7731 });
7732
7733 if (ret) {
7734 offset += data_len;
7735 } else {
7736 ok = false;
7737 }
7738 }
7739 return ok;
7740 };
7741
7742 while (ok && offset < content_length) {
7743 if (!content_provider(offset, content_length - offset, data_sink)) {
7744 error = Error::Canceled;
7745 output_error_log(error, &req);
7746 return nullptr;
7747 }
7748 }
7749 } else {
7750 if (!compressor.compress(body, content_length, true,
7751 [&](const char *data, size_t data_len) {
7752 req.body.append(data, data_len);
7753 return true;
7754 })) {
7755 error = Error::Compression;
7756 output_error_log(error, &req);
7757 return nullptr;
7758 }
7759 }
7760 } else
7761#endif
7762 {
7763 if (content_provider) {
7764 req.content_length_ = content_length;
7765 req.content_provider_ = std::move(content_provider);
7766 req.is_chunked_content_provider_ = false;
7767 } else if (content_provider_without_length) {
7768 req.content_length_ = 0;
7769 req.content_provider_ = detail::ContentProviderAdapter(
7770 std::move(content_provider_without_length));
7771 req.is_chunked_content_provider_ = true;
7772 req.set_header("Transfer-Encoding", "chunked");
7773 } else {
7774 req.body.assign(body, content_length);
7775 }
7776 }
7777
7778 if (content_receiver) {
7779 req.content_receiver =
7780 [content_receiver](const char *data, size_t data_length,
7781 size_t /*offset*/, size_t /*total_length*/) {
7782 return content_receiver(data, data_length);
7783 };
7784 }
7785
7786 auto res = detail::make_unique<Response>();
7787 return send(req, *res, error) ? std::move(res) : nullptr;
7788}
7789
7790Result ClientImpl::send_with_content_provider_and_receiver(
7791 const std::string &method, const std::string &path, const Headers &headers,
7792 const char *body, size_t content_length, ContentProvider content_provider,
7793 ContentProviderWithoutLength content_provider_without_length,
7794 const std::string &content_type, ContentReceiver content_receiver,
7795 UploadProgress progress) {
7796 Request req;
7797 req.method = method;
7798 req.headers = headers;
7799 req.path = path;
7800 req.upload_progress = std::move(progress);
7801 if (max_timeout_msec_ > 0) {
7802 req.start_time_ = std::chrono::steady_clock::now();
7803 }
7804
7805 auto error = Error::Success;
7806
7807 auto res = send_with_content_provider_and_receiver(
7808 req, body, content_length, std::move(content_provider),
7809 std::move(content_provider_without_length), content_type,
7810 std::move(content_receiver), error);
7811
7812#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
7813 return Result{std::move(res), error, std::move(req.headers), last_ssl_error_,
7814 last_openssl_error_};
7815#else
7816 return Result{std::move(res), error, std::move(req.headers)};
7817#endif
7818}
7819
7820void ClientImpl::output_log(const Request &req,
7821 const Response &res) const {
7822 if (logger_) {
7823 std::lock_guard<std::mutex> guard(logger_mutex_);
7824 logger_(req, res);
7825 }
7826}
7827
7828void ClientImpl::output_error_log(const Error &err,
7829 const Request *req) const {
7830 if (error_logger_) {
7831 std::lock_guard<std::mutex> guard(logger_mutex_);
7832 error_logger_(err, req);
7833 }
7834}
7835
7836bool ClientImpl::process_request(Stream &strm, Request &req,
7837 Response &res, bool close_connection,
7838 Error &error) {
7839 // Auto-add Expect: 100-continue for large bodies
7840 if (CPPHTTPLIB_EXPECT_100_THRESHOLD > 0 && !req.has_header("Expect")) {
7841 auto body_size = req.body.empty() ? req.content_length_ : req.body.size();
7842 if (body_size >= CPPHTTPLIB_EXPECT_100_THRESHOLD) {
7843 req.set_header("Expect", "100-continue");
7844 }
7845 }
7846
7847 // Check for Expect: 100-continue
7848 auto expect_100_continue = req.get_header_value("Expect") == "100-continue";
7849
7850 // Send request (skip body if using Expect: 100-continue)
7851 auto write_request_success =
7852 write_request(strm, req, close_connection, error, expect_100_continue);
7853
7854#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
7855 if (is_ssl()) {
7856 auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1;
7857 if (!is_proxy_enabled) {
7858 if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) {
7859 error = Error::SSLPeerCouldBeClosed_;
7860 output_error_log(error, &req);
7861 return false;
7862 }
7863 }
7864 }
7865#endif
7866
7867 // Handle Expect: 100-continue with timeout
7868 if (expect_100_continue && CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND > 0) {
7869 time_t sec = CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND / 1000;
7870 time_t usec = (CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND % 1000) * 1000;
7871 auto ret = detail::select_read(strm.socket(), sec, usec);
7872 if (ret <= 0) {
7873 // Timeout or error: send body anyway (server didn't respond in time)
7874 if (!write_request_body(strm, req, error)) { return false; }
7875 expect_100_continue = false; // Switch to normal response handling
7876 }
7877 }
7878
7879 // Receive response and headers
7880 // When using Expect: 100-continue, don't auto-skip `100 Continue` response
7881 if (!read_response_line(strm, req, res, !expect_100_continue) ||
7882 !detail::read_headers(strm, res.headers)) {
7883 if (write_request_success) { error = Error::Read; }
7884 output_error_log(error, &req);
7885 return false;
7886 }
7887
7888 if (!write_request_success) { return false; }
7889
7890 // Handle Expect: 100-continue response
7891 if (expect_100_continue) {
7892 if (res.status == StatusCode::Continue_100) {
7893 // Server accepted, send the body
7894 if (!write_request_body(strm, req, error)) { return false; }
7895
7896 // Read the actual response
7897 res.headers.clear();
7898 res.body.clear();
7899 if (!read_response_line(strm, req, res) ||
7900 !detail::read_headers(strm, res.headers)) {
7901 error = Error::Read;
7902 output_error_log(error, &req);
7903 return false;
7904 }
7905 }
7906 // If not 100 Continue, server returned an error; proceed with that response
7907 }
7908
7909 // Body
7910 if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
7911 req.method != "CONNECT") {
7912 auto redirect = 300 < res.status && res.status < 400 &&
7913 res.status != StatusCode::NotModified_304 &&
7914 follow_location_;
7915
7916 if (req.response_handler && !redirect) {
7917 if (!req.response_handler(res)) {
7918 error = Error::Canceled;
7919 output_error_log(error, &req);
7920 return false;
7921 }
7922 }
7923
7924 auto out =
7925 req.content_receiver
7926 ? static_cast<ContentReceiverWithProgress>(
7927 [&](const char *buf, size_t n, size_t off, size_t len) {
7928 if (redirect) { return true; }
7929 auto ret = req.content_receiver(buf, n, off, len);
7930 if (!ret) {
7931 error = Error::Canceled;
7932 output_error_log(error, &req);
7933 }
7934 return ret;
7935 })
7936 : static_cast<ContentReceiverWithProgress>(
7937 [&](const char *buf, size_t n, size_t /*off*/,
7938 size_t /*len*/) {
7939 assert(res.body.size() + n <= res.body.max_size());
7940 res.body.append(buf, n);
7941 return true;
7942 });
7943
7944 auto progress = [&](size_t current, size_t total) {
7945 if (!req.download_progress || redirect) { return true; }
7946 auto ret = req.download_progress(current, total);
7947 if (!ret) {
7948 error = Error::Canceled;
7949 output_error_log(error, &req);
7950 }
7951 return ret;
7952 };
7953
7954 if (res.has_header("Content-Length")) {
7955 if (!req.content_receiver) {
7956 auto len = res.get_header_value_u64("Content-Length");
7957 if (len > res.body.max_size()) {
7958 error = Error::Read;
7959 output_error_log(error, &req);
7960 return false;
7961 }
7962 res.body.reserve(static_cast<size_t>(len));
7963 }
7964 }
7965
7966 if (res.status != StatusCode::NotModified_304) {
7967 int dummy_status;
7968 if (!detail::read_content(strm, res, (std::numeric_limits<size_t>::max)(),
7969 dummy_status, std::move(progress),
7970 std::move(out), decompress_)) {
7971 if (error != Error::Canceled) { error = Error::Read; }
7972 output_error_log(error, &req);
7973 return false;
7974 }
7975 }
7976 }
7977
7978 // Log
7979 output_log(req, res);
7980
7981 return true;
7982}
7983
7984ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
7985 const std::string &boundary, const UploadFormDataItems &items,
7986 const FormDataProviderItems &provider_items) const {
7987 size_t cur_item = 0;
7988 size_t cur_start = 0;
7989 // cur_item and cur_start are copied to within the std::function and
7990 // maintain state between successive calls
7991 return [&, cur_item, cur_start](size_t offset,
7992 DataSink &sink) mutable -> bool {
7993 if (!offset && !items.empty()) {
7994 sink.os << detail::serialize_multipart_formdata(items, boundary, false);
7995 return true;
7996 } else if (cur_item < provider_items.size()) {
7997 if (!cur_start) {
7998 const auto &begin = detail::serialize_multipart_formdata_item_begin(
7999 provider_items[cur_item], boundary);
8000 offset += begin.size();
8001 cur_start = offset;
8002 sink.os << begin;
8003 }
8004
8005 DataSink cur_sink;
8006 auto has_data = true;
8007 cur_sink.write = sink.write;
8008 cur_sink.done = [&]() { has_data = false; };
8009
8010 if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) {
8011 return false;
8012 }
8013
8014 if (!has_data) {
8015 sink.os << detail::serialize_multipart_formdata_item_end();
8016 cur_item++;
8017 cur_start = 0;
8018 }
8019 return true;
8020 } else {
8021 sink.os << detail::serialize_multipart_formdata_finish(boundary);
8022 sink.done();
8023 return true;
8024 }
8025 };
8026}
8027
8028bool ClientImpl::process_socket(
8029 const Socket &socket,
8030 std::chrono::time_point<std::chrono::steady_clock> start_time,
8031 std::function<bool(Stream &strm)> callback) {
8032 return detail::process_client_socket(
8033 socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
8034 write_timeout_usec_, max_timeout_msec_, start_time, std::move(callback));
8035}
8036
8037bool ClientImpl::is_ssl() const { return false; }
8038
8039Result ClientImpl::Get(const std::string &path,
8040 DownloadProgress progress) {
8041 return Get(path, Headers(), std::move(progress));
8042}
8043
8044Result ClientImpl::Get(const std::string &path, const Params ¶ms,
8045 const Headers &headers,
8046 DownloadProgress progress) {
8047 if (params.empty()) { return Get(path, headers); }
8048
8049 std::string path_with_query = append_query_params(path, params);
8050 return Get(path_with_query, headers, std::move(progress));
8051}
8052
8053Result ClientImpl::Get(const std::string &path, const Headers &headers,
8054 DownloadProgress progress) {
8055 Request req;
8056 req.method = "GET";
8057 req.path = path;
8058 req.headers = headers;
8059 req.download_progress = std::move(progress);
8060 if (max_timeout_msec_ > 0) {
8061 req.start_time_ = std::chrono::steady_clock::now();
8062 }
8063
8064 return send_(std::move(req));
8065}
8066
8067Result ClientImpl::Get(const std::string &path,
8068 ContentReceiver content_receiver,
8069 DownloadProgress progress) {
8070 return Get(path, Headers(), nullptr, std::move(content_receiver),
8071 std::move(progress));
8072}
8073
8074Result ClientImpl::Get(const std::string &path, const Headers &headers,
8075 ContentReceiver content_receiver,
8076 DownloadProgress progress) {
8077 return Get(path, headers, nullptr, std::move(content_receiver),
8078 std::move(progress));
8079}
8080
8081Result ClientImpl::Get(const std::string &path,
8082 ResponseHandler response_handler,
8083 ContentReceiver content_receiver,
8084 DownloadProgress progress) {
8085 return Get(path, Headers(), std::move(response_handler),
8086 std::move(content_receiver), std::move(progress));
8087}
8088
8089Result ClientImpl::Get(const std::string &path, const Headers &headers,
8090 ResponseHandler response_handler,
8091 ContentReceiver content_receiver,
8092 DownloadProgress progress) {
8093 Request req;
8094 req.method = "GET";
8095 req.path = path;
8096 req.headers = headers;
8097 req.response_handler = std::move(response_handler);
8098 req.content_receiver =
8099 [content_receiver](const char *data, size_t data_length,
8100 size_t /*offset*/, size_t /*total_length*/) {
8101 return content_receiver(data, data_length);
8102 };
8103 req.download_progress = std::move(progress);
8104 if (max_timeout_msec_ > 0) {
8105 req.start_time_ = std::chrono::steady_clock::now();
8106 }
8107
8108 return send_(std::move(req));
8109}
8110
8111Result ClientImpl::Get(const std::string &path, const Params ¶ms,
8112 const Headers &headers,
8113 ContentReceiver content_receiver,
8114 DownloadProgress progress) {
8115 return Get(path, params, headers, nullptr, std::move(content_receiver),
8116 std::move(progress));
8117}
8118
8119Result ClientImpl::Get(const std::string &path, const Params ¶ms,
8120 const Headers &headers,
8121 ResponseHandler response_handler,
8122 ContentReceiver content_receiver,
8123 DownloadProgress progress) {
8124 if (params.empty()) {
8125 return Get(path, headers, std::move(response_handler),
8126 std::move(content_receiver), std::move(progress));
8127 }
8128
8129 std::string path_with_query = append_query_params(path, params);
8130 return Get(path_with_query, headers, std::move(response_handler),
8131 std::move(content_receiver), std::move(progress));
8132}
8133
8134Result ClientImpl::Head(const std::string &path) {
8135 return Head(path, Headers());
8136}
8137
8138Result ClientImpl::Head(const std::string &path,
8139 const Headers &headers) {
8140 Request req;
8141 req.method = "HEAD";
8142 req.headers = headers;
8143 req.path = path;
8144 if (max_timeout_msec_ > 0) {
8145 req.start_time_ = std::chrono::steady_clock::now();
8146 }
8147
8148 return send_(std::move(req));
8149}
8150
8151Result ClientImpl::Post(const std::string &path) {
8152 return Post(path, std::string(), std::string());
8153}
8154
8155Result ClientImpl::Post(const std::string &path,
8156 const Headers &headers) {
8157 return Post(path, headers, nullptr, 0, std::string());
8158}
8159
8160Result ClientImpl::Post(const std::string &path, const char *body,
8161 size_t content_length,
8162 const std::string &content_type,
8163 UploadProgress progress) {
8164 return Post(path, Headers(), body, content_length, content_type, progress);
8165}
8166
8167Result ClientImpl::Post(const std::string &path, const std::string &body,
8168 const std::string &content_type,
8169 UploadProgress progress) {
8170 return Post(path, Headers(), body, content_type, progress);
8171}
8172
8173Result ClientImpl::Post(const std::string &path, const Params ¶ms) {
8174 return Post(path, Headers(), params);
8175}
8176
8177Result ClientImpl::Post(const std::string &path, size_t content_length,
8178 ContentProvider content_provider,
8179 const std::string &content_type,
8180 UploadProgress progress) {
8181 return Post(path, Headers(), content_length, std::move(content_provider),
8182 content_type, progress);
8183}
8184
8185Result ClientImpl::Post(const std::string &path, size_t content_length,
8186 ContentProvider content_provider,
8187 const std::string &content_type,
8188 ContentReceiver content_receiver,
8189 UploadProgress progress) {
8190 return Post(path, Headers(), content_length, std::move(content_provider),
8191 content_type, std::move(content_receiver), progress);
8192}
8193
8194Result ClientImpl::Post(const std::string &path,
8195 ContentProviderWithoutLength content_provider,
8196 const std::string &content_type,
8197 UploadProgress progress) {
8198 return Post(path, Headers(), std::move(content_provider), content_type,
8199 progress);
8200}
8201
8202Result ClientImpl::Post(const std::string &path,
8203 ContentProviderWithoutLength content_provider,
8204 const std::string &content_type,
8205 ContentReceiver content_receiver,
8206 UploadProgress progress) {
8207 return Post(path, Headers(), std::move(content_provider), content_type,
8208 std::move(content_receiver), progress);
8209}
8210
8211Result ClientImpl::Post(const std::string &path, const Headers &headers,
8212 const Params ¶ms) {
8213 auto query = detail::params_to_query_str(params);
8214 return Post(path, headers, query, "application/x-www-form-urlencoded");
8215}
8216
8217Result ClientImpl::Post(const std::string &path,
8218 const UploadFormDataItems &items,
8219 UploadProgress progress) {
8220 return Post(path, Headers(), items, progress);
8221}
8222
8223Result ClientImpl::Post(const std::string &path, const Headers &headers,
8224 const UploadFormDataItems &items,
8225 UploadProgress progress) {
8226 const auto &boundary = detail::make_multipart_data_boundary();
8227 const auto &content_type =
8228 detail::serialize_multipart_formdata_get_content_type(boundary);
8229 const auto &body = detail::serialize_multipart_formdata(items, boundary);
8230 return Post(path, headers, body, content_type, progress);
8231}
8232
8233Result ClientImpl::Post(const std::string &path, const Headers &headers,
8234 const UploadFormDataItems &items,
8235 const std::string &boundary,
8236 UploadProgress progress) {
8237 if (!detail::is_multipart_boundary_chars_valid(boundary)) {
8238 return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
8239 }
8240
8241 const auto &content_type =
8242 detail::serialize_multipart_formdata_get_content_type(boundary);
8243 const auto &body = detail::serialize_multipart_formdata(items, boundary);
8244 return Post(path, headers, body, content_type, progress);
8245}
8246
8247Result ClientImpl::Post(const std::string &path, const Headers &headers,
8248 const char *body, size_t content_length,
8249 const std::string &content_type,
8250 UploadProgress progress) {
8251 return send_with_content_provider_and_receiver(
8252 "POST", path, headers, body, content_length, nullptr, nullptr,
8253 content_type, nullptr, progress);
8254}
8255
8256Result ClientImpl::Post(const std::string &path, const Headers &headers,
8257 const std::string &body,
8258 const std::string &content_type,
8259 UploadProgress progress) {
8260 return send_with_content_provider_and_receiver(
8261 "POST", path, headers, body.data(), body.size(), nullptr, nullptr,
8262 content_type, nullptr, progress);
8263}
8264
8265Result ClientImpl::Post(const std::string &path, const Headers &headers,
8266 size_t content_length,
8267 ContentProvider content_provider,
8268 const std::string &content_type,
8269 UploadProgress progress) {
8270 return send_with_content_provider_and_receiver(
8271 "POST", path, headers, nullptr, content_length,
8272 std::move(content_provider), nullptr, content_type, nullptr, progress);
8273}
8274
8275Result ClientImpl::Post(const std::string &path, const Headers &headers,
8276 size_t content_length,
8277 ContentProvider content_provider,
8278 const std::string &content_type,
8279 ContentReceiver content_receiver,
8280 DownloadProgress progress) {
8281 return send_with_content_provider_and_receiver(
8282 "POST", path, headers, nullptr, content_length,
8283 std::move(content_provider), nullptr, content_type,
8284 std::move(content_receiver), std::move(progress));
8285}
8286
8287Result ClientImpl::Post(const std::string &path, const Headers &headers,
8288 ContentProviderWithoutLength content_provider,
8289 const std::string &content_type,
8290 UploadProgress progress) {
8291 return send_with_content_provider_and_receiver(
8292 "POST", path, headers, nullptr, 0, nullptr, std::move(content_provider),
8293 content_type, nullptr, progress);
8294}
8295
8296Result ClientImpl::Post(const std::string &path, const Headers &headers,
8297 ContentProviderWithoutLength content_provider,
8298 const std::string &content_type,
8299 ContentReceiver content_receiver,
8300 DownloadProgress progress) {
8301 return send_with_content_provider_and_receiver(
8302 "POST", path, headers, nullptr, 0, nullptr, std::move(content_provider),
8303 content_type, std::move(content_receiver), std::move(progress));
8304}
8305
8306Result ClientImpl::Post(const std::string &path, const Headers &headers,
8307 const UploadFormDataItems &items,
8308 const FormDataProviderItems &provider_items,
8309 UploadProgress progress) {
8310 const auto &boundary = detail::make_multipart_data_boundary();
8311 const auto &content_type =
8312 detail::serialize_multipart_formdata_get_content_type(boundary);
8313 return send_with_content_provider_and_receiver(
8314 "POST", path, headers, nullptr, 0, nullptr,
8315 get_multipart_content_provider(boundary, items, provider_items),
8316 content_type, nullptr, progress);
8317}
8318
8319Result ClientImpl::Post(const std::string &path, const Headers &headers,
8320 const std::string &body,
8321 const std::string &content_type,
8322 ContentReceiver content_receiver,
8323 DownloadProgress progress) {
8324 Request req;
8325 req.method = "POST";
8326 req.path = path;
8327 req.headers = headers;
8328 req.body = body;
8329 req.content_receiver =
8330 [content_receiver](const char *data, size_t data_length,
8331 size_t /*offset*/, size_t /*total_length*/) {
8332 return content_receiver(data, data_length);
8333 };
8334 req.download_progress = std::move(progress);
8335
8336 if (max_timeout_msec_ > 0) {
8337 req.start_time_ = std::chrono::steady_clock::now();
8338 }
8339
8340 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
8341
8342 return send_(std::move(req));
8343}
8344
8345Result ClientImpl::Put(const std::string &path) {
8346 return Put(path, std::string(), std::string());
8347}
8348
8349Result ClientImpl::Put(const std::string &path, const Headers &headers) {
8350 return Put(path, headers, nullptr, 0, std::string());
8351}
8352
8353Result ClientImpl::Put(const std::string &path, const char *body,
8354 size_t content_length,
8355 const std::string &content_type,
8356 UploadProgress progress) {
8357 return Put(path, Headers(), body, content_length, content_type, progress);
8358}
8359
8360Result ClientImpl::Put(const std::string &path, const std::string &body,
8361 const std::string &content_type,
8362 UploadProgress progress) {
8363 return Put(path, Headers(), body, content_type, progress);
8364}
8365
8366Result ClientImpl::Put(const std::string &path, const Params ¶ms) {
8367 return Put(path, Headers(), params);
8368}
8369
8370Result ClientImpl::Put(const std::string &path, size_t content_length,
8371 ContentProvider content_provider,
8372 const std::string &content_type,
8373 UploadProgress progress) {
8374 return Put(path, Headers(), content_length, std::move(content_provider),
8375 content_type, progress);
8376}
8377
8378Result ClientImpl::Put(const std::string &path, size_t content_length,
8379 ContentProvider content_provider,
8380 const std::string &content_type,
8381 ContentReceiver content_receiver,
8382 UploadProgress progress) {
8383 return Put(path, Headers(), content_length, std::move(content_provider),
8384 content_type, std::move(content_receiver), progress);
8385}
8386
8387Result ClientImpl::Put(const std::string &path,
8388 ContentProviderWithoutLength content_provider,
8389 const std::string &content_type,
8390 UploadProgress progress) {
8391 return Put(path, Headers(), std::move(content_provider), content_type,
8392 progress);
8393}
8394
8395Result ClientImpl::Put(const std::string &path,
8396 ContentProviderWithoutLength content_provider,
8397 const std::string &content_type,
8398 ContentReceiver content_receiver,
8399 UploadProgress progress) {
8400 return Put(path, Headers(), std::move(content_provider), content_type,
8401 std::move(content_receiver), progress);
8402}
8403
8404Result ClientImpl::Put(const std::string &path, const Headers &headers,
8405 const Params ¶ms) {
8406 auto query = detail::params_to_query_str(params);
8407 return Put(path, headers, query, "application/x-www-form-urlencoded");
8408}
8409
8410Result ClientImpl::Put(const std::string &path,
8411 const UploadFormDataItems &items,
8412 UploadProgress progress) {
8413 return Put(path, Headers(), items, progress);
8414}
8415
8416Result ClientImpl::Put(const std::string &path, const Headers &headers,
8417 const UploadFormDataItems &items,
8418 UploadProgress progress) {
8419 const auto &boundary = detail::make_multipart_data_boundary();
8420 const auto &content_type =
8421 detail::serialize_multipart_formdata_get_content_type(boundary);
8422 const auto &body = detail::serialize_multipart_formdata(items, boundary);
8423 return Put(path, headers, body, content_type, progress);
8424}
8425
8426Result ClientImpl::Put(const std::string &path, const Headers &headers,
8427 const UploadFormDataItems &items,
8428 const std::string &boundary,
8429 UploadProgress progress) {
8430 if (!detail::is_multipart_boundary_chars_valid(boundary)) {
8431 return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
8432 }
8433
8434 const auto &content_type =
8435 detail::serialize_multipart_formdata_get_content_type(boundary);
8436 const auto &body = detail::serialize_multipart_formdata(items, boundary);
8437 return Put(path, headers, body, content_type, progress);
8438}
8439
8440Result ClientImpl::Put(const std::string &path, const Headers &headers,
8441 const char *body, size_t content_length,
8442 const std::string &content_type,
8443 UploadProgress progress) {
8444 return send_with_content_provider_and_receiver(
8445 "PUT", path, headers, body, content_length, nullptr, nullptr,
8446 content_type, nullptr, progress);
8447}
8448
8449Result ClientImpl::Put(const std::string &path, const Headers &headers,
8450 const std::string &body,
8451 const std::string &content_type,
8452 UploadProgress progress) {
8453 return send_with_content_provider_and_receiver(
8454 "PUT", path, headers, body.data(), body.size(), nullptr, nullptr,
8455 content_type, nullptr, progress);
8456}
8457
8458Result ClientImpl::Put(const std::string &path, const Headers &headers,
8459 size_t content_length,
8460 ContentProvider content_provider,
8461 const std::string &content_type,
8462 UploadProgress progress) {
8463 return send_with_content_provider_and_receiver(
8464 "PUT", path, headers, nullptr, content_length,
8465 std::move(content_provider), nullptr, content_type, nullptr, progress);
8466}
8467
8468Result ClientImpl::Put(const std::string &path, const Headers &headers,
8469 size_t content_length,
8470 ContentProvider content_provider,
8471 const std::string &content_type,
8472 ContentReceiver content_receiver,
8473 UploadProgress progress) {
8474 return send_with_content_provider_and_receiver(
8475 "PUT", path, headers, nullptr, content_length,
8476 std::move(content_provider), nullptr, content_type,
8477 std::move(content_receiver), progress);
8478}
8479
8480Result ClientImpl::Put(const std::string &path, const Headers &headers,
8481 ContentProviderWithoutLength content_provider,
8482 const std::string &content_type,
8483 UploadProgress progress) {
8484 return send_with_content_provider_and_receiver(
8485 "PUT", path, headers, nullptr, 0, nullptr, std::move(content_provider),
8486 content_type, nullptr, progress);
8487}
8488
8489Result ClientImpl::Put(const std::string &path, const Headers &headers,
8490 ContentProviderWithoutLength content_provider,
8491 const std::string &content_type,
8492 ContentReceiver content_receiver,
8493 UploadProgress progress) {
8494 return send_with_content_provider_and_receiver(
8495 "PUT", path, headers, nullptr, 0, nullptr, std::move(content_provider),
8496 content_type, std::move(content_receiver), progress);
8497}
8498
8499Result ClientImpl::Put(const std::string &path, const Headers &headers,
8500 const UploadFormDataItems &items,
8501 const FormDataProviderItems &provider_items,
8502 UploadProgress progress) {
8503 const auto &boundary = detail::make_multipart_data_boundary();
8504 const auto &content_type =
8505 detail::serialize_multipart_formdata_get_content_type(boundary);
8506 return send_with_content_provider_and_receiver(
8507 "PUT", path, headers, nullptr, 0, nullptr,
8508 get_multipart_content_provider(boundary, items, provider_items),
8509 content_type, nullptr, progress);
8510}
8511
8512Result ClientImpl::Put(const std::string &path, const Headers &headers,
8513 const std::string &body,
8514 const std::string &content_type,
8515 ContentReceiver content_receiver,
8516 DownloadProgress progress) {
8517 Request req;
8518 req.method = "PUT";
8519 req.path = path;
8520 req.headers = headers;
8521 req.body = body;
8522 req.content_receiver =
8523 [content_receiver](const char *data, size_t data_length,
8524 size_t /*offset*/, size_t /*total_length*/) {
8525 return content_receiver(data, data_length);
8526 };
8527 req.download_progress = std::move(progress);
8528
8529 if (max_timeout_msec_ > 0) {
8530 req.start_time_ = std::chrono::steady_clock::now();
8531 }
8532
8533 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
8534
8535 return send_(std::move(req));
8536}
8537
8538Result ClientImpl::Patch(const std::string &path) {
8539 return Patch(path, std::string(), std::string());
8540}
8541
8542Result ClientImpl::Patch(const std::string &path, const Headers &headers,
8543 UploadProgress progress) {
8544 return Patch(path, headers, nullptr, 0, std::string(), progress);
8545}
8546
8547Result ClientImpl::Patch(const std::string &path, const char *body,
8548 size_t content_length,
8549 const std::string &content_type,
8550 UploadProgress progress) {
8551 return Patch(path, Headers(), body, content_length, content_type, progress);
8552}
8553
8554Result ClientImpl::Patch(const std::string &path,
8555 const std::string &body,
8556 const std::string &content_type,
8557 UploadProgress progress) {
8558 return Patch(path, Headers(), body, content_type, progress);
8559}
8560
8561Result ClientImpl::Patch(const std::string &path, const Params ¶ms) {
8562 return Patch(path, Headers(), params);
8563}
8564
8565Result ClientImpl::Patch(const std::string &path, size_t content_length,
8566 ContentProvider content_provider,
8567 const std::string &content_type,
8568 UploadProgress progress) {
8569 return Patch(path, Headers(), content_length, std::move(content_provider),
8570 content_type, progress);
8571}
8572
8573Result ClientImpl::Patch(const std::string &path, size_t content_length,
8574 ContentProvider content_provider,
8575 const std::string &content_type,
8576 ContentReceiver content_receiver,
8577 UploadProgress progress) {
8578 return Patch(path, Headers(), content_length, std::move(content_provider),
8579 content_type, std::move(content_receiver), progress);
8580}
8581
8582Result ClientImpl::Patch(const std::string &path,
8583 ContentProviderWithoutLength content_provider,
8584 const std::string &content_type,
8585 UploadProgress progress) {
8586 return Patch(path, Headers(), std::move(content_provider), content_type,
8587 progress);
8588}
8589
8590Result ClientImpl::Patch(const std::string &path,
8591 ContentProviderWithoutLength content_provider,
8592 const std::string &content_type,
8593 ContentReceiver content_receiver,
8594 UploadProgress progress) {
8595 return Patch(path, Headers(), std::move(content_provider), content_type,
8596 std::move(content_receiver), progress);
8597}
8598
8599Result ClientImpl::Patch(const std::string &path, const Headers &headers,
8600 const Params ¶ms) {
8601 auto query = detail::params_to_query_str(params);
8602 return Patch(path, headers, query, "application/x-www-form-urlencoded");
8603}
8604
8605Result ClientImpl::Patch(const std::string &path,
8606 const UploadFormDataItems &items,
8607 UploadProgress progress) {
8608 return Patch(path, Headers(), items, progress);
8609}
8610
8611Result ClientImpl::Patch(const std::string &path, const Headers &headers,
8612 const UploadFormDataItems &items,
8613 UploadProgress progress) {
8614 const auto &boundary = detail::make_multipart_data_boundary();
8615 const auto &content_type =
8616 detail::serialize_multipart_formdata_get_content_type(boundary);
8617 const auto &body = detail::serialize_multipart_formdata(items, boundary);
8618 return Patch(path, headers, body, content_type, progress);
8619}
8620
8621Result ClientImpl::Patch(const std::string &path, const Headers &headers,
8622 const UploadFormDataItems &items,
8623 const std::string &boundary,
8624 UploadProgress progress) {
8625 if (!detail::is_multipart_boundary_chars_valid(boundary)) {
8626 return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
8627 }
8628
8629 const auto &content_type =
8630 detail::serialize_multipart_formdata_get_content_type(boundary);
8631 const auto &body = detail::serialize_multipart_formdata(items, boundary);
8632 return Patch(path, headers, body, content_type, progress);
8633}
8634
8635Result ClientImpl::Patch(const std::string &path, const Headers &headers,
8636 const char *body, size_t content_length,
8637 const std::string &content_type,
8638 UploadProgress progress) {
8639 return send_with_content_provider_and_receiver(
8640 "PATCH", path, headers, body, content_length, nullptr, nullptr,
8641 content_type, nullptr, progress);
8642}
8643
8644Result ClientImpl::Patch(const std::string &path, const Headers &headers,
8645 const std::string &body,
8646 const std::string &content_type,
8647 UploadProgress progress) {
8648 return send_with_content_provider_and_receiver(
8649 "PATCH", path, headers, body.data(), body.size(), nullptr, nullptr,
8650 content_type, nullptr, progress);
8651}
8652
8653Result ClientImpl::Patch(const std::string &path, const Headers &headers,
8654 size_t content_length,
8655 ContentProvider content_provider,
8656 const std::string &content_type,
8657 UploadProgress progress) {
8658 return send_with_content_provider_and_receiver(
8659 "PATCH", path, headers, nullptr, content_length,
8660 std::move(content_provider), nullptr, content_type, nullptr, progress);
8661}
8662
8663Result ClientImpl::Patch(const std::string &path, const Headers &headers,
8664 size_t content_length,
8665 ContentProvider content_provider,
8666 const std::string &content_type,
8667 ContentReceiver content_receiver,
8668 UploadProgress progress) {
8669 return send_with_content_provider_and_receiver(
8670 "PATCH", path, headers, nullptr, content_length,
8671 std::move(content_provider), nullptr, content_type,
8672 std::move(content_receiver), progress);
8673}
8674
8675Result ClientImpl::Patch(const std::string &path, const Headers &headers,
8676 ContentProviderWithoutLength content_provider,
8677 const std::string &content_type,
8678 UploadProgress progress) {
8679 return send_with_content_provider_and_receiver(
8680 "PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider),
8681 content_type, nullptr, progress);
8682}
8683
8684Result ClientImpl::Patch(const std::string &path, const Headers &headers,
8685 ContentProviderWithoutLength content_provider,
8686 const std::string &content_type,
8687 ContentReceiver content_receiver,
8688 UploadProgress progress) {
8689 return send_with_content_provider_and_receiver(
8690 "PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider),
8691 content_type, std::move(content_receiver), progress);
8692}
8693
8694Result ClientImpl::Patch(const std::string &path, const Headers &headers,
8695 const UploadFormDataItems &items,
8696 const FormDataProviderItems &provider_items,
8697 UploadProgress progress) {
8698 const auto &boundary = detail::make_multipart_data_boundary();
8699 const auto &content_type =
8700 detail::serialize_multipart_formdata_get_content_type(boundary);
8701 return send_with_content_provider_and_receiver(
8702 "PATCH", path, headers, nullptr, 0, nullptr,
8703 get_multipart_content_provider(boundary, items, provider_items),
8704 content_type, nullptr, progress);
8705}
8706
8707Result ClientImpl::Patch(const std::string &path, const Headers &headers,
8708 const std::string &body,
8709 const std::string &content_type,
8710 ContentReceiver content_receiver,
8711 DownloadProgress progress) {
8712 Request req;
8713 req.method = "PATCH";
8714 req.path = path;
8715 req.headers = headers;
8716 req.body = body;
8717 req.content_receiver =
8718 [content_receiver](const char *data, size_t data_length,
8719 size_t /*offset*/, size_t /*total_length*/) {
8720 return content_receiver(data, data_length);
8721 };
8722 req.download_progress = std::move(progress);
8723
8724 if (max_timeout_msec_ > 0) {
8725 req.start_time_ = std::chrono::steady_clock::now();
8726 }
8727
8728 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
8729
8730 return send_(std::move(req));
8731}
8732
8733Result ClientImpl::Delete(const std::string &path,
8734 DownloadProgress progress) {
8735 return Delete(path, Headers(), std::string(), std::string(), progress);
8736}
8737
8738Result ClientImpl::Delete(const std::string &path,
8739 const Headers &headers,
8740 DownloadProgress progress) {
8741 return Delete(path, headers, std::string(), std::string(), progress);
8742}
8743
8744Result ClientImpl::Delete(const std::string &path, const char *body,
8745 size_t content_length,
8746 const std::string &content_type,
8747 DownloadProgress progress) {
8748 return Delete(path, Headers(), body, content_length, content_type, progress);
8749}
8750
8751Result ClientImpl::Delete(const std::string &path,
8752 const std::string &body,
8753 const std::string &content_type,
8754 DownloadProgress progress) {
8755 return Delete(path, Headers(), body.data(), body.size(), content_type,
8756 progress);
8757}
8758
8759Result ClientImpl::Delete(const std::string &path,
8760 const Headers &headers,
8761 const std::string &body,
8762 const std::string &content_type,
8763 DownloadProgress progress) {
8764 return Delete(path, headers, body.data(), body.size(), content_type,
8765 progress);
8766}
8767
8768Result ClientImpl::Delete(const std::string &path, const Params ¶ms,
8769 DownloadProgress progress) {
8770 return Delete(path, Headers(), params, progress);
8771}
8772
8773Result ClientImpl::Delete(const std::string &path,
8774 const Headers &headers, const Params ¶ms,
8775 DownloadProgress progress) {
8776 auto query = detail::params_to_query_str(params);
8777 return Delete(path, headers, query, "application/x-www-form-urlencoded",
8778 progress);
8779}
8780
8781Result ClientImpl::Delete(const std::string &path,
8782 const Headers &headers, const char *body,
8783 size_t content_length,
8784 const std::string &content_type,
8785 DownloadProgress progress) {
8786 Request req;
8787 req.method = "DELETE";
8788 req.headers = headers;
8789 req.path = path;
8790 req.download_progress = std::move(progress);
8791 if (max_timeout_msec_ > 0) {
8792 req.start_time_ = std::chrono::steady_clock::now();
8793 }
8794
8795 if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
8796 req.body.assign(body, content_length);
8797
8798 return send_(std::move(req));
8799}
8800
8801Result ClientImpl::Options(const std::string &path) {
8802 return Options(path, Headers());
8803}
8804
8805Result ClientImpl::Options(const std::string &path,
8806 const Headers &headers) {
8807 Request req;
8808 req.method = "OPTIONS";
8809 req.headers = headers;
8810 req.path = path;
8811 if (max_timeout_msec_ > 0) {
8812 req.start_time_ = std::chrono::steady_clock::now();
8813 }
8814
8815 return send_(std::move(req));
8816}
8817
8818void ClientImpl::stop() {
8819 std::lock_guard<std::mutex> guard(socket_mutex_);
8820
8821 // If there is anything ongoing right now, the ONLY thread-safe thing we can
8822 // do is to shutdown_socket, so that threads using this socket suddenly
8823 // discover they can't read/write any more and error out. Everything else
8824 // (closing the socket, shutting ssl down) is unsafe because these actions
8825 // are not thread-safe.
8826 if (socket_requests_in_flight_ > 0) {
8827 shutdown_socket(socket_);
8828
8829 // Aside from that, we set a flag for the socket to be closed when we're
8830 // done.
8831 socket_should_be_closed_when_request_is_done_ = true;
8832 return;
8833 }
8834
8835 // Otherwise, still holding the mutex, we can shut everything down ourselves
8836 shutdown_ssl(socket_, true);
8837 shutdown_socket(socket_);
8838 close_socket(socket_);
8839}
8840
8841std::string ClientImpl::host() const { return host_; }
8842
8843int ClientImpl::port() const { return port_; }
8844
8845size_t ClientImpl::is_socket_open() const {
8846 std::lock_guard<std::mutex> guard(socket_mutex_);
8847 return socket_.is_open();
8848}
8849
8850socket_t ClientImpl::socket() const { return socket_.sock; }
8851
8852void ClientImpl::set_connection_timeout(time_t sec, time_t usec) {
8853 connection_timeout_sec_ = sec;
8854 connection_timeout_usec_ = usec;
8855}
8856
8857void ClientImpl::set_read_timeout(time_t sec, time_t usec) {
8858 read_timeout_sec_ = sec;
8859 read_timeout_usec_ = usec;
8860}
8861
8862void ClientImpl::set_write_timeout(time_t sec, time_t usec) {
8863 write_timeout_sec_ = sec;
8864 write_timeout_usec_ = usec;
8865}
8866
8867void ClientImpl::set_max_timeout(time_t msec) {
8868 max_timeout_msec_ = msec;
8869}
8870
8871void ClientImpl::set_basic_auth(const std::string &username,
8872 const std::string &password) {
8873 basic_auth_username_ = username;
8874 basic_auth_password_ = password;
8875}
8876
8877void ClientImpl::set_bearer_token_auth(const std::string &token) {
8878 bearer_token_auth_token_ = token;
8879}
8880
8881#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8882void ClientImpl::set_digest_auth(const std::string &username,
8883 const std::string &password) {
8884 digest_auth_username_ = username;
8885 digest_auth_password_ = password;
8886}
8887#endif
8888
8889void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; }
8890
8891void ClientImpl::set_follow_location(bool on) { follow_location_ = on; }
8892
8893void ClientImpl::set_path_encode(bool on) { path_encode_ = on; }
8894
8895void
8896ClientImpl::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
8897 addr_map_ = std::move(addr_map);
8898}
8899
8900void ClientImpl::set_default_headers(Headers headers) {
8901 default_headers_ = std::move(headers);
8902}
8903
8904void ClientImpl::set_header_writer(
8905 std::function<ssize_t(Stream &, Headers &)> const &writer) {
8906 header_writer_ = writer;
8907}
8908
8909void ClientImpl::set_address_family(int family) {
8910 address_family_ = family;
8911}
8912
8913void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
8914
8915void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
8916
8917void ClientImpl::set_socket_options(SocketOptions socket_options) {
8918 socket_options_ = std::move(socket_options);
8919}
8920
8921void ClientImpl::set_compress(bool on) { compress_ = on; }
8922
8923void ClientImpl::set_decompress(bool on) { decompress_ = on; }
8924
8925void ClientImpl::set_interface(const std::string &intf) {
8926 interface_ = intf;
8927}
8928
8929void ClientImpl::set_proxy(const std::string &host, int port) {
8930 proxy_host_ = host;
8931 proxy_port_ = port;
8932}
8933
8934void ClientImpl::set_proxy_basic_auth(const std::string &username,
8935 const std::string &password) {
8936 proxy_basic_auth_username_ = username;
8937 proxy_basic_auth_password_ = password;
8938}
8939
8940void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) {
8941 proxy_bearer_token_auth_token_ = token;
8942}
8943
8944#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8945void ClientImpl::set_proxy_digest_auth(const std::string &username,
8946 const std::string &password) {
8947 proxy_digest_auth_username_ = username;
8948 proxy_digest_auth_password_ = password;
8949}
8950
8951void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path,
8952 const std::string &ca_cert_dir_path) {
8953 ca_cert_file_path_ = ca_cert_file_path;
8954 ca_cert_dir_path_ = ca_cert_dir_path;
8955}
8956
8957void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) {
8958 if (ca_cert_store && ca_cert_store != ca_cert_store_) {
8959 ca_cert_store_ = ca_cert_store;
8960 }
8961}
8962
8963X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert,
8964 std::size_t size) const {
8965 auto mem = BIO_new_mem_buf(ca_cert, static_cast<int>(size));
8966 auto se = detail::scope_exit([&] { BIO_free_all(mem); });
8967 if (!mem) { return nullptr; }
8968
8969 auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
8970 if (!inf) { return nullptr; }
8971
8972 auto cts = X509_STORE_new();
8973 if (cts) {
8974 for (auto i = 0; i < static_cast<int>(sk_X509_INFO_num(inf)); i++) {
8975 auto itmp = sk_X509_INFO_value(inf, i);
8976 if (!itmp) { continue; }
8977
8978 if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); }
8979 if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); }
8980 }
8981 }
8982
8983 sk_X509_INFO_pop_free(inf, X509_INFO_free);
8984 return cts;
8985}
8986
8987void ClientImpl::enable_server_certificate_verification(bool enabled) {
8988 server_certificate_verification_ = enabled;
8989}
8990
8991void ClientImpl::enable_server_hostname_verification(bool enabled) {
8992 server_hostname_verification_ = enabled;
8993}
8994
8995void ClientImpl::set_server_certificate_verifier(
8996 std::function<SSLVerifierResponse(SSL *ssl)> verifier) {
8997 server_certificate_verifier_ = verifier;
8998}
8999#endif
9000
9001void ClientImpl::set_logger(Logger logger) {
9002 logger_ = std::move(logger);
9003}
9004
9005void ClientImpl::set_error_logger(ErrorLogger error_logger) {
9006 error_logger_ = std::move(error_logger);
9007}
9008
9009/*
9010 * SSL Implementation
9011 */
9012#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
9013namespace detail {
9014
9015bool is_ip_address(const std::string &host) {
9016 struct in_addr addr4;
9017 struct in6_addr addr6;
9018 return inet_pton(AF_INET, host.c_str(), &addr4) == 1 ||
9019 inet_pton(AF_INET6, host.c_str(), &addr6) == 1;
9020}
9021
9022template <typename U, typename V>
9023SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex,
9024 U SSL_connect_or_accept, V setup) {
9025 SSL *ssl = nullptr;
9026 {
9027 std::lock_guard<std::mutex> guard(ctx_mutex);
9028 ssl = SSL_new(ctx);
9029 }
9030
9031 if (ssl) {
9032 set_nonblocking(sock, true);
9033 auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
9034 BIO_set_nbio(bio, 1);
9035 SSL_set_bio(ssl, bio, bio);
9036
9037 if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) {
9038 SSL_shutdown(ssl);
9039 {
9040 std::lock_guard<std::mutex> guard(ctx_mutex);
9041 SSL_free(ssl);
9042 }
9043 set_nonblocking(sock, false);
9044 return nullptr;
9045 }
9046 BIO_set_nbio(bio, 0);
9047 set_nonblocking(sock, false);
9048 }
9049
9050 return ssl;
9051}
9052
9053void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, socket_t sock,
9054 bool shutdown_gracefully) {
9055 // sometimes we may want to skip this to try to avoid SIGPIPE if we know
9056 // the remote has closed the network connection
9057 // Note that it is not always possible to avoid SIGPIPE, this is merely a
9058 // best-efforts.
9059 if (shutdown_gracefully) {
9060 (void)(sock);
9061 // SSL_shutdown() returns 0 on first call (indicating close_notify alert
9062 // sent) and 1 on subsequent call (indicating close_notify alert received)
9063 if (SSL_shutdown(ssl) == 0) {
9064 // Expected to return 1, but even if it doesn't, we free ssl
9065 SSL_shutdown(ssl);
9066 }
9067 }
9068
9069 std::lock_guard<std::mutex> guard(ctx_mutex);
9070 SSL_free(ssl);
9071}
9072
9073template <typename U>
9074bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl,
9075 U ssl_connect_or_accept,
9076 time_t timeout_sec, time_t timeout_usec,
9077 int *ssl_error) {
9078 auto res = 0;
9079 while ((res = ssl_connect_or_accept(ssl)) != 1) {
9080 auto err = SSL_get_error(ssl, res);
9081 switch (err) {
9082 case SSL_ERROR_WANT_READ:
9083 if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; }
9084 break;
9085 case SSL_ERROR_WANT_WRITE:
9086 if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; }
9087 break;
9088 default: break;
9089 }
9090 if (ssl_error) { *ssl_error = err; }
9091 return false;
9092 }
9093 return true;
9094}
9095
9096template <typename T>
9097bool process_server_socket_ssl(
9098 const std::atomic<socket_t> &svr_sock, SSL *ssl, socket_t sock,
9099 size_t keep_alive_max_count, time_t keep_alive_timeout_sec,
9100 time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
9101 time_t write_timeout_usec, T callback) {
9102 return process_server_socket_core(
9103 svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
9104 [&](bool close_connection, bool &connection_closed) {
9105 SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
9106 write_timeout_sec, write_timeout_usec);
9107 return callback(strm, close_connection, connection_closed);
9108 });
9109}
9110
9111template <typename T>
9112bool process_client_socket_ssl(
9113 SSL *ssl, socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
9114 time_t write_timeout_sec, time_t write_timeout_usec,
9115 time_t max_timeout_msec,
9116 std::chrono::time_point<std::chrono::steady_clock> start_time, T callback) {
9117 SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec,
9118 write_timeout_sec, write_timeout_usec, max_timeout_msec,
9119 start_time);
9120 return callback(strm);
9121}
9122
9123// SSL socket stream implementation
9124SSLSocketStream::SSLSocketStream(
9125 socket_t sock, SSL *ssl, time_t read_timeout_sec, time_t read_timeout_usec,
9126 time_t write_timeout_sec, time_t write_timeout_usec,
9127 time_t max_timeout_msec,
9128 std::chrono::time_point<std::chrono::steady_clock> start_time)
9129 : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec),
9130 read_timeout_usec_(read_timeout_usec),
9131 write_timeout_sec_(write_timeout_sec),
9132 write_timeout_usec_(write_timeout_usec),
9133 max_timeout_msec_(max_timeout_msec), start_time_(start_time) {
9134 SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
9135}
9136
9137SSLSocketStream::~SSLSocketStream() = default;
9138
9139bool SSLSocketStream::is_readable() const {
9140 return SSL_pending(ssl_) > 0;
9141}
9142
9143bool SSLSocketStream::wait_readable() const {
9144 if (max_timeout_msec_ <= 0) {
9145 return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
9146 }
9147
9148 time_t read_timeout_sec;
9149 time_t read_timeout_usec;
9150 calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
9151 read_timeout_usec_, read_timeout_sec, read_timeout_usec);
9152
9153 return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
9154}
9155
9156bool SSLSocketStream::wait_writable() const {
9157 return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
9158 is_socket_alive(sock_) && !is_ssl_peer_could_be_closed(ssl_, sock_);
9159}
9160
9161ssize_t SSLSocketStream::read(char *ptr, size_t size) {
9162 if (SSL_pending(ssl_) > 0) {
9163 auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
9164 if (ret == 0) { error_ = Error::ConnectionClosed; }
9165 return ret;
9166 } else if (wait_readable()) {
9167 auto ret = SSL_read(ssl_, ptr, static_cast<int>(size));
9168 if (ret < 0) {
9169 auto err = SSL_get_error(ssl_, ret);
9170 auto n = 1000;
9171#ifdef _WIN32
9172 while (--n >= 0 && (err == SSL_ERROR_WANT_READ ||
9173 (err == SSL_ERROR_SYSCALL &&
9174 WSAGetLastError() == WSAETIMEDOUT))) {
9175#else
9176 while (--n >= 0 && err == SSL_ERROR_WANT_READ) {
9177#endif
9178 if (SSL_pending(ssl_) > 0) {
9179 return SSL_read(ssl_, ptr, static_cast<int>(size));
9180 } else if (wait_readable()) {
9181 std::this_thread::sleep_for(std::chrono::microseconds{10});
9182 ret = SSL_read(ssl_, ptr, static_cast<int>(size));
9183 if (ret >= 0) { return ret; }
9184 err = SSL_get_error(ssl_, ret);
9185 } else {
9186 break;
9187 }
9188 }
9189 assert(ret < 0);
9190 } else if (ret == 0) {
9191 error_ = Error::ConnectionClosed;
9192 }
9193 return ret;
9194 } else {
9195 error_ = Error::Timeout;
9196 return -1;
9197 }
9198}
9199
9200ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
9201 if (wait_writable()) {
9202 auto handle_size = static_cast<int>(
9203 std::min<size_t>(size, (std::numeric_limits<int>::max)()));
9204
9205 auto ret = SSL_write(ssl_, ptr, static_cast<int>(handle_size));
9206 if (ret < 0) {
9207 auto err = SSL_get_error(ssl_, ret);
9208 auto n = 1000;
9209#ifdef _WIN32
9210 while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE ||
9211 (err == SSL_ERROR_SYSCALL &&
9212 WSAGetLastError() == WSAETIMEDOUT))) {
9213#else
9214 while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) {
9215#endif
9216 if (wait_writable()) {
9217 std::this_thread::sleep_for(std::chrono::microseconds{10});
9218 ret = SSL_write(ssl_, ptr, static_cast<int>(handle_size));
9219 if (ret >= 0) { return ret; }
9220 err = SSL_get_error(ssl_, ret);
9221 } else {
9222 break;
9223 }
9224 }
9225 assert(ret < 0);
9226 }
9227 return ret;
9228 }
9229 return -1;
9230}
9231
9232void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
9233 int &port) const {
9234 detail::get_remote_ip_and_port(sock_, ip, port);
9235}
9236
9237void SSLSocketStream::get_local_ip_and_port(std::string &ip,
9238 int &port) const {
9239 detail::get_local_ip_and_port(sock_, ip, port);
9240}
9241
9242socket_t SSLSocketStream::socket() const { return sock_; }
9243
9244time_t SSLSocketStream::duration() const {
9245 return std::chrono::duration_cast<std::chrono::milliseconds>(
9246 std::chrono::steady_clock::now() - start_time_)
9247 .count();
9248}
9249
9250} // namespace detail
9251
9252// SSL HTTP server implementation
9253SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
9254 const char *client_ca_cert_file_path,
9255 const char *client_ca_cert_dir_path,
9256 const char *private_key_password) {
9257 ctx_ = SSL_CTX_new(TLS_server_method());
9258
9259 if (ctx_) {
9260 SSL_CTX_set_options(ctx_,
9261 SSL_OP_NO_COMPRESSION |
9262 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
9263
9264 SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
9265
9266 if (private_key_password != nullptr && (private_key_password[0] != '\0')) {
9267 SSL_CTX_set_default_passwd_cb_userdata(
9268 ctx_,
9269 reinterpret_cast<void *>(const_cast<char *>(private_key_password)));
9270 }
9271
9272 if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 ||
9273 SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) !=
9274 1 ||
9275 SSL_CTX_check_private_key(ctx_) != 1) {
9276 last_ssl_error_ = static_cast<int>(ERR_get_error());
9277 SSL_CTX_free(ctx_);
9278 ctx_ = nullptr;
9279 } else if (client_ca_cert_file_path || client_ca_cert_dir_path) {
9280 SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path,
9281 client_ca_cert_dir_path);
9282
9283 // Set client CA list to be sent to clients during TLS handshake
9284 if (client_ca_cert_file_path) {
9285 auto ca_list = SSL_load_client_CA_file(client_ca_cert_file_path);
9286 if (ca_list != nullptr) {
9287 SSL_CTX_set_client_CA_list(ctx_, ca_list);
9288 } else {
9289 // Failed to load client CA list, but we continue since
9290 // SSL_CTX_load_verify_locations already succeeded and
9291 // certificate verification will still work
9292 last_ssl_error_ = static_cast<int>(ERR_get_error());
9293 }
9294 }
9295
9296 SSL_CTX_set_verify(
9297 ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
9298 }
9299 }
9300}
9301
9302SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key,
9303 X509_STORE *client_ca_cert_store) {
9304 ctx_ = SSL_CTX_new(TLS_server_method());
9305
9306 if (ctx_) {
9307 SSL_CTX_set_options(ctx_,
9308 SSL_OP_NO_COMPRESSION |
9309 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
9310
9311 SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
9312
9313 if (SSL_CTX_use_certificate(ctx_, cert) != 1 ||
9314 SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) {
9315 SSL_CTX_free(ctx_);
9316 ctx_ = nullptr;
9317 } else if (client_ca_cert_store) {
9318 SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
9319
9320 // Extract CA names from the store and set them as the client CA list
9321 auto ca_list = extract_ca_names_from_x509_store(client_ca_cert_store);
9322 if (ca_list) {
9323 SSL_CTX_set_client_CA_list(ctx_, ca_list);
9324 } else {
9325 // Failed to extract CA names, record the error
9326 last_ssl_error_ = static_cast<int>(ERR_get_error());
9327 }
9328
9329 SSL_CTX_set_verify(
9330 ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr);
9331 }
9332 }
9333}
9334
9335SSLServer::SSLServer(
9336 const std::function<bool(SSL_CTX &ssl_ctx)> &setup_ssl_ctx_callback) {
9337 ctx_ = SSL_CTX_new(TLS_method());
9338 if (ctx_) {
9339 if (!setup_ssl_ctx_callback(*ctx_)) {
9340 SSL_CTX_free(ctx_);
9341 ctx_ = nullptr;
9342 }
9343 }
9344}
9345
9346SSLServer::~SSLServer() {
9347 if (ctx_) { SSL_CTX_free(ctx_); }
9348}
9349
9350bool SSLServer::is_valid() const { return ctx_; }
9351
9352SSL_CTX *SSLServer::ssl_context() const { return ctx_; }
9353
9354void SSLServer::update_certs(X509 *cert, EVP_PKEY *private_key,
9355 X509_STORE *client_ca_cert_store) {
9356
9357 std::lock_guard<std::mutex> guard(ctx_mutex_);
9358
9359 SSL_CTX_use_certificate(ctx_, cert);
9360 SSL_CTX_use_PrivateKey(ctx_, private_key);
9361
9362 if (client_ca_cert_store != nullptr) {
9363 SSL_CTX_set_cert_store(ctx_, client_ca_cert_store);
9364 }
9365}
9366
9367bool SSLServer::process_and_close_socket(socket_t sock) {
9368 auto ssl = detail::ssl_new(
9369 sock, ctx_, ctx_mutex_,
9370 [&](SSL *ssl2) {
9371 return detail::ssl_connect_or_accept_nonblocking(
9372 sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_,
9373 &last_ssl_error_);
9374 },
9375 [](SSL * /*ssl2*/) { return true; });
9376
9377 auto ret = false;
9378 if (ssl) {
9379 std::string remote_addr;
9380 int remote_port = 0;
9381 detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
9382
9383 std::string local_addr;
9384 int local_port = 0;
9385 detail::get_local_ip_and_port(sock, local_addr, local_port);
9386
9387 ret = detail::process_server_socket_ssl(
9388 svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
9389 read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
9390 write_timeout_usec_,
9391 [&](Stream &strm, bool close_connection, bool &connection_closed) {
9392 return process_request(strm, remote_addr, remote_port, local_addr,
9393 local_port, close_connection,
9394 connection_closed,
9395 [&](Request &req) { req.ssl = ssl; });
9396 });
9397
9398 // Shutdown gracefully if the result seemed successful, non-gracefully if
9399 // the connection appeared to be closed.
9400 const bool shutdown_gracefully = ret;
9401 detail::ssl_delete(ctx_mutex_, ssl, sock, shutdown_gracefully);
9402 }
9403
9404 detail::shutdown_socket(sock);
9405 detail::close_socket(sock);
9406 return ret;
9407}
9408
9409STACK_OF(X509_NAME) * SSLServer::extract_ca_names_from_x509_store(
9410 X509_STORE *store) {
9411 if (!store) { return nullptr; }
9412
9413 auto ca_list = sk_X509_NAME_new_null();
9414 if (!ca_list) { return nullptr; }
9415
9416 // Get all objects from the store
9417 auto objs = X509_STORE_get0_objects(store);
9418 if (!objs) {
9419 sk_X509_NAME_free(ca_list);
9420 return nullptr;
9421 }
9422
9423 // Iterate through objects and extract certificate subject names
9424 for (int i = 0; i < sk_X509_OBJECT_num(objs); i++) {
9425 auto obj = sk_X509_OBJECT_value(objs, i);
9426 if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
9427 auto cert = X509_OBJECT_get0_X509(obj);
9428 if (cert) {
9429 auto subject = X509_get_subject_name(cert);
9430 if (subject) {
9431 auto name_dup = X509_NAME_dup(subject);
9432 if (name_dup) { sk_X509_NAME_push(ca_list, name_dup); }
9433 }
9434 }
9435 }
9436 }
9437
9438 // If no names were extracted, free the list and return nullptr
9439 if (sk_X509_NAME_num(ca_list) == 0) {
9440 sk_X509_NAME_free(ca_list);
9441 return nullptr;
9442 }
9443
9444 return ca_list;
9445}
9446
9447// SSL HTTP client implementation
9448SSLClient::SSLClient(const std::string &host)
9449 : SSLClient(host, 443, std::string(), std::string()) {}
9450
9451SSLClient::SSLClient(const std::string &host, int port)
9452 : SSLClient(host, port, std::string(), std::string()) {}
9453
9454SSLClient::SSLClient(const std::string &host, int port,
9455 const std::string &client_cert_path,
9456 const std::string &client_key_path,
9457 const std::string &private_key_password)
9458 : ClientImpl(host, port, client_cert_path, client_key_path) {
9459 ctx_ = SSL_CTX_new(TLS_client_method());
9460
9461 SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION);
9462
9463 detail::split(&host_[0], &host_[host_.size()], '.',
9464 [&](const char *b, const char *e) {
9465 host_components_.emplace_back(b, e);
9466 });
9467
9468 if (!client_cert_path.empty() && !client_key_path.empty()) {
9469 if (!private_key_password.empty()) {
9470 SSL_CTX_set_default_passwd_cb_userdata(
9471 ctx_, reinterpret_cast<void *>(
9472 const_cast<char *>(private_key_password.c_str())));
9473 }
9474
9475 if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(),
9476 SSL_FILETYPE_PEM) != 1 ||
9477 SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(),
9478 SSL_FILETYPE_PEM) != 1) {
9479 last_openssl_error_ = ERR_get_error();
9480 SSL_CTX_free(ctx_);
9481 ctx_ = nullptr;
9482 }
9483 }
9484}
9485
9486SSLClient::SSLClient(const std::string &host, int port,
9487 X509 *client_cert, EVP_PKEY *client_key,
9488 const std::string &private_key_password)
9489 : ClientImpl(host, port) {
9490 ctx_ = SSL_CTX_new(TLS_client_method());
9491
9492 detail::split(&host_[0], &host_[host_.size()], '.',
9493 [&](const char *b, const char *e) {
9494 host_components_.emplace_back(b, e);
9495 });
9496
9497 if (client_cert != nullptr && client_key != nullptr) {
9498 if (!private_key_password.empty()) {
9499 SSL_CTX_set_default_passwd_cb_userdata(
9500 ctx_, reinterpret_cast<void *>(
9501 const_cast<char *>(private_key_password.c_str())));
9502 }
9503
9504 if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 ||
9505 SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) {
9506 last_openssl_error_ = ERR_get_error();
9507 SSL_CTX_free(ctx_);
9508 ctx_ = nullptr;
9509 }
9510 }
9511}
9512
9513SSLClient::~SSLClient() {
9514 if (ctx_) { SSL_CTX_free(ctx_); }
9515 // Make sure to shut down SSL since shutdown_ssl will resolve to the
9516 // base function rather than the derived function once we get to the
9517 // base class destructor, and won't free the SSL (causing a leak).
9518 shutdown_ssl_impl(socket_, true);
9519}
9520
9521bool SSLClient::is_valid() const { return ctx_; }
9522
9523void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) {
9524 if (ca_cert_store) {
9525 if (ctx_) {
9526 if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) {
9527 // Free memory allocated for old cert and use new store
9528 // `ca_cert_store`
9529 SSL_CTX_set_cert_store(ctx_, ca_cert_store);
9530 ca_cert_store_ = ca_cert_store;
9531 }
9532 } else {
9533 X509_STORE_free(ca_cert_store);
9534 }
9535 }
9536}
9537
9538void SSLClient::load_ca_cert_store(const char *ca_cert,
9539 std::size_t size) {
9540 set_ca_cert_store(ClientImpl::create_ca_cert_store(ca_cert, size));
9541}
9542
9543long SSLClient::get_openssl_verify_result() const {
9544 return verify_result_;
9545}
9546
9547SSL_CTX *SSLClient::ssl_context() const { return ctx_; }
9548
9549bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) {
9550 if (!is_valid()) {
9551 error = Error::SSLConnection;
9552 return false;
9553 }
9554 return ClientImpl::create_and_connect_socket(socket, error);
9555}
9556
9557// Assumes that socket_mutex_ is locked and that there are no requests in
9558// flight
9559bool SSLClient::connect_with_proxy(
9560 Socket &socket,
9561 std::chrono::time_point<std::chrono::steady_clock> start_time,
9562 Response &res, bool &success, Error &error) {
9563 success = true;
9564 Response proxy_res;
9565 if (!detail::process_client_socket(
9566 socket.sock, read_timeout_sec_, read_timeout_usec_,
9567 write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
9568 start_time, [&](Stream &strm) {
9569 Request req2;
9570 req2.method = "CONNECT";
9571 req2.path =
9572 detail::make_host_and_port_string_always_port(host_, port_);
9573 if (max_timeout_msec_ > 0) {
9574 req2.start_time_ = std::chrono::steady_clock::now();
9575 }
9576 return process_request(strm, req2, proxy_res, false, error);
9577 })) {
9578 // Thread-safe to close everything because we are assuming there are no
9579 // requests in flight
9580 shutdown_ssl(socket, true);
9581 shutdown_socket(socket);
9582 close_socket(socket);
9583 success = false;
9584 return false;
9585 }
9586
9587 if (proxy_res.status == StatusCode::ProxyAuthenticationRequired_407) {
9588 if (!proxy_digest_auth_username_.empty() &&
9589 !proxy_digest_auth_password_.empty()) {
9590 std::map<std::string, std::string> auth;
9591 if (detail::parse_www_authenticate(proxy_res, auth, true)) {
9592 // Close the current socket and create a new one for the authenticated
9593 // request
9594 shutdown_ssl(socket, true);
9595 shutdown_socket(socket);
9596 close_socket(socket);
9597
9598 // Create a new socket for the authenticated CONNECT request
9599 if (!ensure_socket_connection(socket, error)) {
9600 success = false;
9601 output_error_log(error, nullptr);
9602 return false;
9603 }
9604
9605 proxy_res = Response();
9606 if (!detail::process_client_socket(
9607 socket.sock, read_timeout_sec_, read_timeout_usec_,
9608 write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
9609 start_time, [&](Stream &strm) {
9610 Request req3;
9611 req3.method = "CONNECT";
9612 req3.path = detail::make_host_and_port_string_always_port(
9613 host_, port_);
9614 req3.headers.insert(detail::make_digest_authentication_header(
9615 req3, auth, 1, detail::random_string(10),
9616 proxy_digest_auth_username_, proxy_digest_auth_password_,
9617 true));
9618 if (max_timeout_msec_ > 0) {
9619 req3.start_time_ = std::chrono::steady_clock::now();
9620 }
9621 return process_request(strm, req3, proxy_res, false, error);
9622 })) {
9623 // Thread-safe to close everything because we are assuming there are
9624 // no requests in flight
9625 shutdown_ssl(socket, true);
9626 shutdown_socket(socket);
9627 close_socket(socket);
9628 success = false;
9629 return false;
9630 }
9631 }
9632 }
9633 }
9634
9635 // If status code is not 200, proxy request is failed.
9636 // Set error to ProxyConnection and return proxy response
9637 // as the response of the request
9638 if (proxy_res.status != StatusCode::OK_200) {
9639 error = Error::ProxyConnection;
9640 output_error_log(error, nullptr);
9641 res = std::move(proxy_res);
9642 // Thread-safe to close everything because we are assuming there are
9643 // no requests in flight
9644 shutdown_ssl(socket, true);
9645 shutdown_socket(socket);
9646 close_socket(socket);
9647 return false;
9648 }
9649
9650 return true;
9651}
9652
9653bool SSLClient::load_certs() {
9654 auto ret = true;
9655
9656 std::call_once(initialize_cert_, [&]() {
9657 std::lock_guard<std::mutex> guard(ctx_mutex_);
9658 if (!ca_cert_file_path_.empty()) {
9659 if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(),
9660 nullptr)) {
9661 last_openssl_error_ = ERR_get_error();
9662 ret = false;
9663 }
9664 } else if (!ca_cert_dir_path_.empty()) {
9665 if (!SSL_CTX_load_verify_locations(ctx_, nullptr,
9666 ca_cert_dir_path_.c_str())) {
9667 last_openssl_error_ = ERR_get_error();
9668 ret = false;
9669 }
9670 } else if (!ca_cert_store_) {
9671 auto loaded = false;
9672#ifdef _WIN32
9673 loaded =
9674 detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_));
9675#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && TARGET_OS_MAC
9676 loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_));
9677#endif // _WIN32
9678 if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); }
9679 }
9680 });
9681
9682 return ret;
9683}
9684
9685bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
9686 auto ssl = detail::ssl_new(
9687 socket.sock, ctx_, ctx_mutex_,
9688 [&](SSL *ssl2) {
9689 if (server_certificate_verification_) {
9690 if (!load_certs()) {
9691 error = Error::SSLLoadingCerts;
9692 output_error_log(error, nullptr);
9693 return false;
9694 }
9695 SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr);
9696 }
9697
9698 if (!detail::ssl_connect_or_accept_nonblocking(
9699 socket.sock, ssl2, SSL_connect, connection_timeout_sec_,
9700 connection_timeout_usec_, &last_ssl_error_)) {
9701 error = Error::SSLConnection;
9702 output_error_log(error, nullptr);
9703 return false;
9704 }
9705
9706 if (server_certificate_verification_) {
9707 auto verification_status = SSLVerifierResponse::NoDecisionMade;
9708
9709 if (server_certificate_verifier_) {
9710 verification_status = server_certificate_verifier_(ssl2);
9711 }
9712
9713 if (verification_status == SSLVerifierResponse::CertificateRejected) {
9714 last_openssl_error_ = ERR_get_error();
9715 error = Error::SSLServerVerification;
9716 output_error_log(error, nullptr);
9717 return false;
9718 }
9719
9720 if (verification_status == SSLVerifierResponse::NoDecisionMade) {
9721 verify_result_ = SSL_get_verify_result(ssl2);
9722
9723 if (verify_result_ != X509_V_OK) {
9724 last_openssl_error_ = static_cast<unsigned long>(verify_result_);
9725 error = Error::SSLServerVerification;
9726 output_error_log(error, nullptr);
9727 return false;
9728 }
9729
9730 auto server_cert = SSL_get1_peer_certificate(ssl2);
9731 auto se = detail::scope_exit([&] { X509_free(server_cert); });
9732
9733 if (server_cert == nullptr) {
9734 last_openssl_error_ = ERR_get_error();
9735 error = Error::SSLServerVerification;
9736 output_error_log(error, nullptr);
9737 return false;
9738 }
9739
9740 if (server_hostname_verification_) {
9741 if (!verify_host(server_cert)) {
9742 last_openssl_error_ = X509_V_ERR_HOSTNAME_MISMATCH;
9743 error = Error::SSLServerHostnameVerification;
9744 output_error_log(error, nullptr);
9745 return false;
9746 }
9747 }
9748 }
9749 }
9750
9751 return true;
9752 },
9753 [&](SSL *ssl2) {
9754 // Set SNI only if host is not IP address
9755 if (!detail::is_ip_address(host_)) {
9756#if defined(OPENSSL_IS_BORINGSSL)
9757 SSL_set_tlsext_host_name(ssl2, host_.c_str());
9758#else
9759 // NOTE: Direct call instead of using the OpenSSL macro to suppress
9760 // -Wold-style-cast warning
9761 SSL_ctrl(ssl2, SSL_CTRL_SET_TLSEXT_HOSTNAME,
9762 TLSEXT_NAMETYPE_host_name,
9763 static_cast<void *>(const_cast<char *>(host_.c_str())));
9764#endif
9765 }
9766 return true;
9767 });
9768
9769 if (ssl) {
9770 socket.ssl = ssl;
9771 return true;
9772 }
9773
9774 if (ctx_ == nullptr) {
9775 error = Error::SSLConnection;
9776 last_openssl_error_ = ERR_get_error();
9777 }
9778
9779 shutdown_socket(socket);
9780 close_socket(socket);
9781 return false;
9782}
9783
9784void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
9785 shutdown_ssl_impl(socket, shutdown_gracefully);
9786}
9787
9788void SSLClient::shutdown_ssl_impl(Socket &socket,
9789 bool shutdown_gracefully) {
9790 if (socket.sock == INVALID_SOCKET) {
9791 assert(socket.ssl == nullptr);
9792 return;
9793 }
9794 if (socket.ssl) {
9795 detail::ssl_delete(ctx_mutex_, socket.ssl, socket.sock,
9796 shutdown_gracefully);
9797 socket.ssl = nullptr;
9798 }
9799 assert(socket.ssl == nullptr);
9800}
9801
9802bool SSLClient::process_socket(
9803 const Socket &socket,
9804 std::chrono::time_point<std::chrono::steady_clock> start_time,
9805 std::function<bool(Stream &strm)> callback) {
9806 assert(socket.ssl);
9807 return detail::process_client_socket_ssl(
9808 socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
9809 write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, start_time,
9810 std::move(callback));
9811}
9812
9813bool SSLClient::is_ssl() const { return true; }
9814
9815bool SSLClient::verify_host(X509 *server_cert) const {
9816 /* Quote from RFC2818 section 3.1 "Server Identity"
9817
9818 If a subjectAltName extension of type dNSName is present, that MUST
9819 be used as the identity. Otherwise, the (most specific) Common Name
9820 field in the Subject field of the certificate MUST be used. Although
9821 the use of the Common Name is existing practice, it is deprecated and
9822 Certification Authorities are encouraged to use the dNSName instead.
9823
9824 Matching is performed using the matching rules specified by
9825 [RFC2459]. If more than one identity of a given type is present in
9826 the certificate (e.g., more than one dNSName name, a match in any one
9827 of the set is considered acceptable.) Names may contain the wildcard
9828 character * which is considered to match any single domain name
9829 component or component fragment. E.g., *.a.com matches foo.a.com but
9830 not bar.foo.a.com. f*.com matches foo.com but not bar.com.
9831
9832 In some cases, the URI is specified as an IP address rather than a
9833 hostname. In this case, the iPAddress subjectAltName must be present
9834 in the certificate and must exactly match the IP in the URI.
9835
9836 */
9837 return verify_host_with_subject_alt_name(server_cert) ||
9838 verify_host_with_common_name(server_cert);
9839}
9840
9841bool
9842SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const {
9843 auto ret = false;
9844
9845 auto type = GEN_DNS;
9846
9847 struct in6_addr addr6 = {};
9848 struct in_addr addr = {};
9849 size_t addr_len = 0;
9850
9851#ifndef __MINGW32__
9852 if (inet_pton(AF_INET6, host_.c_str(), &addr6)) {
9853 type = GEN_IPADD;
9854 addr_len = sizeof(struct in6_addr);
9855 } else if (inet_pton(AF_INET, host_.c_str(), &addr)) {
9856 type = GEN_IPADD;
9857 addr_len = sizeof(struct in_addr);
9858 }
9859#endif
9860
9861 auto alt_names = static_cast<const struct stack_st_GENERAL_NAME *>(
9862 X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr));
9863
9864 if (alt_names) {
9865 auto dsn_matched = false;
9866 auto ip_matched = false;
9867
9868 auto count = sk_GENERAL_NAME_num(alt_names);
9869
9870 for (decltype(count) i = 0; i < count && !dsn_matched; i++) {
9871 auto val = sk_GENERAL_NAME_value(alt_names, i);
9872 if (!val || val->type != type) { continue; }
9873
9874 auto name =
9875 reinterpret_cast<const char *>(ASN1_STRING_get0_data(val->d.ia5));
9876 if (name == nullptr) { continue; }
9877
9878 auto name_len = static_cast<size_t>(ASN1_STRING_length(val->d.ia5));
9879
9880 switch (type) {
9881 case GEN_DNS: dsn_matched = check_host_name(name, name_len); break;
9882
9883 case GEN_IPADD:
9884 if (!memcmp(&addr6, name, addr_len) || !memcmp(&addr, name, addr_len)) {
9885 ip_matched = true;
9886 }
9887 break;
9888 }
9889 }
9890
9891 if (dsn_matched || ip_matched) { ret = true; }
9892 }
9893
9894 GENERAL_NAMES_free(const_cast<STACK_OF(GENERAL_NAME) *>(
9895 reinterpret_cast<const STACK_OF(GENERAL_NAME) *>(alt_names)));
9896 return ret;
9897}
9898
9899bool SSLClient::verify_host_with_common_name(X509 *server_cert) const {
9900 const auto subject_name = X509_get_subject_name(server_cert);
9901
9902 if (subject_name != nullptr) {
9903 char name[BUFSIZ];
9904 auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName,
9905 name, sizeof(name));
9906
9907 if (name_len != -1) {
9908 return check_host_name(name, static_cast<size_t>(name_len));
9909 }
9910 }
9911
9912 return false;
9913}
9914
9915bool SSLClient::check_host_name(const char *pattern,
9916 size_t pattern_len) const {
9917 // Exact match (case-insensitive)
9918 if (host_.size() == pattern_len &&
9919 detail::case_ignore::equal(host_, std::string(pattern, pattern_len))) {
9920 return true;
9921 }
9922
9923 // Wildcard match
9924 // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
9925 std::vector<std::string> pattern_components;
9926 detail::split(&pattern[0], &pattern[pattern_len], '.',
9927 [&](const char *b, const char *e) {
9928 pattern_components.emplace_back(b, e);
9929 });
9930
9931 if (host_components_.size() != pattern_components.size()) { return false; }
9932
9933 auto itr = pattern_components.begin();
9934 for (const auto &h : host_components_) {
9935 auto &p = *itr;
9936 if (!httplib::detail::case_ignore::equal(p, h) && p != "*") {
9937 bool partial_match = false;
9938 if (!p.empty() && p[p.size() - 1] == '*') {
9939 const auto prefix_length = p.size() - 1;
9940 if (prefix_length == 0) {
9941 partial_match = true;
9942 } else if (h.size() >= prefix_length) {
9943 partial_match =
9944 std::equal(p.begin(),
9945 p.begin() + static_cast<std::string::difference_type>(
9946 prefix_length),
9947 h.begin(), [](const char ca, const char cb) {
9948 return httplib::detail::case_ignore::to_lower(ca) ==
9949 httplib::detail::case_ignore::to_lower(cb);
9950 });
9951 }
9952 }
9953 if (!partial_match) { return false; }
9954 }
9955 ++itr;
9956 }
9957
9958 return true;
9959}
9960#endif
9961
9962// Universal client implementation
9963Client::Client(const std::string &scheme_host_port)
9964 : Client(scheme_host_port, std::string(), std::string()) {}
9965
9966Client::Client(const std::string &scheme_host_port,
9967 const std::string &client_cert_path,
9968 const std::string &client_key_path) {
9969 const static std::regex re(
9970 R"((?:([a-z]+):\/\/)?(?:\[([a-fA-F\d:]+)\]|([^:/?#]+))(?::(\d+))?)");
9971
9972 std::smatch m;
9973 if (std::regex_match(scheme_host_port, m, re)) {
9974 auto scheme = m[1].str();
9975
9976#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
9977 if (!scheme.empty() && (scheme != "http" && scheme != "https")) {
9978#else
9979 if (!scheme.empty() && scheme != "http") {
9980#endif
9981#ifndef CPPHTTPLIB_NO_EXCEPTIONS
9982 std::string msg = "'" + scheme + "' scheme is not supported.";
9983 throw std::invalid_argument(msg);
9984#endif
9985 return;
9986 }
9987
9988 auto is_ssl = scheme == "https";
9989
9990 auto host = m[2].str();
9991 if (host.empty()) { host = m[3].str(); }
9992
9993 auto port_str = m[4].str();
9994 auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80);
9995
9996 if (is_ssl) {
9997#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
9998 cli_ = detail::make_unique<SSLClient>(host, port, client_cert_path,
9999 client_key_path);
10000 is_ssl_ = is_ssl;
10001#endif
10002 } else {
10003 cli_ = detail::make_unique<ClientImpl>(host, port, client_cert_path,
10004 client_key_path);
10005 }
10006 } else {
10007 // NOTE: Update TEST(UniversalClientImplTest, Ipv6LiteralAddress)
10008 // if port param below changes.
10009 cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80,
10010 client_cert_path, client_key_path);
10011 }
10012} // namespace detail
10013
10014Client::Client(const std::string &host, int port)
10015 : cli_(detail::make_unique<ClientImpl>(host, port)) {}
10016
10017Client::Client(const std::string &host, int port,
10018 const std::string &client_cert_path,
10019 const std::string &client_key_path)
10020 : cli_(detail::make_unique<ClientImpl>(host, port, client_cert_path,
10021 client_key_path)) {}
10022
10023Client::~Client() = default;
10024
10025bool Client::is_valid() const {
10026 return cli_ != nullptr && cli_->is_valid();
10027}
10028
10029Result Client::Get(const std::string &path, DownloadProgress progress) {
10030 return cli_->Get(path, std::move(progress));
10031}
10032Result Client::Get(const std::string &path, const Headers &headers,
10033 DownloadProgress progress) {
10034 return cli_->Get(path, headers, std::move(progress));
10035}
10036Result Client::Get(const std::string &path,
10037 ContentReceiver content_receiver,
10038 DownloadProgress progress) {
10039 return cli_->Get(path, std::move(content_receiver), std::move(progress));
10040}
10041Result Client::Get(const std::string &path, const Headers &headers,
10042 ContentReceiver content_receiver,
10043 DownloadProgress progress) {
10044 return cli_->Get(path, headers, std::move(content_receiver),
10045 std::move(progress));
10046}
10047Result Client::Get(const std::string &path,
10048 ResponseHandler response_handler,
10049 ContentReceiver content_receiver,
10050 DownloadProgress progress) {
10051 return cli_->Get(path, std::move(response_handler),
10052 std::move(content_receiver), std::move(progress));
10053}
10054Result Client::Get(const std::string &path, const Headers &headers,
10055 ResponseHandler response_handler,
10056 ContentReceiver content_receiver,
10057 DownloadProgress progress) {
10058 return cli_->Get(path, headers, std::move(response_handler),
10059 std::move(content_receiver), std::move(progress));
10060}
10061Result Client::Get(const std::string &path, const Params ¶ms,
10062 const Headers &headers, DownloadProgress progress) {
10063 return cli_->Get(path, params, headers, std::move(progress));
10064}
10065Result Client::Get(const std::string &path, const Params ¶ms,
10066 const Headers &headers,
10067 ContentReceiver content_receiver,
10068 DownloadProgress progress) {
10069 return cli_->Get(path, params, headers, std::move(content_receiver),
10070 std::move(progress));
10071}
10072Result Client::Get(const std::string &path, const Params ¶ms,
10073 const Headers &headers,
10074 ResponseHandler response_handler,
10075 ContentReceiver content_receiver,
10076 DownloadProgress progress) {
10077 return cli_->Get(path, params, headers, std::move(response_handler),
10078 std::move(content_receiver), std::move(progress));
10079}
10080
10081Result Client::Head(const std::string &path) { return cli_->Head(path); }
10082Result Client::Head(const std::string &path, const Headers &headers) {
10083 return cli_->Head(path, headers);
10084}
10085
10086Result Client::Post(const std::string &path) { return cli_->Post(path); }
10087Result Client::Post(const std::string &path, const Headers &headers) {
10088 return cli_->Post(path, headers);
10089}
10090Result Client::Post(const std::string &path, const char *body,
10091 size_t content_length,
10092 const std::string &content_type,
10093 UploadProgress progress) {
10094 return cli_->Post(path, body, content_length, content_type, progress);
10095}
10096Result Client::Post(const std::string &path, const Headers &headers,
10097 const char *body, size_t content_length,
10098 const std::string &content_type,
10099 UploadProgress progress) {
10100 return cli_->Post(path, headers, body, content_length, content_type,
10101 progress);
10102}
10103Result Client::Post(const std::string &path, const std::string &body,
10104 const std::string &content_type,
10105 UploadProgress progress) {
10106 return cli_->Post(path, body, content_type, progress);
10107}
10108Result Client::Post(const std::string &path, const Headers &headers,
10109 const std::string &body,
10110 const std::string &content_type,
10111 UploadProgress progress) {
10112 return cli_->Post(path, headers, body, content_type, progress);
10113}
10114Result Client::Post(const std::string &path, size_t content_length,
10115 ContentProvider content_provider,
10116 const std::string &content_type,
10117 UploadProgress progress) {
10118 return cli_->Post(path, content_length, std::move(content_provider),
10119 content_type, progress);
10120}
10121Result Client::Post(const std::string &path, size_t content_length,
10122 ContentProvider content_provider,
10123 const std::string &content_type,
10124 ContentReceiver content_receiver,
10125 UploadProgress progress) {
10126 return cli_->Post(path, content_length, std::move(content_provider),
10127 content_type, std::move(content_receiver), progress);
10128}
10129Result Client::Post(const std::string &path,
10130 ContentProviderWithoutLength content_provider,
10131 const std::string &content_type,
10132 UploadProgress progress) {
10133 return cli_->Post(path, std::move(content_provider), content_type, progress);
10134}
10135Result Client::Post(const std::string &path,
10136 ContentProviderWithoutLength content_provider,
10137 const std::string &content_type,
10138 ContentReceiver content_receiver,
10139 UploadProgress progress) {
10140 return cli_->Post(path, std::move(content_provider), content_type,
10141 std::move(content_receiver), progress);
10142}
10143Result Client::Post(const std::string &path, const Headers &headers,
10144 size_t content_length,
10145 ContentProvider content_provider,
10146 const std::string &content_type,
10147 UploadProgress progress) {
10148 return cli_->Post(path, headers, content_length, std::move(content_provider),
10149 content_type, progress);
10150}
10151Result Client::Post(const std::string &path, const Headers &headers,
10152 size_t content_length,
10153 ContentProvider content_provider,
10154 const std::string &content_type,
10155 ContentReceiver content_receiver,
10156 DownloadProgress progress) {
10157 return cli_->Post(path, headers, content_length, std::move(content_provider),
10158 content_type, std::move(content_receiver), progress);
10159}
10160Result Client::Post(const std::string &path, const Headers &headers,
10161 ContentProviderWithoutLength content_provider,
10162 const std::string &content_type,
10163 UploadProgress progress) {
10164 return cli_->Post(path, headers, std::move(content_provider), content_type,
10165 progress);
10166}
10167Result Client::Post(const std::string &path, const Headers &headers,
10168 ContentProviderWithoutLength content_provider,
10169 const std::string &content_type,
10170 ContentReceiver content_receiver,
10171 DownloadProgress progress) {
10172 return cli_->Post(path, headers, std::move(content_provider), content_type,
10173 std::move(content_receiver), progress);
10174}
10175Result Client::Post(const std::string &path, const Params ¶ms) {
10176 return cli_->Post(path, params);
10177}
10178Result Client::Post(const std::string &path, const Headers &headers,
10179 const Params ¶ms) {
10180 return cli_->Post(path, headers, params);
10181}
10182Result Client::Post(const std::string &path,
10183 const UploadFormDataItems &items,
10184 UploadProgress progress) {
10185 return cli_->Post(path, items, progress);
10186}
10187Result Client::Post(const std::string &path, const Headers &headers,
10188 const UploadFormDataItems &items,
10189 UploadProgress progress) {
10190 return cli_->Post(path, headers, items, progress);
10191}
10192Result Client::Post(const std::string &path, const Headers &headers,
10193 const UploadFormDataItems &items,
10194 const std::string &boundary,
10195 UploadProgress progress) {
10196 return cli_->Post(path, headers, items, boundary, progress);
10197}
10198Result Client::Post(const std::string &path, const Headers &headers,
10199 const UploadFormDataItems &items,
10200 const FormDataProviderItems &provider_items,
10201 UploadProgress progress) {
10202 return cli_->Post(path, headers, items, provider_items, progress);
10203}
10204Result Client::Post(const std::string &path, const Headers &headers,
10205 const std::string &body,
10206 const std::string &content_type,
10207 ContentReceiver content_receiver,
10208 DownloadProgress progress) {
10209 return cli_->Post(path, headers, body, content_type,
10210 std::move(content_receiver), progress);
10211}
10212
10213Result Client::Put(const std::string &path) { return cli_->Put(path); }
10214Result Client::Put(const std::string &path, const Headers &headers) {
10215 return cli_->Put(path, headers);
10216}
10217Result Client::Put(const std::string &path, const char *body,
10218 size_t content_length,
10219 const std::string &content_type,
10220 UploadProgress progress) {
10221 return cli_->Put(path, body, content_length, content_type, progress);
10222}
10223Result Client::Put(const std::string &path, const Headers &headers,
10224 const char *body, size_t content_length,
10225 const std::string &content_type,
10226 UploadProgress progress) {
10227 return cli_->Put(path, headers, body, content_length, content_type, progress);
10228}
10229Result Client::Put(const std::string &path, const std::string &body,
10230 const std::string &content_type,
10231 UploadProgress progress) {
10232 return cli_->Put(path, body, content_type, progress);
10233}
10234Result Client::Put(const std::string &path, const Headers &headers,
10235 const std::string &body,
10236 const std::string &content_type,
10237 UploadProgress progress) {
10238 return cli_->Put(path, headers, body, content_type, progress);
10239}
10240Result Client::Put(const std::string &path, size_t content_length,
10241 ContentProvider content_provider,
10242 const std::string &content_type,
10243 UploadProgress progress) {
10244 return cli_->Put(path, content_length, std::move(content_provider),
10245 content_type, progress);
10246}
10247Result Client::Put(const std::string &path, size_t content_length,
10248 ContentProvider content_provider,
10249 const std::string &content_type,
10250 ContentReceiver content_receiver,
10251 UploadProgress progress) {
10252 return cli_->Put(path, content_length, std::move(content_provider),
10253 content_type, std::move(content_receiver), progress);
10254}
10255Result Client::Put(const std::string &path,
10256 ContentProviderWithoutLength content_provider,
10257 const std::string &content_type,
10258 UploadProgress progress) {
10259 return cli_->Put(path, std::move(content_provider), content_type, progress);
10260}
10261Result Client::Put(const std::string &path,
10262 ContentProviderWithoutLength content_provider,
10263 const std::string &content_type,
10264 ContentReceiver content_receiver,
10265 UploadProgress progress) {
10266 return cli_->Put(path, std::move(content_provider), content_type,
10267 std::move(content_receiver), progress);
10268}
10269Result Client::Put(const std::string &path, const Headers &headers,
10270 size_t content_length,
10271 ContentProvider content_provider,
10272 const std::string &content_type,
10273 UploadProgress progress) {
10274 return cli_->Put(path, headers, content_length, std::move(content_provider),
10275 content_type, progress);
10276}
10277Result Client::Put(const std::string &path, const Headers &headers,
10278 size_t content_length,
10279 ContentProvider content_provider,
10280 const std::string &content_type,
10281 ContentReceiver content_receiver,
10282 UploadProgress progress) {
10283 return cli_->Put(path, headers, content_length, std::move(content_provider),
10284 content_type, std::move(content_receiver), progress);
10285}
10286Result Client::Put(const std::string &path, const Headers &headers,
10287 ContentProviderWithoutLength content_provider,
10288 const std::string &content_type,
10289 UploadProgress progress) {
10290 return cli_->Put(path, headers, std::move(content_provider), content_type,
10291 progress);
10292}
10293Result Client::Put(const std::string &path, const Headers &headers,
10294 ContentProviderWithoutLength content_provider,
10295 const std::string &content_type,
10296 ContentReceiver content_receiver,
10297 UploadProgress progress) {
10298 return cli_->Put(path, headers, std::move(content_provider), content_type,
10299 std::move(content_receiver), progress);
10300}
10301Result Client::Put(const std::string &path, const Params ¶ms) {
10302 return cli_->Put(path, params);
10303}
10304Result Client::Put(const std::string &path, const Headers &headers,
10305 const Params ¶ms) {
10306 return cli_->Put(path, headers, params);
10307}
10308Result Client::Put(const std::string &path,
10309 const UploadFormDataItems &items,
10310 UploadProgress progress) {
10311 return cli_->Put(path, items, progress);
10312}
10313Result Client::Put(const std::string &path, const Headers &headers,
10314 const UploadFormDataItems &items,
10315 UploadProgress progress) {
10316 return cli_->Put(path, headers, items, progress);
10317}
10318Result Client::Put(const std::string &path, const Headers &headers,
10319 const UploadFormDataItems &items,
10320 const std::string &boundary,
10321 UploadProgress progress) {
10322 return cli_->Put(path, headers, items, boundary, progress);
10323}
10324Result Client::Put(const std::string &path, const Headers &headers,
10325 const UploadFormDataItems &items,
10326 const FormDataProviderItems &provider_items,
10327 UploadProgress progress) {
10328 return cli_->Put(path, headers, items, provider_items, progress);
10329}
10330Result Client::Put(const std::string &path, const Headers &headers,
10331 const std::string &body,
10332 const std::string &content_type,
10333 ContentReceiver content_receiver,
10334 DownloadProgress progress) {
10335 return cli_->Put(path, headers, body, content_type, content_receiver,
10336 progress);
10337}
10338
10339Result Client::Patch(const std::string &path) {
10340 return cli_->Patch(path);
10341}
10342Result Client::Patch(const std::string &path, const Headers &headers) {
10343 return cli_->Patch(path, headers);
10344}
10345Result Client::Patch(const std::string &path, const char *body,
10346 size_t content_length,
10347 const std::string &content_type,
10348 UploadProgress progress) {
10349 return cli_->Patch(path, body, content_length, content_type, progress);
10350}
10351Result Client::Patch(const std::string &path, const Headers &headers,
10352 const char *body, size_t content_length,
10353 const std::string &content_type,
10354 UploadProgress progress) {
10355 return cli_->Patch(path, headers, body, content_length, content_type,
10356 progress);
10357}
10358Result Client::Patch(const std::string &path, const std::string &body,
10359 const std::string &content_type,
10360 UploadProgress progress) {
10361 return cli_->Patch(path, body, content_type, progress);
10362}
10363Result Client::Patch(const std::string &path, const Headers &headers,
10364 const std::string &body,
10365 const std::string &content_type,
10366 UploadProgress progress) {
10367 return cli_->Patch(path, headers, body, content_type, progress);
10368}
10369Result Client::Patch(const std::string &path, size_t content_length,
10370 ContentProvider content_provider,
10371 const std::string &content_type,
10372 UploadProgress progress) {
10373 return cli_->Patch(path, content_length, std::move(content_provider),
10374 content_type, progress);
10375}
10376Result Client::Patch(const std::string &path, size_t content_length,
10377 ContentProvider content_provider,
10378 const std::string &content_type,
10379 ContentReceiver content_receiver,
10380 UploadProgress progress) {
10381 return cli_->Patch(path, content_length, std::move(content_provider),
10382 content_type, std::move(content_receiver), progress);
10383}
10384Result Client::Patch(const std::string &path,
10385 ContentProviderWithoutLength content_provider,
10386 const std::string &content_type,
10387 UploadProgress progress) {
10388 return cli_->Patch(path, std::move(content_provider), content_type, progress);
10389}
10390Result Client::Patch(const std::string &path,
10391 ContentProviderWithoutLength content_provider,
10392 const std::string &content_type,
10393 ContentReceiver content_receiver,
10394 UploadProgress progress) {
10395 return cli_->Patch(path, std::move(content_provider), content_type,
10396 std::move(content_receiver), progress);
10397}
10398Result Client::Patch(const std::string &path, const Headers &headers,
10399 size_t content_length,
10400 ContentProvider content_provider,
10401 const std::string &content_type,
10402 UploadProgress progress) {
10403 return cli_->Patch(path, headers, content_length, std::move(content_provider),
10404 content_type, progress);
10405}
10406Result Client::Patch(const std::string &path, const Headers &headers,
10407 size_t content_length,
10408 ContentProvider content_provider,
10409 const std::string &content_type,
10410 ContentReceiver content_receiver,
10411 UploadProgress progress) {
10412 return cli_->Patch(path, headers, content_length, std::move(content_provider),
10413 content_type, std::move(content_receiver), progress);
10414}
10415Result Client::Patch(const std::string &path, const Headers &headers,
10416 ContentProviderWithoutLength content_provider,
10417 const std::string &content_type,
10418 UploadProgress progress) {
10419 return cli_->Patch(path, headers, std::move(content_provider), content_type,
10420 progress);
10421}
10422Result Client::Patch(const std::string &path, const Headers &headers,
10423 ContentProviderWithoutLength content_provider,
10424 const std::string &content_type,
10425 ContentReceiver content_receiver,
10426 UploadProgress progress) {
10427 return cli_->Patch(path, headers, std::move(content_provider), content_type,
10428 std::move(content_receiver), progress);
10429}
10430Result Client::Patch(const std::string &path, const Params ¶ms) {
10431 return cli_->Patch(path, params);
10432}
10433Result Client::Patch(const std::string &path, const Headers &headers,
10434 const Params ¶ms) {
10435 return cli_->Patch(path, headers, params);
10436}
10437Result Client::Patch(const std::string &path,
10438 const UploadFormDataItems &items,
10439 UploadProgress progress) {
10440 return cli_->Patch(path, items, progress);
10441}
10442Result Client::Patch(const std::string &path, const Headers &headers,
10443 const UploadFormDataItems &items,
10444 UploadProgress progress) {
10445 return cli_->Patch(path, headers, items, progress);
10446}
10447Result Client::Patch(const std::string &path, const Headers &headers,
10448 const UploadFormDataItems &items,
10449 const std::string &boundary,
10450 UploadProgress progress) {
10451 return cli_->Patch(path, headers, items, boundary, progress);
10452}
10453Result Client::Patch(const std::string &path, const Headers &headers,
10454 const UploadFormDataItems &items,
10455 const FormDataProviderItems &provider_items,
10456 UploadProgress progress) {
10457 return cli_->Patch(path, headers, items, provider_items, progress);
10458}
10459Result Client::Patch(const std::string &path, const Headers &headers,
10460 const std::string &body,
10461 const std::string &content_type,
10462 ContentReceiver content_receiver,
10463 DownloadProgress progress) {
10464 return cli_->Patch(path, headers, body, content_type, content_receiver,
10465 progress);
10466}
10467
10468Result Client::Delete(const std::string &path,
10469 DownloadProgress progress) {
10470 return cli_->Delete(path, progress);
10471}
10472Result Client::Delete(const std::string &path, const Headers &headers,
10473 DownloadProgress progress) {
10474 return cli_->Delete(path, headers, progress);
10475}
10476Result Client::Delete(const std::string &path, const char *body,
10477 size_t content_length,
10478 const std::string &content_type,
10479 DownloadProgress progress) {
10480 return cli_->Delete(path, body, content_length, content_type, progress);
10481}
10482Result Client::Delete(const std::string &path, const Headers &headers,
10483 const char *body, size_t content_length,
10484 const std::string &content_type,
10485 DownloadProgress progress) {
10486 return cli_->Delete(path, headers, body, content_length, content_type,
10487 progress);
10488}
10489Result Client::Delete(const std::string &path, const std::string &body,
10490 const std::string &content_type,
10491 DownloadProgress progress) {
10492 return cli_->Delete(path, body, content_type, progress);
10493}
10494Result Client::Delete(const std::string &path, const Headers &headers,
10495 const std::string &body,
10496 const std::string &content_type,
10497 DownloadProgress progress) {
10498 return cli_->Delete(path, headers, body, content_type, progress);
10499}
10500Result Client::Delete(const std::string &path, const Params ¶ms,
10501 DownloadProgress progress) {
10502 return cli_->Delete(path, params, progress);
10503}
10504Result Client::Delete(const std::string &path, const Headers &headers,
10505 const Params ¶ms, DownloadProgress progress) {
10506 return cli_->Delete(path, headers, params, progress);
10507}
10508
10509Result Client::Options(const std::string &path) {
10510 return cli_->Options(path);
10511}
10512Result Client::Options(const std::string &path, const Headers &headers) {
10513 return cli_->Options(path, headers);
10514}
10515
10516ClientImpl::StreamHandle
10517Client::open_stream(const std::string &method, const std::string &path,
10518 const Params ¶ms, const Headers &headers,
10519 const std::string &body, const std::string &content_type) {
10520 return cli_->open_stream(method, path, params, headers, body, content_type);
10521}
10522
10523bool Client::send(Request &req, Response &res, Error &error) {
10524 return cli_->send(req, res, error);
10525}
10526
10527Result Client::send(const Request &req) { return cli_->send(req); }
10528
10529void Client::stop() { cli_->stop(); }
10530
10531std::string Client::host() const { return cli_->host(); }
10532
10533int Client::port() const { return cli_->port(); }
10534
10535size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
10536
10537socket_t Client::socket() const { return cli_->socket(); }
10538
10539void
10540Client::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
10541 cli_->set_hostname_addr_map(std::move(addr_map));
10542}
10543
10544void Client::set_default_headers(Headers headers) {
10545 cli_->set_default_headers(std::move(headers));
10546}
10547
10548void Client::set_header_writer(
10549 std::function<ssize_t(Stream &, Headers &)> const &writer) {
10550 cli_->set_header_writer(writer);
10551}
10552
10553void Client::set_address_family(int family) {
10554 cli_->set_address_family(family);
10555}
10556
10557void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); }
10558
10559void Client::set_socket_options(SocketOptions socket_options) {
10560 cli_->set_socket_options(std::move(socket_options));
10561}
10562
10563void Client::set_connection_timeout(time_t sec, time_t usec) {
10564 cli_->set_connection_timeout(sec, usec);
10565}
10566
10567void Client::set_read_timeout(time_t sec, time_t usec) {
10568 cli_->set_read_timeout(sec, usec);
10569}
10570
10571void Client::set_write_timeout(time_t sec, time_t usec) {
10572 cli_->set_write_timeout(sec, usec);
10573}
10574
10575void Client::set_basic_auth(const std::string &username,
10576 const std::string &password) {
10577 cli_->set_basic_auth(username, password);
10578}
10579void Client::set_bearer_token_auth(const std::string &token) {
10580 cli_->set_bearer_token_auth(token);
10581}
10582#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
10583void Client::set_digest_auth(const std::string &username,
10584 const std::string &password) {
10585 cli_->set_digest_auth(username, password);
10586}
10587#endif
10588
10589void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); }
10590void Client::set_follow_location(bool on) {
10591 cli_->set_follow_location(on);
10592}
10593
10594void Client::set_path_encode(bool on) { cli_->set_path_encode(on); }
10595
10596[[deprecated("Use set_path_encode instead")]]
10597void Client::set_url_encode(bool on) {
10598 cli_->set_path_encode(on);
10599}
10600
10601void Client::set_compress(bool on) { cli_->set_compress(on); }
10602
10603void Client::set_decompress(bool on) { cli_->set_decompress(on); }
10604
10605void Client::set_interface(const std::string &intf) {
10606 cli_->set_interface(intf);
10607}
10608
10609void Client::set_proxy(const std::string &host, int port) {
10610 cli_->set_proxy(host, port);
10611}
10612void Client::set_proxy_basic_auth(const std::string &username,
10613 const std::string &password) {
10614 cli_->set_proxy_basic_auth(username, password);
10615}
10616void Client::set_proxy_bearer_token_auth(const std::string &token) {
10617 cli_->set_proxy_bearer_token_auth(token);
10618}
10619#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
10620void Client::set_proxy_digest_auth(const std::string &username,
10621 const std::string &password) {
10622 cli_->set_proxy_digest_auth(username, password);
10623}
10624#endif
10625
10626#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
10627void Client::enable_server_certificate_verification(bool enabled) {
10628 cli_->enable_server_certificate_verification(enabled);
10629}
10630
10631void Client::enable_server_hostname_verification(bool enabled) {
10632 cli_->enable_server_hostname_verification(enabled);
10633}
10634
10635void Client::set_server_certificate_verifier(
10636 std::function<SSLVerifierResponse(SSL *ssl)> verifier) {
10637 cli_->set_server_certificate_verifier(verifier);
10638}
10639#endif
10640
10641void Client::set_logger(Logger logger) {
10642 cli_->set_logger(std::move(logger));
10643}
10644
10645void Client::set_error_logger(ErrorLogger error_logger) {
10646 cli_->set_error_logger(std::move(error_logger));
10647}
10648
10649#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
10650void Client::set_ca_cert_path(const std::string &ca_cert_file_path,
10651 const std::string &ca_cert_dir_path) {
10652 cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path);
10653}
10654
10655void Client::set_ca_cert_store(X509_STORE *ca_cert_store) {
10656 if (is_ssl_) {
10657 static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
10658 } else {
10659 cli_->set_ca_cert_store(ca_cert_store);
10660 }
10661}
10662
10663void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) {
10664 set_ca_cert_store(cli_->create_ca_cert_store(ca_cert, size));
10665}
10666
10667long Client::get_openssl_verify_result() const {
10668 if (is_ssl_) {
10669 return static_cast<SSLClient &>(*cli_).get_openssl_verify_result();
10670 }
10671 return -1; // NOTE: -1 doesn't match any of X509_V_ERR_???
10672}
10673
10674SSL_CTX *Client::ssl_context() const {
10675 if (is_ssl_) { return static_cast<SSLClient &>(*cli_).ssl_context(); }
10676 return nullptr;
10677}
10678#endif
10679
10680} // namespace httplib