char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Alessandro de Oliveira Faria (A.K.A.CABELO)vendor : update cpp-httplib to 0.56.0 (#28787)718f7b417

char/aliceai-t5-moe
602.4 KiB18209 linesraw
1#include "httplib.h"
2namespace httplib {
3
4/*
5 * Implementation that will be part of the .cc file if split into .h + .cc.
6 */
7
8namespace stream {
9
10// stream::Result implementations
11Result::Result() : chunk_size_(8192) {}
12
13Result::Result(ClientImpl::StreamHandle &&handle, size_t chunk_size)
14    : handle_(std::move(handle)), chunk_size_(chunk_size) {}
15
16Result::Result(Result &&other) noexcept
17    : handle_(std::move(other.handle_)), buffer_(std::move(other.buffer_)),
18      current_size_(other.current_size_), chunk_size_(other.chunk_size_),
19      finished_(other.finished_) {
20  other.current_size_ = 0;
21  other.finished_ = true;
22}
23
24Result &Result::operator=(Result &&other) noexcept {
25  if (this != &other) {
26    handle_ = std::move(other.handle_);
27    buffer_ = std::move(other.buffer_);
28    current_size_ = other.current_size_;
29    chunk_size_ = other.chunk_size_;
30    finished_ = other.finished_;
31    other.current_size_ = 0;
32    other.finished_ = true;
33  }
34  return *this;
35}
36
37bool Result::is_valid() const { return handle_.is_valid(); }
38Result::operator bool() const { return is_valid(); }
39
40int Result::status() const {
41  return handle_.response ? handle_.response->status : -1;
42}
43
44const Headers &Result::headers() const {
45  static const Headers empty_headers;
46  return handle_.response ? handle_.response->headers : empty_headers;
47}
48
49std::string Result::get_header_value(const std::string &key,
50                                            const char *def) const {
51  return handle_.response ? handle_.response->get_header_value(key, def) : def;
52}
53
54bool Result::has_header(const std::string &key) const {
55  return handle_.response ? handle_.response->has_header(key) : false;
56}
57
58Error Result::error() const { return handle_.error; }
59Error Result::read_error() const { return handle_.get_read_error(); }
60bool Result::has_read_error() const { return handle_.has_read_error(); }
61
62bool Result::next() {
63  if (!handle_.is_valid() || finished_) { return false; }
64
65  if (buffer_.size() < chunk_size_) { buffer_.resize(chunk_size_); }
66
67  ssize_t n = handle_.read(&buffer_[0], chunk_size_);
68  if (n > 0) {
69    current_size_ = static_cast<size_t>(n);
70    return true;
71  }
72
73  current_size_ = 0;
74  finished_ = true;
75  return false;
76}
77
78const char *Result::data() const { return buffer_.data(); }
79size_t Result::size() const { return current_size_; }
80
81std::string Result::read_all() {
82  std::string result;
83  while (next()) {
84    result.append(data(), size());
85  }
86  return result;
87}
88
89} // namespace stream
90
91namespace sse {
92
93// SSEMessage implementations
94SSEMessage::SSEMessage() : event("message") {}
95
96void SSEMessage::clear() {
97  event = "message";
98  data.clear();
99  id.clear();
100}
101
102// SSEClient implementations
103SSEClient::SSEClient(Client &client, const std::string &path)
104    : client_(client), path_(path) {}
105
106SSEClient::SSEClient(Client &client, const std::string &path,
107                            const Headers &headers)
108    : client_(client), path_(path), headers_(headers) {}
109
110SSEClient::~SSEClient() { stop(); }
111
112SSEClient &SSEClient::on_message(MessageHandler handler) {
113  on_message_ = std::move(handler);
114  return *this;
115}
116
117SSEClient &SSEClient::on_event(const std::string &type,
118                                      MessageHandler handler) {
119  event_handlers_[type] = std::move(handler);
120  return *this;
121}
122
123SSEClient &SSEClient::on_open(OpenHandler handler) {
124  on_open_ = std::move(handler);
125  return *this;
126}
127
128SSEClient &SSEClient::on_error(ErrorHandler handler) {
129  on_error_ = std::move(handler);
130  return *this;
131}
132
133SSEClient &SSEClient::set_reconnect_interval(int ms) {
134  reconnect_interval_ms_ = ms;
135  return *this;
136}
137
138SSEClient &SSEClient::set_max_reconnect_attempts(int n) {
139  max_reconnect_attempts_ = n;
140  return *this;
141}
142
143SSEClient &SSEClient::set_headers(const Headers &headers) {
144  std::lock_guard<std::mutex> lock(headers_mutex_);
145  headers_ = headers;
146  return *this;
147}
148
149bool SSEClient::is_connected() const { return connected_.load(); }
150
151const std::string &SSEClient::last_event_id() const {
152  return last_event_id_;
153}
154
155void SSEClient::start() {
156  running_.store(true);
157  run_event_loop();
158}
159
160void SSEClient::start_async() {
161  running_.store(true);
162  async_thread_ = std::thread([this]() { run_event_loop(); });
163}
164
165void SSEClient::stop() {
166  running_.store(false);
167  client_.stop(); // Cancel any pending operations
168  if (async_thread_.joinable()) { async_thread_.join(); }
169}
170
171bool SSEClient::parse_sse_line(const std::string &line, SSEMessage &msg,
172                                      int &retry_ms) {
173  // Blank line signals end of event
174  if (line.empty() || line == "\r") { return true; }
175
176  // Lines starting with ':' are comments (ignored)
177  if (!line.empty() && line[0] == ':') { return false; }
178
179  // Find the colon separator
180  auto colon_pos = line.find(':');
181  if (colon_pos == std::string::npos) {
182    // Line with no colon is treated as field name with empty value
183    return false;
184  }
185
186  auto field = line.substr(0, colon_pos);
187  std::string value;
188
189  // Value starts after colon, skip optional single space
190  if (colon_pos + 1 < line.size()) {
191    auto value_start = colon_pos + 1;
192    if (line[value_start] == ' ') { value_start++; }
193    value = line.substr(value_start);
194    // Remove trailing \r if present
195    if (!value.empty() && value.back() == '\r') { value.pop_back(); }
196  }
197
198  // Handle known fields
199  if (field == "event") {
200    msg.event = value;
201  } else if (field == "data") {
202    // Multiple data lines are concatenated with newlines
203    if (!msg.data.empty()) { msg.data += "\n"; }
204    msg.data += value;
205  } else if (field == "id") {
206    // Empty id is valid (clears the last event ID)
207    msg.id = value;
208  } else if (field == "retry") {
209    // Parse retry interval in milliseconds
210    {
211      int v = 0;
212      auto res =
213          detail::from_chars(value.data(), value.data() + value.size(), v);
214      if (res.ec == std::errc{}) { retry_ms = v; }
215    }
216  }
217  // Unknown fields are ignored per SSE spec
218
219  return false;
220}
221
222void SSEClient::run_event_loop() {
223  auto reconnect_count = 0;
224
225  while (running_.load()) {
226    // Build headers, including Last-Event-ID if we have one
227    Headers request_headers;
228    {
229      std::lock_guard<std::mutex> lock(headers_mutex_);
230      request_headers = headers_;
231    }
232    if (!last_event_id_.empty()) {
233      request_headers.emplace("Last-Event-ID", last_event_id_);
234    }
235
236    // Open streaming connection
237    auto result = stream::Get(client_, path_, request_headers);
238
239    // Connection error handling
240    if (!result) {
241      connected_.store(false);
242      if (on_error_) { on_error_(result.error()); }
243
244      if (!should_reconnect(reconnect_count)) { break; }
245      wait_for_reconnect();
246      reconnect_count++;
247      continue;
248    }
249
250    if (result.status() != StatusCode::OK_200) {
251      connected_.store(false);
252      if (on_error_) { on_error_(Error::Connection); }
253
254      // For certain errors, don't reconnect.
255      // Note: 401 is intentionally absent so that handlers can refresh
256      // credentials via set_headers() and let the client reconnect.
257      if (result.status() == StatusCode::NoContent_204 ||
258          result.status() == StatusCode::NotFound_404 ||
259          result.status() == StatusCode::Forbidden_403) {
260        break;
261      }
262
263      if (!should_reconnect(reconnect_count)) { break; }
264      wait_for_reconnect();
265      reconnect_count++;
266      continue;
267    }
268
269    // Connection successful
270    connected_.store(true);
271    reconnect_count = 0;
272    if (on_open_) { on_open_(); }
273
274    // Event receiving loop
275    std::string buffer;
276    SSEMessage current_msg;
277
278    while (running_.load() && result.next()) {
279      buffer.append(result.data(), result.size());
280
281      // Process complete lines in the buffer
282      size_t line_start = 0;
283      size_t newline_pos;
284
285      while ((newline_pos = buffer.find('\n', line_start)) !=
286             std::string::npos) {
287        auto line = buffer.substr(line_start, newline_pos - line_start);
288        line_start = newline_pos + 1;
289
290        // Parse the line and check if event is complete
291        auto event_complete =
292            parse_sse_line(line, current_msg, reconnect_interval_ms_);
293
294        if (event_complete && !current_msg.data.empty()) {
295          // Update last_event_id for reconnection
296          if (!current_msg.id.empty()) { last_event_id_ = current_msg.id; }
297
298          // Dispatch event to appropriate handler
299          dispatch_event(current_msg);
300
301          current_msg.clear();
302        }
303      }
304
305      // Keep unprocessed data in buffer
306      buffer.erase(0, line_start);
307    }
308
309    // Connection ended
310    connected_.store(false);
311
312    if (!running_.load()) { break; }
313
314    // Check for read errors
315    if (result.has_read_error()) {
316      if (on_error_) { on_error_(result.read_error()); }
317    }
318
319    if (!should_reconnect(reconnect_count)) { break; }
320    wait_for_reconnect();
321    reconnect_count++;
322  }
323
324  connected_.store(false);
325}
326
327void SSEClient::dispatch_event(const SSEMessage &msg) {
328  // Check for specific event type handler first
329  auto it = event_handlers_.find(msg.event);
330  if (it != event_handlers_.end()) {
331    it->second(msg);
332    return;
333  }
334
335  // Fall back to generic message handler
336  if (on_message_) { on_message_(msg); }
337}
338
339bool SSEClient::should_reconnect(int count) const {
340  if (!running_.load()) { return false; }
341  if (max_reconnect_attempts_ == 0) { return true; } // unlimited
342  return count < max_reconnect_attempts_;
343}
344
345void SSEClient::wait_for_reconnect() {
346  // Use small increments to check running_ flag frequently
347  auto waited = 0;
348  while (running_.load() && waited < reconnect_interval_ms_) {
349    std::this_thread::sleep_for(std::chrono::milliseconds(100));
350    waited += 100;
351  }
352}
353
354} // namespace sse
355
356#ifdef CPPHTTPLIB_SSL_ENABLED
357/*
358 * TLS abstraction layer - internal function declarations
359 * These are implementation details and not part of the public API.
360 */
361namespace tls {
362
363// Client context
364ctx_t create_client_context();
365void free_context(ctx_t ctx);
366bool set_min_version(ctx_t ctx, Version version);
367bool load_ca_pem(ctx_t ctx, const char *pem, size_t len);
368bool load_ca_file(ctx_t ctx, const char *file_path);
369bool load_ca_dir(ctx_t ctx, const char *dir_path);
370bool load_system_certs(ctx_t ctx);
371bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
372                         const char *password);
373bool set_client_cert_file(ctx_t ctx, const char *cert_path,
374                          const char *key_path, const char *password);
375
376// Server context
377ctx_t create_server_context();
378bool set_server_cert_pem(ctx_t ctx, const char *cert, const char *key,
379                         const char *password);
380bool set_server_cert_file(ctx_t ctx, const char *cert_path,
381                          const char *key_path, const char *password);
382bool set_client_ca_file(ctx_t ctx, const char *ca_file, const char *ca_dir);
383void set_verify_client(ctx_t ctx, bool require);
384
385// Session management
386session_t create_session(ctx_t ctx, socket_t sock);
387void free_session(session_t session);
388bool set_sni(session_t session, const char *hostname, bool verify_hostname);
389
390// Handshake (non-blocking capable)
391TlsError connect(session_t session);
392TlsError accept(session_t session);
393
394// Handshake with timeout (blocking until timeout)
395bool connect_nonblocking(session_t session, socket_t sock, time_t timeout_sec,
396                         time_t timeout_usec, TlsError *err);
397bool accept_nonblocking(session_t session, socket_t sock, time_t timeout_sec,
398                        time_t timeout_usec, TlsError *err);
399
400// I/O (non-blocking capable)
401ssize_t read(session_t session, void *buf, size_t len, TlsError &err);
402ssize_t write(session_t session, const void *buf, size_t len, TlsError &err);
403int pending(const_session_t session);
404void shutdown(session_t session, bool graceful);
405
406// Connection state
407bool is_peer_closed(session_t session, socket_t sock);
408
409// Certificate verification
410cert_t get_peer_cert(const_session_t session);
411void free_cert(cert_t cert);
412bool verify_hostname(cert_t cert, const char *hostname);
413uint64_t hostname_mismatch_code();
414long get_verify_result(const_session_t session);
415
416// Certificate introspection
417std::string get_cert_subject_cn(cert_t cert);
418std::string get_cert_issuer_name(cert_t cert);
419bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans);
420bool get_cert_validity(cert_t cert, time_t &not_before, time_t &not_after);
421std::string get_cert_serial(cert_t cert);
422bool get_cert_der(cert_t cert, std::vector<unsigned char> &der);
423const char *get_sni(const_session_t session);
424
425// CA store management
426ca_store_t create_ca_store(const char *pem, size_t len);
427void free_ca_store(ca_store_t store);
428bool set_ca_store(ctx_t ctx, ca_store_t store);
429size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs);
430std::vector<std::string> get_ca_names(ctx_t ctx);
431
432// Dynamic certificate update (for servers)
433bool update_server_cert(ctx_t ctx, const char *cert_pem, const char *key_pem,
434                        const char *password);
435bool update_server_client_ca(ctx_t ctx, const char *ca_pem);
436
437// Certificate verification callback
438bool set_verify_callback(ctx_t ctx, VerifyCallback callback);
439long get_verify_error(const_session_t session);
440std::string verify_error_string(long error_code);
441
442// TlsError information
443uint64_t peek_error();
444uint64_t get_error();
445std::string error_string(uint64_t code);
446
447} // namespace tls
448#endif // CPPHTTPLIB_SSL_ENABLED
449
450/*
451 * Group 1: detail namespace - Non-SSL utilities
452 */
453
454namespace detail {
455
456bool set_socket_opt_impl(socket_t sock, int level, int optname,
457                                const void *optval, socklen_t optlen) {
458  return setsockopt(sock, level, optname,
459#ifdef _WIN32
460                    reinterpret_cast<const char *>(optval),
461#else
462                    optval,
463#endif
464                    optlen) == 0;
465}
466
467bool set_socket_opt_time(socket_t sock, int level, int optname,
468                                time_t sec, time_t usec) {
469#ifdef _WIN32
470  auto timeout = static_cast<uint32_t>(sec * 1000 + usec / 1000);
471#else
472  timeval timeout;
473  timeout.tv_sec = static_cast<long>(sec);
474  timeout.tv_usec = static_cast<decltype(timeout.tv_usec)>(usec);
475#endif
476  return set_socket_opt_impl(sock, level, optname, &timeout, sizeof(timeout));
477}
478
479bool is_hex(char c, int &v) {
480  if (is_ascii_digit(c)) {
481    v = c - '0';
482    return true;
483  } else if ('A' <= c && c <= 'F') {
484    v = c - 'A' + 10;
485    return true;
486  } else if ('a' <= c && c <= 'f') {
487    v = c - 'a' + 10;
488    return true;
489  }
490  return false;
491}
492
493bool from_hex_to_i(const std::string &s, size_t i, size_t cnt,
494                          int &val) {
495  if (i >= s.size()) { return false; }
496
497  val = 0;
498  for (; cnt; i++, cnt--) {
499    if (!s[i]) { return false; }
500    auto v = 0;
501    if (is_hex(s[i], v)) {
502      val = val * 16 + v;
503    } else {
504      return false;
505    }
506  }
507  return true;
508}
509
510std::string from_i_to_hex(size_t n) {
511  static const auto charset = "0123456789abcdef";
512  std::string ret;
513  do {
514    ret = charset[n & 15] + ret;
515    n >>= 4;
516  } while (n > 0);
517  return ret;
518}
519
520std::string compute_etag(const FileStat &fs,
521                                const std::string &suffix = std::string()) {
522  if (!fs.is_file()) { return std::string(); }
523
524  // If mtime cannot be determined (negative value indicates an error
525  // or sentinel), do not generate an ETag. Returning a neutral / fixed
526  // value like 0 could collide with a real file that legitimately has
527  // mtime == 0 (epoch) and lead to misleading validators.
528  auto mtime_raw = fs.mtime();
529  if (mtime_raw < 0) { return std::string(); }
530
531  auto mtime = static_cast<size_t>(mtime_raw);
532  auto size = fs.size();
533
534  return std::string("W/\"") + from_i_to_hex(mtime) + "-" +
535         from_i_to_hex(size) + suffix + "\"";
536}
537
538// Format time_t as HTTP-date (RFC 9110 Section 5.6.7): "Sun, 06 Nov 1994
539// 08:49:37 GMT" This implementation is defensive: it validates `mtime`, checks
540// return values from `gmtime_r`/`gmtime_s`, and ensures `strftime` succeeds.
541std::string file_mtime_to_http_date(time_t mtime) {
542  if (mtime < 0) { return std::string(); }
543
544  struct tm tm_buf;
545#ifdef _WIN32
546  if (gmtime_s(&tm_buf, &mtime) != 0) { return std::string(); }
547#else
548  if (gmtime_r(&mtime, &tm_buf) == nullptr) { return std::string(); }
549#endif
550  char buf[64];
551  if (strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", &tm_buf) == 0) {
552    return std::string();
553  }
554
555  return std::string(buf);
556}
557
558// Parse HTTP-date (RFC 9110 Section 5.6.7) to time_t. Returns -1 on failure.
559time_t parse_http_date(const std::string &date_str) {
560  struct tm tm_buf;
561
562  // Create a classic locale object once for all parsing attempts
563  const std::locale classic_locale = std::locale::classic();
564
565  // Try to parse using std::get_time (C++11, cross-platform)
566  auto try_parse = [&](const char *fmt) -> bool {
567    std::istringstream ss(date_str);
568    ss.imbue(classic_locale);
569
570    memset(&tm_buf, 0, sizeof(tm_buf));
571    ss >> std::get_time(&tm_buf, fmt);
572
573    return !ss.fail();
574  };
575
576  // RFC 9110 preferred format (HTTP-date): "Sun, 06 Nov 1994 08:49:37 GMT"
577  if (!try_parse("%a, %d %b %Y %H:%M:%S")) {
578    // RFC 850 format: "Sunday, 06-Nov-94 08:49:37 GMT"
579    if (!try_parse("%A, %d-%b-%y %H:%M:%S")) {
580      // asctime format: "Sun Nov  6 08:49:37 1994"
581      if (!try_parse("%a %b %d %H:%M:%S %Y")) {
582        return static_cast<time_t>(-1);
583      }
584    }
585  }
586
587#ifdef _WIN32
588  return _mkgmtime(&tm_buf);
589#elif defined _AIX
590  return mktime(&tm_buf);
591#else
592  return timegm(&tm_buf);
593#endif
594}
595
596bool is_weak_etag(const std::string &s) {
597  // Check if the string is a weak ETag (starts with 'W/"')
598  return s.size() > 3 && s[0] == 'W' && s[1] == '/' && s[2] == '"';
599}
600
601bool is_strong_etag(const std::string &s) {
602  // Check if the string is a strong ETag (starts and ends with '"', at least 2
603  // chars)
604  return s.size() >= 2 && s[0] == '"' && s.back() == '"';
605}
606
607size_t to_utf8(int code, char *buff) {
608  if (code < 0x0080) {
609    buff[0] = static_cast<char>(code & 0x7F);
610    return 1;
611  } else if (code < 0x0800) {
612    buff[0] = static_cast<char>(0xC0 | ((code >> 6) & 0x1F));
613    buff[1] = static_cast<char>(0x80 | (code & 0x3F));
614    return 2;
615  } else if (code < 0xD800) {
616    buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
617    buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
618    buff[2] = static_cast<char>(0x80 | (code & 0x3F));
619    return 3;
620  } else if (code < 0xE000) { // D800 - DFFF is invalid...
621    return 0;
622  } else if (code < 0x10000) {
623    buff[0] = static_cast<char>(0xE0 | ((code >> 12) & 0xF));
624    buff[1] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
625    buff[2] = static_cast<char>(0x80 | (code & 0x3F));
626    return 3;
627  } else if (code < 0x110000) {
628    buff[0] = static_cast<char>(0xF0 | ((code >> 18) & 0x7));
629    buff[1] = static_cast<char>(0x80 | ((code >> 12) & 0x3F));
630    buff[2] = static_cast<char>(0x80 | ((code >> 6) & 0x3F));
631    buff[3] = static_cast<char>(0x80 | (code & 0x3F));
632    return 4;
633  }
634
635  // NOTREACHED
636  return 0;
637}
638
639} // namespace detail
640
641namespace ws {
642namespace impl {
643
644bool is_valid_utf8(const std::string &s) {
645  size_t i = 0;
646  auto n = s.size();
647  while (i < n) {
648    auto c = static_cast<unsigned char>(s[i]);
649    size_t len;
650    uint32_t cp;
651    if (c < 0x80) {
652      i++;
653      continue;
654    } else if ((c & 0xE0) == 0xC0) {
655      len = 2;
656      cp = c & 0x1F;
657    } else if ((c & 0xF0) == 0xE0) {
658      len = 3;
659      cp = c & 0x0F;
660    } else if ((c & 0xF8) == 0xF0) {
661      len = 4;
662      cp = c & 0x07;
663    } else {
664      return false;
665    }
666    if (i + len > n) { return false; }
667    for (size_t j = 1; j < len; j++) {
668      auto b = static_cast<unsigned char>(s[i + j]);
669      if ((b & 0xC0) != 0x80) { return false; }
670      cp = (cp << 6) | (b & 0x3F);
671    }
672    // Overlong encoding check
673    if (len == 2 && cp < 0x80) { return false; }
674    if (len == 3 && cp < 0x800) { return false; }
675    if (len == 4 && cp < 0x10000) { return false; }
676    // Surrogate halves (U+D800..U+DFFF) and beyond U+10FFFF are invalid
677    if (cp >= 0xD800 && cp <= 0xDFFF) { return false; }
678    if (cp > 0x10FFFF) { return false; }
679    i += len;
680  }
681  return true;
682}
683
684} // namespace impl
685} // namespace ws
686
687namespace detail {
688
689// NOTE: This code came up with the following stackoverflow post:
690// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c
691std::string base64_encode(const std::string &in) {
692  static const auto lookup =
693      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
694
695  std::string out;
696  out.reserve(in.size());
697
698  // Unsigned: the accumulator is never masked, so with a signed int the
699  // `val << 8` below overflows once enough bytes are folded in (undefined
700  // behaviour before C++20). Only the low bits are ever emitted, so the
701  // wrap-around of an unsigned accumulator does not affect the output.
702  uint32_t val = 0;
703  auto valb = -6;
704
705  for (auto c : in) {
706    val = (val << 8) + static_cast<uint8_t>(c);
707    valb += 8;
708    while (valb >= 0) {
709      out.push_back(lookup[(val >> valb) & 0x3F]);
710      valb -= 6;
711    }
712  }
713
714  if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); }
715
716  while (out.size() % 4) {
717    out.push_back('=');
718  }
719
720  return out;
721}
722
723std::string sha1(const std::string &input) {
724  // RFC 3174 SHA-1 implementation
725  auto left_rotate = [](uint32_t x, uint32_t n) -> uint32_t {
726    return (x << n) | (x >> (32 - n));
727  };
728
729  uint32_t h0 = 0x67452301;
730  uint32_t h1 = 0xEFCDAB89;
731  uint32_t h2 = 0x98BADCFE;
732  uint32_t h3 = 0x10325476;
733  uint32_t h4 = 0xC3D2E1F0;
734
735  // Pre-processing: adding padding bits
736  std::string msg = input;
737  uint64_t original_bit_len = static_cast<uint64_t>(msg.size()) * 8;
738  msg.push_back(static_cast<char>(0x80u));
739  while (msg.size() % 64 != 56) {
740    msg.push_back(0);
741  }
742
743  // Append original length in bits as 64-bit big-endian
744  for (int i = 56; i >= 0; i -= 8) {
745    msg.push_back(static_cast<char>((original_bit_len >> i) & 0xFF));
746  }
747
748  // Process each 512-bit chunk
749  for (size_t offset = 0; offset < msg.size(); offset += 64) {
750    uint32_t w[80];
751
752    for (size_t i = 0; i < 16; i++) {
753      w[i] =
754          (static_cast<uint32_t>(static_cast<uint8_t>(msg[offset + i * 4]))
755           << 24) |
756          (static_cast<uint32_t>(static_cast<uint8_t>(msg[offset + i * 4 + 1]))
757           << 16) |
758          (static_cast<uint32_t>(static_cast<uint8_t>(msg[offset + i * 4 + 2]))
759           << 8) |
760          (static_cast<uint32_t>(
761              static_cast<uint8_t>(msg[offset + i * 4 + 3])));
762    }
763
764    for (int i = 16; i < 80; i++) {
765      w[i] = left_rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
766    }
767
768    uint32_t a = h0, b = h1, c = h2, d = h3, e = h4;
769
770    for (int i = 0; i < 80; i++) {
771      uint32_t f, k;
772      if (i < 20) {
773        f = (b & c) | ((~b) & d);
774        k = 0x5A827999;
775      } else if (i < 40) {
776        f = b ^ c ^ d;
777        k = 0x6ED9EBA1;
778      } else if (i < 60) {
779        f = (b & c) | (b & d) | (c & d);
780        k = 0x8F1BBCDC;
781      } else {
782        f = b ^ c ^ d;
783        k = 0xCA62C1D6;
784      }
785
786      uint32_t temp = left_rotate(a, 5) + f + e + k + w[i];
787      e = d;
788      d = c;
789      c = left_rotate(b, 30);
790      b = a;
791      a = temp;
792    }
793
794    h0 += a;
795    h1 += b;
796    h2 += c;
797    h3 += d;
798    h4 += e;
799  }
800
801  // Produce the final hash as a 20-byte binary string
802  std::string hash(20, '\0');
803  for (size_t i = 0; i < 4; i++) {
804    hash[i] = static_cast<char>((h0 >> (24 - i * 8)) & 0xFF);
805    hash[4 + i] = static_cast<char>((h1 >> (24 - i * 8)) & 0xFF);
806    hash[8 + i] = static_cast<char>((h2 >> (24 - i * 8)) & 0xFF);
807    hash[12 + i] = static_cast<char>((h3 >> (24 - i * 8)) & 0xFF);
808    hash[16 + i] = static_cast<char>((h4 >> (24 - i * 8)) & 0xFF);
809  }
810  return hash;
811}
812
813std::string websocket_accept_key(const std::string &client_key) {
814  const std::string magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
815  return base64_encode(sha1(client_key + magic));
816}
817
818bool is_websocket_upgrade(const Request &req) {
819  if (req.method != "GET") { return false; }
820
821  // Check Upgrade: websocket. RFC 9110 7.8 defines Upgrade as a comma-separated
822  // list of protocols and asks recipients to match each name
823  // case-insensitively, so look for the token rather than compare the whole
824  // field value.
825  if (!has_header_token(req.headers, "Upgrade", "websocket")) { return false; }
826
827  // Check Connection: Upgrade
828  if (!has_header_token(req.headers, "Connection", "upgrade")) { return false; }
829
830  // Check Sec-WebSocket-Key is a valid base64-encoded 16-byte value (24 chars)
831  // RFC 6455 Section 4.2.1
832  auto ws_key = req.get_header_value("Sec-WebSocket-Key");
833  if (ws_key.size() != 24 || ws_key[22] != '=' || ws_key[23] != '=') {
834    return false;
835  }
836  static const std::string b64chars =
837      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
838  for (size_t i = 0; i < 22; i++) {
839    if (b64chars.find(ws_key[i]) == std::string::npos) { return false; }
840  }
841
842  // Check Sec-WebSocket-Version: 13
843  auto version = req.get_header_value("Sec-WebSocket-Version");
844  if (version != "13") { return false; }
845
846  return true;
847}
848
849bool write_websocket_frame(Stream &strm, ws::Opcode opcode,
850                                  const char *data, size_t len, bool fin,
851                                  bool mask) {
852  // First byte: FIN + opcode
853  uint8_t header[2];
854  header[0] = static_cast<uint8_t>((fin ? 0x80 : 0x00) |
855                                   (static_cast<uint8_t>(opcode) & 0x0F));
856
857  // Second byte: MASK + payload length
858  if (len < 126) {
859    header[1] = static_cast<uint8_t>(len);
860    if (mask) { header[1] |= 0x80; }
861    if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
862  } else if (len <= 0xFFFF) {
863    header[1] = 126;
864    if (mask) { header[1] |= 0x80; }
865    if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
866    uint8_t ext[2];
867    ext[0] = static_cast<uint8_t>((len >> 8) & 0xFF);
868    ext[1] = static_cast<uint8_t>(len & 0xFF);
869    if (strm.write(reinterpret_cast<char *>(ext), 2) < 0) { return false; }
870  } else {
871    header[1] = 127;
872    if (mask) { header[1] |= 0x80; }
873    if (strm.write(reinterpret_cast<char *>(header), 2) < 0) { return false; }
874    uint8_t ext[8];
875    for (int i = 7; i >= 0; i--) {
876      ext[7 - i] =
877          static_cast<uint8_t>((static_cast<uint64_t>(len) >> (i * 8)) & 0xFF);
878    }
879    if (strm.write(reinterpret_cast<char *>(ext), 8) < 0) { return false; }
880  }
881
882  if (mask) {
883    // Generate random mask key
884    thread_local std::mt19937 rng(std::random_device{}());
885    uint8_t mask_key[4];
886    auto r = rng();
887    std::memcpy(mask_key, &r, 4);
888    if (strm.write(reinterpret_cast<char *>(mask_key), 4) < 0) { return false; }
889
890    // Write masked payload in chunks
891    const size_t chunk_size = 4096;
892    std::vector<char> buf((std::min)(len, chunk_size));
893    for (size_t offset = 0; offset < len; offset += chunk_size) {
894      size_t n = (std::min)(chunk_size, len - offset);
895      for (size_t i = 0; i < n; i++) {
896        buf[i] =
897            data[offset + i] ^ static_cast<char>(mask_key[(offset + i) % 4]);
898      }
899      if (strm.write(buf.data(), n) < 0) { return false; }
900    }
901  } else {
902    if (len > 0) {
903      if (strm.write(data, len) < 0) { return false; }
904    }
905  }
906
907  return true;
908}
909
910} // namespace detail
911
912namespace ws {
913namespace impl {
914
915// Read exactly `size` bytes. Stream::read may return less than asked for -- it
916// hands back whatever its buffer already holds -- so every multi-byte field has
917// to loop. Reading a 2-byte header with a single read() fails whenever the
918// header straddles the read buffer's boundary.
919//
920// Timeout is reported only when nothing at all was consumed. Once a byte has
921// been taken the stream sits mid-field and cannot be resumed, so a timeout
922// there is a failure like any other. (When read() fails it always records why,
923// so the error belongs to this call and not to an earlier one.)
924FrameRead read_exact(Stream &strm, void *buf, size_t size) {
925  auto p = static_cast<char *>(buf);
926  size_t total = 0;
927  while (total < size) {
928    auto n = strm.read(p + total, size - total);
929    if (n <= 0) {
930      auto timed_out = total == 0 && strm.get_error() == Error::Timeout;
931      return timed_out ? FrameRead::Timeout : FrameRead::Fail;
932    }
933    total += static_cast<size_t>(n);
934  }
935  return FrameRead::Ok;
936}
937
938FrameRead read_websocket_frame(Stream &strm, Opcode &opcode,
939                                      std::string &payload, bool &fin,
940                                      bool expect_masked, size_t max_len) {
941  // Read first 2 bytes. This is the only read that may report a timeout: it
942  // sits on a frame boundary, where nothing has been consumed yet.
943  uint8_t header[2];
944  FrameRead first = read_exact(strm, header, 2);
945  if (first != FrameRead::Ok) { return first; }
946
947  fin = (header[0] & 0x80) != 0;
948
949  // RSV1, RSV2, RSV3 must be 0 when no extension is negotiated
950  if (header[0] & 0x70) { return FrameRead::Fail; }
951
952  opcode = static_cast<Opcode>(header[0] & 0x0F);
953  bool masked = (header[1] & 0x80) != 0;
954  uint64_t payload_len = header[1] & 0x7F;
955
956  // RFC 6455 Section 5.5: control frames MUST NOT be fragmented and
957  // MUST have a payload length of 125 bytes or less
958  bool is_control = (static_cast<uint8_t>(opcode) & 0x08) != 0;
959  if (is_control) {
960    if (!fin) { return FrameRead::Fail; }
961    if (payload_len > 125) { return FrameRead::Fail; }
962  }
963
964  if (masked != expect_masked) { return FrameRead::Fail; }
965
966  // Extended payload length
967  if (payload_len == 126) {
968    uint8_t ext[2];
969    if (read_exact(strm, ext, 2) != FrameRead::Ok) { return FrameRead::Fail; }
970    payload_len = (static_cast<uint64_t>(ext[0]) << 8) | ext[1];
971  } else if (payload_len == 127) {
972    uint8_t ext[8];
973    if (read_exact(strm, ext, 8) != FrameRead::Ok) { return FrameRead::Fail; }
974    // RFC 6455 Section 5.2: the most significant bit MUST be 0
975    if (ext[0] & 0x80) { return FrameRead::Fail; }
976    payload_len = 0;
977    for (int i = 0; i < 8; i++) {
978      payload_len = (payload_len << 8) | ext[i];
979    }
980  }
981
982  if (payload_len > max_len) { return FrameRead::Fail; }
983
984  // Read mask key if present
985  uint8_t mask_key[4] = {0};
986  if (masked) {
987    if (read_exact(strm, mask_key, 4) != FrameRead::Ok) {
988      return FrameRead::Fail;
989    }
990  }
991
992  // Read payload
993  payload.resize(static_cast<size_t>(payload_len));
994  if (payload_len > 0 &&
995      read_exact(strm, &payload[0], static_cast<size_t>(payload_len)) !=
996          FrameRead::Ok) {
997    return FrameRead::Fail;
998  }
999
1000  // Unmask if needed
1001  if (masked) {
1002    for (size_t i = 0; i < payload.size(); i++) {
1003      payload[i] ^= static_cast<char>(mask_key[i % 4]);
1004    }
1005  }
1006
1007  return FrameRead::Ok;
1008}
1009
1010} // namespace impl
1011} // namespace ws
1012
1013namespace detail {
1014
1015bool is_valid_path(const std::string &path) {
1016  size_t level = 0;
1017  size_t i = 0;
1018
1019  // Skip slash
1020  while (i < path.size() && path[i] == '/') {
1021    i++;
1022  }
1023
1024  while (i < path.size()) {
1025    // Read component
1026    auto beg = i;
1027    while (i < path.size() && path[i] != '/') {
1028      if (path[i] == '\0') {
1029        return false;
1030      } else if (path[i] == '\\') {
1031        return false;
1032      }
1033      i++;
1034    }
1035
1036    auto len = i - beg;
1037    assert(len > 0);
1038
1039    if (!path.compare(beg, len, ".")) {
1040      ;
1041    } else if (!path.compare(beg, len, "..")) {
1042      if (level == 0) { return false; }
1043      level--;
1044    } else {
1045      level++;
1046    }
1047
1048    // Skip slash
1049    while (i < path.size() && path[i] == '/') {
1050      i++;
1051    }
1052  }
1053
1054  return true;
1055}
1056
1057bool canonicalize_path(const char *path, std::string &resolved) {
1058#if defined(_WIN32)
1059  char buf[_MAX_PATH];
1060  if (_fullpath(buf, path, _MAX_PATH) == nullptr) { return false; }
1061  resolved = buf;
1062#elif defined(PATH_MAX)
1063  char buf[PATH_MAX];
1064  if (realpath(path, buf) == nullptr) { return false; }
1065  resolved = buf;
1066#else
1067  auto buf = realpath(path, nullptr);
1068  auto guard = scope_exit([&]() { std::free(buf); });
1069  if (buf == nullptr) { return false; }
1070  resolved = buf;
1071#endif
1072  return true;
1073}
1074
1075bool is_path_within_base(const std::string &resolved_path,
1076                                const std::string &resolved_base) {
1077#if defined(_WIN32)
1078  return _strnicmp(resolved_path.c_str(), resolved_base.c_str(),
1079                   resolved_base.size()) == 0;
1080#else
1081  return strncmp(resolved_path.c_str(), resolved_base.c_str(),
1082                 resolved_base.size()) == 0;
1083#endif
1084}
1085
1086FileStat::FileStat(const std::string &path) {
1087#if defined(_WIN32)
1088  auto wpath = u8string_to_wstring(path.c_str());
1089  ret_ = _wstat(wpath.c_str(), &st_);
1090#else
1091  ret_ = stat(path.c_str(), &st_);
1092#endif
1093}
1094bool FileStat::is_file() const {
1095  return ret_ >= 0 && S_ISREG(st_.st_mode);
1096}
1097bool FileStat::is_dir() const {
1098  return ret_ >= 0 && S_ISDIR(st_.st_mode);
1099}
1100
1101time_t FileStat::mtime() const {
1102  return ret_ >= 0 ? static_cast<time_t>(st_.st_mtime)
1103                   : static_cast<time_t>(-1);
1104}
1105
1106size_t FileStat::size() const {
1107  return ret_ >= 0 ? static_cast<size_t>(st_.st_size) : 0;
1108}
1109
1110std::string encode_path(const std::string &s) {
1111  std::string result;
1112  result.reserve(s.size());
1113
1114  for (size_t i = 0; s[i]; i++) {
1115    switch (s[i]) {
1116    case ' ': result += "%20"; break;
1117    case '+': result += "%2B"; break;
1118    case '\r': result += "%0D"; break;
1119    case '\n': result += "%0A"; break;
1120    case '\'': result += "%27"; break;
1121    case ',': result += "%2C"; break;
1122    // case ':': result += "%3A"; break; // ok? probably...
1123    case ';': result += "%3B"; break;
1124    default:
1125      auto c = static_cast<uint8_t>(s[i]);
1126      if (c >= 0x80) {
1127        result += '%';
1128        char hex[4];
1129        auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c);
1130        assert(len == 2);
1131        result.append(hex, static_cast<size_t>(len));
1132      } else {
1133        result += s[i];
1134      }
1135      break;
1136    }
1137  }
1138
1139  return result;
1140}
1141
1142std::string file_extension(const std::string &path) {
1143  std::smatch m;
1144  thread_local auto re = std::regex("\\.([a-zA-Z0-9]+)$");
1145  if (std::regex_search(path, m, re)) { return m[1].str(); }
1146  return std::string();
1147}
1148
1149bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; }
1150
1151template <typename T>
1152bool parse_header(const char *beg, const char *end, T fn);
1153
1154template <typename T>
1155bool parse_header(const char *beg, const char *end, T fn) {
1156  // Skip trailing spaces and tabs.
1157  while (beg < end && is_space_or_tab(end[-1])) {
1158    end--;
1159  }
1160
1161  auto p = beg;
1162  while (p < end && *p != ':') {
1163    p++;
1164  }
1165
1166  auto name = std::string(beg, p);
1167  if (!detail::fields::is_field_name(name)) { return false; }
1168
1169  if (p == end) { return false; }
1170
1171  auto key_end = p;
1172
1173  if (*p++ != ':') { return false; }
1174
1175  while (p < end && is_space_or_tab(*p)) {
1176    p++;
1177  }
1178
1179  if (p <= end) {
1180    auto key_len = key_end - beg;
1181    if (!key_len) { return false; }
1182
1183    auto key = std::string(beg, key_end);
1184    auto val = std::string(p, end);
1185
1186    if (!detail::fields::is_field_value(val)) { return false; }
1187
1188    // RFC 9110 §5.5: header field values are opaque octets and MUST NOT be
1189    // percent-decoded by the recipient. Applications that need to interpret a
1190    // value as a URI component should call httplib::decode_uri_component()
1191    // (or decode_path_component()) explicitly.
1192    fn(key, val);
1193
1194    return true;
1195  }
1196
1197  return false;
1198}
1199
1200bool parse_trailers(stream_line_reader &line_reader, Headers &dest,
1201                           const Headers &src_headers) {
1202  // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentions "The chunked
1203  // transfer coding is complete when a chunk with a chunk-size of zero is
1204  // received, possibly followed by a trailer section, and finally terminated by
1205  // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1
1206  //
1207  // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section
1208  // doesn't care for the existence of the final CRLF. In other words, it seems
1209  // to be ok whether the final CRLF exists or not in the chunked data.
1210  // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3
1211  //
1212  // According to the reference code in RFC 9112, cpp-httplib now allows
1213  // chunked transfer coding data without the final CRLF.
1214
1215  // RFC 7230 Section 4.1.2 - Headers prohibited in trailers
1216  thread_local case_ignore::unordered_set<std::string> prohibited_trailers = {
1217      "transfer-encoding",
1218      "content-length",
1219      "host",
1220      "authorization",
1221      "www-authenticate",
1222      "proxy-authenticate",
1223      "proxy-authorization",
1224      "cookie",
1225      "set-cookie",
1226      "cache-control",
1227      "expect",
1228      "max-forwards",
1229      "pragma",
1230      "range",
1231      "te",
1232      "age",
1233      "expires",
1234      "date",
1235      "location",
1236      "retry-after",
1237      "vary",
1238      "warning",
1239      "content-encoding",
1240      "content-type",
1241      "content-range",
1242      "trailer"};
1243
1244  case_ignore::unordered_set<std::string> declared_trailers;
1245  auto trailer_header = get_combined_header_value(src_headers, "Trailer");
1246  if (!trailer_header.empty()) {
1247    // split() trims each token and skips empty ones, so the name arrives ready
1248    // to look up.
1249    split(trailer_header.data(), trailer_header.data() + trailer_header.size(),
1250          ',', [&](const char *b, const char *e) {
1251            std::string key(b, e);
1252            if (prohibited_trailers.find(key) == prohibited_trailers.end()) {
1253              declared_trailers.insert(key);
1254            }
1255          });
1256  }
1257
1258  size_t trailer_header_count = 0;
1259  while (strcmp(line_reader.ptr(), "\r\n") != 0) {
1260    if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
1261    if (trailer_header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
1262
1263    constexpr auto line_terminator_len = 2;
1264    auto line_beg = line_reader.ptr();
1265    auto line_end =
1266        line_reader.ptr() + line_reader.size() - line_terminator_len;
1267
1268    if (!parse_header(line_beg, line_end,
1269                      [&](const std::string &key, const std::string &val) {
1270                        if (declared_trailers.find(key) !=
1271                            declared_trailers.end()) {
1272                          dest.emplace(key, val);
1273                          trailer_header_count++;
1274                        }
1275                      })) {
1276      return false;
1277    }
1278
1279    if (!line_reader.getline()) { return false; }
1280  }
1281
1282  return true;
1283}
1284
1285std::pair<size_t, size_t> trim(const char *b, const char *e, size_t left,
1286                                      size_t right) {
1287  while (b + left < e && is_space_or_tab(b[left])) {
1288    left++;
1289  }
1290  while (right > 0 && is_space_or_tab(b[right - 1])) {
1291    right--;
1292  }
1293  return std::make_pair(left, right);
1294}
1295
1296std::string trim_copy(const std::string &s) {
1297  auto r = trim(s.data(), s.data() + s.size(), 0, s.size());
1298  return s.substr(r.first, r.second - r.first);
1299}
1300
1301std::string trim_double_quotes_copy(const std::string &s) {
1302  if (s.length() >= 2 && s.front() == '"' && s.back() == '"') {
1303    return s.substr(1, s.size() - 2);
1304  }
1305  return s;
1306}
1307
1308void
1309divide(const char *data, std::size_t size, char d,
1310       std::function<void(const char *, std::size_t, const char *, std::size_t)>
1311           fn) {
1312  const auto it = std::find(data, data + size, d);
1313  const auto found = static_cast<std::size_t>(it != data + size);
1314  const auto lhs_data = data;
1315  const auto lhs_size = static_cast<std::size_t>(it - data);
1316  const auto rhs_data = it + found;
1317  const auto rhs_size = size - lhs_size - found;
1318
1319  fn(lhs_data, lhs_size, rhs_data, rhs_size);
1320}
1321
1322void
1323divide(const std::string &str, char d,
1324       std::function<void(const char *, std::size_t, const char *, std::size_t)>
1325           fn) {
1326  divide(str.data(), str.size(), d, std::move(fn));
1327}
1328
1329void split(const char *b, const char *e, char d,
1330                  std::function<void(const char *, const char *)> fn) {
1331  return split(b, e, d, (std::numeric_limits<size_t>::max)(), std::move(fn));
1332}
1333
1334void split(const char *b, const char *e, char d, size_t m,
1335                  std::function<void(const char *, const char *)> fn) {
1336  size_t i = 0;
1337  size_t beg = 0;
1338  size_t count = 1;
1339
1340  while (e ? (b + i < e) : (b[i] != '\0')) {
1341    if (b[i] == d && count < m) {
1342      auto r = trim(b, e, beg, i);
1343      if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
1344      beg = i + 1;
1345      count++;
1346    }
1347    i++;
1348  }
1349
1350  if (i) {
1351    auto r = trim(b, e, beg, i);
1352    if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
1353  }
1354}
1355
1356// Same contract as split(), except that a delimiter inside a quoted-string is
1357// not a delimiter. RFC 9110 Section 5.6.6 lets a parameter value be a
1358// quoted-string, and ';' and '=' are legal characters inside one.
1359void split_unquoted(const char *b, const char *e, char d, size_t m,
1360                           std::function<void(const char *, const char *)> fn) {
1361  size_t i = 0;
1362  size_t beg = 0;
1363  size_t count = 1;
1364  auto in_quotes = false;
1365
1366  while (e ? (b + i < e) : (b[i] != '\0')) {
1367    if (b[i] == '"') {
1368      in_quotes = !in_quotes;
1369    } else if (b[i] == d && !in_quotes && count < m) {
1370      auto r = trim(b, e, beg, i);
1371      if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
1372      beg = i + 1;
1373      count++;
1374    }
1375    i++;
1376  }
1377
1378  if (i) {
1379    auto r = trim(b, e, beg, i);
1380    if (r.first < r.second) { fn(&b[r.first], &b[r.second]); }
1381  }
1382}
1383
1384void split_unquoted(const char *b, const char *e, char d,
1385                           std::function<void(const char *, const char *)> fn) {
1386  return split_unquoted(b, e, d, (std::numeric_limits<size_t>::max)(),
1387                        std::move(fn));
1388}
1389
1390// Divide a header parameter at its first '='. RFC 9110 Section 5.6.6 makes the
1391// key a token, so the first '=' is the separator even when the value is a
1392// quoted-string carrying more of them.
1393void divide_param_pair(const char *b, const char *e, std::string &key,
1394                              std::string &val) {
1395  divide(
1396      b, static_cast<std::size_t>(e - b), '=',
1397      [&](const char *kb, std::size_t klen, const char *vb, std::size_t vlen) {
1398        const auto kr = trim(kb, kb + klen, 0, klen);
1399        key.assign(kb + kr.first, kb + kr.second);
1400        const auto vr = trim(vb, vb + vlen, 0, vlen);
1401        val.assign(vb + vr.first, vb + vr.second);
1402      });
1403}
1404
1405bool split_find(const char *b, const char *e, char d, size_t m,
1406                       std::function<bool(const char *, const char *)> fn) {
1407  size_t i = 0;
1408  size_t beg = 0;
1409  size_t count = 1;
1410
1411  while (e ? (b + i < e) : (b[i] != '\0')) {
1412    if (b[i] == d && count < m) {
1413      auto r = trim(b, e, beg, i);
1414      if (r.first < r.second) {
1415        auto found = fn(&b[r.first], &b[r.second]);
1416        if (found) { return true; }
1417      }
1418      beg = i + 1;
1419      count++;
1420    }
1421    i++;
1422  }
1423
1424  if (i) {
1425    auto r = trim(b, e, beg, i);
1426    if (r.first < r.second) {
1427      auto found = fn(&b[r.first], &b[r.second]);
1428      if (found) { return true; }
1429    }
1430  }
1431
1432  return false;
1433}
1434
1435bool split_find(const char *b, const char *e, char d,
1436                       std::function<bool(const char *, const char *)> fn) {
1437  return split_find(b, e, d, (std::numeric_limits<size_t>::max)(),
1438                    std::move(fn));
1439}
1440
1441stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer,
1442                                              size_t fixed_buffer_size)
1443    : strm_(strm), fixed_buffer_(fixed_buffer),
1444      fixed_buffer_size_(fixed_buffer_size) {}
1445
1446const char *stream_line_reader::ptr() const {
1447  if (growable_buffer_.empty()) {
1448    return fixed_buffer_;
1449  } else {
1450    return growable_buffer_.data();
1451  }
1452}
1453
1454size_t stream_line_reader::size() const {
1455  if (growable_buffer_.empty()) {
1456    return fixed_buffer_used_size_;
1457  } else {
1458    return growable_buffer_.size();
1459  }
1460}
1461
1462bool stream_line_reader::end_with_crlf() const {
1463  auto end = ptr() + size();
1464  return size() >= 2 && end[-2] == '\r' && end[-1] == '\n';
1465}
1466
1467bool stream_line_reader::getline() {
1468  fixed_buffer_used_size_ = 0;
1469  growable_buffer_.clear();
1470
1471#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
1472  char prev_byte = 0;
1473#endif
1474
1475  for (size_t i = 0;; i++) {
1476    // Fast path: whatever the stream has already buffered can be scanned for
1477    // the terminator in one pass. Asking for a byte at a time costs a virtual
1478    // call, a bounds check and a one-byte copy per character of the request.
1479    size_t buffered_size = 0;
1480    if (auto buffered = strm_.buffered_data(buffered_size)) {
1481      auto take = buffered_size;
1482      auto terminated = false;
1483
1484      for (size_t at = 0; at < buffered_size;) {
1485        auto nl = static_cast<const char *>(
1486            memchr(buffered + at, '\n', buffered_size - at));
1487        if (!nl) { break; }
1488        auto pos = static_cast<size_t>(nl - buffered);
1489#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
1490        take = pos + 1;
1491        terminated = true;
1492        break;
1493#else
1494        // A bare LF does not end the line; keep looking for CRLF. The CR may
1495        // be the last byte of an earlier chunk, hence prev_byte.
1496        if ((pos > 0 ? buffered[pos - 1] : prev_byte) == '\r') {
1497          take = pos + 1;
1498          terminated = true;
1499          break;
1500        }
1501        at = pos + 1;
1502#endif
1503      }
1504
1505      if (size() + take > CPPHTTPLIB_MAX_LINE_LENGTH) { return false; }
1506#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
1507      prev_byte = buffered[take - 1];
1508#endif
1509      append(buffered, take);
1510      strm_.consume_buffered(take);
1511      i += take;
1512      if (terminated) { return true; }
1513      continue;
1514    }
1515
1516    if (size() >= CPPHTTPLIB_MAX_LINE_LENGTH) {
1517      // Treat exceptionally long lines as an error to
1518      // prevent infinite loops/memory exhaustion
1519      return false;
1520    }
1521    char byte;
1522    auto n = strm_.read(&byte, 1);
1523
1524    if (n < 0) {
1525      return false;
1526    } else if (n == 0) {
1527      if (i == 0) {
1528        return false;
1529      } else {
1530        break;
1531      }
1532    }
1533
1534    append(byte);
1535
1536#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
1537    if (byte == '\n') { break; }
1538#else
1539    if (prev_byte == '\r' && byte == '\n') { break; }
1540    prev_byte = byte;
1541#endif
1542  }
1543
1544  return true;
1545}
1546
1547void stream_line_reader::append(char c) { append(&c, 1); }
1548
1549void stream_line_reader::append(const char *data, size_t size) {
1550  // Once the line has outgrown the fixed buffer everything must keep going to
1551  // the growable one, even if a later chunk would have fit. Without the
1552  // emptiness check a short append after a long one would land in the fixed
1553  // buffer, which ptr() and size() no longer look at, and be lost.
1554  if (growable_buffer_.empty() &&
1555      fixed_buffer_used_size_ + size < fixed_buffer_size_) {
1556    memcpy(fixed_buffer_ + fixed_buffer_used_size_, data, size);
1557    fixed_buffer_used_size_ += size;
1558    fixed_buffer_[fixed_buffer_used_size_] = '\0';
1559  } else {
1560    // Unlike the per-character overload, this can be the very first append of
1561    // the line, so the fixed buffer may hold nothing and carry no terminator
1562    // yet. assign() takes an explicit length and does not need one.
1563    if (growable_buffer_.empty()) {
1564      growable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_);
1565    }
1566    growable_buffer_.append(data, size);
1567  }
1568}
1569
1570mmap::mmap(const char *path) { open(path); }
1571
1572mmap::~mmap() { close(); }
1573
1574bool mmap::open(const char *path) {
1575  close();
1576
1577#if defined(_WIN32)
1578  auto wpath = u8string_to_wstring(path);
1579  if (wpath.empty()) { return false; }
1580
1581  hFile_ =
1582      ::CreateFile2(wpath.c_str(), GENERIC_READ,
1583                    FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, NULL);
1584
1585  if (hFile_ == INVALID_HANDLE_VALUE) { return false; }
1586
1587  LARGE_INTEGER size{};
1588  if (!::GetFileSizeEx(hFile_, &size)) { return false; }
1589  // If the following line doesn't compile due to QuadPart, update Windows SDK.
1590  // See:
1591  // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721
1592  if (static_cast<ULONGLONG>(size.QuadPart) >
1593      (std::numeric_limits<decltype(size_)>::max)()) {
1594    // `size_t` might be 32-bits, on 32-bits Windows.
1595    return false;
1596  }
1597  size_ = static_cast<size_t>(size.QuadPart);
1598
1599  hMapping_ =
1600      ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL);
1601
1602  // Special treatment for an empty file...
1603  if (hMapping_ == NULL && size_ == 0) {
1604    close();
1605    is_open_empty_file = true;
1606    return true;
1607  }
1608
1609  if (hMapping_ == NULL) {
1610    close();
1611    return false;
1612  }
1613
1614  addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0);
1615
1616  if (addr_ == nullptr) {
1617    close();
1618    return false;
1619  }
1620#else
1621  fd_ = ::open(path, O_RDONLY);
1622  if (fd_ == -1) { return false; }
1623
1624  struct stat sb;
1625  if (fstat(fd_, &sb) == -1) {
1626    close();
1627    return false;
1628  }
1629  size_ = static_cast<size_t>(sb.st_size);
1630
1631  addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0);
1632
1633  // Special treatment for an empty file...
1634  if (addr_ == MAP_FAILED && size_ == 0) {
1635    close();
1636    is_open_empty_file = true;
1637    return false;
1638  }
1639
1640  if (addr_ == MAP_FAILED) {
1641    // Clear the sentinel before `close()`, since `is_open()` only checks
1642    // `addr_` against nullptr and `munmap()` must not be called with it.
1643    addr_ = nullptr;
1644    close();
1645    return false;
1646  }
1647#endif
1648
1649  return true;
1650}
1651
1652bool mmap::is_open() const {
1653  return is_open_empty_file ? true : addr_ != nullptr;
1654}
1655
1656size_t mmap::size() const { return size_; }
1657
1658const char *mmap::data() const {
1659  return is_open_empty_file ? "" : static_cast<const char *>(addr_);
1660}
1661
1662void mmap::close() {
1663#if defined(_WIN32)
1664  if (addr_) {
1665    ::UnmapViewOfFile(addr_);
1666    addr_ = nullptr;
1667  }
1668
1669  if (hMapping_) {
1670    ::CloseHandle(hMapping_);
1671    hMapping_ = NULL;
1672  }
1673
1674  if (hFile_ != INVALID_HANDLE_VALUE) {
1675    ::CloseHandle(hFile_);
1676    hFile_ = INVALID_HANDLE_VALUE;
1677  }
1678
1679  is_open_empty_file = false;
1680#else
1681  if (addr_ != nullptr) {
1682    munmap(addr_, size_);
1683    addr_ = nullptr;
1684  }
1685
1686  if (fd_ != -1) {
1687    ::close(fd_);
1688    fd_ = -1;
1689  }
1690#endif
1691  size_ = 0;
1692}
1693int close_socket(socket_t sock) noexcept {
1694#ifdef _WIN32
1695  return closesocket(sock);
1696#else
1697  return close(sock);
1698#endif
1699}
1700
1701template <typename T> ssize_t handle_EINTR(T fn) {
1702  ssize_t res = 0;
1703  while (true) {
1704    res = fn();
1705    if (res < 0 && errno == EINTR) {
1706      std::this_thread::sleep_for(std::chrono::microseconds{1});
1707      continue;
1708    }
1709    break;
1710  }
1711  return res;
1712}
1713
1714ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) {
1715  return handle_EINTR([&]() {
1716    return recv(sock,
1717#ifdef _WIN32
1718                static_cast<char *>(ptr), static_cast<int>(size),
1719#else
1720                ptr, size,
1721#endif
1722                flags);
1723  });
1724}
1725
1726ssize_t send_socket(socket_t sock, const void *ptr, size_t size,
1727                           int flags) {
1728  return handle_EINTR([&]() {
1729    return send(sock,
1730#ifdef _WIN32
1731                static_cast<const char *>(ptr), static_cast<int>(size),
1732#else
1733                ptr, size,
1734#endif
1735                flags);
1736  });
1737}
1738
1739int poll_wrapper(struct pollfd *fds, nfds_t nfds, int timeout) {
1740#ifdef _WIN32
1741  return ::WSAPoll(fds, nfds, timeout);
1742#else
1743  return ::poll(fds, nfds, timeout);
1744#endif
1745}
1746
1747ssize_t select_impl(socket_t sock, short events, time_t sec,
1748                           time_t usec) {
1749  struct pollfd pfd;
1750  pfd.fd = sock;
1751  pfd.events = events;
1752  pfd.revents = 0;
1753
1754  // A negative timeout waits forever, poll's own convention. 0 keeps meaning
1755  // "return immediately", which callers here rely on to probe a socket.
1756  auto timeout = sec < 0 ? -1 : static_cast<int>(sec * 1000 + usec / 1000);
1757
1758  return handle_EINTR([&]() { return poll_wrapper(&pfd, 1, timeout); });
1759}
1760
1761ssize_t select_read(socket_t sock, time_t sec, time_t usec) {
1762  return select_impl(sock, POLLIN, sec, usec);
1763}
1764
1765ssize_t select_write(socket_t sock, time_t sec, time_t usec) {
1766  return select_impl(sock, POLLOUT, sec, usec);
1767}
1768
1769Error wait_until_socket_is_ready(socket_t sock, time_t sec,
1770                                        time_t usec) {
1771  struct pollfd pfd_read;
1772  pfd_read.fd = sock;
1773  pfd_read.events = POLLIN | POLLOUT;
1774  pfd_read.revents = 0;
1775
1776  auto timeout = static_cast<int>(sec * 1000 + usec / 1000);
1777
1778  auto poll_res =
1779      handle_EINTR([&]() { return poll_wrapper(&pfd_read, 1, timeout); });
1780
1781  if (poll_res == 0) { return Error::ConnectionTimeout; }
1782
1783  if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) {
1784    auto error = 0;
1785    socklen_t len = sizeof(error);
1786    auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR,
1787                          reinterpret_cast<char *>(&error), &len);
1788    auto successful = res >= 0 && !error;
1789    return successful ? Error::Success : Error::Connection;
1790  }
1791
1792  return Error::Connection;
1793}
1794
1795bool is_socket_alive(socket_t sock) {
1796  const auto val = detail::select_read(sock, 0, 0);
1797  if (val == 0) {
1798    return true;
1799  } else if (val < 0 && errno == EBADF) {
1800    return false;
1801  }
1802  char buf[1];
1803  return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0;
1804}
1805
1806class SocketStream final : public Stream {
1807public:
1808  SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
1809               time_t write_timeout_sec, time_t write_timeout_usec,
1810               time_t max_timeout_msec = 0,
1811               std::chrono::time_point<std::chrono::steady_clock> start_time =
1812                   (std::chrono::steady_clock::time_point::min)());
1813  ~SocketStream() override;
1814
1815  bool is_readable() const override;
1816  bool wait_readable() const override;
1817  bool wait_writable() const override;
1818  bool is_peer_alive() const override;
1819  ssize_t read(char *ptr, size_t size) override;
1820  ssize_t write(const char *ptr, size_t size) override;
1821  void get_remote_ip_and_port(std::string &ip, int &port) const override;
1822  void get_local_ip_and_port(std::string &ip, int &port) const override;
1823  socket_t socket() const override;
1824  time_t duration() const override;
1825  void set_read_timeout(time_t sec, time_t usec = 0) override;
1826  const char *buffered_data(size_t &size) const override;
1827  void consume_buffered(size_t size) override;
1828
1829  // The caller has just seen this socket become readable. Lets the next read
1830  // skip its own readiness wait, which would otherwise ask the kernel a
1831  // question that was answered a moment ago. Consumed by that read.
1832  void set_readable_hint() { readable_hint_ = true; }
1833
1834private:
1835  bool ensure_readable();
1836
1837  socket_t sock_;
1838  // Atomic because ws::WebSocket::set_read_timeout() reaches this from another
1839  // thread while a read is in flight -- that is the point of it, for a caller
1840  // holding one connection and wanting control back to send on it.
1841  std::atomic<time_t> read_timeout_sec_;
1842  std::atomic<time_t> read_timeout_usec_;
1843  time_t write_timeout_sec_;
1844  time_t write_timeout_usec_;
1845  time_t max_timeout_msec_;
1846  const std::chrono::time_point<std::chrono::steady_clock> start_time_;
1847
1848  std::vector<char> read_buff_;
1849  size_t read_buff_off_ = 0;
1850  size_t read_buff_content_size_ = 0;
1851  bool readable_hint_ = false;
1852
1853  static const size_t read_buff_size_ = 1024l * 4;
1854};
1855
1856bool keep_alive(const std::atomic<socket_t> &svr_sock, socket_t sock,
1857                       time_t keep_alive_timeout_sec) {
1858  using namespace std::chrono;
1859
1860  const auto interval_usec =
1861      CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND;
1862
1863  // Avoid expensive `steady_clock::now()` call for the first time
1864  if (select_read(sock, 0, interval_usec) > 0) { return true; }
1865
1866  const auto start = steady_clock::now() - microseconds{interval_usec};
1867  const auto timeout = seconds{keep_alive_timeout_sec};
1868
1869  while (true) {
1870    if (svr_sock == INVALID_SOCKET) {
1871      break; // Server socket is closed
1872    }
1873
1874    auto val = select_read(sock, 0, interval_usec);
1875    if (val < 0) {
1876      break; // Ssocket error
1877    } else if (val == 0) {
1878      if (steady_clock::now() - start > timeout) {
1879        break; // Timeout
1880      }
1881    } else {
1882      return true; // Ready for read
1883    }
1884  }
1885
1886  return false;
1887}
1888
1889template <typename T>
1890bool
1891process_server_socket_core(const std::atomic<socket_t> &svr_sock, socket_t sock,
1892                           size_t keep_alive_max_count,
1893                           time_t keep_alive_timeout_sec, T callback) {
1894  assert(keep_alive_max_count > 0);
1895  auto ret = false;
1896  auto count = keep_alive_max_count;
1897  while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) {
1898    auto close_connection = count == 1;
1899    auto connection_closed = false;
1900    ret = callback(close_connection, connection_closed);
1901    if (!ret || connection_closed) { break; }
1902    count--;
1903  }
1904  return ret;
1905}
1906
1907template <typename T>
1908bool
1909process_server_socket(const std::atomic<socket_t> &svr_sock, socket_t sock,
1910                      size_t keep_alive_max_count,
1911                      time_t keep_alive_timeout_sec, time_t read_timeout_sec,
1912                      time_t read_timeout_usec, time_t write_timeout_sec,
1913                      time_t write_timeout_usec, T callback) {
1914  return process_server_socket_core(
1915      svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
1916      [&](bool close_connection, bool &connection_closed) {
1917        SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
1918                          write_timeout_sec, write_timeout_usec);
1919        // process_server_socket_core() only gets here once keep_alive() has
1920        // seen the socket go readable.
1921        strm.set_readable_hint();
1922        return callback(strm, close_connection, connection_closed);
1923      });
1924}
1925
1926bool process_client_socket(
1927    socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
1928    time_t write_timeout_sec, time_t write_timeout_usec,
1929    time_t max_timeout_msec,
1930    std::chrono::time_point<std::chrono::steady_clock> start_time,
1931    std::function<bool(Stream &)> callback) {
1932  SocketStream strm(sock, read_timeout_sec, read_timeout_usec,
1933                    write_timeout_sec, write_timeout_usec, max_timeout_msec,
1934                    start_time);
1935  return callback(strm);
1936}
1937
1938int shutdown_socket(socket_t sock) noexcept {
1939#ifdef _WIN32
1940  return shutdown(sock, SD_BOTH);
1941#else
1942  return shutdown(sock, SHUT_RDWR);
1943#endif
1944}
1945
1946// Half-closes the write side and drains any in-flight/queued bytes before
1947// the final shutdown+close. Closing with unread data in the receive queue
1948// (or bytes arriving after the receive side is closed) makes the stack send
1949// an abortive RST instead of a graceful FIN, which can make the peer see the
1950// response as a failed read even though it was fully written.
1951void drain_and_close_socket(socket_t sock) noexcept {
1952#ifdef _WIN32
1953  shutdown(sock, SD_SEND);
1954#else
1955  shutdown(sock, SHUT_WR);
1956#endif
1957
1958  char buf[CPPHTTPLIB_RECV_BUFSIZ];
1959  size_t total = 0;
1960  const auto deadline = std::chrono::steady_clock::now() +
1961                        std::chrono::milliseconds(100); // bound #1
1962
1963  while (total < size_t(1024u * 1024u)) { // bound #2
1964    const auto remaining =
1965        std::chrono::duration_cast<std::chrono::microseconds>(
1966            deadline - std::chrono::steady_clock::now())
1967            .count();
1968    if (remaining <= 0) { break; }
1969    if (select_read(sock, 0, static_cast<time_t>(remaining)) <= 0) { break; }
1970    const auto n = read_socket(sock, buf, sizeof(buf), CPPHTTPLIB_RECV_FLAGS);
1971    if (n <= 0) { break; }
1972    total += static_cast<size_t>(n);
1973  }
1974
1975  shutdown_socket(sock);
1976  close_socket(sock);
1977}
1978
1979std::string escape_abstract_namespace_unix_domain(const std::string &s) {
1980  if (s.size() > 1 && s[0] == '\0') {
1981    auto ret = s;
1982    ret[0] = '@';
1983    return ret;
1984  }
1985  return s;
1986}
1987
1988std::string
1989unescape_abstract_namespace_unix_domain(const std::string &s) {
1990  if (s.size() > 1 && s[0] == '@') {
1991    auto ret = s;
1992    ret[0] = '\0';
1993    return ret;
1994  }
1995  return s;
1996}
1997
1998int getaddrinfo_with_timeout(const char *node, const char *service,
1999                                    const struct addrinfo *hints,
2000                                    struct addrinfo **res, time_t timeout_sec) {
2001#ifdef CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO
2002  if (timeout_sec <= 0) {
2003    // No timeout specified, use standard getaddrinfo
2004    return getaddrinfo(node, service, hints, res);
2005  }
2006
2007#ifdef _WIN32
2008  // Windows-specific implementation using GetAddrInfoEx with overlapped I/O
2009  OVERLAPPED overlapped = {};
2010  HANDLE event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
2011  if (!event) { return EAI_FAIL; }
2012
2013  overlapped.hEvent = event;
2014
2015  PADDRINFOEXW result_addrinfo = nullptr;
2016  HANDLE cancel_handle = nullptr;
2017
2018  ADDRINFOEXW hints_ex = {};
2019  if (hints) {
2020    hints_ex.ai_flags = hints->ai_flags;
2021    hints_ex.ai_family = hints->ai_family;
2022    hints_ex.ai_socktype = hints->ai_socktype;
2023    hints_ex.ai_protocol = hints->ai_protocol;
2024  }
2025
2026  auto wnode = u8string_to_wstring(node);
2027  auto wservice = u8string_to_wstring(service);
2028
2029  auto ret = ::GetAddrInfoExW(wnode.data(), wservice.data(), NS_DNS, nullptr,
2030                              hints ? &hints_ex : nullptr, &result_addrinfo,
2031                              nullptr, &overlapped, nullptr, &cancel_handle);
2032
2033  if (ret == WSA_IO_PENDING) {
2034    auto wait_result =
2035        ::WaitForSingleObject(event, static_cast<DWORD>(timeout_sec * 1000));
2036    if (wait_result == WAIT_TIMEOUT) {
2037      if (cancel_handle) { ::GetAddrInfoExCancel(&cancel_handle); }
2038      ::CloseHandle(event);
2039      return EAI_AGAIN;
2040    }
2041
2042    DWORD bytes_returned;
2043    if (!::GetOverlappedResult((HANDLE)INVALID_SOCKET, &overlapped,
2044                               &bytes_returned, FALSE)) {
2045      ::CloseHandle(event);
2046      return ::WSAGetLastError();
2047    }
2048  }
2049
2050  ::CloseHandle(event);
2051
2052  if (ret == NO_ERROR || ret == WSA_IO_PENDING) {
2053    *res = reinterpret_cast<struct addrinfo *>(result_addrinfo);
2054    return 0;
2055  }
2056
2057  return ret;
2058#elif TARGET_OS_MAC && defined(__clang__)
2059  if (!node) { return EAI_NONAME; }
2060  // macOS implementation using CFHost API for asynchronous DNS resolution
2061  CFStringRef hostname_ref = CFStringCreateWithCString(
2062      kCFAllocatorDefault, node, kCFStringEncodingUTF8);
2063  if (!hostname_ref) { return EAI_MEMORY; }
2064
2065  CFHostRef host_ref = CFHostCreateWithName(kCFAllocatorDefault, hostname_ref);
2066  CFRelease(hostname_ref);
2067  if (!host_ref) { return EAI_MEMORY; }
2068
2069  // Set up context for callback
2070  struct CFHostContext {
2071    bool completed = false;
2072    bool success = false;
2073    CFArrayRef addresses = nullptr;
2074    std::mutex mutex;
2075    std::condition_variable cv;
2076  } context;
2077
2078  CFHostClientContext client_context;
2079  memset(&client_context, 0, sizeof(client_context));
2080  client_context.info = &context;
2081
2082  // Set callback
2083  auto callback = [](CFHostRef theHost, CFHostInfoType /*typeInfo*/,
2084                     const CFStreamError *error, void *info) {
2085    auto ctx = static_cast<CFHostContext *>(info);
2086    std::lock_guard<std::mutex> lock(ctx->mutex);
2087
2088    if (error && error->error != 0) {
2089      ctx->success = false;
2090    } else {
2091      Boolean hasBeenResolved;
2092      ctx->addresses = CFHostGetAddressing(theHost, &hasBeenResolved);
2093      if (ctx->addresses && hasBeenResolved) {
2094        CFRetain(ctx->addresses);
2095        ctx->success = true;
2096      } else {
2097        ctx->success = false;
2098      }
2099    }
2100    ctx->completed = true;
2101    ctx->cv.notify_one();
2102  };
2103
2104  if (!CFHostSetClient(host_ref, callback, &client_context)) {
2105    CFRelease(host_ref);
2106    return EAI_SYSTEM;
2107  }
2108
2109  // Schedule on run loop
2110  CFRunLoopRef run_loop = CFRunLoopGetCurrent();
2111  CFHostScheduleWithRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
2112
2113  // Start resolution
2114  CFStreamError stream_error;
2115  if (!CFHostStartInfoResolution(host_ref, kCFHostAddresses, &stream_error)) {
2116    CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
2117    CFRelease(host_ref);
2118    return EAI_FAIL;
2119  }
2120
2121  // Wait for completion with timeout
2122  auto timeout_time =
2123      std::chrono::steady_clock::now() + std::chrono::seconds(timeout_sec);
2124  bool timed_out = false;
2125
2126  {
2127    std::unique_lock<std::mutex> lock(context.mutex);
2128
2129    while (!context.completed) {
2130      auto now = std::chrono::steady_clock::now();
2131      if (now >= timeout_time) {
2132        timed_out = true;
2133        break;
2134      }
2135
2136      // Run the runloop for a short time
2137      lock.unlock();
2138      CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, true);
2139      lock.lock();
2140    }
2141  }
2142
2143  // Clean up
2144  CFHostUnscheduleFromRunLoop(host_ref, run_loop, kCFRunLoopDefaultMode);
2145  CFHostSetClient(host_ref, nullptr, nullptr);
2146
2147  if (timed_out || !context.completed) {
2148    CFHostCancelInfoResolution(host_ref, kCFHostAddresses);
2149    CFRelease(host_ref);
2150    return EAI_AGAIN;
2151  }
2152
2153  if (!context.success || !context.addresses) {
2154    CFRelease(host_ref);
2155    return EAI_NODATA;
2156  }
2157
2158  // Convert CFArray to addrinfo
2159  CFIndex count = CFArrayGetCount(context.addresses);
2160  if (count == 0) {
2161    CFRelease(context.addresses);
2162    CFRelease(host_ref);
2163    return EAI_NODATA;
2164  }
2165
2166  struct addrinfo *result_addrinfo = nullptr;
2167  struct addrinfo **current = &result_addrinfo;
2168
2169  for (CFIndex i = 0; i < count; i++) {
2170    CFDataRef addr_data =
2171        static_cast<CFDataRef>(CFArrayGetValueAtIndex(context.addresses, i));
2172    if (!addr_data) continue;
2173
2174    const struct sockaddr *sockaddr_ptr =
2175        reinterpret_cast<const struct sockaddr *>(CFDataGetBytePtr(addr_data));
2176    socklen_t sockaddr_len = static_cast<socklen_t>(CFDataGetLength(addr_data));
2177
2178    // Allocate addrinfo structure
2179    *current = static_cast<struct addrinfo *>(malloc(sizeof(struct addrinfo)));
2180    if (!*current) {
2181      freeaddrinfo(result_addrinfo);
2182      CFRelease(context.addresses);
2183      CFRelease(host_ref);
2184      return EAI_MEMORY;
2185    }
2186
2187    memset(*current, 0, sizeof(struct addrinfo));
2188
2189    // Set up addrinfo fields
2190    (*current)->ai_family = sockaddr_ptr->sa_family;
2191    (*current)->ai_socktype = hints ? hints->ai_socktype : SOCK_STREAM;
2192    (*current)->ai_protocol = hints ? hints->ai_protocol : IPPROTO_TCP;
2193    (*current)->ai_addrlen = sockaddr_len;
2194
2195    // Copy sockaddr
2196    (*current)->ai_addr = static_cast<struct sockaddr *>(malloc(sockaddr_len));
2197    if (!(*current)->ai_addr) {
2198      freeaddrinfo(result_addrinfo);
2199      CFRelease(context.addresses);
2200      CFRelease(host_ref);
2201      return EAI_MEMORY;
2202    }
2203    memcpy((*current)->ai_addr, sockaddr_ptr, sockaddr_len);
2204
2205    // Set port if service is specified
2206    if (service && *service) {
2207      int port = 0;
2208      if (parse_port(service, strlen(service), port)) {
2209        if (sockaddr_ptr->sa_family == AF_INET) {
2210          reinterpret_cast<struct sockaddr_in *>((*current)->ai_addr)
2211              ->sin_port = htons(static_cast<uint16_t>(port));
2212        } else if (sockaddr_ptr->sa_family == AF_INET6) {
2213          reinterpret_cast<struct sockaddr_in6 *>((*current)->ai_addr)
2214              ->sin6_port = htons(static_cast<uint16_t>(port));
2215        }
2216      }
2217    }
2218
2219    current = &((*current)->ai_next);
2220  }
2221
2222  CFRelease(context.addresses);
2223  CFRelease(host_ref);
2224
2225  *res = result_addrinfo;
2226  return 0;
2227#elif defined(_GNU_SOURCE) && defined(__GLIBC__) &&                            \
2228    (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2))
2229  // #2431: gai_cancel() is non-blocking and may return EAI_NOTCANCELED while
2230  // the resolver worker still references the stack-local gaicb. The cancel
2231  // path therefore waits (gai_suspend with no timeout) for the worker to
2232  // actually finish before letting the stack frame go. The trade-off is that
2233  // a wedged DNS server can hold this thread for the system resolver timeout
2234  // (~30s by default) past the caller's connection timeout.
2235  struct gaicb request{};
2236  struct gaicb *requests[1] = {&request};
2237  struct sigevent sevp{};
2238  struct timespec timeout{timeout_sec, 0};
2239
2240  request.ar_name = node;
2241  request.ar_service = service;
2242  request.ar_request = hints;
2243  sevp.sigev_notify = SIGEV_NONE;
2244
2245  int rc = getaddrinfo_a(GAI_NOWAIT, requests, 1, &sevp);
2246  if (rc != 0) { return rc; }
2247
2248  auto cleanup = scope_exit([&] {
2249    if (request.ar_result) { freeaddrinfo(request.ar_result); }
2250  });
2251
2252  int wait_result = gai_suspend(requests, 1, &timeout);
2253
2254  if (wait_result == 0 || wait_result == EAI_ALLDONE) {
2255    int gai_result = gai_error(&request);
2256    if (gai_result == 0) {
2257      *res = request.ar_result;
2258      request.ar_result = nullptr;
2259      return 0;
2260    }
2261    return gai_result;
2262  }
2263
2264  gai_cancel(&request);
2265  while (gai_error(&request) == EAI_INPROGRESS) {
2266    gai_suspend(requests, 1, nullptr);
2267  }
2268  return wait_result;
2269#else
2270  // Fallback implementation using thread-based timeout for other Unix systems.
2271
2272  struct GetAddrInfoState {
2273    ~GetAddrInfoState() {
2274      if (info) { freeaddrinfo(info); }
2275    }
2276
2277    std::mutex mutex;
2278    std::condition_variable result_cv;
2279    bool completed = false;
2280    int result = EAI_SYSTEM;
2281    std::string node;
2282    std::string service;
2283    struct addrinfo hints;
2284    struct addrinfo *info = nullptr;
2285  };
2286
2287  // Allocate on the heap, so the resolver thread can keep using the data.
2288  auto state = std::make_shared<GetAddrInfoState>();
2289  if (node) { state->node = node; }
2290  state->service = service;
2291  state->hints = *hints;
2292
2293  std::thread resolve_thread([state]() {
2294    auto thread_result =
2295        getaddrinfo(state->node.c_str(), state->service.c_str(), &state->hints,
2296                    &state->info);
2297
2298    std::lock_guard<std::mutex> lock(state->mutex);
2299    state->result = thread_result;
2300    state->completed = true;
2301    state->result_cv.notify_one();
2302  });
2303
2304  // Wait for completion or timeout
2305  std::unique_lock<std::mutex> lock(state->mutex);
2306  auto finished =
2307      state->result_cv.wait_for(lock, std::chrono::seconds(timeout_sec),
2308                                [&] { return state->completed; });
2309
2310  if (finished) {
2311    // Operation completed within timeout
2312    resolve_thread.join();
2313    *res = state->info;
2314    state->info = nullptr; // Pass ownership to caller
2315    return state->result;
2316  } else {
2317    // Timeout occurred
2318    resolve_thread.detach(); // Let the thread finish in background
2319    return EAI_AGAIN;        // Return timeout error
2320  }
2321#endif
2322#else
2323  (void)(timeout_sec); // Unused parameter for non-blocking getaddrinfo
2324  return getaddrinfo(node, service, hints, res);
2325#endif
2326}
2327
2328template <typename BindOrConnect>
2329socket_t create_socket(const std::string &host, const std::string &ip, int port,
2330                       int address_family, int socket_flags, bool tcp_nodelay,
2331                       bool ipv6_v6only, SocketOptions socket_options,
2332                       BindOrConnect bind_or_connect, time_t timeout_sec = 0) {
2333  // Get address info
2334  const char *node = nullptr;
2335  struct addrinfo hints;
2336  struct addrinfo *result;
2337
2338  memset(&hints, 0, sizeof(struct addrinfo));
2339  hints.ai_socktype = SOCK_STREAM;
2340  hints.ai_protocol = IPPROTO_IP;
2341
2342  if (!ip.empty()) {
2343    node = ip.c_str();
2344    // Ask getaddrinfo to convert IP in c-string to address
2345    hints.ai_family = AF_UNSPEC;
2346    hints.ai_flags = AI_NUMERICHOST;
2347  } else {
2348    if (!host.empty()) { node = host.c_str(); }
2349    hints.ai_family = address_family;
2350    hints.ai_flags = socket_flags;
2351  }
2352
2353#if !defined(_WIN32) || defined(CPPHTTPLIB_HAVE_AFUNIX_H)
2354  if (hints.ai_family == AF_UNIX) {
2355    const auto addrlen = host.length();
2356    if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; }
2357
2358#ifdef SOCK_CLOEXEC
2359    auto sock = socket(hints.ai_family, hints.ai_socktype | SOCK_CLOEXEC,
2360                       hints.ai_protocol);
2361#else
2362    auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
2363#endif
2364
2365    if (sock != INVALID_SOCKET) {
2366      sockaddr_un addr{};
2367      addr.sun_family = AF_UNIX;
2368
2369      auto unescaped_host = unescape_abstract_namespace_unix_domain(host);
2370      std::copy(unescaped_host.begin(), unescaped_host.end(), addr.sun_path);
2371
2372      hints.ai_addr = reinterpret_cast<sockaddr *>(&addr);
2373      hints.ai_addrlen = static_cast<socklen_t>(
2374          sizeof(addr) - sizeof(addr.sun_path) + addrlen);
2375
2376#ifndef SOCK_CLOEXEC
2377#ifndef _WIN32
2378      fcntl(sock, F_SETFD, FD_CLOEXEC);
2379#endif
2380#endif
2381
2382      if (socket_options) { socket_options(sock); }
2383
2384#ifdef _WIN32
2385      // Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so
2386      // remove the option.
2387      set_socket_opt(sock, SOL_SOCKET, SO_REUSEADDR, 0);
2388#endif
2389
2390      bool dummy;
2391      if (!bind_or_connect(sock, hints, dummy)) {
2392        close_socket(sock);
2393        sock = INVALID_SOCKET;
2394      }
2395    }
2396    return sock;
2397  }
2398#endif
2399
2400  auto service = std::to_string(port);
2401
2402  if (getaddrinfo_with_timeout(node, service.c_str(), &hints, &result,
2403                               timeout_sec)) {
2404#if defined __linux__ && !defined __ANDROID__
2405    res_init();
2406#endif
2407    return INVALID_SOCKET;
2408  }
2409  auto se = detail::scope_exit([&] { freeaddrinfo(result); });
2410
2411  for (auto rp = result; rp; rp = rp->ai_next) {
2412    // Create a socket
2413#ifdef _WIN32
2414    auto sock =
2415        WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0,
2416                   WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED);
2417    /**
2418     * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1
2419     * and above the socket creation fails on older Windows Systems.
2420     *
2421     * Let's try to create a socket the old way in this case.
2422     *
2423     * Reference:
2424     * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa
2425     *
2426     * WSA_FLAG_NO_HANDLE_INHERIT:
2427     * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with
2428     * SP1, and later
2429     *
2430     */
2431    if (sock == INVALID_SOCKET) {
2432      sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
2433    }
2434#else
2435
2436#ifdef SOCK_CLOEXEC
2437    auto sock =
2438        socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC, rp->ai_protocol);
2439#else
2440    auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
2441#endif
2442
2443#endif
2444    if (sock == INVALID_SOCKET) { continue; }
2445
2446#if !defined _WIN32 && !defined SOCK_CLOEXEC
2447    if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) {
2448      close_socket(sock);
2449      continue;
2450    }
2451#endif
2452
2453    if (tcp_nodelay) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); }
2454
2455    if (rp->ai_family == AF_INET6) {
2456      set_socket_opt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ipv6_v6only ? 1 : 0);
2457    }
2458
2459    if (socket_options) { socket_options(sock); }
2460
2461    // bind or connect
2462    auto quit = false;
2463    if (bind_or_connect(sock, *rp, quit)) { return sock; }
2464
2465    close_socket(sock);
2466
2467    if (quit) { break; }
2468  }
2469
2470  return INVALID_SOCKET;
2471}
2472
2473void set_nonblocking(socket_t sock, bool nonblocking) {
2474#ifdef _WIN32
2475  auto flags = nonblocking ? 1UL : 0UL;
2476  ioctlsocket(sock, FIONBIO, &flags);
2477#else
2478  auto flags = fcntl(sock, F_GETFL, 0);
2479  fcntl(sock, F_SETFL,
2480        nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK)));
2481#endif
2482}
2483
2484bool is_connection_error() {
2485#ifdef _WIN32
2486  return WSAGetLastError() != WSAEWOULDBLOCK;
2487#else
2488  return errno != EINPROGRESS;
2489#endif
2490}
2491
2492// accept() failed because the process or the network stack is temporarily out
2493// of resources. The listening socket is still usable, so back off briefly and
2494// try again.
2495bool is_accept_resource_error() {
2496#ifdef _WIN32
2497  auto err = WSAGetLastError();
2498  return err == WSAEMFILE || err == WSAENOBUFS;
2499#else
2500  auto err = errno;
2501  return err == EMFILE || err == ENFILE || err == ENOBUFS || err == ENOMEM;
2502#endif
2503}
2504
2505// accept() failed for a reason that says nothing about the listening socket:
2506// the pending connection went away before it could be accepted, or the call
2507// was interrupted. Retry immediately. WSAAccept()'s own documentation omits
2508// WSAECONNRESET, but the accept() it wraps reports an aborted pending
2509// connection that way.
2510bool is_accept_transient_error() {
2511#ifdef _WIN32
2512  auto err = WSAGetLastError();
2513  return err == WSAEINTR || err == WSAEWOULDBLOCK || err == WSAECONNRESET ||
2514         err == WSAECONNABORTED;
2515#else
2516  auto err = errno;
2517  return err == EINTR || err == EAGAIN || err == EWOULDBLOCK ||
2518         err == ECONNABORTED;
2519#endif
2520}
2521
2522bool bind_ip_address(socket_t sock, const std::string &host) {
2523  struct addrinfo hints;
2524  struct addrinfo *result;
2525
2526  memset(&hints, 0, sizeof(struct addrinfo));
2527  hints.ai_family = AF_UNSPEC;
2528  hints.ai_socktype = SOCK_STREAM;
2529  hints.ai_protocol = 0;
2530
2531  if (getaddrinfo_with_timeout(host.c_str(), "0", &hints, &result, 0)) {
2532    return false;
2533  }
2534
2535  auto se = detail::scope_exit([&] { freeaddrinfo(result); });
2536
2537  auto ret = false;
2538  for (auto rp = result; rp; rp = rp->ai_next) {
2539    const auto &ai = *rp;
2540    if (!::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
2541      ret = true;
2542      break;
2543    }
2544  }
2545
2546  return ret;
2547}
2548
2549#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__
2550#define USE_IF2IP
2551#endif
2552
2553#ifdef USE_IF2IP
2554std::string if2ip(int address_family, const std::string &ifn) {
2555  struct ifaddrs *ifap;
2556  getifaddrs(&ifap);
2557  auto se = detail::scope_exit([&] { freeifaddrs(ifap); });
2558
2559  std::string addr_candidate;
2560  for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) {
2561    if (ifa->ifa_addr && ifn == ifa->ifa_name &&
2562        (AF_UNSPEC == address_family ||
2563         ifa->ifa_addr->sa_family == address_family)) {
2564      if (ifa->ifa_addr->sa_family == AF_INET) {
2565        auto sa = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
2566        char buf[INET_ADDRSTRLEN];
2567        if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) {
2568          return std::string(buf, INET_ADDRSTRLEN);
2569        }
2570      } else if (ifa->ifa_addr->sa_family == AF_INET6) {
2571        auto sa = reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_addr);
2572        if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) {
2573          char buf[INET6_ADDRSTRLEN] = {};
2574          if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) {
2575            // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL
2576            auto s6_addr_head = sa->sin6_addr.s6_addr[0];
2577            if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) {
2578              addr_candidate = std::string(buf, INET6_ADDRSTRLEN);
2579            } else {
2580              return std::string(buf, INET6_ADDRSTRLEN);
2581            }
2582          }
2583        }
2584      }
2585    }
2586  }
2587  return addr_candidate;
2588}
2589#endif
2590
2591socket_t create_client_socket(
2592    const std::string &host, const std::string &ip, int port,
2593    int address_family, bool tcp_nodelay, bool ipv6_v6only,
2594    SocketOptions socket_options, time_t connection_timeout_sec,
2595    time_t connection_timeout_usec, time_t read_timeout_sec,
2596    time_t read_timeout_usec, time_t write_timeout_sec,
2597    time_t write_timeout_usec, const std::string &intf, Error &error) {
2598  auto sock = create_socket(
2599      host, ip, port, address_family, 0, tcp_nodelay, ipv6_v6only,
2600      std::move(socket_options),
2601      [&](socket_t sock2, struct addrinfo &ai, bool &quit) -> bool {
2602        if (!intf.empty()) {
2603#ifdef USE_IF2IP
2604          auto ip_from_if = if2ip(address_family, intf);
2605          if (ip_from_if.empty()) { ip_from_if = intf; }
2606          if (!bind_ip_address(sock2, ip_from_if)) {
2607            error = Error::BindIPAddress;
2608            return false;
2609          }
2610#endif
2611        }
2612
2613        set_nonblocking(sock2, true);
2614
2615        auto ret =
2616            ::connect(sock2, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen));
2617
2618        if (ret < 0) {
2619          if (is_connection_error()) {
2620            error = Error::Connection;
2621            return false;
2622          }
2623          error = wait_until_socket_is_ready(sock2, connection_timeout_sec,
2624                                             connection_timeout_usec);
2625          if (error != Error::Success) {
2626            if (error == Error::ConnectionTimeout) { quit = true; }
2627            return false;
2628          }
2629        }
2630
2631        set_nonblocking(sock2, false);
2632        set_socket_opt_time(sock2, SOL_SOCKET, SO_RCVTIMEO, read_timeout_sec,
2633                            read_timeout_usec);
2634        set_socket_opt_time(sock2, SOL_SOCKET, SO_SNDTIMEO, write_timeout_sec,
2635                            write_timeout_usec);
2636
2637        error = Error::Success;
2638        return true;
2639      },
2640      connection_timeout_sec); // Pass DNS timeout
2641
2642  if (sock != INVALID_SOCKET) {
2643    error = Error::Success;
2644  } else {
2645    if (error == Error::Success) { error = Error::Connection; }
2646  }
2647
2648  return sock;
2649}
2650
2651bool get_ip_and_port(const struct sockaddr_storage &addr,
2652                            socklen_t addr_len, std::string &ip, int &port) {
2653  if (addr.ss_family == AF_INET) {
2654    port = ntohs(reinterpret_cast<const struct sockaddr_in *>(&addr)->sin_port);
2655  } else if (addr.ss_family == AF_INET6) {
2656    port =
2657        ntohs(reinterpret_cast<const struct sockaddr_in6 *>(&addr)->sin6_port);
2658  } else {
2659    return false;
2660  }
2661
2662  std::array<char, NI_MAXHOST> ipstr{};
2663  if (getnameinfo(reinterpret_cast<const struct sockaddr *>(&addr), addr_len,
2664                  ipstr.data(), static_cast<socklen_t>(ipstr.size()), nullptr,
2665                  0, NI_NUMERICHOST)) {
2666    return false;
2667  }
2668
2669  ip = ipstr.data();
2670  return true;
2671}
2672
2673void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) {
2674  struct sockaddr_storage addr;
2675  socklen_t addr_len = sizeof(addr);
2676  if (!getsockname(sock, reinterpret_cast<struct sockaddr *>(&addr),
2677                   &addr_len)) {
2678    get_ip_and_port(addr, addr_len, ip, port);
2679  }
2680}
2681
2682void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) {
2683  struct sockaddr_storage addr;
2684  socklen_t addr_len = sizeof(addr);
2685
2686  if (!getpeername(sock, reinterpret_cast<struct sockaddr *>(&addr),
2687                   &addr_len)) {
2688#ifndef _WIN32
2689    if (addr.ss_family == AF_UNIX) {
2690#if defined(__linux__)
2691      struct ucred ucred;
2692      socklen_t len = sizeof(ucred);
2693      if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) {
2694        port = ucred.pid;
2695      }
2696#elif defined(SOL_LOCAL) && defined(SO_PEERPID)
2697      pid_t pid;
2698      socklen_t len = sizeof(pid);
2699      if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) {
2700        port = pid;
2701      }
2702#endif
2703      return;
2704    }
2705#endif
2706    get_ip_and_port(addr, addr_len, ip, port);
2707  }
2708}
2709
2710// Recursive form retained so operator""_t below can compute hashes for
2711// switch-case labels at compile time (C++11 constexpr forbids loops). Do not
2712// call from runtime paths with arbitrary-length inputs — use str2tag()
2713// instead, which is iterative and stack-safe.
2714constexpr unsigned int str2tag_core(const char *s, size_t l,
2715                                           unsigned int h) {
2716  return (l == 0)
2717             ? h
2718             : str2tag_core(
2719                   s + 1, l - 1,
2720                   // Unsets the 6 high bits of h, therefore no overflow happens
2721                   (((std::numeric_limits<unsigned int>::max)() >> 6) &
2722                    h * 33) ^
2723                       static_cast<unsigned char>(*s));
2724}
2725
2726unsigned int str2tag(const std::string &s) {
2727  // Iterative form of str2tag_core: the recursive constexpr version is kept
2728  // for compile-time UDL evaluation of short string literals, but at runtime
2729  // we may receive arbitrarily long inputs (e.g. fuzzed Content-Type) that
2730  // would blow the stack with one frame per character.
2731  unsigned int h = 0;
2732  for (auto c : s) {
2733    h = (((std::numeric_limits<unsigned int>::max)() >> 6) & h * 33) ^
2734        static_cast<unsigned char>(c);
2735  }
2736  return h;
2737}
2738
2739namespace udl {
2740
2741constexpr unsigned int operator""_t(const char *s, size_t l) {
2742  return str2tag_core(s, l, 0);
2743}
2744
2745} // namespace udl
2746
2747std::string
2748find_content_type(const std::string &path,
2749                  const std::map<std::string, std::string> &user_data,
2750                  const std::string &default_content_type) {
2751  auto ext = file_extension(path);
2752
2753  auto it = user_data.find(ext);
2754  if (it != user_data.end()) { return it->second; }
2755
2756  using udl::operator""_t;
2757
2758  switch (str2tag(ext)) {
2759  default: return default_content_type;
2760
2761  case "css"_t: return "text/css";
2762  case "csv"_t: return "text/csv";
2763  case "htm"_t:
2764  case "html"_t: return "text/html";
2765  case "js"_t:
2766  case "mjs"_t: return "text/javascript";
2767  case "txt"_t: return "text/plain";
2768  case "vtt"_t: return "text/vtt";
2769
2770  case "apng"_t: return "image/apng";
2771  case "avif"_t: return "image/avif";
2772  case "bmp"_t: return "image/bmp";
2773  case "gif"_t: return "image/gif";
2774  case "png"_t: return "image/png";
2775  case "svg"_t: return "image/svg+xml";
2776  case "webp"_t: return "image/webp";
2777  case "ico"_t: return "image/x-icon";
2778  case "tif"_t: return "image/tiff";
2779  case "tiff"_t: return "image/tiff";
2780  case "jpg"_t:
2781  case "jpeg"_t: return "image/jpeg";
2782
2783  case "mp4"_t: return "video/mp4";
2784  case "mpeg"_t: return "video/mpeg";
2785  case "webm"_t: return "video/webm";
2786
2787  case "mp3"_t: return "audio/mp3";
2788  case "mpga"_t: return "audio/mpeg";
2789  case "weba"_t: return "audio/webm";
2790  case "wav"_t: return "audio/wave";
2791
2792  case "otf"_t: return "font/otf";
2793  case "ttf"_t: return "font/ttf";
2794  case "woff"_t: return "font/woff";
2795  case "woff2"_t: return "font/woff2";
2796
2797  case "7z"_t: return "application/x-7z-compressed";
2798  case "atom"_t: return "application/atom+xml";
2799  case "pdf"_t: return "application/pdf";
2800  case "json"_t: return "application/json";
2801  case "rss"_t: return "application/rss+xml";
2802  case "tar"_t: return "application/x-tar";
2803  case "xht"_t:
2804  case "xhtml"_t: return "application/xhtml+xml";
2805  case "xslt"_t: return "application/xslt+xml";
2806  case "xml"_t: return "application/xml";
2807  case "gz"_t: return "application/gzip";
2808  case "zip"_t: return "application/zip";
2809  case "wasm"_t: return "application/wasm";
2810  }
2811}
2812
2813std::string
2814extract_media_type(const std::string &content_type,
2815                   std::map<std::string, std::string> *params = nullptr) {
2816  // Extract type/subtype from Content-Type value (RFC 2045)
2817  // e.g. "application/json; charset=utf-8" -> "application/json"
2818  auto media_type = content_type;
2819  auto semicolon_pos = media_type.find(';');
2820  if (semicolon_pos != std::string::npos) {
2821    auto param_str = media_type.substr(semicolon_pos + 1);
2822    media_type = media_type.substr(0, semicolon_pos);
2823
2824    if (params) {
2825      // Parse parameters: key=value pairs separated by ';'
2826      split_unquoted(param_str.data(), param_str.data() + param_str.size(), ';',
2827                     [&](const char *b, const char *e) {
2828                       std::string key;
2829                       std::string val;
2830                       divide_param_pair(b, e, key, val);
2831                       if (!key.empty()) {
2832                         params->emplace(trim_copy(key),
2833                                         trim_double_quotes_copy(val));
2834                       }
2835                     });
2836    }
2837  }
2838
2839  // Trim whitespace from media type
2840  return trim_copy(media_type);
2841}
2842
2843bool can_compress_content_type(const std::string &content_type) {
2844  using udl::operator""_t;
2845
2846  auto mime_type = extract_media_type(content_type);
2847  auto tag = str2tag(mime_type);
2848
2849  switch (tag) {
2850  case "image/svg+xml"_t:
2851  case "application/javascript"_t:
2852  case "application/x-javascript"_t:
2853  case "application/json"_t:
2854  case "application/ld+json"_t:
2855  case "application/xml"_t:
2856  case "application/xhtml+xml"_t:
2857  case "application/rss+xml"_t:
2858  case "application/atom+xml"_t:
2859  case "application/xslt+xml"_t:
2860  case "application/protobuf"_t: return true;
2861
2862  case "text/event-stream"_t: return false;
2863
2864  default: return !mime_type.rfind("text/", 0);
2865  }
2866}
2867
2868bool parse_quality(const char *b, const char *e, std::string &token,
2869                          double &quality) {
2870  quality = 1.0;
2871  token.clear();
2872
2873  // Split on first ';': left = token name, right = parameters
2874  const char *params_b = nullptr;
2875  std::size_t params_len = 0;
2876
2877  divide(
2878      b, static_cast<std::size_t>(e - b), ';',
2879      [&](const char *lb, std::size_t llen, const char *rb, std::size_t rlen) {
2880        auto r = trim(lb, lb + llen, 0, llen);
2881        if (r.first < r.second) { token.assign(lb + r.first, lb + r.second); }
2882        params_b = rb;
2883        params_len = rlen;
2884      });
2885
2886  if (token.empty()) { return false; }
2887  if (params_len == 0) { return true; }
2888
2889  // Scan parameters for q= (stops on first match)
2890  bool invalid = false;
2891  split_find(params_b, params_b + params_len, ';',
2892             (std::numeric_limits<size_t>::max)(),
2893             [&](const char *pb, const char *pe) -> bool {
2894               // Match exactly "q=" or "Q=" (not "query=" etc.)
2895               auto len = static_cast<size_t>(pe - pb);
2896               if (len < 2) { return false; }
2897               if ((pb[0] != 'q' && pb[0] != 'Q') || pb[1] != '=') {
2898                 return false;
2899               }
2900
2901               // Trim the value portion
2902               auto r = trim(pb, pe, 2, len);
2903               if (r.first >= r.second) {
2904                 invalid = true;
2905                 return true;
2906               }
2907
2908               double v = 0.0;
2909               auto res = from_chars(pb + r.first, pb + r.second, v);
2910               if (res.ec != std::errc{} || v < 0.0 || v > 1.0) {
2911                 invalid = true;
2912                 return true;
2913               }
2914               quality = v;
2915               return true;
2916             });
2917
2918  return !invalid;
2919}
2920
2921EncodingType encoding_type(const Request &req,
2922                                  const std::string &content_type) {
2923  if (!can_compress_content_type(content_type)) { return EncodingType::None; }
2924
2925  auto s = get_combined_header_value(req.headers, "Accept-Encoding");
2926  if (s.empty()) { return EncodingType::None; }
2927
2928  // Single-pass: iterate tokens and track the best supported encoding.
2929  // Server preference breaks ties (br > gzip > zstd).
2930  EncodingType best = EncodingType::None;
2931  double best_q = 0.0; // q=0 means "not acceptable"
2932
2933  // Server preference: Brotli > Gzip > Zstd (lower = more preferred)
2934  auto priority = [](EncodingType t) -> int {
2935    switch (t) {
2936    case EncodingType::Brotli: return 0;
2937    case EncodingType::Gzip: return 1;
2938    case EncodingType::Zstd: return 2;
2939    default: return 3;
2940    }
2941  };
2942
2943  std::string name;
2944  split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) {
2945    double quality = 1.0;
2946    if (!parse_quality(b, e, name, quality)) { return; }
2947    if (quality <= 0.0) { return; }
2948
2949    EncodingType type = EncodingType::None;
2950#ifdef CPPHTTPLIB_BROTLI_SUPPORT
2951    if (case_ignore::equal(name, "br")) { type = EncodingType::Brotli; }
2952#endif
2953#ifdef CPPHTTPLIB_ZLIB_SUPPORT
2954    if (type == EncodingType::None && case_ignore::equal(name, "gzip")) {
2955      type = EncodingType::Gzip;
2956    }
2957#endif
2958#ifdef CPPHTTPLIB_ZSTD_SUPPORT
2959    if (type == EncodingType::None && case_ignore::equal(name, "zstd")) {
2960      type = EncodingType::Zstd;
2961    }
2962#endif
2963
2964    if (type == EncodingType::None) { return; }
2965
2966    // Higher q-value wins; for equal q, server preference breaks ties
2967    if (quality > best_q ||
2968        (quality == best_q && priority(type) < priority(best))) {
2969      best_q = quality;
2970      best = type;
2971    }
2972  });
2973
2974  return best;
2975}
2976
2977// `content_type` is taken separately because a file-backed response has not
2978// been given one yet when its coding has to be decided.
2979EncodingType encoding_type(const Request &req, const Response &res,
2980                                  const std::string &content_type) {
2981  // The response already names a content coding of its own: a handler serving
2982  // a body it encoded itself (pre-compressed static assets, say), or a mount
2983  // point whose headers name the coding its files are stored in. Applying one
2984  // on top of that would double-encode the body and append a second
2985  // `Content-Encoding` field line.
2986  if (res.has_header("Content-Encoding")) { return EncodingType::None; }
2987  return encoding_type(req, content_type);
2988}
2989
2990EncodingType encoding_type(const Request &req, const Response &res) {
2991  return encoding_type(req, res, res.get_header_value("Content-Type"));
2992}
2993
2994std::unique_ptr<compressor> make_compressor(EncodingType type) {
2995#ifdef CPPHTTPLIB_ZLIB_SUPPORT
2996  if (type == EncodingType::Gzip) {
2997    return detail::make_unique<gzip_compressor>();
2998  }
2999#endif
3000#ifdef CPPHTTPLIB_BROTLI_SUPPORT
3001  if (type == EncodingType::Brotli) {
3002    return detail::make_unique<brotli_compressor>();
3003  }
3004#endif
3005#ifdef CPPHTTPLIB_ZSTD_SUPPORT
3006  if (type == EncodingType::Zstd) {
3007    return detail::make_unique<zstd_compressor>();
3008  }
3009#endif
3010  (void)type;
3011  return nullptr;
3012}
3013
3014const char *encoding_name(EncodingType type) {
3015  switch (type) {
3016  case EncodingType::Gzip: return "gzip";
3017  case EncodingType::Brotli: return "br";
3018  case EncodingType::Zstd: return "zstd";
3019  default: return "";
3020  }
3021}
3022
3023bool nocompressor::compress(const char *data, size_t data_length,
3024                                   bool /*last*/, Callback callback) {
3025  if (!data_length) { return true; }
3026  return callback(data, data_length);
3027}
3028
3029#ifdef CPPHTTPLIB_ZLIB_SUPPORT
3030gzip_compressor::gzip_compressor() {
3031  std::memset(&strm_, 0, sizeof(strm_));
3032  strm_.zalloc = Z_NULL;
3033  strm_.zfree = Z_NULL;
3034  strm_.opaque = Z_NULL;
3035
3036  is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
3037                           Z_DEFAULT_STRATEGY) == Z_OK;
3038}
3039
3040gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); }
3041
3042bool gzip_compressor::compress(const char *data, size_t data_length,
3043                                      bool last, Callback callback) {
3044  assert(is_valid_);
3045
3046  do {
3047    constexpr size_t max_avail_in =
3048        (std::numeric_limits<decltype(strm_.avail_in)>::max)();
3049
3050    strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
3051        (std::min)(data_length, max_avail_in));
3052    strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
3053
3054    data_length -= strm_.avail_in;
3055    data += strm_.avail_in;
3056
3057    auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH;
3058    auto ret = Z_OK;
3059
3060    std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
3061    do {
3062      strm_.avail_out = static_cast<uInt>(buff.size());
3063      strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
3064
3065      ret = deflate(&strm_, flush);
3066      if (ret == Z_STREAM_ERROR) { return false; }
3067
3068      if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
3069        return false;
3070      }
3071    } while (strm_.avail_out == 0);
3072
3073    assert((flush == Z_FINISH && ret == Z_STREAM_END) ||
3074           (flush == Z_NO_FLUSH && ret == Z_OK));
3075    assert(strm_.avail_in == 0);
3076  } while (data_length > 0);
3077
3078  return true;
3079}
3080
3081gzip_decompressor::gzip_decompressor() {
3082  std::memset(&strm_, 0, sizeof(strm_));
3083  strm_.zalloc = Z_NULL;
3084  strm_.zfree = Z_NULL;
3085  strm_.opaque = Z_NULL;
3086
3087  // 15 is the value of wbits, which should be at the maximum possible value
3088  // to ensure that any gzip stream can be decoded. The offset of 32 specifies
3089  // that the stream type should be automatically detected either gzip or
3090  // deflate.
3091  is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK;
3092}
3093
3094gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); }
3095
3096bool gzip_decompressor::is_valid() const { return is_valid_; }
3097
3098bool gzip_decompressor::decompress(const char *data, size_t data_length,
3099                                          Callback callback) {
3100  assert(is_valid_);
3101
3102  auto ret = Z_OK;
3103
3104  do {
3105    constexpr size_t max_avail_in =
3106        (std::numeric_limits<decltype(strm_.avail_in)>::max)();
3107
3108    strm_.avail_in = static_cast<decltype(strm_.avail_in)>(
3109        (std::min)(data_length, max_avail_in));
3110    strm_.next_in = const_cast<Bytef *>(reinterpret_cast<const Bytef *>(data));
3111
3112    data_length -= strm_.avail_in;
3113    data += strm_.avail_in;
3114
3115    std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
3116    while (strm_.avail_in > 0 && ret == Z_OK) {
3117      strm_.avail_out = static_cast<uInt>(buff.size());
3118      strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
3119
3120      ret = inflate(&strm_, Z_NO_FLUSH);
3121
3122      assert(ret != Z_STREAM_ERROR);
3123      switch (ret) {
3124      case Z_NEED_DICT:
3125      case Z_DATA_ERROR:
3126      case Z_MEM_ERROR: inflateEnd(&strm_); return false;
3127      }
3128
3129      if (!callback(buff.data(), buff.size() - strm_.avail_out)) {
3130        return false;
3131      }
3132    }
3133
3134    if (ret != Z_OK && ret != Z_STREAM_END) { return false; }
3135
3136  } while (data_length > 0);
3137
3138  return true;
3139}
3140#endif
3141
3142#ifdef CPPHTTPLIB_BROTLI_SUPPORT
3143brotli_compressor::brotli_compressor() {
3144  state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
3145}
3146
3147brotli_compressor::~brotli_compressor() {
3148  BrotliEncoderDestroyInstance(state_);
3149}
3150
3151bool brotli_compressor::compress(const char *data, size_t data_length,
3152                                        bool last, Callback callback) {
3153  std::array<uint8_t, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
3154
3155  auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS;
3156  auto available_in = data_length;
3157  auto next_in = reinterpret_cast<const uint8_t *>(data);
3158
3159  for (;;) {
3160    if (last) {
3161      if (BrotliEncoderIsFinished(state_)) { break; }
3162    } else {
3163      if (!available_in) { break; }
3164    }
3165
3166    auto available_out = buff.size();
3167    auto next_out = buff.data();
3168
3169    if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in,
3170                                     &available_out, &next_out, nullptr)) {
3171      return false;
3172    }
3173
3174    auto output_bytes = buff.size() - available_out;
3175    if (output_bytes) {
3176      callback(reinterpret_cast<const char *>(buff.data()), output_bytes);
3177    }
3178  }
3179
3180  return true;
3181}
3182
3183brotli_decompressor::brotli_decompressor() {
3184  decoder_s = BrotliDecoderCreateInstance(0, 0, 0);
3185  decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
3186                        : BROTLI_DECODER_RESULT_ERROR;
3187}
3188
3189brotli_decompressor::~brotli_decompressor() {
3190  if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); }
3191}
3192
3193bool brotli_decompressor::is_valid() const { return decoder_s; }
3194
3195bool brotli_decompressor::decompress(const char *data,
3196                                            size_t data_length,
3197                                            Callback callback) {
3198  if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
3199      decoder_r == BROTLI_DECODER_RESULT_ERROR) {
3200    return 0;
3201  }
3202
3203  auto next_in = reinterpret_cast<const uint8_t *>(data);
3204  size_t avail_in = data_length;
3205  size_t total_out;
3206
3207  decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
3208
3209  std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
3210  while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
3211    char *next_out = buff.data();
3212    size_t avail_out = buff.size();
3213
3214    decoder_r = BrotliDecoderDecompressStream(
3215        decoder_s, &avail_in, &next_in, &avail_out,
3216        reinterpret_cast<uint8_t **>(&next_out), &total_out);
3217
3218    if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; }
3219
3220    if (!callback(buff.data(), buff.size() - avail_out)) { return false; }
3221  }
3222
3223  return decoder_r == BROTLI_DECODER_RESULT_SUCCESS ||
3224         decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
3225}
3226#endif
3227
3228#ifdef CPPHTTPLIB_ZSTD_SUPPORT
3229zstd_compressor::zstd_compressor() {
3230  ctx_ = ZSTD_createCCtx();
3231  ZSTD_CCtx_setParameter(ctx_, ZSTD_c_compressionLevel, ZSTD_fast);
3232}
3233
3234zstd_compressor::~zstd_compressor() { ZSTD_freeCCtx(ctx_); }
3235
3236bool zstd_compressor::compress(const char *data, size_t data_length,
3237                                      bool last, Callback callback) {
3238  std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
3239
3240  ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue;
3241  ZSTD_inBuffer input = {data, data_length, 0};
3242
3243  bool finished;
3244  do {
3245    ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
3246    size_t const remaining = ZSTD_compressStream2(ctx_, &output, &input, mode);
3247
3248    if (ZSTD_isError(remaining)) { return false; }
3249
3250    if (!callback(buff.data(), output.pos)) { return false; }
3251
3252    finished = last ? (remaining == 0) : (input.pos == input.size);
3253
3254  } while (!finished);
3255
3256  return true;
3257}
3258
3259zstd_decompressor::zstd_decompressor() { ctx_ = ZSTD_createDCtx(); }
3260
3261zstd_decompressor::~zstd_decompressor() { ZSTD_freeDCtx(ctx_); }
3262
3263bool zstd_decompressor::is_valid() const { return ctx_ != nullptr; }
3264
3265bool zstd_decompressor::decompress(const char *data, size_t data_length,
3266                                          Callback callback) {
3267  std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
3268  ZSTD_inBuffer input = {data, data_length, 0};
3269
3270  while (input.pos < input.size) {
3271    ZSTD_outBuffer output = {buff.data(), CPPHTTPLIB_COMPRESSION_BUFSIZ, 0};
3272    size_t const remaining = ZSTD_decompressStream(ctx_, &output, &input);
3273
3274    if (ZSTD_isError(remaining)) { return false; }
3275
3276    if (!callback(buff.data(), output.pos)) { return false; }
3277  }
3278
3279  return true;
3280}
3281#endif
3282
3283// Content codings are case-insensitive (RFC 9110 8.4.1). Matching them
3284// case-sensitively would make a response labeled e.g. "GZIP" look like an
3285// unknown coding, and its payload would be handed back still compressed.
3286bool is_zlib_encoding(const std::string &encoding) {
3287  return case_ignore::equal(encoding, "gzip") ||
3288         case_ignore::equal(encoding, "deflate");
3289}
3290
3291bool is_brotli_encoding(const std::string &encoding) {
3292  return case_ignore::equal(encoding, "br");
3293}
3294
3295bool is_zstd_encoding(const std::string &encoding) {
3296  return case_ignore::equal(encoding, "zstd");
3297}
3298
3299// Returns true if the content coding is one cpp-httplib is able to decompress
3300// when the corresponding support is compiled in.
3301bool is_known_content_encoding(const std::string &encoding) {
3302  return is_zlib_encoding(encoding) || is_brotli_encoding(encoding) ||
3303         is_zstd_encoding(encoding);
3304}
3305
3306std::unique_ptr<decompressor>
3307create_decompressor(const std::string &encoding) {
3308  std::unique_ptr<decompressor> decompressor;
3309
3310  if (is_zlib_encoding(encoding)) {
3311#ifdef CPPHTTPLIB_ZLIB_SUPPORT
3312    decompressor = detail::make_unique<gzip_decompressor>();
3313#endif
3314  } else if (is_brotli_encoding(encoding)) {
3315#ifdef CPPHTTPLIB_BROTLI_SUPPORT
3316    decompressor = detail::make_unique<brotli_decompressor>();
3317#endif
3318  } else if (is_zstd_encoding(encoding)) {
3319#ifdef CPPHTTPLIB_ZSTD_SUPPORT
3320    decompressor = detail::make_unique<zstd_decompressor>();
3321#endif
3322  }
3323
3324  return decompressor;
3325}
3326
3327// Returns the best available compressor and its Content-Encoding name.
3328// Priority: Brotli > Gzip > Zstd (matches server-side preference).
3329std::pair<std::unique_ptr<compressor>, const char *>
3330create_compressor() {
3331#ifdef CPPHTTPLIB_BROTLI_SUPPORT
3332  return {detail::make_unique<brotli_compressor>(), "br"};
3333#elif defined(CPPHTTPLIB_ZLIB_SUPPORT)
3334  return {detail::make_unique<gzip_compressor>(), "gzip"};
3335#elif defined(CPPHTTPLIB_ZSTD_SUPPORT)
3336  return {detail::make_unique<zstd_compressor>(), "zstd"};
3337#else
3338  return {nullptr, nullptr};
3339#endif
3340}
3341
3342bool is_prohibited_header_name(const std::string &name) {
3343  using udl::operator""_t;
3344
3345  switch (str2tag(name)) {
3346  case "REMOTE_ADDR"_t:
3347  case "REMOTE_PORT"_t:
3348  case "LOCAL_ADDR"_t:
3349  case "LOCAL_PORT"_t: return true;
3350  default: return false;
3351  }
3352}
3353
3354bool has_header(const Headers &headers, const std::string &key) {
3355  if (is_prohibited_header_name(key)) { return false; }
3356  return headers.find(key) != headers.end();
3357}
3358
3359const char *get_header_value(const Headers &headers,
3360                                    const std::string &key, const char *def,
3361                                    size_t id) {
3362  if (is_prohibited_header_name(key)) {
3363#ifndef CPPHTTPLIB_NO_EXCEPTIONS
3364    std::string msg = "Prohibited header name '" + key + "' is specified.";
3365    throw std::invalid_argument(msg);
3366#else
3367    return "";
3368#endif
3369  }
3370
3371  auto rng = headers.equal_range(key);
3372  auto it = rng.first;
3373  std::advance(it, static_cast<ssize_t>(id));
3374  if (it != rng.second) { return it->second.c_str(); }
3375  return def;
3376}
3377
3378size_t get_header_value_count(const Headers &headers,
3379                                     const std::string &key) {
3380  return headers.count(key);
3381}
3382
3383// RFC 9110 Section 5.2 and 5.3: a field that is defined as a comma-separated
3384// list may be sent as several field lines, and the combined field value is
3385// those values joined by commas in the order they were received. Callers that
3386// parse such a list must work on the combined value; reading only the first
3387// occurrence silently drops whatever the later field lines carry.
3388std::string get_combined_header_value(const Headers &headers,
3389                                             const std::string &key) {
3390  std::string combined;
3391  auto rng = headers.equal_range(key);
3392  for (auto it = rng.first; it != rng.second; ++it) {
3393    // RFC 9110 Section 5.6.1.2: a recipient has to parse and ignore empty list
3394    // elements, so an empty field line must not contribute a bare comma to the
3395    // combined value.
3396    if (it->second.empty()) { continue; }
3397    if (!combined.empty()) { combined += ", "; }
3398    combined += it->second;
3399  }
3400  return combined;
3401}
3402
3403bool has_header_token(const Headers &headers, const std::string &key,
3404                             const std::string &token) {
3405  // RFC 9110 7.6.1: a comma-separated token list field such as Connection may
3406  // carry several tokens, and RFC 9110 5.3 lets that list be split across
3407  // several lines. Match complete tokens rather than searching the raw value,
3408  // so that a value such as "notupgrade" is not read as the token "upgrade".
3409  auto rng = headers.equal_range(key);
3410  for (auto it = rng.first; it != rng.second; ++it) {
3411    const auto &value = it->second;
3412    if (split_find(value.data(), value.data() + value.size(), ',',
3413                   [&](const char *b, const char *e) {
3414                     return case_ignore::equal(std::string(b, e), token);
3415                   })) {
3416      return true;
3417    }
3418  }
3419  return false;
3420}
3421
3422template <typename Map>
3423typename Map::mapped_type
3424get_multimap_value(const Map &m, const std::string &key, size_t id) {
3425  auto rng = m.equal_range(key);
3426  auto it = rng.first;
3427  std::advance(it, static_cast<ssize_t>(id));
3428  if (it != rng.second) { return it->second; }
3429  return typename Map::mapped_type();
3430}
3431
3432void set_header(Headers &headers, const std::string &key,
3433                       const std::string &val) {
3434  if (fields::is_field_valid(key, val)) { headers.emplace(key, val); }
3435}
3436
3437bool read_headers(Stream &strm, Headers &headers) {
3438  const auto bufsiz = 2048;
3439  char buf[bufsiz];
3440  stream_line_reader line_reader(strm, buf, bufsiz);
3441
3442  size_t header_count = 0;
3443
3444  for (;;) {
3445    if (!line_reader.getline()) { return false; }
3446
3447    // Check if the line ends with CRLF.
3448    auto line_terminator_len = 2;
3449    if (line_reader.end_with_crlf()) {
3450      // Blank line indicates end of headers.
3451      if (line_reader.size() == 2) { break; }
3452    } else {
3453#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
3454      // Blank line indicates end of headers.
3455      if (line_reader.size() == 1) { break; }
3456      line_terminator_len = 1;
3457#else
3458      continue; // Skip invalid line.
3459#endif
3460    }
3461
3462    if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
3463
3464    // Check header count limit
3465    if (header_count >= CPPHTTPLIB_HEADER_MAX_COUNT) { return false; }
3466
3467    // Exclude line terminator
3468    auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
3469
3470    if (!parse_header(line_reader.ptr(), end,
3471                      [&](const std::string &key, const std::string &val) {
3472                        headers.emplace(key, val);
3473                      })) {
3474      return false;
3475    }
3476
3477    header_count++;
3478  }
3479
3480  // RFC 9110 Section 8.6: Reject requests with multiple Content-Length
3481  // headers that have different values to prevent request smuggling.
3482  auto cl_range = headers.equal_range("Content-Length");
3483  if (cl_range.first != cl_range.second) {
3484    const auto &first_val = cl_range.first->second;
3485    for (auto it = std::next(cl_range.first); it != cl_range.second; ++it) {
3486      if (it->second != first_val) { return false; }
3487    }
3488  }
3489
3490  return true;
3491}
3492
3493bool parse_status_line(const char *line, std::string &version,
3494                              int &status, std::string &reason) {
3495#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
3496  thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n");
3497#else
3498  thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n");
3499#endif
3500
3501  std::cmatch m;
3502  if (!std::regex_match(line, m, re)) { return false; }
3503  version = std::string(m[1]);
3504  status = std::stoi(std::string(m[2]));
3505  reason = std::string(m[3]);
3506  return true;
3507}
3508
3509// Everything WebSocketClient::connect() reports about the upgrade exchange.
3510// status stays -1 until a status line is parsed, mirroring stream::Result.
3511struct WebSocketUpgradeResponse {
3512  Error error = Error::Success;
3513  int status = -1;
3514  Headers headers;
3515  std::string selected_subprotocol;
3516};
3517
3518bool read_websocket_upgrade_response(Stream &strm,
3519                                            const std::string &expected_accept,
3520                                            WebSocketUpgradeResponse &upgrade) {
3521  // Read status line
3522  const auto bufsiz = 2048;
3523  char buf[bufsiz];
3524  stream_line_reader line_reader(strm, buf, bufsiz);
3525  if (!line_reader.getline()) {
3526    upgrade.error = Error::Read;
3527    return false;
3528  }
3529
3530  std::string version;
3531  std::string reason;
3532  if (!parse_status_line(line_reader.ptr(), version, upgrade.status, reason)) {
3533    upgrade.error = Error::WebSocketHandshake;
3534    return false;
3535  }
3536
3537  // Read the headers even for a rejection so the caller can see why the
3538  // server refused the upgrade. A non-101 response may carry a body; it is
3539  // deliberately left unread since the caller closes the socket right away.
3540  if (!read_headers(strm, upgrade.headers)) {
3541    upgrade.error = Error::Read;
3542    return false;
3543  }
3544
3545  const auto &headers = upgrade.headers;
3546
3547  if (upgrade.status != StatusCode::SwitchingProtocol_101) {
3548    upgrade.error = Error::WebSocketHandshake;
3549    return false;
3550  }
3551
3552  // Verify Upgrade: websocket (a comma-separated list, matched per token)
3553  if (!has_header_token(headers, "Upgrade", "websocket")) {
3554    upgrade.error = Error::WebSocketHandshake;
3555    return false;
3556  }
3557
3558  // Verify Connection: Upgrade
3559  if (!has_header_token(headers, "Connection", "upgrade")) {
3560    upgrade.error = Error::WebSocketHandshake;
3561    return false;
3562  }
3563
3564  // Verify Sec-WebSocket-Accept header value
3565  auto it = headers.find("Sec-WebSocket-Accept");
3566  if (it == headers.end() || it->second != expected_accept) {
3567    upgrade.error = Error::WebSocketHandshake;
3568    return false;
3569  }
3570
3571  // Extract negotiated subprotocol
3572  auto proto_it = headers.find("Sec-WebSocket-Protocol");
3573  if (proto_it != headers.end()) {
3574    upgrade.selected_subprotocol = proto_it->second;
3575  }
3576
3577  return true;
3578}
3579
3580enum class ReadContentResult {
3581  Success,         // Successfully read the content
3582  PayloadTooLarge, // The content exceeds the specified payload limit
3583  Error            // An error occurred while reading the content
3584};
3585
3586ReadContentResult read_content_with_length(
3587    Stream &strm, size_t len, DownloadProgress progress,
3588    ContentReceiverWithProgress out,
3589    size_t payload_max_length = (std::numeric_limits<size_t>::max)()) {
3590  char buf[CPPHTTPLIB_RECV_BUFSIZ];
3591
3592  detail::BodyReader br;
3593  br.stream = &strm;
3594  br.has_content_length = true;
3595  br.content_length = len;
3596  br.payload_max_length = payload_max_length;
3597  br.chunked = false;
3598  br.bytes_read = 0;
3599  br.last_error = Error::Success;
3600
3601  size_t r = 0;
3602  while (r < len) {
3603    auto read_len = static_cast<size_t>(len - r);
3604    auto to_read = (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ);
3605    auto n = detail::read_body_content(&strm, br, buf, to_read);
3606    if (n <= 0) {
3607      // Check if it was a payload size error
3608      if (br.last_error == Error::ExceedMaxPayloadSize) {
3609        return ReadContentResult::PayloadTooLarge;
3610      }
3611      return ReadContentResult::Error;
3612    }
3613
3614    if (!out(buf, static_cast<size_t>(n), r, len)) {
3615      return ReadContentResult::Error;
3616    }
3617    r += static_cast<size_t>(n);
3618
3619    if (progress) {
3620      if (!progress(r, len)) { return ReadContentResult::Error; }
3621    }
3622  }
3623
3624  return ReadContentResult::Success;
3625}
3626
3627ReadContentResult
3628read_content_without_length(Stream &strm, size_t payload_max_length,
3629                            ContentReceiverWithProgress out) {
3630  char buf[CPPHTTPLIB_RECV_BUFSIZ];
3631  size_t r = 0;
3632  for (;;) {
3633    auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
3634    if (n == 0) { return ReadContentResult::Success; }
3635    if (n < 0) { return ReadContentResult::Error; }
3636
3637    // Check if adding this data would exceed the payload limit
3638    if (r > payload_max_length ||
3639        payload_max_length - r < static_cast<size_t>(n)) {
3640      return ReadContentResult::PayloadTooLarge;
3641    }
3642
3643    if (!out(buf, static_cast<size_t>(n), r, 0)) {
3644      return ReadContentResult::Error;
3645    }
3646    r += static_cast<size_t>(n);
3647  }
3648
3649  return ReadContentResult::Success;
3650}
3651
3652template <typename T>
3653ReadContentResult read_content_chunked(Stream &strm, T &x,
3654                                              size_t payload_max_length,
3655                                              ContentReceiverWithProgress out) {
3656  detail::ChunkedDecoder dec(strm);
3657
3658  char buf[CPPHTTPLIB_RECV_BUFSIZ];
3659  size_t total_len = 0;
3660
3661  for (;;) {
3662    size_t chunk_offset = 0;
3663    size_t chunk_total = 0;
3664    auto n = dec.read_payload(buf, sizeof(buf), chunk_offset, chunk_total);
3665    if (n < 0) { return ReadContentResult::Error; }
3666
3667    if (n == 0) {
3668      if (!dec.parse_trailers_into(x.trailers, x.headers)) {
3669        return ReadContentResult::Error;
3670      }
3671      return ReadContentResult::Success;
3672    }
3673
3674    if (total_len > payload_max_length ||
3675        payload_max_length - total_len < static_cast<size_t>(n)) {
3676      return ReadContentResult::PayloadTooLarge;
3677    }
3678
3679    if (!out(buf, static_cast<size_t>(n), chunk_offset, chunk_total)) {
3680      return ReadContentResult::Error;
3681    }
3682
3683    total_len += static_cast<size_t>(n);
3684  }
3685}
3686
3687bool is_chunked_transfer_encoding(const Headers &headers) {
3688  // RFC 9112 6.1: a message is framed with the chunked coding when "chunked"
3689  // is the final transfer coding. A single field value may list several
3690  // codings ("gzip, chunked"), and RFC 9110 5.3 lets that list be split across
3691  // several Transfer-Encoding lines, which combine into one comma-separated
3692  // list in the order the lines were received. Headers preserves that order,
3693  // so the final coding is the last token of the last line. Match it
3694  // case-insensitively rather than comparing the whole value against
3695  // "chunked".
3696  //
3697  // Security: reading a chunked message as unframed leaves its body in the
3698  // socket, where a keep-alive connection parses it as a smuggled request.
3699  // Server::process_request() answers 400 and closes when the final coding is
3700  // not chunked, so a request whose framing cannot be determined never
3701  // reaches the "no body" path.
3702  auto rng = headers.equal_range("Transfer-Encoding");
3703  if (rng.first == rng.second) { return false; }
3704
3705  // Cleared per line, so a trailing line carrying no coding at all leaves the
3706  // combined list ending in nothing rather than inheriting the line before it.
3707  std::string last_coding;
3708
3709  for (auto it = rng.first; it != rng.second; ++it) {
3710    const auto &value = it->second;
3711    last_coding.clear();
3712    split(value.data(), value.data() + value.size(), ',',
3713          [&](const char *b, const char *e) { last_coding.assign(b, e); });
3714  }
3715
3716  return case_ignore::equal(last_coding, "chunked");
3717}
3718
3719bool has_conflicting_content_length(const Headers &headers) {
3720  // RFC 9112 §6.3: a message carrying both Transfer-Encoding and a non-zero
3721  // Content-Length is framed ambiguously. The body readers here delimit it by
3722  // the transfer coding and drop Content-Length, while an intermediary may do
3723  // the reverse, so the two disagree on where the body ends and a reused
3724  // connection is desynchronised (request/response smuggling). Content-Length:
3725  // 0 is tolerated for compatibility with existing peers.
3726  return has_header(headers, "Transfer-Encoding") &&
3727         get_header_value_u64(headers, "Content-Length", 0, 0) > 0;
3728}
3729
3730template <typename T, typename U>
3731bool prepare_content_receiver(T &x, int &status,
3732                              ContentReceiverWithProgress receiver,
3733                              bool decompress, size_t payload_max_length,
3734                              bool &exceed_payload_max_length, U callback) {
3735  if (decompress) {
3736    auto encoding = get_combined_header_value(x.headers, "Content-Encoding");
3737    std::unique_ptr<decompressor> decompressor;
3738
3739    if (!encoding.empty()) {
3740      // A coding we know about but were not built with is an error. An
3741      // unrecognized coding (including "identity") is left alone and the
3742      // payload is passed through as-is, since some servers misuse the header,
3743      // e.g. by sending a character set such as "Content-Encoding: UTF-8".
3744      decompressor = detail::create_decompressor(encoding);
3745      if (!decompressor && detail::is_known_content_encoding(encoding)) {
3746        status = StatusCode::UnsupportedMediaType_415;
3747        return false;
3748      }
3749    }
3750
3751    if (decompressor) {
3752      if (decompressor->is_valid()) {
3753        size_t decompressed_size = 0;
3754        ContentReceiverWithProgress out = [&](const char *buf, size_t n,
3755                                              size_t off, size_t len) {
3756          return decompressor->decompress(
3757              buf, n, [&](const char *buf2, size_t n2) {
3758                // Guard against zip-bomb: check
3759                // decompressed size against limit.
3760                if (payload_max_length > 0 &&
3761                    (decompressed_size >= payload_max_length ||
3762                     n2 > payload_max_length - decompressed_size)) {
3763                  exceed_payload_max_length = true;
3764                  return false;
3765                }
3766                decompressed_size += n2;
3767                return receiver(buf2, n2, off, len);
3768              });
3769        };
3770        return callback(std::move(out));
3771      } else {
3772        status = StatusCode::InternalServerError_500;
3773        return false;
3774      }
3775    }
3776  }
3777
3778  ContentReceiverWithProgress out = [&](const char *buf, size_t n, size_t off,
3779                                        size_t len) {
3780    return receiver(buf, n, off, len);
3781  };
3782  return callback(std::move(out));
3783}
3784
3785template <typename T>
3786bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
3787                  DownloadProgress progress,
3788                  ContentReceiverWithProgress receiver, bool decompress) {
3789  bool exceed_payload_max_length = false;
3790  return prepare_content_receiver(
3791      x, status, std::move(receiver), decompress, payload_max_length,
3792      exceed_payload_max_length, [&](const ContentReceiverWithProgress &out) {
3793        auto ret = true;
3794        // Note: exceed_payload_max_length may also be set by the decompressor
3795        // wrapper in prepare_content_receiver when the decompressed payload
3796        // size exceeds the limit.
3797
3798        if (is_chunked_transfer_encoding(x.headers)) {
3799          auto result = read_content_chunked(strm, x, payload_max_length, out);
3800          if (result == ReadContentResult::Success) {
3801            ret = true;
3802          } else if (result == ReadContentResult::PayloadTooLarge) {
3803            exceed_payload_max_length = true;
3804            ret = false;
3805          } else {
3806            ret = false;
3807          }
3808        } else if (!has_header(x.headers, "Content-Length")) {
3809          auto result =
3810              read_content_without_length(strm, payload_max_length, out);
3811          if (result == ReadContentResult::Success) {
3812            ret = true;
3813          } else if (result == ReadContentResult::PayloadTooLarge) {
3814            exceed_payload_max_length = true;
3815            ret = false;
3816          } else {
3817            ret = false;
3818          }
3819        } else {
3820          auto is_invalid_value = false;
3821          auto len = get_header_value_u64(x.headers, "Content-Length",
3822                                          (std::numeric_limits<size_t>::max)(),
3823                                          0, is_invalid_value);
3824
3825          if (is_invalid_value) {
3826            ret = false;
3827          } else if (len > 0) {
3828            auto result = read_content_with_length(
3829                strm, len, std::move(progress), out, payload_max_length);
3830            ret = (result == ReadContentResult::Success);
3831            if (result == ReadContentResult::PayloadTooLarge) {
3832              exceed_payload_max_length = true;
3833            }
3834          }
3835        }
3836
3837        if (!ret) {
3838          status = exceed_payload_max_length ? StatusCode::PayloadTooLarge_413
3839                                             : StatusCode::BadRequest_400;
3840        }
3841        return ret;
3842      });
3843}
3844
3845ssize_t write_request_line(Stream &strm, const std::string &method,
3846                                  const std::string &path) {
3847  // A request target must not carry CR/LF (or other control octets); otherwise
3848  // a value smuggled into it splits the request line and injects headers or a
3849  // whole request. The same field-value check already guards header values in
3850  // check_and_write_headers and the request target in
3851  // perform_websocket_handshake; apply it here too.
3852  if (!fields::is_field_value(path)) { return -1; }
3853
3854  std::string s = method;
3855  s += ' ';
3856  s += path;
3857  s += " HTTP/1.1\r\n";
3858  return strm.write(s.data(), s.size());
3859}
3860
3861ssize_t write_response_line(Stream &strm, int status) {
3862  std::string s = "HTTP/1.1 ";
3863  s += std::to_string(status);
3864  s += ' ';
3865  s += httplib::status_message(status);
3866  s += "\r\n";
3867  return strm.write(s.data(), s.size());
3868}
3869
3870ssize_t write_headers(Stream &strm, const Headers &headers) {
3871  ssize_t write_len = 0;
3872  for (const auto &x : headers) {
3873    // Skip fields with invalid names or values to prevent response splitting
3874    // via CR/LF injection, matching set_header(). The client validates request
3875    // headers up front in check_and_write_headers, but the server passes
3876    // res.headers straight to this writer, and res.headers is a public field
3877    // an application can populate directly with request-derived values.
3878    if (!fields::is_field_valid(x.first, x.second)) { continue; }
3879
3880    std::string s;
3881    s = x.first;
3882    s += ": ";
3883    s += x.second;
3884    s += "\r\n";
3885
3886    auto len = strm.write(s.data(), s.size());
3887    if (len < 0) { return len; }
3888    write_len += len;
3889  }
3890  auto len = strm.write("\r\n");
3891  if (len < 0) { return len; }
3892  write_len += len;
3893  return write_len;
3894}
3895
3896bool write_data(Stream &strm, const char *d, size_t l) {
3897  size_t offset = 0;
3898  while (offset < l) {
3899    auto length = strm.write(d + offset, l - offset);
3900    if (length < 0) { return false; }
3901    offset += static_cast<size_t>(length);
3902  }
3903  return true;
3904}
3905
3906template <typename T>
3907bool write_content_with_progress(Stream &strm,
3908                                        const ContentProvider &content_provider,
3909                                        size_t offset, size_t length,
3910                                        T is_shutting_down,
3911                                        const UploadProgress &upload_progress,
3912                                        Error &error) {
3913  size_t end_offset = offset + length;
3914  size_t start_offset = offset;
3915  auto ok = true;
3916  auto finished = false;
3917  DataSink data_sink;
3918
3919  data_sink.write = [&](const char *d, size_t l) -> bool {
3920    if (ok) {
3921      if (write_data(strm, d, l)) {
3922        offset += l;
3923
3924        if (upload_progress && length > 0) {
3925          size_t current_written = offset - start_offset;
3926          if (!upload_progress(current_written, length)) {
3927            ok = false;
3928            return false;
3929          }
3930        }
3931      } else {
3932        ok = false;
3933      }
3934    }
3935    return ok;
3936  };
3937
3938  data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
3939
3940  // The body is framed by `length`, so a provider that reports itself done
3941  // early has truncated it. Record that and let the short-body check below
3942  // fail the write, rather than calling the provider again forever.
3943  data_sink.done = [&]() { finished = true; };
3944
3945  while (offset < end_offset && !finished && !is_shutting_down()) {
3946    auto last_offset = offset;
3947
3948    if (!strm.wait_writable() || !strm.is_peer_alive()) {
3949      error = Error::Write;
3950      return false;
3951    } else if (!content_provider(offset, end_offset - offset, data_sink)) {
3952      error = Error::Canceled;
3953      return false;
3954    } else if (!ok) {
3955      error = Error::Write;
3956      return false;
3957    }
3958
3959    // A provider that reports success without writing anything and without
3960    // reporting itself done gets handed the same offset and length again on
3961    // the next pass, so it would spin here for as long as the peer stays
3962    // connected. Treat making no progress as a short body, like done() early.
3963    if (!finished && offset == last_offset) {
3964      error = Error::Write;
3965      return false;
3966    }
3967  }
3968
3969  if (offset < end_offset) { // done() called early, or is_shutting_down()
3970    error = Error::Write;
3971    return false;
3972  }
3973
3974  error = Error::Success;
3975  return true;
3976}
3977
3978template <typename T>
3979bool write_content(Stream &strm, const ContentProvider &content_provider,
3980                          size_t offset, size_t length, T is_shutting_down,
3981                          Error &error) {
3982  return write_content_with_progress<T>(strm, content_provider, offset, length,
3983                                        is_shutting_down, nullptr, error);
3984}
3985
3986template <typename T>
3987bool write_content(Stream &strm, const ContentProvider &content_provider,
3988                          size_t offset, size_t length,
3989                          const T &is_shutting_down) {
3990  auto error = Error::Success;
3991  return write_content(strm, content_provider, offset, length, is_shutting_down,
3992                       error);
3993}
3994
3995template <typename T>
3996bool
3997write_content_without_length(Stream &strm,
3998                             const ContentProvider &content_provider,
3999                             const T &is_shutting_down) {
4000  size_t offset = 0;
4001  auto data_available = true;
4002  auto ok = true;
4003  DataSink data_sink;
4004
4005  data_sink.write = [&](const char *d, size_t l) -> bool {
4006    if (ok) {
4007      offset += l;
4008      if (!write_data(strm, d, l)) { ok = false; }
4009    }
4010    return ok;
4011  };
4012
4013  data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
4014
4015  data_sink.done = [&](void) { data_available = false; };
4016
4017  while (data_available && !is_shutting_down()) {
4018    if (!strm.wait_writable() || !strm.is_peer_alive()) {
4019      return false;
4020    } else if (!content_provider(offset, 0, data_sink)) {
4021      return false;
4022    } else if (!ok) {
4023      return false;
4024    }
4025  }
4026  return !data_available; // true only if done() was called, false if shutting
4027                          // down
4028}
4029
4030// Runs a known-length content provider to completion and compresses what it
4031// writes into `out`. Nothing is buffered in identity form: a provider backed
4032// by an mmap hands the compressor a pointer straight into the mapping.
4033bool compress_content_provider(const ContentProvider &content_provider,
4034                                      size_t length, compressor &cmp,
4035                                      std::string &out) {
4036  size_t offset = 0;
4037  auto ok = true;
4038  auto finished = false;
4039  DataSink data_sink;
4040
4041  auto append = [&](const char *data, size_t data_len) {
4042    out.append(data, data_len);
4043    return true;
4044  };
4045
4046  data_sink.write = [&](const char *d, size_t l) -> bool {
4047    if (!ok) { return false; }
4048    offset += l;
4049    if (l > 0 && !cmp.compress(d, l, false, append)) { ok = false; }
4050    return ok;
4051  };
4052
4053  // The body is framed by `length`, so a provider that reports itself done
4054  // early has truncated it; the short-body check below turns that into a
4055  // failure rather than calling the provider again forever.
4056  data_sink.done = [&]() { finished = true; };
4057
4058  while (offset < length && !finished) {
4059    auto prev_offset = offset;
4060    if (!content_provider(offset, length - offset, data_sink) || !ok) {
4061      return false;
4062    }
4063    // No Stream to block on here, so a provider that keeps returning true
4064    // without writing would spin. Treat a pass that made no progress as a
4065    // failure.
4066    if (offset == prev_offset) { return false; }
4067  }
4068
4069  if (offset != length) { return false; }
4070
4071  return cmp.compress(nullptr, 0, true, append);
4072}
4073
4074// Serves `m` as the response body. `set_content_provider()` clears the coding,
4075// so recording it has to come after; keeping both here means a third
4076// file-serving path cannot get that order wrong.
4077void set_file_content_provider(Response &res,
4078                                      const std::shared_ptr<mmap> &m,
4079                                      const std::string &content_type,
4080                                      EncodingType encoding) {
4081  res.set_content_provider(
4082      m->size(), content_type,
4083      [m](size_t offset, size_t length, DataSink &sink) -> bool {
4084        sink.write(m->data() + offset, length);
4085        return true;
4086      });
4087
4088  res.content_coding_ = encoding;
4089}
4090
4091template <typename T, typename U>
4092bool
4093write_content_chunked(Stream &strm, const ContentProvider &content_provider,
4094                      const T &is_shutting_down, U &compressor, Error &error) {
4095  size_t offset = 0;
4096  auto data_available = true;
4097  auto ok = true;
4098  DataSink data_sink;
4099
4100  data_sink.write = [&](const char *d, size_t l) -> bool {
4101    // Only done()/done_with_trailer() end a chunked body. A pass with nothing
4102    // to hand over is ordinary (an empty buffer popped off a queue), and a
4103    // zero-length chunk is the terminator, so it must not be emitted here.
4104    if (ok && l > 0) {
4105      offset += l;
4106
4107      std::string payload;
4108      if (compressor.compress(d, l, false,
4109                              [&](const char *data, size_t data_len) {
4110                                payload.append(data, data_len);
4111                                return true;
4112                              })) {
4113        if (!payload.empty()) {
4114          // Emit chunked response header and footer for each chunk
4115          auto chunk =
4116              from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
4117          if (!write_data(strm, chunk.data(), chunk.size())) { ok = false; }
4118        }
4119      } else {
4120        ok = false;
4121      }
4122    }
4123    return ok;
4124  };
4125
4126  data_sink.is_writable = [&]() -> bool { return strm.is_peer_alive(); };
4127
4128  auto done_with_trailer = [&](const Headers *trailer) {
4129    if (!ok) { return; }
4130
4131    data_available = false;
4132
4133    std::string payload;
4134    if (!compressor.compress(nullptr, 0, true,
4135                             [&](const char *data, size_t data_len) {
4136                               payload.append(data, data_len);
4137                               return true;
4138                             })) {
4139      ok = false;
4140      return;
4141    }
4142
4143    if (!payload.empty()) {
4144      // Emit chunked response header and footer for each chunk
4145      auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n";
4146      if (!write_data(strm, chunk.data(), chunk.size())) {
4147        ok = false;
4148        return;
4149      }
4150    }
4151
4152    constexpr const char done_marker[] = "0\r\n";
4153    if (!write_data(strm, done_marker, str_len(done_marker))) { ok = false; }
4154
4155    // Trailer
4156    if (trailer) {
4157      for (const auto &kv : *trailer) {
4158        // Skip fields with invalid names or values to prevent response
4159        // splitting via CR/LF injection, matching set_header().
4160        if (!fields::is_field_valid(kv.first, kv.second)) { continue; }
4161        std::string field_line = kv.first + ": " + kv.second + "\r\n";
4162        if (!write_data(strm, field_line.data(), field_line.size())) {
4163          ok = false;
4164        }
4165      }
4166    }
4167
4168    constexpr const char crlf[] = "\r\n";
4169    if (!write_data(strm, crlf, str_len(crlf))) { ok = false; }
4170  };
4171
4172  data_sink.done = [&](void) { done_with_trailer(nullptr); };
4173
4174  data_sink.done_with_trailer = [&](const Headers &trailer) {
4175    done_with_trailer(&trailer);
4176  };
4177
4178  while (data_available && !is_shutting_down()) {
4179    if (!strm.wait_writable() || !strm.is_peer_alive()) {
4180      error = Error::Write;
4181      return false;
4182    } else if (!content_provider(offset, 0, data_sink)) {
4183      error = Error::Canceled;
4184      return false;
4185    } else if (!ok) {
4186      error = Error::Write;
4187      return false;
4188    }
4189  }
4190
4191  if (data_available) { // exited due to is_shutting_down(), not done()
4192    error = Error::Write;
4193    return false;
4194  }
4195
4196  error = Error::Success;
4197  return true;
4198}
4199
4200template <typename T, typename U>
4201bool write_content_chunked(Stream &strm,
4202                                  const ContentProvider &content_provider,
4203                                  const T &is_shutting_down, U &compressor) {
4204  auto error = Error::Success;
4205  return write_content_chunked(strm, content_provider, is_shutting_down,
4206                               compressor, error);
4207}
4208
4209template <typename T>
4210bool redirect(T &cli, Request &req, Response &res,
4211                     const std::string &path, const std::string &location,
4212                     Error &error) {
4213  Request new_req = req;
4214  new_req.path = path;
4215  new_req.redirect_count_ -= 1;
4216
4217  if (res.status == StatusCode::SeeOther_303 &&
4218      (req.method != "GET" && req.method != "HEAD")) {
4219    new_req.method = "GET";
4220    new_req.body.clear();
4221    new_req.headers.clear();
4222  }
4223
4224  Response new_res;
4225
4226  auto ret = cli.send(new_req, new_res, error);
4227  if (ret) {
4228    req = std::move(new_req);
4229    res = std::move(new_res);
4230
4231    if (res.location.empty()) { res.location = location; }
4232  }
4233  return ret;
4234}
4235
4236std::string params_to_query_str(const Params &params) {
4237  std::string query;
4238
4239  for (auto it = params.begin(); it != params.end(); ++it) {
4240    if (it != params.begin()) { query += '&'; }
4241    query += encode_query_component(it->first);
4242    query += '=';
4243    query += encode_query_component(it->second);
4244  }
4245  return query;
4246}
4247
4248// Splits one "key=value" span of a query string at its first '='. A span with
4249// no '=' at all lands entirely in key, leaving val empty, which is how a bare
4250// "?flag" keeps its name.
4251void divide_query_pair(const char *b, const char *e, std::string &key,
4252                              std::string &val) {
4253  divide(b, static_cast<std::size_t>(e - b), '=',
4254         [&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data,
4255             std::size_t rhs_size) {
4256           key.assign(lhs_data, lhs_size);
4257           val.assign(rhs_data, rhs_size);
4258         });
4259}
4260
4261void parse_query_text(const char *data, std::size_t size,
4262                             Params &params) {
4263  std::set<std::string> cache;
4264  split(data, data + size, '&', [&](const char *b, const char *e) {
4265    std::string kv(b, e);
4266    if (cache.find(kv) != cache.end()) { return; }
4267    cache.insert(std::move(kv));
4268
4269    std::string key;
4270    std::string val;
4271    divide_query_pair(b, e, key, val);
4272
4273    if (!key.empty()) {
4274      params.emplace(decode_query_component(key), decode_query_component(val));
4275    }
4276  });
4277}
4278
4279void parse_query_text(const std::string &s, Params &params) {
4280  parse_query_text(s.data(), s.size(), params);
4281}
4282
4283// Normalize a query string by decoding and re-encoding each key/value pair
4284// while preserving the original parameter order. This avoids double-encoding
4285// and ensures consistent encoding. It works on the raw string rather than
4286// parsing into Params and re-serializing, because that round trip cannot
4287// reproduce the input: params_to_query_str() always emits '=', so a bare
4288// "flag" would come back as "flag=", and parse_query_text() drops exactly
4289// duplicated pairs.
4290std::string normalize_query_string(const std::string &query) {
4291  std::string result;
4292  split(query.data(), query.data() + query.size(), '&',
4293        [&](const char *b, const char *e) {
4294          std::string key;
4295          std::string val;
4296          divide_query_pair(b, e, key, val);
4297
4298          if (!key.empty()) {
4299            auto dec_key = decode_query_component(key);
4300            auto dec_val = decode_query_component(val);
4301
4302            if (!result.empty()) { result += '&'; }
4303            result += encode_query_component(dec_key);
4304            if (!val.empty() || std::find(b, e, '=') != e) {
4305              result += '=';
4306              result += encode_query_component(dec_val);
4307            }
4308          }
4309        });
4310  return result;
4311}
4312
4313// Build the request target that goes on the wire from a caller-supplied path.
4314// Shared by the buffered send path and the streaming API so that both put the
4315// same bytes in the request line for the same input.
4316std::string encode_request_target(const std::string &target,
4317                                         bool path_encode) {
4318  // `substr(0, npos)` yields the whole string, which is what the no-query
4319  // case needs.
4320  auto query_pos = target.find('?');
4321  auto path_part = target.substr(0, query_pos);
4322  std::string query_part;
4323  if (query_pos != std::string::npos) {
4324    query_part = target.substr(query_pos + 1);
4325  }
4326
4327  auto result = path_encode ? encode_path(path_part) : std::move(path_part);
4328
4329  if (!query_part.empty()) {
4330    // When path encoding is disabled the caller has supplied an already-encoded
4331    // target and expects the exact bytes to be sent on the wire, so skip
4332    // normalization for the query too. Normalizing would decode-then-re-encode
4333    // it and corrupt pre-encoded binary payloads (e.g. turning `%20` into `+`,
4334    // which a strict RFC 3986 server decodes back as `+`, not a space).
4335    if (path_encode) {
4336      auto normalized = normalize_query_string(query_part);
4337      if (!normalized.empty()) {
4338        result += '?';
4339        result += normalized;
4340      }
4341    } else {
4342      result += '?';
4343      result += query_part;
4344    }
4345  }
4346
4347  return result;
4348}
4349
4350bool parse_multipart_boundary(const std::string &content_type,
4351                                     std::string &boundary) {
4352  std::map<std::string, std::string> params;
4353  extract_media_type(content_type, &params);
4354  auto it = params.find("boundary");
4355  if (it == params.end()) { return false; }
4356  boundary = it->second;
4357  // RFC 2046 5.1.1 caps a boundary at 70 characters. The parser scans the body
4358  // for "--" + boundary, so a body crafted to repeat that delimiter's leading
4359  // bytes costs a nearly full comparison at nearly every position: the
4360  // boundary's length multiplies the worst-case cost of scanning a body.
4361  return !boundary.empty() && boundary.size() <= 70;
4362}
4363
4364void parse_disposition_params(const std::string &s, Params &params) {
4365  std::set<std::string> cache;
4366  split_unquoted(s.data(), s.data() + s.size(), ';',
4367                 [&](const char *b, const char *e) {
4368                   std::string kv(b, e);
4369                   if (cache.find(kv) != cache.end()) { return; }
4370                   cache.insert(kv);
4371
4372                   std::string key;
4373                   std::string val;
4374                   divide_param_pair(b, e, key, val);
4375
4376                   if (!key.empty()) {
4377                     params.emplace(trim_double_quotes_copy(key),
4378                                    trim_double_quotes_copy(val));
4379                   }
4380                 });
4381}
4382
4383#ifdef CPPHTTPLIB_NO_EXCEPTIONS
4384bool parse_range_header(const std::string &s, Ranges &ranges) {
4385#else
4386bool parse_range_header(const std::string &s, Ranges &ranges) try {
4387#endif
4388  auto is_valid = [](const std::string &str) {
4389    return std::all_of(str.cbegin(), str.cend(), is_ascii_digit);
4390  };
4391
4392  if (s.size() > 7 && s.compare(0, 6, "bytes=") == 0) {
4393    const auto pos = static_cast<size_t>(6);
4394    const auto len = static_cast<size_t>(s.size() - 6);
4395    auto all_valid_ranges = true;
4396    split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
4397      if (!all_valid_ranges) { return; }
4398
4399      const auto it = std::find(b, e, '-');
4400      if (it == e) {
4401        all_valid_ranges = false;
4402        return;
4403      }
4404
4405      const auto lhs = std::string(b, it);
4406      const auto rhs = std::string(it + 1, e);
4407      if (!is_valid(lhs) || !is_valid(rhs)) {
4408        all_valid_ranges = false;
4409        return;
4410      }
4411
4412      ssize_t first = -1;
4413      if (!lhs.empty()) {
4414        // Reject an overflowing first-byte-pos; treating it as absent (-1)
4415        // would turn the range into a suffix range.
4416        auto res =
4417            detail::from_chars(lhs.data(), lhs.data() + lhs.size(), first);
4418        if (res.ec != std::errc{}) {
4419          all_valid_ranges = false;
4420          return;
4421        }
4422      }
4423
4424      ssize_t last = -1;
4425      if (!rhs.empty()) {
4426        // An overflowing last-byte-pos is past any content length, so keeping
4427        // -1 ("remainder", RFC 9110 14.1.2) is correct here.
4428        ssize_t v;
4429        auto res = detail::from_chars(rhs.data(), rhs.data() + rhs.size(), v);
4430        if (res.ec == std::errc{}) { last = v; }
4431      }
4432
4433      if ((first == -1 && last == -1) ||
4434          (first != -1 && last != -1 && first > last)) {
4435        all_valid_ranges = false;
4436        return;
4437      }
4438
4439      ranges.emplace_back(first, last);
4440    });
4441    return all_valid_ranges && !ranges.empty();
4442  }
4443  return false;
4444#ifdef CPPHTTPLIB_NO_EXCEPTIONS
4445}
4446#else
4447} catch (...) { return false; }
4448#endif
4449
4450bool parse_accept_header(const std::string &s,
4451                                std::vector<std::string> &content_types) {
4452  content_types.clear();
4453
4454  // Empty string is considered valid (no preference)
4455  if (s.empty()) { return true; }
4456
4457  struct AcceptEntry {
4458    std::string media_type;
4459    double quality;
4460    int order;
4461  };
4462
4463  std::vector<AcceptEntry> entries;
4464  int order = 0;
4465  bool has_invalid_entry = false;
4466
4467  // Split by comma and parse each entry. RFC 9110 Section 5.6.1.2: a recipient
4468  // has to parse and ignore empty list elements, so a leading, trailing or
4469  // doubled comma must not turn a legal Accept value into 400 Bad Request.
4470  // split() skips them, and the header length limit bounds how many a sender
4471  // can send, so ignoring all of them cannot be used as a denial-of-service
4472  // vector.
4473  split(s.data(), s.data() + s.size(), ',', [&](const char *b, const char *e) {
4474    std::string entry(b, e);
4475    entry = trim_copy(entry);
4476
4477    AcceptEntry accept_entry;
4478    accept_entry.order = order++;
4479
4480    if (!parse_quality(entry.data(), entry.data() + entry.size(),
4481                       accept_entry.media_type, accept_entry.quality)) {
4482      has_invalid_entry = true;
4483      return;
4484    }
4485
4486    // Remove additional parameters from media type
4487    accept_entry.media_type = extract_media_type(accept_entry.media_type);
4488
4489    // Basic validation of media type format
4490    if (accept_entry.media_type.empty()) {
4491      has_invalid_entry = true;
4492      return;
4493    }
4494
4495    // Check for basic media type format (should contain '/' or be '*')
4496    if (accept_entry.media_type != "*" &&
4497        accept_entry.media_type.find('/') == std::string::npos) {
4498      has_invalid_entry = true;
4499      return;
4500    }
4501
4502    entries.push_back(std::move(accept_entry));
4503  });
4504
4505  // Return false if any invalid entry was found
4506  if (has_invalid_entry) { return false; }
4507
4508  // Sort by quality (descending), then by original order (ascending)
4509  std::sort(entries.begin(), entries.end(),
4510            [](const AcceptEntry &a, const AcceptEntry &b) {
4511              if (a.quality != b.quality) {
4512                return a.quality > b.quality; // Higher quality first
4513              }
4514              return a.order < b.order; // Earlier order first for same quality
4515            });
4516
4517  // Extract sorted media types
4518  content_types.reserve(entries.size());
4519  for (auto &entry : entries) {
4520    content_types.push_back(std::move(entry.media_type));
4521  }
4522
4523  return true;
4524}
4525
4526class FormDataParser {
4527public:
4528  FormDataParser() = default;
4529
4530  void set_boundary(std::string &&boundary) {
4531    boundary_ = std::move(boundary);
4532    dash_boundary_crlf_ = dash_ + boundary_ + crlf_;
4533    crlf_dash_boundary_ = crlf_ + dash_ + boundary_;
4534  }
4535
4536  bool is_valid() const { return is_valid_; }
4537
4538  bool parse(const char *buf, size_t n, const FormDataHeader &header_callback,
4539             const ContentReceiver &content_callback) {
4540
4541    // Once the close delimiter has been seen the rest of the body is epilogue
4542    // to be discarded (RFC 2046). Drop it without buffering so a large epilogue
4543    // spread across reads is not copied in only to be erased right away.
4544    if (state_ == 5) { return true; }
4545
4546    buf_append(buf, n);
4547
4548    while (buf_size() > 0) {
4549      switch (state_) {
4550      case 0: { // Initial boundary
4551        auto pos = buf_find(dash_boundary_crlf_);
4552        if (pos == buf_size()) {
4553          // Not found yet: keep only a possible partial boundary at the tail so
4554          // that a body which never contains the boundary cannot grow the
4555          // buffer (and get rescanned from the start) without bound.
4556          auto keep = dash_boundary_crlf_.size() - 1;
4557          if (buf_size() > keep) { buf_erase(buf_size() - keep); }
4558          return true;
4559        }
4560        buf_erase(pos + dash_boundary_crlf_.size());
4561        state_ = 1;
4562        break;
4563      }
4564      case 1: { // New entry
4565        clear_file_info();
4566        state_ = 2;
4567        break;
4568      }
4569      case 2: { // Headers
4570        auto pos = buf_find(crlf_);
4571        if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; }
4572        while (pos < buf_size()) {
4573          // Empty line
4574          if (pos == 0) {
4575            if (!header_callback(file_)) {
4576              is_valid_ = false;
4577              return false;
4578            }
4579            buf_erase(crlf_.size());
4580            state_ = 3;
4581            break;
4582          }
4583
4584          // Check header count limit
4585          if (header_count_ >= CPPHTTPLIB_HEADER_MAX_COUNT) {
4586            is_valid_ = false;
4587            return false;
4588          }
4589          header_count_++;
4590
4591          const auto header = buf_head(pos);
4592
4593          if (!parse_header(header.data(), header.data() + header.size(),
4594                            [&](const std::string &, const std::string &) {})) {
4595            is_valid_ = false;
4596            return false;
4597          }
4598
4599          // Parse and emplace space trimmed headers into a map
4600          if (!parse_header(
4601                  header.data(), header.data() + header.size(),
4602                  [&](const std::string &key, const std::string &val) {
4603                    file_.headers.emplace(key, val);
4604                  })) {
4605            is_valid_ = false;
4606            return false;
4607          }
4608
4609          constexpr const char header_content_type[] = "Content-Type:";
4610
4611          if (start_with_case_ignore(header, header_content_type)) {
4612            file_.content_type =
4613                trim_copy(header.substr(str_len(header_content_type)));
4614          } else {
4615            std::string disposition_params;
4616            if (parse_content_disposition(header, disposition_params)) {
4617              Params params;
4618              parse_disposition_params(disposition_params, params);
4619
4620              auto it = params.find("name");
4621              if (it != params.end()) {
4622                file_.name = it->second;
4623              } else {
4624                is_valid_ = false;
4625                return false;
4626              }
4627
4628              it = params.find("filename");
4629              if (it != params.end()) { file_.filename = it->second; }
4630
4631              it = params.find("filename*");
4632              if (it != params.end()) {
4633                // RFC 5987: only UTF-8 encoding is allowed
4634                const auto &val = it->second;
4635                constexpr const char utf8_prefix[] = "UTF-8''";
4636                constexpr size_t prefix_len = str_len(utf8_prefix);
4637                if (val.size() > prefix_len &&
4638                    start_with_case_ignore(val, utf8_prefix)) {
4639                  file_.filename = decode_path_component(
4640                      val.substr(prefix_len)); // override...
4641                } else {
4642                  is_valid_ = false;
4643                  return false;
4644                }
4645              }
4646            }
4647          }
4648          buf_erase(pos + crlf_.size());
4649          pos = buf_find(crlf_);
4650        }
4651        if (state_ != 3) { return true; }
4652        break;
4653      }
4654      case 3: { // Body
4655        if (crlf_dash_boundary_.size() > buf_size()) { return true; }
4656        auto pos = buf_find(crlf_dash_boundary_);
4657        if (pos < buf_size()) {
4658          if (!content_callback(buf_data(), pos)) {
4659            is_valid_ = false;
4660            return false;
4661          }
4662          buf_erase(pos + crlf_dash_boundary_.size());
4663          state_ = 4;
4664        } else {
4665          auto len = buf_size() - crlf_dash_boundary_.size();
4666          if (len > 0) {
4667            if (!content_callback(buf_data(), len)) {
4668              is_valid_ = false;
4669              return false;
4670            }
4671            buf_erase(len);
4672          }
4673          return true;
4674        }
4675        break;
4676      }
4677      case 4: { // Boundary
4678        if (crlf_.size() > buf_size()) { return true; }
4679        if (buf_start_with(crlf_)) {
4680          buf_erase(crlf_.size());
4681          state_ = 1;
4682        } else if (buf_start_with(dash_)) {
4683          buf_erase(dash_.size());
4684          is_valid_ = true;
4685          state_ = 5;
4686        } else {
4687          // Only CRLF (another part follows) and "--" (close-delimiter) are
4688          // accepted after a boundary; RFC 2046 allows transport-padding in
4689          // between, but this parser has never supported it. Either way the
4690          // body is already destined to be rejected, so fail now instead of
4691          // buffering the rest of it. Both are two bytes, so the check above
4692          // already guarantees enough buffered data to decide.
4693          is_valid_ = false;
4694          return false;
4695        }
4696        break;
4697      }
4698      case 5: { // Epilogue
4699        buf_erase(buf_size());
4700        break;
4701      }
4702      }
4703    }
4704
4705    return true;
4706  }
4707
4708private:
4709  void clear_file_info() {
4710    file_.name.clear();
4711    file_.filename.clear();
4712    file_.content_type.clear();
4713    file_.headers.clear();
4714    header_count_ = 0;
4715  }
4716
4717  bool start_with_case_ignore(const std::string &a, const char *b,
4718                              size_t offset = 0) const {
4719    const auto b_len = strlen(b);
4720    if (a.size() < offset + b_len) { return false; }
4721    for (size_t i = 0; i < b_len; i++) {
4722      if (case_ignore::to_lower(a[offset + i]) != case_ignore::to_lower(b[i])) {
4723        return false;
4724      }
4725    }
4726    return true;
4727  }
4728
4729  // Parses "Content-Disposition: form-data; <params>" without std::regex.
4730  // Returns true if header matches, with the params portion in `params_out`.
4731  bool parse_content_disposition(const std::string &header,
4732                                 std::string &params_out) const {
4733    constexpr const char prefix[] = "Content-Disposition:";
4734    constexpr size_t prefix_len = str_len(prefix);
4735
4736    if (!start_with_case_ignore(header, prefix)) { return false; }
4737
4738    // Skip whitespace after "Content-Disposition:"
4739    auto pos = prefix_len;
4740    while (pos < header.size() && (header[pos] == ' ' || header[pos] == '\t')) {
4741      pos++;
4742    }
4743
4744    // Match "form-data;" (case-insensitive)
4745    constexpr const char form_data[] = "form-data;";
4746    constexpr size_t form_data_len = str_len(form_data);
4747    if (!start_with_case_ignore(header, form_data, pos)) { return false; }
4748    pos += form_data_len;
4749
4750    // Skip whitespace after "form-data;"
4751    while (pos < header.size() && (header[pos] == ' ' || header[pos] == '\t')) {
4752      pos++;
4753    }
4754
4755    params_out = header.substr(pos);
4756    return true;
4757  }
4758
4759  const std::string dash_ = "--";
4760  const std::string crlf_ = "\r\n";
4761  std::string boundary_;
4762  std::string dash_boundary_crlf_;
4763  std::string crlf_dash_boundary_;
4764
4765  size_t state_ = 0;
4766  bool is_valid_ = false;
4767  FormData file_;
4768  size_t header_count_ = 0;
4769
4770  // Buffer
4771  bool start_with(const std::string &a, size_t spos, size_t epos,
4772                  const std::string &b) const {
4773    if (epos - spos < b.size()) { return false; }
4774    for (size_t i = 0; i < b.size(); i++) {
4775      if (a[i + spos] != b[i]) { return false; }
4776    }
4777    return true;
4778  }
4779
4780  size_t buf_size() const { return buf_epos_ - buf_spos_; }
4781
4782  const char *buf_data() const { return &buf_[buf_spos_]; }
4783
4784  std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); }
4785
4786  bool buf_start_with(const std::string &s) const {
4787    return start_with(buf_, buf_spos_, buf_epos_, s);
4788  }
4789
4790  size_t buf_find(const std::string &s) const {
4791    auto c = s.front();
4792
4793    size_t off = buf_spos_;
4794    while (off < buf_epos_) {
4795      auto pos = off;
4796      while (true) {
4797        if (pos == buf_epos_) { return buf_size(); }
4798        if (buf_[pos] == c) { break; }
4799        pos++;
4800      }
4801
4802      auto remaining_size = buf_epos_ - pos;
4803      if (s.size() > remaining_size) { return buf_size(); }
4804
4805      if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; }
4806
4807      off = pos + 1;
4808    }
4809
4810    return buf_size();
4811  }
4812
4813  void buf_append(const char *data, size_t n) {
4814    auto remaining_size = buf_size();
4815    if (remaining_size > 0 && buf_spos_ > 0) {
4816      for (size_t i = 0; i < remaining_size; i++) {
4817        buf_[i] = buf_[buf_spos_ + i];
4818      }
4819    }
4820    buf_spos_ = 0;
4821    buf_epos_ = remaining_size;
4822
4823    if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); }
4824
4825    for (size_t i = 0; i < n; i++) {
4826      buf_[buf_epos_ + i] = data[i];
4827    }
4828    buf_epos_ += n;
4829  }
4830
4831  void buf_erase(size_t size) { buf_spos_ += size; }
4832
4833  std::string buf_;
4834  size_t buf_spos_ = 0;
4835  size_t buf_epos_ = 0;
4836};
4837
4838std::string random_string(size_t length) {
4839  constexpr const char data[] =
4840      "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
4841
4842  thread_local auto engine([]() {
4843    // std::random_device might actually be deterministic on some
4844    // platforms, but due to lack of support in the c++ standard library,
4845    // doing better requires either some ugly hacks or breaking portability.
4846    std::random_device seed_gen;
4847    // Request 128 bits of entropy for initialization
4848    std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), seed_gen()};
4849    return std::mt19937(seed_sequence);
4850  }());
4851
4852  std::string result;
4853  for (size_t i = 0; i < length; i++) {
4854    result += data[engine() % (sizeof(data) - 1)];
4855  }
4856  return result;
4857}
4858
4859std::string make_multipart_data_boundary() {
4860  return "--cpp-httplib-multipart-data-" + detail::random_string(16);
4861}
4862
4863bool is_multipart_boundary_chars_valid(const std::string &boundary) {
4864  auto valid = true;
4865  for (size_t i = 0; i < boundary.size(); i++) {
4866    auto c = boundary[i];
4867    if (!is_ascii_alnum(c) && c != '-' && c != '_') {
4868      valid = false;
4869      break;
4870    }
4871  }
4872  return valid;
4873}
4874
4875// Escape a multipart field name/filename following the WHATWG HTML standard
4876// ("escape a multipart form-data name"), which is what browsers send:
4877// '"' -> %22, CR -> %0D, LF -> %0A
4878// With escape_quote = false, only CR and LF are escaped; this is for header
4879// values outside a quoted-string (e.g. Content-Type), where '"' is legal.
4880std::string escape_multipart_field(const std::string &s,
4881                                          bool escape_quote = true) {
4882  std::string result;
4883  result.reserve(s.size());
4884  for (auto c : s) {
4885    switch (c) {
4886    case '"':
4887      if (escape_quote) {
4888        result += "%22";
4889      } else {
4890        result += c;
4891      }
4892      break;
4893    case '\r': result += "%0D"; break;
4894    case '\n': result += "%0A"; break;
4895    default: result += c; break;
4896    }
4897  }
4898  return result;
4899}
4900
4901template <typename T>
4902std::string
4903serialize_multipart_formdata_item_begin(const T &item,
4904                                        const std::string &boundary) {
4905  std::string body = "--" + boundary + "\r\n";
4906  body += "Content-Disposition: form-data; name=\"" +
4907          escape_multipart_field(item.name) + "\"";
4908  if (!item.filename.empty()) {
4909    body += "; filename=\"" + escape_multipart_field(item.filename) + "\"";
4910  }
4911  body += "\r\n";
4912  if (!item.content_type.empty()) {
4913    body +=
4914        "Content-Type: " + escape_multipart_field(item.content_type, false) +
4915        "\r\n";
4916  }
4917  body += "\r\n";
4918
4919  return body;
4920}
4921
4922std::string serialize_multipart_formdata_item_end() { return "\r\n"; }
4923
4924std::string
4925serialize_multipart_formdata_finish(const std::string &boundary) {
4926  return "--" + boundary + "--\r\n";
4927}
4928
4929std::string
4930serialize_multipart_formdata_get_content_type(const std::string &boundary) {
4931  return "multipart/form-data; boundary=" + boundary;
4932}
4933
4934std::string
4935serialize_multipart_formdata(const UploadFormDataItems &items,
4936                             const std::string &boundary, bool finish = true) {
4937  std::string body;
4938
4939  for (const auto &item : items) {
4940    body += serialize_multipart_formdata_item_begin(item, boundary);
4941    body += item.content + serialize_multipart_formdata_item_end();
4942  }
4943
4944  if (finish) { body += serialize_multipart_formdata_finish(boundary); }
4945
4946  return body;
4947}
4948
4949size_t get_multipart_content_length(const UploadFormDataItems &items,
4950                                           const std::string &boundary) {
4951  size_t total = 0;
4952  for (const auto &item : items) {
4953    total += serialize_multipart_formdata_item_begin(item, boundary).size();
4954    total += item.content.size();
4955    total += serialize_multipart_formdata_item_end().size();
4956  }
4957  total += serialize_multipart_formdata_finish(boundary).size();
4958  return total;
4959}
4960
4961struct MultipartSegment {
4962  const char *data;
4963  size_t size;
4964};
4965
4966// NOTE: items must outlive the returned ContentProvider
4967//       (safe for synchronous use inside Post/Put/Patch)
4968ContentProvider
4969make_multipart_content_provider(const UploadFormDataItems &items,
4970                                const std::string &boundary) {
4971  // Own the per-item header strings and the finish string
4972  std::vector<std::string> owned;
4973  owned.reserve(items.size() + 1);
4974  for (const auto &item : items)
4975    owned.push_back(serialize_multipart_formdata_item_begin(item, boundary));
4976  owned.push_back(serialize_multipart_formdata_finish(boundary));
4977
4978  // Flat segment list: [header, content, "\r\n"] * N + [finish]
4979  std::vector<MultipartSegment> segs;
4980  segs.reserve(items.size() * 3 + 1);
4981  static const char crlf[] = "\r\n";
4982  for (size_t i = 0; i < items.size(); i++) {
4983    segs.push_back({owned[i].data(), owned[i].size()});
4984    segs.push_back({items[i].content.data(), items[i].content.size()});
4985    segs.push_back({crlf, 2});
4986  }
4987  segs.push_back({owned.back().data(), owned.back().size()});
4988
4989  struct MultipartState {
4990    std::vector<std::string> owned;
4991    std::vector<MultipartSegment> segs;
4992    std::vector<char> buf = std::vector<char>(CPPHTTPLIB_SEND_BUFSIZ);
4993  };
4994  auto state = std::make_shared<MultipartState>();
4995  state->owned = std::move(owned);
4996  // `segs` holds raw pointers into owned strings; std::string move preserves
4997  // the data pointer, so these pointers remain valid after the move above.
4998  state->segs = std::move(segs);
4999
5000  return [state](size_t offset, size_t length, DataSink &sink) -> bool {
5001    // Buffer multiple small segments into fewer, larger writes to avoid
5002    // excessive TCP packets when there are many form data items (#2410)
5003    auto &buf = state->buf;
5004    auto buf_size = buf.size();
5005    size_t buf_len = 0;
5006    size_t remaining = length;
5007
5008    // Find the first segment containing 'offset'
5009    size_t pos = 0;
5010    size_t seg_idx = 0;
5011    for (; seg_idx < state->segs.size(); seg_idx++) {
5012      const auto &seg = state->segs[seg_idx];
5013      if (seg.size > 0 && offset - pos < seg.size) { break; }
5014      pos += seg.size;
5015    }
5016
5017    size_t seg_offset = (seg_idx < state->segs.size()) ? offset - pos : 0;
5018
5019    for (; seg_idx < state->segs.size() && remaining > 0; seg_idx++) {
5020      const auto &seg = state->segs[seg_idx];
5021      size_t available = seg.size - seg_offset;
5022      size_t to_copy = (std::min)(available, remaining);
5023      const char *src = seg.data + seg_offset;
5024      seg_offset = 0; // only the first segment has a non-zero offset
5025
5026      while (to_copy > 0) {
5027        size_t space = buf_size - buf_len;
5028        size_t chunk = (std::min)(to_copy, space);
5029        std::memcpy(buf.data() + buf_len, src, chunk);
5030        buf_len += chunk;
5031        src += chunk;
5032        to_copy -= chunk;
5033        remaining -= chunk;
5034
5035        if (buf_len == buf_size) {
5036          if (!sink.write(buf.data(), buf_len)) { return false; }
5037          buf_len = 0;
5038        }
5039      }
5040    }
5041
5042    if (buf_len > 0) { return sink.write(buf.data(), buf_len); }
5043    return true;
5044  };
5045}
5046
5047void coalesce_ranges(Ranges &ranges, size_t content_length) {
5048  if (ranges.size() <= 1) return;
5049
5050  // Sort ranges by start position
5051  std::sort(ranges.begin(), ranges.end(),
5052            [](const Range &a, const Range &b) { return a.first < b.first; });
5053
5054  Ranges coalesced;
5055  coalesced.reserve(ranges.size());
5056
5057  for (auto &r : ranges) {
5058    auto first_pos = r.first;
5059    auto last_pos = r.second;
5060
5061    // Handle special cases like in range_error
5062    if (first_pos == -1 && last_pos == -1) {
5063      first_pos = 0;
5064      last_pos = static_cast<ssize_t>(content_length);
5065    }
5066
5067    if (first_pos == -1) {
5068      first_pos = static_cast<ssize_t>(content_length) - last_pos;
5069      last_pos = static_cast<ssize_t>(content_length) - 1;
5070    }
5071
5072    if (last_pos == -1 || last_pos >= static_cast<ssize_t>(content_length)) {
5073      last_pos = static_cast<ssize_t>(content_length) - 1;
5074    }
5075
5076    // Skip invalid ranges
5077    if (!(0 <= first_pos && first_pos <= last_pos &&
5078          last_pos < static_cast<ssize_t>(content_length))) {
5079      continue;
5080    }
5081
5082    // Coalesce with previous range if overlapping or adjacent (but not
5083    // identical)
5084    if (!coalesced.empty()) {
5085      auto &prev = coalesced.back();
5086      // Check if current range overlaps or is adjacent to previous range
5087      // but don't coalesce identical ranges (allow duplicates)
5088      if (first_pos <= prev.second + 1 &&
5089          !(first_pos == prev.first && last_pos == prev.second)) {
5090        // Extend the previous range
5091        prev.second = (std::max)(prev.second, last_pos);
5092        continue;
5093      }
5094    }
5095
5096    // Add new range
5097    coalesced.emplace_back(first_pos, last_pos);
5098  }
5099
5100  ranges = std::move(coalesced);
5101}
5102
5103bool range_error(Request &req, Response &res) {
5104  if (!req.ranges.empty() && 200 <= res.status && res.status < 300) {
5105    if (res.body.empty() && res.content_provider_ && res.content_length_ == 0) {
5106      req.ranges.clear();
5107      if (res.status == StatusCode::PartialContent_206) {
5108        res.status = StatusCode::OK_200;
5109      }
5110      return false;
5111    }
5112
5113    ssize_t content_len = static_cast<ssize_t>(
5114        res.content_length_ ? res.content_length_ : res.body.size());
5115
5116    std::vector<std::pair<ssize_t, ssize_t>> processed_ranges;
5117    size_t overwrapping_count = 0;
5118
5119    // NOTE: The following Range check is based on '14.2. Range' in RFC 9110
5120    // 'HTTP Semantics' to avoid potential denial-of-service attacks.
5121    // https://www.rfc-editor.org/rfc/rfc9110#section-14.2
5122
5123    // Too many ranges
5124    if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return true; }
5125
5126    for (auto &r : req.ranges) {
5127      auto &first_pos = r.first;
5128      auto &last_pos = r.second;
5129
5130      if (first_pos == -1 && last_pos == -1) {
5131        first_pos = 0;
5132        last_pos = content_len;
5133      }
5134
5135      if (first_pos == -1) {
5136        first_pos = content_len - last_pos;
5137        last_pos = content_len - 1;
5138      }
5139
5140      // NOTE: RFC-9110 '14.1.2. Byte Ranges':
5141      // A client can limit the number of bytes requested without knowing the
5142      // size of the selected representation. If the last-pos value is absent,
5143      // or if the value is greater than or equal to the current length of the
5144      // representation data, the byte range is interpreted as the remainder of
5145      // the representation (i.e., the server replaces the value of last-pos
5146      // with a value that is one less than the current length of the selected
5147      // representation).
5148      // https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6
5149      if (last_pos == -1 || last_pos >= content_len) {
5150        last_pos = content_len - 1;
5151      }
5152
5153      // Range must be within content length
5154      if (!(0 <= first_pos && first_pos <= last_pos &&
5155            last_pos <= content_len - 1)) {
5156        return true;
5157      }
5158
5159      // Request must not have more than two overlapping ranges
5160      for (const auto &processed_range : processed_ranges) {
5161        if (!(last_pos < processed_range.first ||
5162              first_pos > processed_range.second)) {
5163          overwrapping_count++;
5164          if (overwrapping_count > 2) { return true; }
5165          break; // Only count once per range
5166        }
5167      }
5168
5169      processed_ranges.emplace_back(first_pos, last_pos);
5170    }
5171
5172    // After validation, coalesce overlapping ranges as per RFC 9110
5173    coalesce_ranges(req.ranges, static_cast<size_t>(content_len));
5174  }
5175
5176  return false;
5177}
5178
5179std::pair<size_t, size_t>
5180get_range_offset_and_length(Range r, size_t content_length) {
5181  assert(r.first != -1 && r.second != -1);
5182  assert(0 <= r.first && r.first < static_cast<ssize_t>(content_length));
5183  assert(r.first <= r.second &&
5184         r.second < static_cast<ssize_t>(content_length));
5185  (void)(content_length);
5186  return std::make_pair(static_cast<size_t>(r.first),
5187                        static_cast<size_t>(r.second - r.first) + 1);
5188}
5189
5190std::string make_content_range_header_field(
5191    const std::pair<size_t, size_t> &offset_and_length, size_t content_length) {
5192  auto st = offset_and_length.first;
5193  auto ed = st + offset_and_length.second - 1;
5194
5195  std::string field = "bytes ";
5196  field += std::to_string(st);
5197  field += '-';
5198  field += std::to_string(ed);
5199  field += '/';
5200  field += std::to_string(content_length);
5201  return field;
5202}
5203
5204template <typename SToken, typename CToken, typename Content>
5205bool process_multipart_ranges_data(const Request &req,
5206                                   const std::string &boundary,
5207                                   const std::string &content_type,
5208                                   size_t content_length, SToken stoken,
5209                                   CToken ctoken, Content content) {
5210  for (size_t i = 0; i < req.ranges.size(); i++) {
5211    ctoken("--");
5212    stoken(boundary);
5213    ctoken("\r\n");
5214    if (!content_type.empty()) {
5215      ctoken("Content-Type: ");
5216      stoken(content_type);
5217      ctoken("\r\n");
5218    }
5219
5220    auto offset_and_length =
5221        get_range_offset_and_length(req.ranges[i], content_length);
5222
5223    ctoken("Content-Range: ");
5224    stoken(make_content_range_header_field(offset_and_length, content_length));
5225    ctoken("\r\n");
5226    ctoken("\r\n");
5227
5228    if (!content(offset_and_length.first, offset_and_length.second)) {
5229      return false;
5230    }
5231    ctoken("\r\n");
5232  }
5233
5234  ctoken("--");
5235  stoken(boundary);
5236  ctoken("--");
5237
5238  return true;
5239}
5240
5241void make_multipart_ranges_data(const Request &req, Response &res,
5242                                       const std::string &boundary,
5243                                       const std::string &content_type,
5244                                       size_t content_length,
5245                                       std::string &data) {
5246  process_multipart_ranges_data(
5247      req, boundary, content_type, content_length,
5248      [&](const std::string &token) { data += token; },
5249      [&](const std::string &token) { data += token; },
5250      [&](size_t offset, size_t length) {
5251        assert(offset + length <= content_length);
5252        data += res.body.substr(offset, length);
5253        return true;
5254      });
5255}
5256
5257size_t get_multipart_ranges_data_length(const Request &req,
5258                                               const std::string &boundary,
5259                                               const std::string &content_type,
5260                                               size_t content_length) {
5261  size_t data_length = 0;
5262
5263  process_multipart_ranges_data(
5264      req, boundary, content_type, content_length,
5265      [&](const std::string &token) { data_length += token.size(); },
5266      [&](const std::string &token) { data_length += token.size(); },
5267      [&](size_t /*offset*/, size_t length) {
5268        data_length += length;
5269        return true;
5270      });
5271
5272  return data_length;
5273}
5274
5275template <typename T>
5276bool
5277write_multipart_ranges_data(Stream &strm, const Request &req, Response &res,
5278                            const std::string &boundary,
5279                            const std::string &content_type,
5280                            size_t content_length, const T &is_shutting_down) {
5281  return process_multipart_ranges_data(
5282      req, boundary, content_type, content_length,
5283      [&](const std::string &token) { strm.write(token); },
5284      [&](const std::string &token) { strm.write(token); },
5285      [&](size_t offset, size_t length) {
5286        return write_content(strm, res.content_provider_, offset, length,
5287                             is_shutting_down);
5288      });
5289}
5290
5291bool has_framed_body(const Request &req) {
5292  return is_chunked_transfer_encoding(req.headers) ||
5293         req.get_header_value_u64("Content-Length") > 0;
5294}
5295
5296bool is_connection_persistent(const Request &req) {
5297  if (has_header_token(req.headers, "Connection", "close")) { return false; }
5298  if (req.version == "HTTP/1.0" &&
5299      !has_header_token(req.headers, "Connection", "keep-alive")) {
5300    return false;
5301  }
5302  return true;
5303}
5304
5305bool expect_content(const Request &req) {
5306  if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" ||
5307      req.method == "DELETE") {
5308    return true;
5309  }
5310  return has_framed_body(req);
5311}
5312
5313#ifdef _WIN32
5314class WSInit {
5315public:
5316  WSInit() {
5317    WSADATA wsaData;
5318    if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true;
5319  }
5320
5321  ~WSInit() {
5322    if (is_valid_) WSACleanup();
5323  }
5324
5325  bool is_valid_ = false;
5326};
5327
5328static WSInit wsinit_;
5329#endif
5330
5331// RFC 9110 Section 11.6.1 defines a challenge list as
5332//   WWW-Authenticate = #challenge
5333//   challenge        = auth-scheme [ 1*SP ( token68 / [ #auth-param ] ) ]
5334//   auth-param       = token BWS "=" BWS ( token / quoted-string )
5335// so a server may offer several schemes, each with its own comma-separated
5336// auth-param list, in either order and either as separate field lines or
5337// packed into one. Splitting on every comma would break apart a challenge's
5338// own param list; splitting only on the first space would miss a Digest
5339// challenge that isn't first. Split on commas that aren't inside a
5340// quoted-string instead, then track which scheme each resulting segment
5341// belongs to: a segment whose text before "=" contains whitespace (or that
5342// has no "=" at all) starts a new challenge named by its leading token.
5343std::vector<std::string> split_challenge_segments(const std::string &s) {
5344  std::vector<std::string> segments;
5345  size_t start = 0;
5346  auto in_quotes = false;
5347  for (size_t i = 0; i < s.size(); i++) {
5348    auto c = s[i];
5349    if (in_quotes) {
5350      if (c == '\\' && i + 1 < s.size()) {
5351        i++;
5352      } else if (c == '"') {
5353        in_quotes = false;
5354      }
5355    } else if (c == '"') {
5356      in_quotes = true;
5357    } else if (c == ',') {
5358      segments.push_back(s.substr(start, i - start));
5359      start = i + 1;
5360    }
5361  }
5362  segments.push_back(s.substr(start));
5363  return segments;
5364}
5365
5366std::string unescape_quoted_pairs(const std::string &s) {
5367  std::string out;
5368  out.reserve(s.size());
5369  for (size_t i = 0; i < s.size(); i++) {
5370    if (s[i] == '\\' && i + 1 < s.size()) {
5371      out += s[++i];
5372    } else {
5373      out += s[i];
5374    }
5375  }
5376  return out;
5377}
5378
5379bool parse_www_authenticate(const Response &res,
5380                                   std::map<std::string, std::string> &auth,
5381                                   bool is_proxy) {
5382  auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate";
5383  auto combined = get_combined_header_value(res.headers, auth_key);
5384  if (combined.empty()) { return false; }
5385
5386  auto found_digest = false;
5387  auto in_digest_challenge = false;
5388  for (const auto &raw_segment : split_challenge_segments(combined)) {
5389    auto segment = trim_copy(raw_segment);
5390    if (segment.empty()) { continue; }
5391
5392    auto eq_pos = segment.find('=');
5393    // BWS is allowed on both sides of "=", so the text naming the key (or,
5394    // for the first segment of a challenge, "<scheme> <key>") must be
5395    // trimmed before its boundaries are inspected.
5396    auto key_part = trim_copy(
5397        eq_pos == std::string::npos ? segment : segment.substr(0, eq_pos));
5398    auto space_pos = key_part.find_last_of(" \t");
5399    if (space_pos != std::string::npos || eq_pos == std::string::npos) {
5400      // "<scheme>[ <key>]" starts a new challenge.
5401      auto scheme_end =
5402          space_pos == std::string::npos ? key_part.size() : space_pos;
5403      // RFC 7616 Section 3.7: a server may offer more than one Digest
5404      // challenge (e.g. SHA-256 and MD5); keep only the first so a nonce
5405      // from one challenge is never paired with another's algorithm.
5406      in_digest_challenge =
5407          !found_digest &&
5408          case_ignore::equal(key_part.substr(0, scheme_end), "Digest");
5409      if (in_digest_challenge) { found_digest = true; }
5410      if (space_pos == std::string::npos) {
5411        // Bare scheme (or a token68), no auth-param on this segment.
5412        continue;
5413      }
5414      key_part = key_part.substr(space_pos + 1);
5415    }
5416
5417    if (!in_digest_challenge) { continue; }
5418
5419    auto val = trim_copy(segment.substr(eq_pos + 1));
5420    auto unquoted = trim_double_quotes_copy(val);
5421    if (unquoted.size() != val.size()) {
5422      unquoted = unescape_quoted_pairs(unquoted);
5423    }
5424    auth[std::move(key_part)] = std::move(unquoted);
5425  }
5426
5427  // RFC 7616 Section 3.3 requires realm and nonce on every Digest challenge;
5428  // make_digest_authentication_header() dereferences both unconditionally, so
5429  // a challenge missing either can't produce a usable Authorization header.
5430  // Treat it the same as no Digest challenge at all.
5431  return found_digest && auth.find("realm") != auth.end() &&
5432         auth.find("nonce") != auth.end();
5433}
5434
5435class ContentProviderAdapter {
5436public:
5437  explicit ContentProviderAdapter(
5438      ContentProviderWithoutLength &&content_provider)
5439      : content_provider_(std::move(content_provider)) {}
5440
5441  bool operator()(size_t offset, size_t, DataSink &sink) {
5442    return content_provider_(offset, sink);
5443  }
5444
5445private:
5446  ContentProviderWithoutLength content_provider_;
5447};
5448
5449// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
5450namespace fields {
5451
5452bool is_token_char(char c) {
5453  return is_ascii_alnum(c) || c == '!' || c == '#' || c == '$' || c == '%' ||
5454         c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' ||
5455         c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
5456}
5457
5458bool is_token(const std::string &s) {
5459  if (s.empty()) { return false; }
5460  for (auto c : s) {
5461    if (!is_token_char(c)) { return false; }
5462  }
5463  return true;
5464}
5465
5466bool is_field_name(const std::string &s) { return is_token(s); }
5467
5468bool is_vchar(char c) { return c >= 33 && c <= 126; }
5469
5470bool is_obs_text(char c) { return 128 <= static_cast<unsigned char>(c); }
5471
5472bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); }
5473
5474bool is_field_content(const std::string &s) {
5475  if (s.empty()) { return true; }
5476
5477  if (s.size() == 1) {
5478    return is_field_vchar(s[0]);
5479  } else if (s.size() == 2) {
5480    return is_field_vchar(s[0]) && is_field_vchar(s[1]);
5481  } else {
5482    size_t i = 0;
5483
5484    if (!is_field_vchar(s[i])) { return false; }
5485    i++;
5486
5487    while (i < s.size() - 1) {
5488      auto c = s[i++];
5489      if (c == ' ' || c == '\t' || is_field_vchar(c)) {
5490      } else {
5491        return false;
5492      }
5493    }
5494
5495    return is_field_vchar(s[i]);
5496  }
5497}
5498
5499bool is_field_value(const std::string &s) { return is_field_content(s); }
5500
5501bool is_field_valid(const std::string &name, const std::string &value) {
5502  return is_field_name(name) && is_field_value(value);
5503}
5504
5505} // namespace fields
5506
5507bool perform_websocket_handshake(Stream &strm, Request &req,
5508                                        WebSocketUpgradeResponse &upgrade) {
5509  // Generate random Sec-WebSocket-Key
5510  thread_local std::mt19937 rng(std::random_device{}());
5511  std::string key_bytes(16, '\0');
5512  for (size_t i = 0; i < 16; i += 4) {
5513    auto r = rng();
5514    std::memcpy(&key_bytes[i], &r, (std::min)(size_t(4), size_t(16 - i)));
5515  }
5516  auto client_key = base64_encode(key_bytes);
5517
5518  req.headers.erase("Upgrade");
5519  req.headers.erase("Connection");
5520  req.headers.erase("Sec-WebSocket-Key");
5521  req.headers.erase("Sec-WebSocket-Version");
5522  req.headers.emplace("Upgrade", "websocket");
5523  req.headers.emplace("Connection", "Upgrade");
5524  req.headers.emplace("Sec-WebSocket-Key", client_key);
5525  req.headers.emplace("Sec-WebSocket-Version", "13");
5526
5527  // Build the request in memory first, like ClientImpl::write_request does.
5528  // Writing straight to the socket would leak a request line onto the wire
5529  // before check_and_write_headers gets a chance to reject an invalid header,
5530  // and would emit one small write per header.
5531  BufferStream bstrm;
5532
5533  if (write_request_line(bstrm, req.method, req.path) < 0) {
5534    upgrade.error = Error::Write;
5535    return false;
5536  }
5537
5538  auto error = Error::Success;
5539  if (!check_and_write_headers(bstrm, req.headers, write_headers, error)) {
5540    upgrade.error = error;
5541    return false;
5542  }
5543
5544  const auto &data = bstrm.get_buffer();
5545  if (!write_data(strm, data.data(), data.size())) {
5546    upgrade.error = Error::Write;
5547    return false;
5548  }
5549
5550  // Verify 101 response and Sec-WebSocket-Accept header
5551  auto expected_accept = websocket_accept_key(client_key);
5552  return read_websocket_upgrade_response(strm, expected_accept, upgrade);
5553}
5554
5555bool is_ip_address(const std::string &host) {
5556  struct in_addr addr4;
5557  struct in6_addr addr6;
5558  return inet_pton(AF_INET, host.c_str(), &addr4) == 1 ||
5559         inet_pton(AF_INET6, host.c_str(), &addr6) == 1;
5560}
5561
5562// Resolve where a client should connect for `host`, honoring a user-supplied
5563// hostname-to-address map. `host` itself is never rewritten, so it keeps
5564// supplying the Host header and SNI; only the connection target changes.
5565//
5566// A mapped IP literal goes to `ip`, which keeps create_socket's AI_NUMERICHOST
5567// path. Anything else goes to `connect_host`, which create_socket resolves as
5568// a name, or uses as the socket path when the address family is AF_UNIX. An
5569// absent or empty mapping leaves `host` as the connection target; without the
5570// empty check the value would reach getaddrinfo as a null node and silently
5571// resolve to loopback.
5572void apply_addr_map(const std::map<std::string, std::string> &addr_map,
5573                           const std::string &host, std::string &connect_host,
5574                           std::string &ip) {
5575  connect_host = host;
5576  ip.clear();
5577
5578  auto it = addr_map.find(host);
5579  if (it == addr_map.end() || it->second.empty()) { return; }
5580
5581  if (is_ip_address(it->second)) {
5582    ip = it->second;
5583  } else {
5584    connect_host = it->second;
5585  }
5586}
5587
5588} // namespace detail
5589
5590/*
5591 * Group 2: detail namespace - SSL common utilities
5592 */
5593
5594#ifdef CPPHTTPLIB_SSL_ENABLED
5595namespace detail {
5596
5597class SSLSocketStream final : public Stream {
5598public:
5599  SSLSocketStream(
5600      socket_t sock, tls::session_t session, time_t read_timeout_sec,
5601      time_t read_timeout_usec, time_t write_timeout_sec,
5602      time_t write_timeout_usec, time_t max_timeout_msec = 0,
5603      std::chrono::time_point<std::chrono::steady_clock> start_time =
5604          (std::chrono::steady_clock::time_point::min)());
5605  ~SSLSocketStream() override;
5606
5607  bool is_readable() const override;
5608  bool wait_readable() const override;
5609  bool wait_writable() const override;
5610  bool is_peer_alive() const override;
5611  ssize_t read(char *ptr, size_t size) override;
5612  ssize_t write(const char *ptr, size_t size) override;
5613  void get_remote_ip_and_port(std::string &ip, int &port) const override;
5614  void get_local_ip_and_port(std::string &ip, int &port) const override;
5615  socket_t socket() const override;
5616  time_t duration() const override;
5617  void set_read_timeout(time_t sec, time_t usec = 0) override;
5618
5619  // See SocketStream::set_readable_hint().
5620  void set_readable_hint() { readable_hint_ = true; }
5621
5622private:
5623  bool ensure_readable();
5624
5625  socket_t sock_;
5626  tls::session_t session_;
5627  time_t read_timeout_sec_;
5628  time_t read_timeout_usec_;
5629  time_t write_timeout_sec_;
5630  time_t write_timeout_usec_;
5631  time_t max_timeout_msec_;
5632  const std::chrono::time_point<std::chrono::steady_clock> start_time_;
5633  bool readable_hint_ = false;
5634};
5635
5636// A TLS stream for WebSocket connections, where the receive path and the
5637// send path (application send() plus the heartbeat ping thread) run on
5638// different threads. A single TLS session must never be entered
5639// concurrently, so every call into the session is serialized by one mutex.
5640//
5641// Unlike SSLSocketStream, the socket is kept non-blocking for the stream's
5642// whole lifetime and each read()/write() performs a single non-blocking TLS
5643// call under the lock, then waits for readiness with select() outside the
5644// lock. The lock is therefore held only for CPU-bound work, so a reader
5645// blocked waiting for data never stalls a concurrent sender.
5646//
5647// This stream is used only for wss:// connections. Plain ws:// and ordinary
5648// HTTP/HTTPS keep using SocketStream/SSLSocketStream unchanged.
5649class WebSocketSSLStream final : public Stream {
5650public:
5651  WebSocketSSLStream(socket_t sock, tls::session_t session,
5652                     time_t read_timeout_sec, time_t read_timeout_usec,
5653                     time_t write_timeout_sec, time_t write_timeout_usec);
5654  ~WebSocketSSLStream() override;
5655
5656  bool is_readable() const override;
5657  bool wait_readable() const override;
5658  bool wait_writable() const override;
5659  ssize_t read(char *ptr, size_t size) override;
5660  ssize_t write(const char *ptr, size_t size) override;
5661  void get_remote_ip_and_port(std::string &ip, int &port) const override;
5662  void get_local_ip_and_port(std::string &ip, int &port) const override;
5663  socket_t socket() const override;
5664  time_t duration() const override;
5665  void set_read_timeout(time_t sec, time_t usec = 0) override;
5666
5667private:
5668  mutable std::mutex session_mutex_;
5669
5670  socket_t sock_;
5671  tls::session_t session_;
5672  // WebSocket::close() shortens the read timeout from the closing thread
5673  // while the receive thread is inside wait_readable(), so these two are read
5674  // and written concurrently. The write timeouts are never mutated.
5675  std::atomic<time_t> read_timeout_sec_;
5676  std::atomic<time_t> read_timeout_usec_;
5677  time_t write_timeout_sec_;
5678  time_t write_timeout_usec_;
5679  const std::chrono::time_point<std::chrono::steady_clock> start_time_;
5680};
5681
5682#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
5683std::string message_digest(const std::string &s, const EVP_MD *algo) {
5684  auto context = std::unique_ptr<EVP_MD_CTX, decltype(&EVP_MD_CTX_free)>(
5685      EVP_MD_CTX_new(), EVP_MD_CTX_free);
5686
5687  unsigned int hash_length = 0;
5688  unsigned char hash[EVP_MAX_MD_SIZE];
5689
5690  EVP_DigestInit_ex(context.get(), algo, nullptr);
5691  EVP_DigestUpdate(context.get(), s.c_str(), s.size());
5692  EVP_DigestFinal_ex(context.get(), hash, &hash_length);
5693
5694  std::stringstream ss;
5695  for (auto i = 0u; i < hash_length; ++i) {
5696    ss << std::hex << std::setw(2) << std::setfill('0')
5697       << static_cast<unsigned int>(hash[i]);
5698  }
5699
5700  return ss.str();
5701}
5702
5703std::string MD5(const std::string &s) {
5704  return message_digest(s, EVP_md5());
5705}
5706
5707std::string SHA_256(const std::string &s) {
5708  return message_digest(s, EVP_sha256());
5709}
5710
5711std::string SHA_512(const std::string &s) {
5712  return message_digest(s, EVP_sha512());
5713}
5714#elif defined(CPPHTTPLIB_MBEDTLS_SUPPORT)
5715namespace {
5716template <size_t N>
5717std::string hash_to_hex(const unsigned char (&hash)[N]) {
5718  std::stringstream ss;
5719  for (size_t i = 0; i < N; ++i) {
5720    ss << std::hex << std::setw(2) << std::setfill('0')
5721       << static_cast<unsigned int>(hash[i]);
5722  }
5723  return ss.str();
5724}
5725} // namespace
5726
5727#ifdef CPPHTTPLIB_MBEDTLS_V4
5728// Mbed TLS 4.x provides hashing (and TLS RNG) via PSA Crypto, which must be
5729// initialized once. PSA state is process-global; do not free it.
5730bool ensure_mbedtls_psa_crypto() {
5731  static std::once_flag once;
5732  static bool ok = false;
5733  std::call_once(once, []() { ok = (psa_crypto_init() == PSA_SUCCESS); });
5734  return ok;
5735}
5736
5737bool psa_hash(psa_algorithm_t alg, const std::string &s,
5738                     unsigned char *out, size_t out_size) {
5739  if (!ensure_mbedtls_psa_crypto()) { return false; }
5740  size_t olen = 0;
5741  return psa_hash_compute(alg, reinterpret_cast<const uint8_t *>(s.data()),
5742                          s.size(), out, out_size, &olen) == PSA_SUCCESS &&
5743         olen == out_size;
5744}
5745#endif
5746
5747std::string MD5(const std::string &s) {
5748  unsigned char hash[16];
5749#ifdef CPPHTTPLIB_MBEDTLS_V4
5750  if (!psa_hash(PSA_ALG_MD5, s, hash, sizeof(hash))) { return {}; }
5751#elif defined(CPPHTTPLIB_MBEDTLS_V3)
5752  mbedtls_md5(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
5753              hash);
5754#else
5755  mbedtls_md5_ret(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
5756                  hash);
5757#endif
5758  return hash_to_hex(hash);
5759}
5760
5761std::string SHA_256(const std::string &s) {
5762  unsigned char hash[32];
5763#ifdef CPPHTTPLIB_MBEDTLS_V4
5764  if (!psa_hash(PSA_ALG_SHA_256, s, hash, sizeof(hash))) { return {}; }
5765#elif defined(CPPHTTPLIB_MBEDTLS_V3)
5766  mbedtls_sha256(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
5767                 hash, 0);
5768#else
5769  mbedtls_sha256_ret(reinterpret_cast<const unsigned char *>(s.c_str()),
5770                     s.size(), hash, 0);
5771#endif
5772  return hash_to_hex(hash);
5773}
5774
5775std::string SHA_512(const std::string &s) {
5776  unsigned char hash[64];
5777#ifdef CPPHTTPLIB_MBEDTLS_V4
5778  if (!psa_hash(PSA_ALG_SHA_512, s, hash, sizeof(hash))) { return {}; }
5779#elif defined(CPPHTTPLIB_MBEDTLS_V3)
5780  mbedtls_sha512(reinterpret_cast<const unsigned char *>(s.c_str()), s.size(),
5781                 hash, 0);
5782#else
5783  mbedtls_sha512_ret(reinterpret_cast<const unsigned char *>(s.c_str()),
5784                     s.size(), hash, 0);
5785#endif
5786  return hash_to_hex(hash);
5787}
5788#elif defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
5789namespace {
5790template <size_t N>
5791std::string hash_to_hex(const unsigned char (&hash)[N]) {
5792  std::stringstream ss;
5793  for (size_t i = 0; i < N; ++i) {
5794    ss << std::hex << std::setw(2) << std::setfill('0')
5795       << static_cast<unsigned int>(hash[i]);
5796  }
5797  return ss.str();
5798}
5799} // namespace
5800
5801std::string MD5(const std::string &s) {
5802  unsigned char hash[WC_MD5_DIGEST_SIZE];
5803  wc_Md5Hash(reinterpret_cast<const unsigned char *>(s.c_str()),
5804             static_cast<word32>(s.size()), hash);
5805  return hash_to_hex(hash);
5806}
5807
5808std::string SHA_256(const std::string &s) {
5809  unsigned char hash[WC_SHA256_DIGEST_SIZE];
5810  wc_Sha256Hash(reinterpret_cast<const unsigned char *>(s.c_str()),
5811                static_cast<word32>(s.size()), hash);
5812  return hash_to_hex(hash);
5813}
5814
5815std::string SHA_512(const std::string &s) {
5816  unsigned char hash[WC_SHA512_DIGEST_SIZE];
5817  wc_Sha512Hash(reinterpret_cast<const unsigned char *>(s.c_str()),
5818                static_cast<word32>(s.size()), hash);
5819  return hash_to_hex(hash);
5820}
5821#endif
5822
5823template <typename T>
5824bool process_server_socket_ssl(
5825    const std::atomic<socket_t> &svr_sock, tls::session_t session,
5826    socket_t sock, size_t keep_alive_max_count, time_t keep_alive_timeout_sec,
5827    time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec,
5828    time_t write_timeout_usec, T callback) {
5829  return process_server_socket_core(
5830      svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec,
5831      [&](bool close_connection, bool &connection_closed) {
5832        SSLSocketStream strm(sock, session, read_timeout_sec, read_timeout_usec,
5833                             write_timeout_sec, write_timeout_usec);
5834        // See the non-TLS path in process_server_socket().
5835        strm.set_readable_hint();
5836        return callback(strm, close_connection, connection_closed);
5837      });
5838}
5839
5840template <typename T>
5841bool process_client_socket_ssl(
5842    tls::session_t session, socket_t sock, time_t read_timeout_sec,
5843    time_t read_timeout_usec, time_t write_timeout_sec,
5844    time_t write_timeout_usec, time_t max_timeout_msec,
5845    std::chrono::time_point<std::chrono::steady_clock> start_time, T callback) {
5846  SSLSocketStream strm(sock, session, read_timeout_sec, read_timeout_usec,
5847                       write_timeout_sec, write_timeout_usec, max_timeout_msec,
5848                       start_time);
5849  return callback(strm);
5850}
5851
5852std::pair<std::string, std::string> make_digest_authentication_header(
5853    const Request &req, const std::map<std::string, std::string> &auth,
5854    size_t cnonce_count, const std::string &cnonce, const std::string &username,
5855    const std::string &password, bool is_proxy = false) {
5856  std::string nc;
5857  {
5858    std::stringstream ss;
5859    ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count;
5860    nc = ss.str();
5861  }
5862
5863  std::string qop;
5864  if (auth.find("qop") != auth.end()) {
5865    qop = auth.at("qop");
5866    if (qop.find("auth-int") != std::string::npos) {
5867      qop = "auth-int";
5868    } else if (qop.find("auth") != std::string::npos) {
5869      qop = "auth";
5870    } else {
5871      qop.clear();
5872    }
5873  }
5874
5875  std::string algo = "MD5";
5876  if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); }
5877
5878  std::string response;
5879  {
5880    auto H = algo == "SHA-256"   ? detail::SHA_256
5881             : algo == "SHA-512" ? detail::SHA_512
5882                                 : detail::MD5;
5883
5884    auto A1 = username + ":" + auth.at("realm") + ":" + password;
5885
5886    auto A2 = req.method + ":" + req.path;
5887    if (qop == "auth-int") { A2 += ":" + H(req.body); }
5888
5889    if (qop.empty()) {
5890      response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2));
5891    } else {
5892      response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce +
5893                   ":" + qop + ":" + H(A2));
5894    }
5895  }
5896
5897  auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : "";
5898
5899  auto field = "Digest username=\"" + username + "\", realm=\"" +
5900               auth.at("realm") + "\", nonce=\"" + auth.at("nonce") +
5901               "\", uri=\"" + req.path + "\", algorithm=" + algo +
5902               (qop.empty() ? ", response=\""
5903                            : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" +
5904                                  cnonce + "\", response=\"") +
5905               response + "\"" +
5906               (opaque.empty() ? "" : ", opaque=\"" + opaque + "\"");
5907
5908  auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
5909  return std::make_pair(key, field);
5910}
5911
5912bool match_hostname(const std::string &pattern,
5913                           const std::string &hostname) {
5914  // Exact match (case-insensitive)
5915  if (detail::case_ignore::equal(hostname, pattern)) { return true; }
5916
5917  // Split both pattern and hostname into components by '.'
5918  std::vector<std::string> pattern_components;
5919  if (!pattern.empty()) {
5920    split(pattern.data(), pattern.data() + pattern.size(), '.',
5921          [&](const char *b, const char *e) {
5922            pattern_components.emplace_back(b, e);
5923          });
5924  }
5925
5926  std::vector<std::string> host_components;
5927  if (!hostname.empty()) {
5928    split(hostname.data(), hostname.data() + hostname.size(), '.',
5929          [&](const char *b, const char *e) {
5930            host_components.emplace_back(b, e);
5931          });
5932  }
5933
5934  // Component count must match
5935  if (host_components.size() != pattern_components.size()) { return false; }
5936
5937  // Compare each component with wildcard support
5938  // Supports: "*" (full wildcard), "prefix*" (partial wildcard)
5939  // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
5940  auto itr = pattern_components.begin();
5941  for (const auto &h : host_components) {
5942    auto &p = *itr;
5943    if (!detail::case_ignore::equal(p, h) && p != "*") {
5944      bool partial_match = false;
5945      if (!p.empty() && p[p.size() - 1] == '*') {
5946        const auto prefix_length = p.size() - 1;
5947        if (prefix_length == 0) {
5948          partial_match = true;
5949        } else if (h.size() >= prefix_length) {
5950          partial_match =
5951              std::equal(p.begin(),
5952                         p.begin() + static_cast<std::string::difference_type>(
5953                                         prefix_length),
5954                         h.begin(), [](const char ca, const char cb) {
5955                           return detail::case_ignore::to_lower(ca) ==
5956                                  detail::case_ignore::to_lower(cb);
5957                         });
5958        }
5959      }
5960      if (!partial_match) { return false; }
5961    }
5962    ++itr;
5963  }
5964
5965  return true;
5966}
5967
5968#ifdef _WIN32
5969// Verify certificate using Windows CertGetCertificateChain API.
5970// This provides real-time certificate validation with Windows Update
5971// integration, independent of the TLS backend (OpenSSL or MbedTLS).
5972bool
5973verify_cert_with_windows_schannel(const std::vector<unsigned char> &der_cert,
5974                                  const std::string &hostname,
5975                                  bool verify_hostname, uint64_t &out_error) {
5976  if (der_cert.empty()) { return false; }
5977
5978  out_error = 0;
5979
5980  // Create Windows certificate context from DER data
5981  auto cert_context = CertCreateCertificateContext(
5982      X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, der_cert.data(),
5983      static_cast<DWORD>(der_cert.size()));
5984
5985  if (!cert_context) {
5986    out_error = GetLastError();
5987    return false;
5988  }
5989
5990  auto cert_guard =
5991      scope_exit([&] { CertFreeCertificateContext(cert_context); });
5992
5993  // Setup chain parameters
5994  CERT_CHAIN_PARA chain_para = {};
5995  chain_para.cbSize = sizeof(chain_para);
5996
5997  // Build certificate chain with revocation checking
5998  PCCERT_CHAIN_CONTEXT chain_context = nullptr;
5999  auto chain_result = CertGetCertificateChain(
6000      nullptr, cert_context, nullptr, cert_context->hCertStore, &chain_para,
6001      CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_END_CERT |
6002          CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT,
6003      nullptr, &chain_context);
6004
6005  if (!chain_result || !chain_context) {
6006    out_error = GetLastError();
6007    return false;
6008  }
6009
6010  auto chain_guard =
6011      scope_exit([&] { CertFreeCertificateChain(chain_context); });
6012
6013  // Check if chain has errors
6014  if (chain_context->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) {
6015    out_error = chain_context->TrustStatus.dwErrorStatus;
6016    return false;
6017  }
6018
6019  // Verify SSL policy
6020  SSL_EXTRA_CERT_CHAIN_POLICY_PARA extra_policy_para = {};
6021  extra_policy_para.cbSize = sizeof(extra_policy_para);
6022#ifdef AUTHTYPE_SERVER
6023  extra_policy_para.dwAuthType = AUTHTYPE_SERVER;
6024#endif
6025
6026  std::wstring whost;
6027  if (verify_hostname) {
6028    whost = u8string_to_wstring(hostname.c_str());
6029    extra_policy_para.pwszServerName = const_cast<wchar_t *>(whost.c_str());
6030  }
6031
6032  CERT_CHAIN_POLICY_PARA policy_para = {};
6033  policy_para.cbSize = sizeof(policy_para);
6034#ifdef CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS
6035  policy_para.dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS;
6036#else
6037  policy_para.dwFlags = 0;
6038#endif
6039  policy_para.pvExtraPolicyPara = &extra_policy_para;
6040
6041  CERT_CHAIN_POLICY_STATUS policy_status = {};
6042  policy_status.cbSize = sizeof(policy_status);
6043
6044  if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, chain_context,
6045                                        &policy_para, &policy_status)) {
6046    out_error = GetLastError();
6047    return false;
6048  }
6049
6050  if (policy_status.dwError != 0) {
6051    out_error = policy_status.dwError;
6052    return false;
6053  }
6054
6055  return true;
6056}
6057#endif // _WIN32
6058
6059// Loads CA file/dir configuration and applies the system CA policy to a
6060// client TLS context. PEM data and native stores are applied to the context
6061// directly at set time; has_custom_store reflects them for the Auto policy
6062// decision.
6063bool load_client_ca_config(tls::ctx_t ctx,
6064                                  const std::string &ca_cert_file_path,
6065                                  const std::string &ca_cert_dir_path,
6066                                  bool has_custom_store, SystemCAMode mode,
6067                                  uint64_t &backend_error) {
6068  auto ret = true;
6069
6070  if (!ca_cert_file_path.empty()) {
6071    if (!tls::load_ca_file(ctx, ca_cert_file_path.c_str())) {
6072      backend_error = tls::get_error();
6073      ret = false;
6074    }
6075  } else if (!ca_cert_dir_path.empty()) {
6076    if (!tls::load_ca_dir(ctx, ca_cert_dir_path.c_str())) {
6077      backend_error = tls::get_error();
6078      ret = false;
6079    }
6080  }
6081
6082  auto has_custom_ca = !ca_cert_file_path.empty() ||
6083                       !ca_cert_dir_path.empty() || has_custom_store;
6084  if (mode == SystemCAMode::Enabled ||
6085      (mode == SystemCAMode::Auto && !has_custom_ca)) {
6086    if (!tls::load_system_certs(ctx)) { backend_error = tls::get_error(); }
6087  }
6088
6089  return ret;
6090}
6091
6092// The parts of session setup that only SSLClient needs, plus the handful
6093// WebSocketClient also exposes; everything else takes the defaults, which is
6094// what keeps the two clients on one implementation.
6095struct ClientTlsSessionOptions {
6096  // Both SSLClient and WebSocketClient expose this independently of
6097  // certificate verification.
6098  bool server_hostname_verification = true;
6099  std::function<SSLVerifierResponse(tls::session_t)> session_verifier;
6100  // When non-null, guards session creation against concurrent use of the
6101  // context. A WebSocketClient is not safe to use from several threads to
6102  // begin with, so it passes nothing.
6103  std::mutex *ctx_mutex = nullptr;
6104#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
6105  // The caller decides whether Schannel has anything to say about this
6106  // connection; see SSLClient::initialize_ssl().
6107  bool windows_cert_verification = false;
6108#endif
6109};
6110
6111// Filled in on failure for callers that report error details.
6112struct ClientTlsSessionError {
6113  Error error = Error::Success;
6114  int ssl_error = 0;
6115  uint64_t backend_error = 0;
6116};
6117
6118// Establishes a client TLS session on an already connected socket. On failure
6119// the session is left for the caller to free: SSLClient frees it right away,
6120// WebSocketClient keeps it in a member that shutdown_and_close() cleans up.
6121bool setup_client_tls_session(
6122    const std::string &host, tls::ctx_t ctx, tls::session_t &session,
6123    socket_t sock, bool server_certificate_verification, time_t timeout_sec,
6124    time_t timeout_usec, ClientTlsSessionError *out_error = nullptr,
6125    const ClientTlsSessionOptions &options = ClientTlsSessionOptions()) {
6126  using namespace tls;
6127
6128  auto fail = [&](Error error, int ssl_error, uint64_t backend_error) {
6129    if (out_error) {
6130      out_error->error = error;
6131      out_error->ssl_error = ssl_error;
6132      out_error->backend_error = backend_error;
6133    }
6134    return false;
6135  };
6136
6137  if (!ctx) {
6138    session = nullptr;
6139    return fail(Error::SSLConnection, 0, 0);
6140  }
6141
6142#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
6143  // Mbed TLS and wolfSSL need the verification mode set explicitly; OpenSSL
6144  // uses SSL_VERIFY_NONE and does all verification post-handshake. Chain
6145  // verification happens during the handshake even for IP hosts; the
6146  // certificate identity is verified post-handshake via verify_hostname().
6147  set_verify_client(ctx, server_certificate_verification);
6148#endif
6149
6150  {
6151    std::unique_lock<std::mutex> guard;
6152    if (options.ctx_mutex) {
6153      guard = std::unique_lock<std::mutex>(*options.ctx_mutex);
6154    }
6155    session = create_session(ctx, sock);
6156  }
6157  if (!session) { return fail(Error::SSLConnection, 0, get_error()); }
6158
6159  // RFC 6066: SNI must not be set for IP addresses; skip it for IP hosts, so
6160  // their identity is checked post-handshake below instead. On Mbed TLS and
6161  // wolfSSL, set_sni also drives handshake-time hostname verification, so
6162  // options.server_hostname_verification is threaded through here.
6163  if (!is_ip_address(host)) {
6164    if (!set_sni(session, host.c_str(), options.server_hostname_verification)) {
6165      return fail(Error::SSLConnection, 0, get_error());
6166    }
6167  }
6168
6169  TlsError tls_err;
6170  if (!connect_nonblocking(session, sock, timeout_sec, timeout_usec,
6171                           &tls_err)) {
6172    auto error = Error::SSLConnection;
6173    if (tls_err.code == ErrorCode::CertVerifyFailed) {
6174      error = Error::SSLServerVerification;
6175    } else if (tls_err.code == ErrorCode::HostnameMismatch) {
6176      error = Error::SSLServerHostnameVerification;
6177    }
6178    return fail(error, static_cast<int>(tls_err.code), tls_err.backend_code);
6179  }
6180
6181  auto verification_status = SSLVerifierResponse::NoDecisionMade;
6182  if (options.session_verifier) {
6183    verification_status = options.session_verifier(session);
6184  }
6185
6186  if (verification_status == SSLVerifierResponse::CertificateRejected) {
6187    return fail(Error::SSLServerVerification, 0, get_error());
6188  }
6189
6190  if (verification_status == SSLVerifierResponse::NoDecisionMade &&
6191      server_certificate_verification) {
6192    auto verify_result = get_verify_result(session);
6193    if (verify_result != 0) {
6194      return fail(Error::SSLServerVerification, 0,
6195                  static_cast<uint64_t>(verify_result));
6196    }
6197
6198    auto server_cert = get_peer_cert(session);
6199    if (!server_cert) {
6200      return fail(Error::SSLServerVerification, 0, get_error());
6201    }
6202    auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); });
6203
6204    // Identity check against the peer certificate, post-handshake for all
6205    // backends. For IP hosts this is the only identity verification, since no
6206    // hostname is bound during the handshake.
6207    if (options.server_hostname_verification) {
6208      if (!verify_hostname(server_cert, host.c_str())) {
6209        return fail(Error::SSLServerHostnameVerification, 0,
6210                    hostname_mismatch_code());
6211      }
6212    }
6213
6214#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
6215    // Additional Windows Schannel verification.
6216    // This provides real-time certificate validation with Windows Update
6217    // integration, working with both OpenSSL and MbedTLS backends.
6218    if (options.windows_cert_verification) {
6219      std::vector<unsigned char> der;
6220      if (get_cert_der(server_cert, der)) {
6221        uint64_t wincrypt_error = 0;
6222        if (!verify_cert_with_windows_schannel(
6223                der, host, options.server_hostname_verification,
6224                wincrypt_error)) {
6225          return fail(Error::SSLServerVerification, 0, wincrypt_error);
6226        }
6227      }
6228    }
6229#endif
6230  }
6231
6232  return true;
6233}
6234
6235} // namespace detail
6236#endif // CPPHTTPLIB_SSL_ENABLED
6237
6238/*
6239 * Group 3: httplib namespace - Non-SSL public API implementations
6240 */
6241
6242void default_socket_options(socket_t sock) {
6243  set_socket_opt(sock, SOL_SOCKET,
6244#ifdef SO_REUSEPORT
6245                 SO_REUSEPORT,
6246#else
6247                 SO_REUSEADDR,
6248#endif
6249                 1);
6250}
6251
6252bool set_socket_opt(socket_t sock, int level, int optname, int optval) {
6253  return detail::set_socket_opt_impl(sock, level, optname, &optval,
6254                                     sizeof(optval));
6255}
6256
6257std::string get_bearer_token_auth(const Request &req) {
6258  // The auth scheme is case-insensitive (RFC 9110 11.1), and a value shorter
6259  // than the prefix carries no token.
6260  constexpr const char bearer_prefix[] = "Bearer ";
6261  constexpr auto bearer_prefix_len = detail::str_len(bearer_prefix);
6262  auto value = req.get_header_value("Authorization");
6263  if (value.size() >= bearer_prefix_len &&
6264      detail::case_ignore::equal(value.substr(0, bearer_prefix_len),
6265                                 bearer_prefix)) {
6266    return value.substr(bearer_prefix_len);
6267  }
6268  return "";
6269}
6270
6271const char *status_message(int status) {
6272  switch (status) {
6273  case StatusCode::Continue_100: return "Continue";
6274  case StatusCode::SwitchingProtocol_101: return "Switching Protocol";
6275  case StatusCode::Processing_102: return "Processing";
6276  case StatusCode::EarlyHints_103: return "Early Hints";
6277  case StatusCode::OK_200: return "OK";
6278  case StatusCode::Created_201: return "Created";
6279  case StatusCode::Accepted_202: return "Accepted";
6280  case StatusCode::NonAuthoritativeInformation_203:
6281    return "Non-Authoritative Information";
6282  case StatusCode::NoContent_204: return "No Content";
6283  case StatusCode::ResetContent_205: return "Reset Content";
6284  case StatusCode::PartialContent_206: return "Partial Content";
6285  case StatusCode::MultiStatus_207: return "Multi-Status";
6286  case StatusCode::AlreadyReported_208: return "Already Reported";
6287  case StatusCode::IMUsed_226: return "IM Used";
6288  case StatusCode::MultipleChoices_300: return "Multiple Choices";
6289  case StatusCode::MovedPermanently_301: return "Moved Permanently";
6290  case StatusCode::Found_302: return "Found";
6291  case StatusCode::SeeOther_303: return "See Other";
6292  case StatusCode::NotModified_304: return "Not Modified";
6293  case StatusCode::UseProxy_305: return "Use Proxy";
6294  case StatusCode::unused_306: return "unused";
6295  case StatusCode::TemporaryRedirect_307: return "Temporary Redirect";
6296  case StatusCode::PermanentRedirect_308: return "Permanent Redirect";
6297  case StatusCode::BadRequest_400: return "Bad Request";
6298  case StatusCode::Unauthorized_401: return "Unauthorized";
6299  case StatusCode::PaymentRequired_402: return "Payment Required";
6300  case StatusCode::Forbidden_403: return "Forbidden";
6301  case StatusCode::NotFound_404: return "Not Found";
6302  case StatusCode::MethodNotAllowed_405: return "Method Not Allowed";
6303  case StatusCode::NotAcceptable_406: return "Not Acceptable";
6304  case StatusCode::ProxyAuthenticationRequired_407:
6305    return "Proxy Authentication Required";
6306  case StatusCode::RequestTimeout_408: return "Request Timeout";
6307  case StatusCode::Conflict_409: return "Conflict";
6308  case StatusCode::Gone_410: return "Gone";
6309  case StatusCode::LengthRequired_411: return "Length Required";
6310  case StatusCode::PreconditionFailed_412: return "Precondition Failed";
6311  case StatusCode::PayloadTooLarge_413: return "Payload Too Large";
6312  case StatusCode::UriTooLong_414: return "URI Too Long";
6313  case StatusCode::UnsupportedMediaType_415: return "Unsupported Media Type";
6314  case StatusCode::RangeNotSatisfiable_416: return "Range Not Satisfiable";
6315  case StatusCode::ExpectationFailed_417: return "Expectation Failed";
6316  case StatusCode::ImATeapot_418: return "I'm a teapot";
6317  case StatusCode::MisdirectedRequest_421: return "Misdirected Request";
6318  case StatusCode::UnprocessableContent_422: return "Unprocessable Content";
6319  case StatusCode::Locked_423: return "Locked";
6320  case StatusCode::FailedDependency_424: return "Failed Dependency";
6321  case StatusCode::TooEarly_425: return "Too Early";
6322  case StatusCode::UpgradeRequired_426: return "Upgrade Required";
6323  case StatusCode::PreconditionRequired_428: return "Precondition Required";
6324  case StatusCode::TooManyRequests_429: return "Too Many Requests";
6325  case StatusCode::RequestHeaderFieldsTooLarge_431:
6326    return "Request Header Fields Too Large";
6327  case StatusCode::UnavailableForLegalReasons_451:
6328    return "Unavailable For Legal Reasons";
6329  case StatusCode::NotImplemented_501: return "Not Implemented";
6330  case StatusCode::BadGateway_502: return "Bad Gateway";
6331  case StatusCode::ServiceUnavailable_503: return "Service Unavailable";
6332  case StatusCode::GatewayTimeout_504: return "Gateway Timeout";
6333  case StatusCode::HttpVersionNotSupported_505:
6334    return "HTTP Version Not Supported";
6335  case StatusCode::VariantAlsoNegotiates_506: return "Variant Also Negotiates";
6336  case StatusCode::InsufficientStorage_507: return "Insufficient Storage";
6337  case StatusCode::LoopDetected_508: return "Loop Detected";
6338  case StatusCode::NotExtended_510: return "Not Extended";
6339  case StatusCode::NetworkAuthenticationRequired_511:
6340    return "Network Authentication Required";
6341
6342  default:
6343  case StatusCode::InternalServerError_500: return "Internal Server Error";
6344  }
6345}
6346
6347std::string to_string(const Error error) {
6348  switch (error) {
6349  case Error::Success: return "Success (no error)";
6350  case Error::Unknown: return "Unknown";
6351  case Error::Connection: return "Could not establish connection";
6352  case Error::BindIPAddress: return "Failed to bind IP address";
6353  case Error::Read: return "Failed to read connection";
6354  case Error::Write: return "Failed to write connection";
6355  case Error::ExceedRedirectCount: return "Maximum redirect count exceeded";
6356  case Error::Canceled: return "Connection handling canceled";
6357  case Error::SSLConnection: return "SSL connection failed";
6358  case Error::SSLLoadingCerts: return "SSL certificate loading failed";
6359  case Error::SSLServerVerification: return "SSL server verification failed";
6360  case Error::SSLServerHostnameVerification:
6361    return "SSL server hostname verification failed";
6362  case Error::UnsupportedMultipartBoundaryChars:
6363    return "Unsupported HTTP multipart boundary characters";
6364  case Error::Compression: return "Compression failed";
6365  case Error::ConnectionTimeout: return "Connection timed out";
6366  case Error::ProxyConnection: return "Proxy connection failed";
6367  case Error::ConnectionClosed: return "Connection closed by server";
6368  case Error::Timeout: return "Read timeout";
6369  case Error::ResourceExhaustion: return "Resource exhaustion";
6370  case Error::TooManyFormDataFiles: return "Too many form data files";
6371  case Error::ExceedMaxPayloadSize: return "Exceeded maximum payload size";
6372  case Error::ExceedUriMaxLength: return "Exceeded maximum URI length";
6373  case Error::ExceedMaxSocketDescriptorCount:
6374    return "Exceeded maximum socket descriptor count";
6375  case Error::InvalidRequestLine: return "Invalid request line";
6376  case Error::InvalidHTTPMethod: return "Invalid HTTP method";
6377  case Error::InvalidHTTPVersion: return "Invalid HTTP version";
6378  case Error::InvalidHeaders: return "Invalid headers";
6379  case Error::MultipartParsing: return "Multipart parsing failed";
6380  case Error::OpenFile: return "Failed to open file";
6381  case Error::Listen: return "Failed to listen on socket";
6382  case Error::GetSockName: return "Failed to get socket name";
6383  case Error::UnsupportedAddressFamily: return "Unsupported address family";
6384  case Error::HTTPParsing: return "HTTP parsing failed";
6385  case Error::InvalidRangeHeader: return "Invalid Range header";
6386  case Error::UnsupportedContentEncoding: return "Unsupported Content-Encoding";
6387  case Error::WebSocketHandshake: return "WebSocket handshake failed";
6388  case Error::UserCallbackException: return "User callback threw an exception";
6389  default: break;
6390  }
6391
6392  return "Invalid";
6393}
6394
6395std::ostream &operator<<(std::ostream &os, const Error &obj) {
6396  os << to_string(obj);
6397  os << " (" << static_cast<std::underlying_type<Error>::type>(obj) << ')';
6398  return os;
6399}
6400
6401std::string hosted_at(const std::string &hostname) {
6402  std::vector<std::string> addrs;
6403  hosted_at(hostname, addrs);
6404  if (addrs.empty()) { return std::string(); }
6405  return addrs[0];
6406}
6407
6408void hosted_at(const std::string &hostname,
6409                      std::vector<std::string> &addrs) {
6410  struct addrinfo hints;
6411  struct addrinfo *result;
6412
6413  memset(&hints, 0, sizeof(struct addrinfo));
6414  hints.ai_family = AF_UNSPEC;
6415  hints.ai_socktype = SOCK_STREAM;
6416  hints.ai_protocol = 0;
6417
6418  if (detail::getaddrinfo_with_timeout(hostname.c_str(), nullptr, &hints,
6419                                       &result, 0)) {
6420#if defined __linux__ && !defined __ANDROID__
6421    res_init();
6422#endif
6423    return;
6424  }
6425  auto se = detail::scope_exit([&] { freeaddrinfo(result); });
6426
6427  for (auto rp = result; rp; rp = rp->ai_next) {
6428    const auto &addr =
6429        *reinterpret_cast<struct sockaddr_storage *>(rp->ai_addr);
6430    std::string ip;
6431    auto dummy = -1;
6432    if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip,
6433                                dummy)) {
6434      addrs.emplace_back(std::move(ip));
6435    }
6436  }
6437}
6438
6439std::string encode_uri_component(const std::string &value) {
6440  std::ostringstream escaped;
6441  escaped.fill('0');
6442  escaped << std::hex;
6443
6444  for (auto c : value) {
6445    if (detail::is_ascii_alnum(c) || c == '-' || c == '_' || c == '.' ||
6446        c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')') {
6447      escaped << c;
6448    } else {
6449      escaped << std::uppercase;
6450      escaped << '%' << std::setw(2)
6451              << static_cast<int>(static_cast<unsigned char>(c));
6452      escaped << std::nouppercase;
6453    }
6454  }
6455
6456  return escaped.str();
6457}
6458
6459std::string encode_uri(const std::string &value) {
6460  std::ostringstream escaped;
6461  escaped.fill('0');
6462  escaped << std::hex;
6463
6464  for (auto c : value) {
6465    if (detail::is_ascii_alnum(c) || c == '-' || c == '_' || c == '.' ||
6466        c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')' ||
6467        c == ';' || c == '/' || c == '?' || c == ':' || c == '@' || c == '&' ||
6468        c == '=' || c == '+' || c == '$' || c == ',' || c == '#') {
6469      escaped << c;
6470    } else {
6471      escaped << std::uppercase;
6472      escaped << '%' << std::setw(2)
6473              << static_cast<int>(static_cast<unsigned char>(c));
6474      escaped << std::nouppercase;
6475    }
6476  }
6477
6478  return escaped.str();
6479}
6480
6481std::string decode_uri_component(const std::string &value) {
6482  std::string result;
6483
6484  for (size_t i = 0; i < value.size(); i++) {
6485    if (value[i] == '%' && i + 2 < value.size()) {
6486      auto val = 0;
6487      if (detail::from_hex_to_i(value, i + 1, 2, val)) {
6488        result += static_cast<char>(val);
6489        i += 2;
6490      } else {
6491        result += value[i];
6492      }
6493    } else {
6494      result += value[i];
6495    }
6496  }
6497
6498  return result;
6499}
6500
6501std::string decode_uri(const std::string &value) {
6502  std::string result;
6503
6504  for (size_t i = 0; i < value.size(); i++) {
6505    if (value[i] == '%' && i + 2 < value.size()) {
6506      auto val = 0;
6507      if (detail::from_hex_to_i(value, i + 1, 2, val)) {
6508        auto c = static_cast<char>(val);
6509        // Keep escapes of the reserved characters that encode_uri leaves
6510        // literal, so decode_uri is the inverse of encode_uri and an escaped
6511        // delimiter is not promoted into a real one (as with JS decodeURI).
6512        if (c == ';' || c == '/' || c == '?' || c == ':' || c == '@' ||
6513            c == '&' || c == '=' || c == '+' || c == '$' || c == ',' ||
6514            c == '#') {
6515          result += value[i];
6516          result += value[i + 1];
6517          result += value[i + 2];
6518        } else {
6519          result += c;
6520        }
6521        i += 2;
6522      } else {
6523        result += value[i];
6524      }
6525    } else {
6526      result += value[i];
6527    }
6528  }
6529
6530  return result;
6531}
6532
6533std::string encode_path_component(const std::string &component) {
6534  std::string result;
6535  result.reserve(component.size() * 3);
6536
6537  for (size_t i = 0; i < component.size(); i++) {
6538    auto c = static_cast<unsigned char>(component[i]);
6539
6540    // Unreserved characters per RFC 3986: ALPHA / DIGIT / "-" / "." / "_" / "~"
6541    if (detail::is_ascii_alnum(static_cast<char>(c)) || c == '-' || c == '.' ||
6542        c == '_' || c == '~') {
6543      result += static_cast<char>(c);
6544    }
6545    // Path-safe sub-delimiters: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" /
6546    // "," / ";" / "="
6547    else if (c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' ||
6548             c == ')' || c == '*' || c == '+' || c == ',' || c == ';' ||
6549             c == '=') {
6550      result += static_cast<char>(c);
6551    }
6552    // Colon is allowed in path segments except first segment
6553    else if (c == ':') {
6554      result += static_cast<char>(c);
6555    }
6556    // @ is allowed in path
6557    else if (c == '@') {
6558      result += static_cast<char>(c);
6559    } else {
6560      result += '%';
6561      char hex[3];
6562      snprintf(hex, sizeof(hex), "%02X", c);
6563      result.append(hex, 2);
6564    }
6565  }
6566  return result;
6567}
6568
6569std::string decode_path_component(const std::string &component) {
6570  std::string result;
6571  result.reserve(component.size());
6572
6573  for (size_t i = 0; i < component.size(); i++) {
6574    if (component[i] == '%' && i + 1 < component.size()) {
6575      if (component[i + 1] == 'u') {
6576        // Unicode %uXXXX encoding
6577        auto val = 0;
6578        if (detail::from_hex_to_i(component, i + 2, 4, val)) {
6579          // 4 digits Unicode codes: val is 0x0000-0xFFFF (from 4 hex digits),
6580          // so to_utf8 writes at most 3 bytes. buff[4] is safe.
6581          char buff[4];
6582          size_t len = detail::to_utf8(val, buff);
6583          if (len > 0) { result.append(buff, len); }
6584          i += 5; // 'u0000'
6585        } else {
6586          result += component[i];
6587        }
6588      } else {
6589        // Standard %XX encoding
6590        auto val = 0;
6591        if (detail::from_hex_to_i(component, i + 1, 2, val)) {
6592          // 2 digits hex codes
6593          result += static_cast<char>(val);
6594          i += 2; // 'XX'
6595        } else {
6596          result += component[i];
6597        }
6598      }
6599    } else {
6600      result += component[i];
6601    }
6602  }
6603  return result;
6604}
6605
6606std::string encode_query_component(const std::string &component,
6607                                          bool space_as_plus) {
6608  std::string result;
6609  result.reserve(component.size() * 3);
6610
6611  for (size_t i = 0; i < component.size(); i++) {
6612    auto c = static_cast<unsigned char>(component[i]);
6613
6614    // Unreserved characters per RFC 3986
6615    if (detail::is_ascii_alnum(static_cast<char>(c)) || c == '-' || c == '.' ||
6616        c == '_' || c == '~') {
6617      result += static_cast<char>(c);
6618    }
6619    // Space handling
6620    else if (c == ' ') {
6621      if (space_as_plus) {
6622        result += '+';
6623      } else {
6624        result += "%20";
6625      }
6626    }
6627    // Plus sign handling
6628    else if (c == '+') {
6629      if (space_as_plus) {
6630        result += "%2B";
6631      } else {
6632        result += static_cast<char>(c);
6633      }
6634    }
6635    // Query-safe sub-delimiters (excluding & and = which are query delimiters)
6636    else if (c == '!' || c == '$' || c == '\'' || c == '(' || c == ')' ||
6637             c == '*' || c == ',' || c == ';') {
6638      result += static_cast<char>(c);
6639    }
6640    // Colon and @ are allowed in query
6641    else if (c == ':' || c == '@') {
6642      result += static_cast<char>(c);
6643    }
6644    // Forward slash is allowed in query values
6645    else if (c == '/') {
6646      result += static_cast<char>(c);
6647    }
6648    // Question mark is allowed in query values (after first ?)
6649    else if (c == '?') {
6650      result += static_cast<char>(c);
6651    } else {
6652      result += '%';
6653      char hex[3];
6654      snprintf(hex, sizeof(hex), "%02X", c);
6655      result.append(hex, 2);
6656    }
6657  }
6658  return result;
6659}
6660
6661std::string decode_query_component(const std::string &component,
6662                                          bool plus_as_space) {
6663  std::string result;
6664  result.reserve(component.size());
6665
6666  for (size_t i = 0; i < component.size(); i++) {
6667    if (component[i] == '%' && i + 2 < component.size()) {
6668      auto val = 0;
6669      if (detail::from_hex_to_i(component, i + 1, 2, val)) {
6670        result += static_cast<char>(val);
6671        i += 2;
6672      } else {
6673        result += component[i];
6674      }
6675    } else if (component[i] == '+' && plus_as_space) {
6676      result += ' '; // + becomes space in form-urlencoded
6677    } else {
6678      result += component[i];
6679    }
6680  }
6681  return result;
6682}
6683
6684std::string sanitize_filename(const std::string &filename) {
6685  // Extract basename: find the last path separator (/ or \)
6686  auto pos = filename.find_last_of("/\\");
6687  auto result =
6688      (pos != std::string::npos) ? filename.substr(pos + 1) : filename;
6689
6690  // Strip null bytes
6691  result.erase(std::remove(result.begin(), result.end(), '\0'), result.end());
6692
6693  // Trim whitespace
6694  {
6695    auto start = result.find_first_not_of(" \t");
6696    auto end = result.find_last_not_of(" \t");
6697    result = (start == std::string::npos)
6698                 ? ""
6699                 : result.substr(start, end - start + 1);
6700  }
6701
6702  // Reject . and ..
6703  if (result == "." || result == "..") { return ""; }
6704
6705  return result;
6706}
6707
6708std::string append_query_params(const std::string &path,
6709                                       const Params &params) {
6710  std::string path_with_query = path;
6711  thread_local const std::regex re("[^?]+\\?.*");
6712  auto delm = std::regex_match(path, re) ? '&' : '?';
6713  path_with_query += delm + detail::params_to_query_str(params);
6714  return path_with_query;
6715}
6716
6717// Header utilities
6718std::pair<std::string, std::string>
6719make_range_header(const Ranges &ranges) {
6720  std::string field = "bytes=";
6721  auto i = 0;
6722  for (const auto &r : ranges) {
6723    if (i != 0) { field += ", "; }
6724    if (r.first != -1) { field += std::to_string(r.first); }
6725    field += '-';
6726    if (r.second != -1) { field += std::to_string(r.second); }
6727    i++;
6728  }
6729  return std::make_pair("Range", std::move(field));
6730}
6731
6732std::pair<std::string, std::string>
6733make_basic_authentication_header(const std::string &username,
6734                                 const std::string &password, bool is_proxy) {
6735  auto field = "Basic " + detail::base64_encode(username + ":" + password);
6736  auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
6737  return std::make_pair(key, std::move(field));
6738}
6739
6740std::pair<std::string, std::string>
6741make_bearer_token_authentication_header(const std::string &token,
6742                                        bool is_proxy = false) {
6743  auto field = "Bearer " + token;
6744  auto key = is_proxy ? "Proxy-Authorization" : "Authorization";
6745  return std::make_pair(key, std::move(field));
6746}
6747
6748// Request implementation
6749size_t Request::get_header_value_u64(const std::string &key, size_t def,
6750                                            size_t id) const {
6751  return detail::get_header_value_u64(headers, key, def, id);
6752}
6753
6754bool Request::has_header(const std::string &key) const {
6755  return detail::has_header(headers, key);
6756}
6757
6758std::string Request::get_header_value(const std::string &key,
6759                                             const char *def, size_t id) const {
6760  return detail::get_header_value(headers, key, def, id);
6761}
6762
6763size_t Request::get_header_value_count(const std::string &key) const {
6764  return detail::get_header_value_count(headers, key);
6765}
6766
6767void Request::set_header(const std::string &key,
6768                                const std::string &val) {
6769  detail::set_header(headers, key, val);
6770}
6771
6772bool Request::has_trailer(const std::string &key) const {
6773  return trailers.find(key) != trailers.end();
6774}
6775
6776std::string Request::get_trailer_value(const std::string &key,
6777                                              size_t id) const {
6778  return detail::get_multimap_value(trailers, key, id);
6779}
6780
6781size_t Request::get_trailer_value_count(const std::string &key) const {
6782  return trailers.count(key);
6783}
6784
6785bool Request::has_param(const std::string &key) const {
6786  return params.find(key) != params.end();
6787}
6788
6789std::string Request::get_param_value(const std::string &key,
6790                                            size_t id) const {
6791  return detail::get_multimap_value(params, key, id);
6792}
6793
6794std::vector<std::string>
6795Request::get_param_values(const std::string &key) const {
6796  auto rng = params.equal_range(key);
6797  std::vector<std::string> values;
6798  values.reserve(static_cast<size_t>(std::distance(rng.first, rng.second)));
6799  for (auto it = rng.first; it != rng.second; ++it) {
6800    values.push_back(it->second);
6801  }
6802  return values;
6803}
6804
6805size_t Request::get_param_value_count(const std::string &key) const {
6806  return params.count(key);
6807}
6808
6809bool Request::is_multipart_form_data() const {
6810  const auto &content_type = get_header_value("Content-Type");
6811  return detail::extract_media_type(content_type) == "multipart/form-data";
6812}
6813
6814// Multipart FormData implementation
6815std::string MultipartFormData::get_field(const std::string &key,
6816                                                size_t id) const {
6817  auto rng = fields.equal_range(key);
6818  auto it = rng.first;
6819  std::advance(it, static_cast<ssize_t>(id));
6820  if (it != rng.second) { return it->second.content; }
6821  return std::string();
6822}
6823
6824std::vector<std::string>
6825MultipartFormData::get_fields(const std::string &key) const {
6826  std::vector<std::string> values;
6827  auto rng = fields.equal_range(key);
6828  for (auto it = rng.first; it != rng.second; it++) {
6829    values.push_back(it->second.content);
6830  }
6831  return values;
6832}
6833
6834bool MultipartFormData::has_field(const std::string &key) const {
6835  return fields.find(key) != fields.end();
6836}
6837
6838size_t MultipartFormData::get_field_count(const std::string &key) const {
6839  return fields.count(key);
6840}
6841
6842FormData MultipartFormData::get_file(const std::string &key,
6843                                            size_t id) const {
6844  return detail::get_multimap_value(files, key, id);
6845}
6846
6847std::vector<FormData>
6848MultipartFormData::get_files(const std::string &key) const {
6849  std::vector<FormData> values;
6850  auto rng = files.equal_range(key);
6851  for (auto it = rng.first; it != rng.second; it++) {
6852    values.push_back(it->second);
6853  }
6854  return values;
6855}
6856
6857bool MultipartFormData::has_file(const std::string &key) const {
6858  return files.find(key) != files.end();
6859}
6860
6861size_t MultipartFormData::get_file_count(const std::string &key) const {
6862  return files.count(key);
6863}
6864
6865// Multipart FormData writer implementation
6866bool is_valid_multipart_boundary(const std::string &boundary) {
6867  return detail::is_multipart_boundary_chars_valid(boundary);
6868}
6869
6870MultipartFormDataWriter::MultipartFormDataWriter()
6871    : boundary_(detail::make_multipart_data_boundary()) {}
6872
6873MultipartFormDataWriter::MultipartFormDataWriter(std::string boundary)
6874    : boundary_(std::move(boundary)) {}
6875
6876const std::string &MultipartFormDataWriter::boundary() const {
6877  return boundary_;
6878}
6879
6880std::string MultipartFormDataWriter::content_type() const {
6881  return detail::serialize_multipart_formdata_get_content_type(boundary_);
6882}
6883
6884std::string
6885MultipartFormDataWriter::serialize(const UploadFormDataItems &items) const {
6886  return detail::serialize_multipart_formdata(items, boundary_);
6887}
6888
6889size_t MultipartFormDataWriter::content_length(
6890    const UploadFormDataItems &items) const {
6891  return detail::get_multipart_content_length(items, boundary_);
6892}
6893
6894std::string
6895MultipartFormDataWriter::item_begin(const UploadFormData &item) const {
6896  return detail::serialize_multipart_formdata_item_begin(item, boundary_);
6897}
6898
6899std::string MultipartFormDataWriter::item_end() {
6900  return detail::serialize_multipart_formdata_item_end();
6901}
6902
6903std::string MultipartFormDataWriter::finish() const {
6904  return detail::serialize_multipart_formdata_finish(boundary_);
6905}
6906
6907// Response implementation
6908size_t Response::get_header_value_u64(const std::string &key, size_t def,
6909                                             size_t id) const {
6910  return detail::get_header_value_u64(headers, key, def, id);
6911}
6912
6913bool Response::has_header(const std::string &key) const {
6914  return headers.find(key) != headers.end();
6915}
6916
6917std::string Response::get_header_value(const std::string &key,
6918                                              const char *def,
6919                                              size_t id) const {
6920  return detail::get_header_value(headers, key, def, id);
6921}
6922
6923size_t Response::get_header_value_count(const std::string &key) const {
6924  return detail::get_header_value_count(headers, key);
6925}
6926
6927void Response::set_header(const std::string &key,
6928                                 const std::string &val) {
6929  detail::set_header(headers, key, val);
6930}
6931bool Response::has_trailer(const std::string &key) const {
6932  return trailers.find(key) != trailers.end();
6933}
6934
6935std::string Response::get_trailer_value(const std::string &key,
6936                                               size_t id) const {
6937  return detail::get_multimap_value(trailers, key, id);
6938}
6939
6940size_t Response::get_trailer_value_count(const std::string &key) const {
6941  return trailers.count(key);
6942}
6943
6944void Response::set_redirect(const std::string &url, int stat) {
6945  if (detail::fields::is_field_value(url)) {
6946    set_header("Location", url);
6947    if (300 <= stat && stat < 400) {
6948      this->status = stat;
6949    } else {
6950      this->status = StatusCode::Found_302;
6951    }
6952  }
6953}
6954
6955void Response::set_content(const char *s, size_t n,
6956                                  const std::string &content_type) {
6957  body.assign(s, n);
6958
6959  auto rng = headers.equal_range("Content-Type");
6960  headers.erase(rng.first, rng.second);
6961  set_header("Content-Type", content_type);
6962  content_coding_ = detail::EncodingType::None;
6963}
6964
6965void Response::set_content(const std::string &s,
6966                                  const std::string &content_type) {
6967  set_content(s.data(), s.size(), content_type);
6968}
6969
6970void Response::set_content(std::string &&s,
6971                                  const std::string &content_type) {
6972  body = std::move(s);
6973
6974  auto rng = headers.equal_range("Content-Type");
6975  headers.erase(rng.first, rng.second);
6976  set_header("Content-Type", content_type);
6977  content_coding_ = detail::EncodingType::None;
6978}
6979
6980void Response::set_content_provider(
6981    size_t in_length, const std::string &content_type, ContentProvider provider,
6982    ContentProviderResourceReleaser resource_releaser) {
6983  set_header("Content-Type", content_type);
6984  content_length_ = in_length;
6985  if (in_length > 0) { content_provider_ = std::move(provider); }
6986  content_provider_resource_releaser_ = std::move(resource_releaser);
6987  is_chunked_content_provider_ = false;
6988  content_coding_ = detail::EncodingType::None;
6989}
6990
6991void Response::set_content_provider(
6992    const std::string &content_type, ContentProviderWithoutLength provider,
6993    ContentProviderResourceReleaser resource_releaser) {
6994  set_header("Content-Type", content_type);
6995  content_length_ = 0;
6996  content_provider_ = detail::ContentProviderAdapter(std::move(provider));
6997  content_provider_resource_releaser_ = std::move(resource_releaser);
6998  is_chunked_content_provider_ = false;
6999  content_coding_ = detail::EncodingType::None;
7000}
7001
7002void Response::set_chunked_content_provider(
7003    const std::string &content_type, ContentProviderWithoutLength provider,
7004    ContentProviderResourceReleaser resource_releaser) {
7005  set_header("Content-Type", content_type);
7006  content_length_ = 0;
7007  content_provider_ = detail::ContentProviderAdapter(std::move(provider));
7008  content_provider_resource_releaser_ = std::move(resource_releaser);
7009  is_chunked_content_provider_ = true;
7010  content_coding_ = detail::EncodingType::None;
7011}
7012
7013void Response::set_file_content(const std::string &path,
7014                                       const std::string &content_type) {
7015  file_content_path_ = path;
7016  file_content_content_type_ = content_type;
7017}
7018
7019void Response::set_file_content(const std::string &path) {
7020  file_content_path_ = path;
7021}
7022
7023// Result implementation
7024size_t Result::get_request_header_value_u64(const std::string &key,
7025                                                   size_t def,
7026                                                   size_t id) const {
7027  return detail::get_header_value_u64(request_headers_, key, def, id);
7028}
7029
7030bool Result::has_request_header(const std::string &key) const {
7031  return request_headers_.find(key) != request_headers_.end();
7032}
7033
7034std::string Result::get_request_header_value(const std::string &key,
7035                                                    const char *def,
7036                                                    size_t id) const {
7037  return detail::get_header_value(request_headers_, key, def, id);
7038}
7039
7040size_t
7041Result::get_request_header_value_count(const std::string &key) const {
7042  return request_headers_.count(key);
7043}
7044
7045// Stream implementation
7046ssize_t Stream::write(const char *ptr) {
7047  return write(ptr, strlen(ptr));
7048}
7049
7050ssize_t Stream::write(const std::string &s) {
7051  return write(s.data(), s.size());
7052}
7053
7054// BodyReader implementation
7055ssize_t detail::BodyReader::read(char *buf, size_t len) {
7056  if (!stream) {
7057    last_error = Error::Connection;
7058    return -1;
7059  }
7060  if (eof) { return 0; }
7061
7062  if (!chunked) {
7063    // Content-Length based reading
7064    if (has_content_length && bytes_read >= content_length) {
7065      eof = true;
7066      return 0;
7067    }
7068
7069    auto to_read = len;
7070    if (has_content_length) {
7071      auto remaining = content_length - bytes_read;
7072      to_read = (std::min)(len, remaining);
7073    }
7074    auto n = stream->read(buf, to_read);
7075
7076    if (n < 0) {
7077      last_error = stream->get_error();
7078      if (last_error == Error::Success) { last_error = Error::Read; }
7079      eof = true;
7080      return n;
7081    }
7082    if (n == 0) {
7083      // Unexpected EOF before content_length
7084      last_error = stream->get_error();
7085      if (last_error == Error::Success) { last_error = Error::Read; }
7086      eof = true;
7087      return 0;
7088    }
7089
7090    bytes_read += static_cast<size_t>(n);
7091    if (has_content_length && bytes_read >= content_length) { eof = true; }
7092    if (payload_max_length > 0 && bytes_read > payload_max_length) {
7093      last_error = Error::ExceedMaxPayloadSize;
7094      eof = true;
7095      return -1;
7096    }
7097    return n;
7098  }
7099
7100  // Chunked transfer encoding: delegate to shared decoder instance.
7101  if (!chunked_decoder) { chunked_decoder.reset(new ChunkedDecoder(*stream)); }
7102
7103  size_t chunk_offset = 0;
7104  size_t chunk_total = 0;
7105  auto n = chunked_decoder->read_payload(buf, len, chunk_offset, chunk_total);
7106  if (n < 0) {
7107    last_error = stream->get_error();
7108    if (last_error == Error::Success) { last_error = Error::Read; }
7109    eof = true;
7110    return n;
7111  }
7112
7113  if (n == 0) {
7114    // Final chunk observed. Leave trailer parsing to the caller (StreamHandle).
7115    eof = true;
7116    return 0;
7117  }
7118
7119  bytes_read += static_cast<size_t>(n);
7120  if (payload_max_length > 0 && bytes_read > payload_max_length) {
7121    last_error = Error::ExceedMaxPayloadSize;
7122    eof = true;
7123    return -1;
7124  }
7125  return n;
7126}
7127
7128// ThreadPool implementation
7129ThreadPool::ThreadPool(size_t n, size_t max_n, size_t mqr,
7130                              time_t idle_timeout_sec)
7131    : base_thread_count_(n), max_queued_requests_(mqr),
7132      idle_timeout_sec_(idle_timeout_sec), idle_thread_count_(0),
7133      shutdown_(false) {
7134#ifndef CPPHTTPLIB_NO_EXCEPTIONS
7135  if (max_n != 0 && max_n < n) {
7136    std::string msg = "max_threads must be >= base_threads";
7137    throw std::invalid_argument(msg);
7138  }
7139#endif
7140  max_thread_count_ = max_n == 0 ? n : max_n;
7141  threads_.reserve(base_thread_count_);
7142#ifndef CPPHTTPLIB_NO_EXCEPTIONS
7143  try {
7144#endif
7145    for (size_t i = 0; i < base_thread_count_; i++) {
7146      threads_.emplace_back(std::thread([this]() { worker(false); }));
7147    }
7148#ifndef CPPHTTPLIB_NO_EXCEPTIONS
7149  } catch (...) {
7150    // If thread creation fails partway (e.g., pthread_create returns EAGAIN),
7151    // signal the workers we already spawned to exit and join them so the
7152    // vector destructor does not see joinable threads (which would call
7153    // std::terminate). Then rethrow so the caller learns of the failure.
7154    {
7155      std::unique_lock<std::mutex> lock(mutex_);
7156      shutdown_ = true;
7157    }
7158    cond_.notify_all();
7159    for (auto &t : threads_) {
7160      if (t.joinable()) { t.join(); }
7161    }
7162    throw;
7163  }
7164#endif
7165}
7166
7167bool ThreadPool::enqueue(std::function<void()> fn) {
7168  {
7169    std::unique_lock<std::mutex> lock(mutex_);
7170    if (shutdown_) { return false; }
7171    if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) {
7172      return false;
7173    }
7174    jobs_.push_back(std::move(fn));
7175
7176    // Spawn a dynamic thread if no idle threads and under max
7177    if (idle_thread_count_ == 0 &&
7178        threads_.size() + dynamic_threads_.size() < max_thread_count_) {
7179      cleanup_finished_threads();
7180      dynamic_threads_.emplace_back(std::thread([this]() { worker(true); }));
7181    }
7182  }
7183
7184  cond_.notify_one();
7185  return true;
7186}
7187
7188void ThreadPool::shutdown() {
7189  {
7190    std::unique_lock<std::mutex> lock(mutex_);
7191    shutdown_ = true;
7192  }
7193
7194  cond_.notify_all();
7195
7196  for (auto &t : threads_) {
7197    if (t.joinable()) { t.join(); }
7198  }
7199
7200  // Move dynamic_threads_ to a local list under the lock to avoid racing
7201  // with worker threads that call move_to_finished() concurrently.
7202  std::list<std::thread> remaining_dynamic;
7203  {
7204    std::unique_lock<std::mutex> lock(mutex_);
7205    remaining_dynamic = std::move(dynamic_threads_);
7206  }
7207  for (auto &t : remaining_dynamic) {
7208    if (t.joinable()) { t.join(); }
7209  }
7210
7211  std::unique_lock<std::mutex> lock(mutex_);
7212  cleanup_finished_threads();
7213}
7214
7215void ThreadPool::move_to_finished(std::thread::id id) {
7216  // Must be called with mutex_ held
7217  for (auto it = dynamic_threads_.begin(); it != dynamic_threads_.end(); ++it) {
7218    if (it->get_id() == id) {
7219      finished_threads_.push_back(std::move(*it));
7220      dynamic_threads_.erase(it);
7221      return;
7222    }
7223  }
7224}
7225
7226void ThreadPool::cleanup_finished_threads() {
7227  // Must be called with mutex_ held
7228  for (auto &t : finished_threads_) {
7229    if (t.joinable()) { t.join(); }
7230  }
7231  finished_threads_.clear();
7232}
7233
7234void ThreadPool::worker(bool is_dynamic) {
7235  for (;;) {
7236    std::function<void()> fn;
7237    {
7238      std::unique_lock<std::mutex> lock(mutex_);
7239      idle_thread_count_++;
7240
7241      if (is_dynamic) {
7242        auto has_work =
7243            cond_.wait_for(lock, std::chrono::seconds(idle_timeout_sec_),
7244                           [&] { return !jobs_.empty() || shutdown_; });
7245        if (!has_work) {
7246          // Timed out with no work - exit this dynamic thread
7247          idle_thread_count_--;
7248          move_to_finished(std::this_thread::get_id());
7249          break;
7250        }
7251      } else {
7252        cond_.wait(lock, [&] { return !jobs_.empty() || shutdown_; });
7253      }
7254
7255      idle_thread_count_--;
7256
7257      if (shutdown_ && jobs_.empty()) { break; }
7258
7259      fn = std::move(jobs_.front());
7260      jobs_.pop_front();
7261    }
7262
7263    assert(true == static_cast<bool>(fn));
7264    fn();
7265  }
7266
7267#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) &&   \
7268    !defined(LIBRESSL_VERSION_NUMBER)
7269  OPENSSL_thread_stop();
7270#endif
7271}
7272
7273/*
7274 * Group 1 (continued): detail namespace - Stream implementations
7275 */
7276
7277namespace detail {
7278
7279void calc_actual_timeout(time_t max_timeout_msec, time_t duration_msec,
7280                                time_t timeout_sec, time_t timeout_usec,
7281                                time_t &actual_timeout_sec,
7282                                time_t &actual_timeout_usec) {
7283  auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000);
7284
7285  auto actual_timeout_msec =
7286      (std::min)(max_timeout_msec - duration_msec, timeout_msec);
7287
7288  if (actual_timeout_msec < 0) { actual_timeout_msec = 0; }
7289
7290  actual_timeout_sec = actual_timeout_msec / 1000;
7291  actual_timeout_usec = (actual_timeout_msec % 1000) * 1000;
7292}
7293
7294// Socket stream implementation
7295SocketStream::SocketStream(
7296    socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
7297    time_t write_timeout_sec, time_t write_timeout_usec,
7298    time_t max_timeout_msec,
7299    std::chrono::time_point<std::chrono::steady_clock> start_time)
7300    : sock_(sock), read_timeout_sec_(read_timeout_sec),
7301      read_timeout_usec_(read_timeout_usec),
7302      write_timeout_sec_(write_timeout_sec),
7303      write_timeout_usec_(write_timeout_usec),
7304      max_timeout_msec_(max_timeout_msec), start_time_(start_time),
7305      read_buff_(read_buff_size_, 0) {}
7306
7307SocketStream::~SocketStream() = default;
7308
7309bool SocketStream::is_readable() const {
7310  return read_buff_off_ < read_buff_content_size_;
7311}
7312
7313bool SocketStream::wait_readable() const {
7314  if (max_timeout_msec_ <= 0) {
7315    return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
7316  }
7317
7318  time_t read_timeout_sec;
7319  time_t read_timeout_usec;
7320  calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
7321                      read_timeout_usec_, read_timeout_sec, read_timeout_usec);
7322
7323  return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
7324}
7325
7326bool SocketStream::wait_writable() const {
7327  return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
7328}
7329
7330bool SocketStream::ensure_readable() {
7331  if (readable_hint_) {
7332    readable_hint_ = false;
7333    return true;
7334  }
7335  return wait_readable();
7336}
7337
7338const char *SocketStream::buffered_data(size_t &size) const {
7339  size = read_buff_content_size_ - read_buff_off_;
7340  return size ? read_buff_.data() + read_buff_off_ : nullptr;
7341}
7342
7343void SocketStream::consume_buffered(size_t size) {
7344  assert(size <= read_buff_content_size_ - read_buff_off_);
7345  read_buff_off_ += size;
7346}
7347
7348bool SocketStream::is_peer_alive() const {
7349  return detail::is_socket_alive(sock_);
7350}
7351
7352ssize_t SocketStream::read(char *ptr, size_t size) {
7353#ifdef _WIN32
7354  size =
7355      (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
7356#else
7357  size = (std::min)(size,
7358                    static_cast<size_t>((std::numeric_limits<ssize_t>::max)()));
7359#endif
7360
7361  if (read_buff_off_ < read_buff_content_size_) {
7362    auto remaining_size = read_buff_content_size_ - read_buff_off_;
7363    if (size <= remaining_size) {
7364      memcpy(ptr, read_buff_.data() + read_buff_off_, size);
7365      read_buff_off_ += size;
7366      return static_cast<ssize_t>(size);
7367    } else {
7368      memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size);
7369      read_buff_off_ += remaining_size;
7370      return static_cast<ssize_t>(remaining_size);
7371    }
7372  }
7373
7374  if (!ensure_readable()) {
7375    error_ = Error::Timeout;
7376    return -1;
7377  }
7378
7379  read_buff_off_ = 0;
7380  read_buff_content_size_ = 0;
7381
7382  if (size < read_buff_size_) {
7383    auto n = read_socket(sock_, read_buff_.data(), read_buff_size_,
7384                         CPPHTTPLIB_RECV_FLAGS);
7385    if (n <= 0) {
7386      if (n == 0) {
7387        error_ = Error::ConnectionClosed;
7388      } else {
7389        error_ = Error::Read;
7390      }
7391      return n;
7392    } else if (n <= static_cast<ssize_t>(size)) {
7393      memcpy(ptr, read_buff_.data(), static_cast<size_t>(n));
7394      return n;
7395    } else {
7396      memcpy(ptr, read_buff_.data(), size);
7397      read_buff_off_ = size;
7398      read_buff_content_size_ = static_cast<size_t>(n);
7399      return static_cast<ssize_t>(size);
7400    }
7401  } else {
7402    auto n = read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS);
7403    if (n <= 0) {
7404      if (n == 0) {
7405        error_ = Error::ConnectionClosed;
7406      } else {
7407        error_ = Error::Read;
7408      }
7409    }
7410    return n;
7411  }
7412}
7413
7414ssize_t SocketStream::write(const char *ptr, size_t size) {
7415  if (!wait_writable()) { return -1; }
7416
7417#if defined(_WIN32) && !defined(_WIN64)
7418  size =
7419      (std::min)(size, static_cast<size_t>((std::numeric_limits<int>::max)()));
7420#endif
7421
7422  return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS);
7423}
7424
7425void SocketStream::get_remote_ip_and_port(std::string &ip,
7426                                                 int &port) const {
7427  return detail::get_remote_ip_and_port(sock_, ip, port);
7428}
7429
7430void SocketStream::get_local_ip_and_port(std::string &ip,
7431                                                int &port) const {
7432  return detail::get_local_ip_and_port(sock_, ip, port);
7433}
7434
7435socket_t SocketStream::socket() const { return sock_; }
7436
7437time_t SocketStream::duration() const {
7438  return std::chrono::duration_cast<std::chrono::milliseconds>(
7439             std::chrono::steady_clock::now() - start_time_)
7440      .count();
7441}
7442
7443void SocketStream::set_read_timeout(time_t sec, time_t usec) {
7444  read_timeout_sec_ = sec;
7445  read_timeout_usec_ = usec;
7446}
7447
7448// Buffer stream implementation
7449bool BufferStream::is_readable() const { return true; }
7450
7451bool BufferStream::wait_readable() const { return true; }
7452
7453bool BufferStream::wait_writable() const { return true; }
7454
7455ssize_t BufferStream::read(char *ptr, size_t size) {
7456#if defined(_MSC_VER) && _MSC_VER < 1910
7457  auto len_read = buffer._Copy_s(ptr, size, size, position);
7458#else
7459  auto len_read = buffer.copy(ptr, size, position);
7460#endif
7461  position += static_cast<size_t>(len_read);
7462  return static_cast<ssize_t>(len_read);
7463}
7464
7465ssize_t BufferStream::write(const char *ptr, size_t size) {
7466  buffer.append(ptr, size);
7467  return static_cast<ssize_t>(size);
7468}
7469
7470void BufferStream::get_remote_ip_and_port(std::string & /*ip*/,
7471                                                 int & /*port*/) const {}
7472
7473void BufferStream::get_local_ip_and_port(std::string & /*ip*/,
7474                                                int & /*port*/) const {}
7475
7476socket_t BufferStream::socket() const { return 0; }
7477
7478time_t BufferStream::duration() const { return 0; }
7479
7480const std::string &BufferStream::get_buffer() const { return buffer; }
7481
7482PathParamsMatcher::PathParamsMatcher(const std::string &pattern)
7483    : MatcherBase(pattern) {
7484  constexpr const char marker[] = "/:";
7485
7486  // One past the last ending position of a path param substring
7487  std::size_t last_param_end = 0;
7488
7489#ifndef CPPHTTPLIB_NO_EXCEPTIONS
7490  // Needed to ensure that parameter names are unique during matcher
7491  // construction
7492  // If exceptions are disabled, only last duplicate path
7493  // parameter will be set
7494  std::unordered_set<std::string> param_name_set;
7495#endif
7496
7497  while (true) {
7498    const auto marker_pos = pattern.find(
7499        marker, last_param_end == 0 ? last_param_end : last_param_end - 1);
7500    if (marker_pos == std::string::npos) { break; }
7501
7502    static_fragments_.push_back(
7503        pattern.substr(last_param_end, marker_pos - last_param_end + 1));
7504
7505    const auto param_name_start = marker_pos + str_len(marker);
7506
7507    auto sep_pos = pattern.find(separator, param_name_start);
7508    if (sep_pos == std::string::npos) { sep_pos = pattern.length(); }
7509
7510    auto param_name =
7511        pattern.substr(param_name_start, sep_pos - param_name_start);
7512
7513#ifndef CPPHTTPLIB_NO_EXCEPTIONS
7514    if (param_name_set.find(param_name) != param_name_set.cend()) {
7515      std::string msg = "Encountered path parameter '" + param_name +
7516                        "' multiple times in route pattern '" + pattern + "'.";
7517      throw std::invalid_argument(msg);
7518    }
7519#endif
7520
7521    param_names_.push_back(std::move(param_name));
7522
7523    last_param_end = sep_pos + 1;
7524  }
7525
7526  if (last_param_end < pattern.length()) {
7527    static_fragments_.push_back(pattern.substr(last_param_end));
7528  }
7529}
7530
7531bool PathParamsMatcher::match(Request &request) const {
7532  request.matches = std::smatch();
7533  request.path_params.clear();
7534
7535  // A pattern without parameters is just a literal path to compare against
7536  if (param_names_.empty()) { return request.path == pattern(); }
7537
7538  request.path_params.reserve(param_names_.size());
7539
7540  // One past the position at which the path matched the pattern last time
7541  std::size_t starting_pos = 0;
7542  for (size_t i = 0; i < static_fragments_.size(); ++i) {
7543    const auto &fragment = static_fragments_[i];
7544
7545    if (starting_pos + fragment.length() > request.path.length()) {
7546      return false;
7547    }
7548
7549    // Avoid unnecessary allocation by using strncmp instead of substr +
7550    // comparison
7551    if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(),
7552                     fragment.length()) != 0) {
7553      return false;
7554    }
7555
7556    starting_pos += fragment.length();
7557
7558    // Should only happen when we have a static fragment after a param
7559    // Example: '/users/:id/subscriptions'
7560    // The 'subscriptions' fragment here does not have a corresponding param
7561    if (i >= param_names_.size()) { continue; }
7562
7563    auto sep_pos = request.path.find(separator, starting_pos);
7564    if (sep_pos == std::string::npos) { sep_pos = request.path.length(); }
7565
7566    const auto &param_name = param_names_[i];
7567
7568    request.path_params.emplace(
7569        param_name, request.path.substr(starting_pos, sep_pos - starting_pos));
7570
7571    // Mark everything up to '/' as matched
7572    starting_pos = sep_pos + 1;
7573  }
7574  // Returns false if the path is longer than the pattern
7575  return starting_pos >= request.path.length();
7576}
7577
7578bool RegexMatcher::match(Request &request) const {
7579  request.path_params.clear();
7580  // See CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH: an overlong path is treated as
7581  // a non-match rather than risking a stack overflow in std::regex_match.
7582  if (request.path.length() > CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH) {
7583    return false;
7584  }
7585  return std::regex_match(request.path, request.matches, regex_);
7586}
7587
7588// Enclose IPv6 address in brackets if needed
7589std::string prepare_host_string(const std::string &host) {
7590  // Enclose IPv6 address in brackets (but not if already enclosed)
7591  if (host.find(':') == std::string::npos ||
7592      (!host.empty() && host[0] == '[')) {
7593    // IPv4, hostname, or already bracketed IPv6
7594    return host;
7595  } else {
7596    // IPv6 address without brackets
7597    return "[" + host + "]";
7598  }
7599}
7600
7601std::string make_host_and_port_string(const std::string &host, int port,
7602                                             bool is_ssl) {
7603  auto result = prepare_host_string(host);
7604
7605  // Append port if not default
7606  if ((!is_ssl && port == 80) || (is_ssl && port == 443)) {
7607    ; // do nothing
7608  } else {
7609    result += ":" + std::to_string(port);
7610  }
7611
7612  return result;
7613}
7614
7615// Create "host:port" string always including port number (for CONNECT method)
7616std::string
7617make_host_and_port_string_always_port(const std::string &host, int port) {
7618  return prepare_host_string(host) + ":" + std::to_string(port);
7619}
7620
7621// Value for the Host header a client sends when the caller supplied none.
7622// Only the value: callers decide where in their header list it goes.
7623std::string make_default_host_header_value(const std::string &host,
7624                                                  int port, bool is_ssl,
7625                                                  int address_family) {
7626  if (address_family == AF_UNIX) { return "localhost"; }
7627  return make_host_and_port_string(host, port, is_ssl);
7628}
7629
7630void add_default_user_agent_header(Request &req) {
7631#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT
7632  if (!req.has_header("User-Agent")) {
7633    req.set_header("User-Agent",
7634                   std::string("cpp-httplib/") + CPPHTTPLIB_VERSION);
7635  }
7636#else
7637  (void)req;
7638#endif
7639}
7640
7641bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out);
7642NormalizedTarget normalize_target(const std::string &host);
7643bool ip_in_cidr(const IPBytes &ip, const IPBytes &net, int prefix_bits);
7644bool host_matches_no_proxy(const NormalizedTarget &target,
7645                           const std::vector<NoProxyEntry> &entries);
7646
7647bool ip_in_cidr(const IPBytes &ip, const IPBytes &net, int prefix_bits) {
7648  if (prefix_bits < 0 || prefix_bits > 128) { return false; }
7649  if (prefix_bits == 0) { return true; }
7650  int full_bytes = prefix_bits / 8;
7651  int rem_bits = prefix_bits % 8;
7652  if (full_bytes > 0 && std::memcmp(ip.data(), net.data(),
7653                                    static_cast<size_t>(full_bytes)) != 0) {
7654    return false;
7655  }
7656  if (rem_bits == 0) { return true; }
7657  auto i = static_cast<size_t>(full_bytes);
7658  auto mask = static_cast<uint8_t>(0xFFu << (8 - rem_bits));
7659  return (ip[i] & mask) == (net[i] & mask);
7660}
7661
7662bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out) {
7663  if (token.empty()) { return false; }
7664
7665  if (token == "*") {
7666    out.kind = NoProxyKind::Wildcard;
7667    return true;
7668  }
7669
7670  auto slash = token.find('/');
7671  std::string addr_part =
7672      (slash == std::string::npos) ? token : token.substr(0, slash);
7673  std::string prefix_part =
7674      (slash == std::string::npos) ? std::string() : token.substr(slash + 1);
7675
7676  // A bare slash or trailing-slash CIDR like "10.0.0.0/" is malformed;
7677  // don't silently treat it as a /32 (or /128).
7678  if (slash != std::string::npos && prefix_part.empty()) { return false; }
7679
7680  // Accept the bracketed IPv6 form ("[::1]", "[fe80::]/10") as well as the
7681  // bare form. Brackets have no meaning for IPv4, so skip the IPv4 attempt
7682  // when brackets are present.
7683  bool bracketed = addr_part.size() >= 2 && addr_part.front() == '[' &&
7684                   addr_part.back() == ']';
7685  if (bracketed) { addr_part = addr_part.substr(1, addr_part.size() - 2); }
7686
7687  if (!bracketed) {
7688    struct in_addr v4;
7689    if (inet_pton(AF_INET, addr_part.c_str(), &v4) == 1) {
7690      int prefix = 32;
7691      if (!prefix_part.empty()) {
7692        auto r = from_chars(prefix_part.data(),
7693                            prefix_part.data() + prefix_part.size(), prefix);
7694        if (r.ec != std::errc{} ||
7695            r.ptr != prefix_part.data() + prefix_part.size()) {
7696          return false;
7697        }
7698        if (prefix < 0 || prefix > 32) { return false; }
7699      }
7700      out.kind = NoProxyKind::IPv4Cidr;
7701      std::memcpy(out.net.data(), &v4, sizeof(v4));
7702      out.prefix_bits = prefix;
7703      return true;
7704    }
7705  }
7706
7707  struct in6_addr v6;
7708  if (inet_pton(AF_INET6, addr_part.c_str(), &v6) == 1) {
7709    int prefix = 128;
7710    if (!prefix_part.empty()) {
7711      auto r = from_chars(prefix_part.data(),
7712                          prefix_part.data() + prefix_part.size(), prefix);
7713      if (r.ec != std::errc{} ||
7714          r.ptr != prefix_part.data() + prefix_part.size()) {
7715        return false;
7716      }
7717      if (prefix < 0 || prefix > 128) { return false; }
7718    }
7719    out.kind = NoProxyKind::IPv6Cidr;
7720    std::memcpy(out.net.data(), &v6, sizeof(v6));
7721    out.prefix_bits = prefix;
7722    return true;
7723  }
7724
7725  // Bracketed entries can only be IPv6. If the IPv6 parse above failed,
7726  // the entry is malformed — don't fall through to the hostname branch.
7727  if (bracketed) { return false; }
7728
7729  // A '/' on a non-IP token means a CIDR prefix without an address. Reject.
7730  if (slash != std::string::npos) { return false; }
7731  // Port-specific entries (host:port) are not supported.
7732  if (token.find(':') != std::string::npos) { return false; }
7733
7734  std::string hostname = case_ignore::to_lower(token);
7735  while (!hostname.empty() && hostname.front() == '.') {
7736    hostname.erase(hostname.begin());
7737  }
7738  while (!hostname.empty() && hostname.back() == '.') {
7739    hostname.pop_back();
7740  }
7741  if (hostname.empty()) { return false; }
7742
7743  out.kind = NoProxyKind::HostnameSuffix;
7744  out.hostname_pattern = std::move(hostname);
7745  return true;
7746}
7747
7748NormalizedTarget normalize_target(const std::string &host) {
7749  NormalizedTarget t;
7750  std::string h = host;
7751
7752  if (h.size() >= 2 && h.front() == '[' && h.back() == ']') {
7753    h = h.substr(1, h.size() - 2);
7754  }
7755
7756  // Strip a single trailing dot so "example.com." canonicalizes to
7757  // "example.com".
7758  if (!h.empty() && h.back() == '.') { h.pop_back(); }
7759
7760  t.hostname = case_ignore::to_lower(h);
7761
7762  if (!t.hostname.empty()) {
7763    struct in_addr v4;
7764    struct in6_addr v6;
7765    if (inet_pton(AF_INET, t.hostname.c_str(), &v4) == 1) {
7766      t.is_ipv4 = true;
7767      std::memcpy(t.ip.data(), &v4, sizeof(v4));
7768    } else if (inet_pton(AF_INET6, t.hostname.c_str(), &v6) == 1) {
7769      t.is_ipv6 = true;
7770      std::memcpy(t.ip.data(), &v6, sizeof(v6));
7771    }
7772  }
7773  return t;
7774}
7775
7776bool host_matches_no_proxy(const NormalizedTarget &target,
7777                                  const std::vector<NoProxyEntry> &entries) {
7778  if (target.hostname.empty()) { return false; }
7779  for (const auto &e : entries) {
7780    switch (e.kind) {
7781    case NoProxyKind::Wildcard: return true;
7782    case NoProxyKind::IPv4Cidr:
7783      if (target.is_ipv4 && ip_in_cidr(target.ip, e.net, e.prefix_bits)) {
7784        return true;
7785      }
7786      break;
7787    case NoProxyKind::IPv6Cidr:
7788      if (target.is_ipv6 && ip_in_cidr(target.ip, e.net, e.prefix_bits)) {
7789        return true;
7790      }
7791      break;
7792    case NoProxyKind::HostnameSuffix:
7793      if (target.is_ipv4 || target.is_ipv6) { break; }
7794      if (target.hostname == e.hostname_pattern) { return true; }
7795      // Dot-boundary suffix match: prevents "evilexample.com" from matching
7796      // an entry of "example.com".
7797      if (target.hostname.size() > e.hostname_pattern.size() + 1) {
7798        auto offset = target.hostname.size() - e.hostname_pattern.size();
7799        if (target.hostname[offset - 1] == '.' &&
7800            target.hostname.compare(offset, e.hostname_pattern.size(),
7801                                    e.hostname_pattern) == 0) {
7802          return true;
7803        }
7804      }
7805      break;
7806    }
7807  }
7808  return false;
7809}
7810
7811template <typename T>
7812bool check_and_write_headers(Stream &strm, Headers &headers,
7813                                    T header_writer, Error &error) {
7814  for (const auto &h : headers) {
7815    if (!detail::fields::is_field_valid(h.first, h.second)) {
7816      error = Error::InvalidHeaders;
7817      return false;
7818    }
7819  }
7820  if (header_writer(strm, headers) <= 0) {
7821    error = Error::Write;
7822    return false;
7823  }
7824  return true;
7825}
7826
7827} // namespace detail
7828
7829/*
7830 * Group 2 (continued): detail namespace - SSLSocketStream implementation
7831 */
7832
7833#ifdef CPPHTTPLIB_SSL_ENABLED
7834namespace detail {
7835
7836// SSL socket stream implementation
7837SSLSocketStream::SSLSocketStream(
7838    socket_t sock, tls::session_t session, time_t read_timeout_sec,
7839    time_t read_timeout_usec, time_t write_timeout_sec,
7840    time_t write_timeout_usec, time_t max_timeout_msec,
7841    std::chrono::time_point<std::chrono::steady_clock> start_time)
7842    : sock_(sock), session_(session), read_timeout_sec_(read_timeout_sec),
7843      read_timeout_usec_(read_timeout_usec),
7844      write_timeout_sec_(write_timeout_sec),
7845      write_timeout_usec_(write_timeout_usec),
7846      max_timeout_msec_(max_timeout_msec), start_time_(start_time) {
7847#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
7848  // Clear AUTO_RETRY for proper non-blocking I/O timeout handling
7849  // Note: create_session() also clears this, but SSLClient currently
7850  // uses ssl_new() which does not. Until full TLS API migration is complete,
7851  // we need to ensure AUTO_RETRY is cleared here regardless of how the
7852  // SSL session was created.
7853  SSL_clear_mode(static_cast<SSL *>(session), SSL_MODE_AUTO_RETRY);
7854#endif
7855}
7856
7857SSLSocketStream::~SSLSocketStream() = default;
7858
7859bool SSLSocketStream::is_readable() const {
7860  return tls::pending(session_) > 0;
7861}
7862
7863bool SSLSocketStream::wait_readable() const {
7864  if (max_timeout_msec_ <= 0) {
7865    return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
7866  }
7867
7868  time_t read_timeout_sec;
7869  time_t read_timeout_usec;
7870  calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_,
7871                      read_timeout_usec_, read_timeout_sec, read_timeout_usec);
7872
7873  return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0;
7874}
7875
7876bool SSLSocketStream::wait_writable() const {
7877  return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 &&
7878         !tls::is_peer_closed(session_, sock_);
7879}
7880
7881bool SSLSocketStream::ensure_readable() {
7882  if (readable_hint_) {
7883    readable_hint_ = false;
7884    return true;
7885  }
7886  return wait_readable();
7887}
7888
7889bool SSLSocketStream::is_peer_alive() const {
7890  return !tls::is_peer_closed(session_, sock_);
7891}
7892
7893ssize_t SSLSocketStream::read(char *ptr, size_t size) {
7894  if (tls::pending(session_) > 0) {
7895    tls::TlsError err;
7896    auto ret = tls::read(session_, ptr, size, err);
7897    if (ret == 0 || err.code == tls::ErrorCode::PeerClosed) {
7898      error_ = Error::ConnectionClosed;
7899    }
7900    return ret;
7901  } else if (ensure_readable()) {
7902    tls::TlsError err;
7903    auto ret = tls::read(session_, ptr, size, err);
7904    if (ret < 0) {
7905      auto n = 1000;
7906#ifdef _WIN32
7907      while (--n >= 0 && (err.code == tls::ErrorCode::WantRead ||
7908                          (err.code == tls::ErrorCode::SyscallError &&
7909                           WSAGetLastError() == WSAETIMEDOUT))) {
7910#else
7911      while (--n >= 0 && err.code == tls::ErrorCode::WantRead) {
7912#endif
7913        if (tls::pending(session_) > 0) {
7914          return tls::read(session_, ptr, size, err);
7915        } else if (wait_readable()) {
7916          std::this_thread::sleep_for(std::chrono::microseconds{10});
7917          ret = tls::read(session_, ptr, size, err);
7918          if (ret >= 0) { return ret; }
7919        } else {
7920          break;
7921        }
7922      }
7923      assert(ret < 0);
7924    } else if (ret == 0 || err.code == tls::ErrorCode::PeerClosed) {
7925      error_ = Error::ConnectionClosed;
7926    }
7927    return ret;
7928  } else {
7929    error_ = Error::Timeout;
7930    return -1;
7931  }
7932}
7933
7934ssize_t SSLSocketStream::write(const char *ptr, size_t size) {
7935  if (wait_writable()) {
7936    auto handle_size =
7937        std::min<size_t>(size, (std::numeric_limits<int>::max)());
7938
7939    tls::TlsError err;
7940    auto ret = tls::write(session_, ptr, handle_size, err);
7941    if (ret < 0) {
7942      auto n = 1000;
7943#ifdef _WIN32
7944      while (--n >= 0 && (err.code == tls::ErrorCode::WantWrite ||
7945                          (err.code == tls::ErrorCode::SyscallError &&
7946                           WSAGetLastError() == WSAETIMEDOUT))) {
7947#else
7948      while (--n >= 0 && err.code == tls::ErrorCode::WantWrite) {
7949#endif
7950        if (wait_writable()) {
7951          std::this_thread::sleep_for(std::chrono::microseconds{10});
7952          ret = tls::write(session_, ptr, handle_size, err);
7953          if (ret >= 0) { return ret; }
7954        } else {
7955          break;
7956        }
7957      }
7958      assert(ret < 0);
7959    }
7960    return ret;
7961  }
7962  return -1;
7963}
7964
7965void SSLSocketStream::get_remote_ip_and_port(std::string &ip,
7966                                                    int &port) const {
7967  detail::get_remote_ip_and_port(sock_, ip, port);
7968}
7969
7970void SSLSocketStream::get_local_ip_and_port(std::string &ip,
7971                                                   int &port) const {
7972  detail::get_local_ip_and_port(sock_, ip, port);
7973}
7974
7975socket_t SSLSocketStream::socket() const { return sock_; }
7976
7977time_t SSLSocketStream::duration() const {
7978  return std::chrono::duration_cast<std::chrono::milliseconds>(
7979             std::chrono::steady_clock::now() - start_time_)
7980      .count();
7981}
7982
7983void SSLSocketStream::set_read_timeout(time_t sec, time_t usec) {
7984  read_timeout_sec_ = sec;
7985  read_timeout_usec_ = usec;
7986}
7987
7988WebSocketSSLStream::WebSocketSSLStream(socket_t sock,
7989                                              tls::session_t session,
7990                                              time_t read_timeout_sec,
7991                                              time_t read_timeout_usec,
7992                                              time_t write_timeout_sec,
7993                                              time_t write_timeout_usec)
7994    : sock_(sock), session_(session), read_timeout_sec_(read_timeout_sec),
7995      read_timeout_usec_(read_timeout_usec),
7996      write_timeout_sec_(write_timeout_sec),
7997      write_timeout_usec_(write_timeout_usec),
7998      start_time_(std::chrono::steady_clock::now()) {
7999  // The receive and send paths run on different threads, so each TLS call is
8000  // driven in non-blocking mode and readiness is awaited with select()
8001  // outside the session lock. Set the socket non-blocking once here; it is
8002  // never flipped back, so no thread races on the flag.
8003  detail::set_nonblocking(sock_, true);
8004#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
8005  SSL_clear_mode(static_cast<SSL *>(session_), SSL_MODE_AUTO_RETRY);
8006#endif
8007}
8008
8009WebSocketSSLStream::~WebSocketSSLStream() = default;
8010
8011bool WebSocketSSLStream::is_readable() const {
8012  std::lock_guard<std::mutex> guard(session_mutex_);
8013  return tls::pending(session_) > 0;
8014}
8015
8016bool WebSocketSSLStream::wait_readable() const {
8017  return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0;
8018}
8019
8020bool WebSocketSSLStream::wait_writable() const {
8021  // Unlike SSLSocketStream, this deliberately does not call is_peer_closed():
8022  // that probe toggles the socket's blocking flag, which would race with the
8023  // concurrent reader on a permanently non-blocking socket.
8024  return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0;
8025}
8026
8027ssize_t WebSocketSSLStream::read(char *ptr, size_t size) {
8028  tls::TlsError err;
8029  auto n = 1000;
8030  while (--n >= 0) {
8031    {
8032      std::lock_guard<std::mutex> guard(session_mutex_);
8033      auto ret = tls::read(session_, ptr, size, err);
8034      if (ret > 0) { return ret; }
8035      if (ret == 0 || err.code == tls::ErrorCode::PeerClosed) {
8036        error_ = Error::ConnectionClosed;
8037        return ret;
8038      }
8039    }
8040    // ret < 0. On a non-blocking socket a TLS read can stop needing either
8041    // direction: the send path shares this session, so output it left pending
8042    // has to be flushed before more input can be decrypted. Anything else is
8043    // a hard error.
8044    auto needs_readable = err.code == tls::ErrorCode::WantRead;
8045#ifdef _WIN32
8046    // On Windows a socket timeout surfaces as a syscall error, not WantRead.
8047    needs_readable =
8048        needs_readable || (err.code == tls::ErrorCode::SyscallError &&
8049                           WSAGetLastError() == WSAETIMEDOUT);
8050#endif
8051    if (!needs_readable && err.code != tls::ErrorCode::WantWrite) {
8052      error_ = Error::Read;
8053      return -1;
8054    }
8055    if (!(needs_readable ? wait_readable() : wait_writable())) {
8056      error_ = Error::Timeout;
8057      return -1;
8058    }
8059  }
8060  // Out of retries. Recording a reason matters: a caller that reads get_error()
8061  // to tell a timeout from a close would otherwise see whatever the previous
8062  // failure left behind (error_ is never cleared on success).
8063  error_ = Error::Read;
8064  return -1;
8065}
8066
8067ssize_t WebSocketSSLStream::write(const char *ptr, size_t size) {
8068  auto handle_size = std::min<size_t>(size, (std::numeric_limits<int>::max)());
8069  tls::TlsError err;
8070  auto n = 1000;
8071  while (--n >= 0) {
8072    {
8073      std::lock_guard<std::mutex> guard(session_mutex_);
8074      auto ret = tls::write(session_, ptr, handle_size, err);
8075      if (ret >= 0) { return ret; }
8076    }
8077    // ret < 0. As in read(), either direction can be needed: a renegotiation
8078    // or a post-handshake message must be consumed before the record goes
8079    // out. Anything else is a hard error.
8080    auto needs_writable = err.code == tls::ErrorCode::WantWrite;
8081#ifdef _WIN32
8082    // On Windows a socket timeout surfaces as a syscall error, not WantWrite.
8083    needs_writable =
8084        needs_writable || (err.code == tls::ErrorCode::SyscallError &&
8085                           WSAGetLastError() == WSAETIMEDOUT);
8086#endif
8087    if (!needs_writable && err.code != tls::ErrorCode::WantRead) { return -1; }
8088    if (!(needs_writable ? wait_writable() : wait_readable())) { return -1; }
8089  }
8090  return -1;
8091}
8092
8093void WebSocketSSLStream::get_remote_ip_and_port(std::string &ip,
8094                                                       int &port) const {
8095  detail::get_remote_ip_and_port(sock_, ip, port);
8096}
8097
8098void WebSocketSSLStream::get_local_ip_and_port(std::string &ip,
8099                                                      int &port) const {
8100  detail::get_local_ip_and_port(sock_, ip, port);
8101}
8102
8103socket_t WebSocketSSLStream::socket() const { return sock_; }
8104
8105time_t WebSocketSSLStream::duration() const {
8106  return std::chrono::duration_cast<std::chrono::milliseconds>(
8107             std::chrono::steady_clock::now() - start_time_)
8108      .count();
8109}
8110
8111void WebSocketSSLStream::set_read_timeout(time_t sec, time_t usec) {
8112  read_timeout_sec_ = sec;
8113  read_timeout_usec_ = usec;
8114}
8115
8116} // namespace detail
8117#endif // CPPHTTPLIB_SSL_ENABLED
8118
8119/*
8120 * Group 4: Server implementation
8121 */
8122
8123// HTTP server implementation
8124Server::Server()
8125    : new_task_queue([] {
8126        return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT,
8127                              CPPHTTPLIB_THREAD_POOL_MAX_COUNT);
8128      }) {
8129#ifndef _WIN32
8130  signal(SIGPIPE, SIG_IGN);
8131#endif
8132}
8133
8134Server::~Server() = default;
8135
8136std::unique_ptr<detail::MatcherBase>
8137Server::make_matcher(const std::string &pattern) {
8138  // Path params take precedence, so "/users/:id/(.*)" keeps being matched as
8139  // a path params pattern
8140  if (pattern.find("/:") != std::string::npos) {
8141    return detail::make_unique<detail::PathParamsMatcher>(pattern);
8142  }
8143
8144  // A pattern with no regex metacharacter only has to be compared literally,
8145  // which is what PathParamsMatcher already does when it captures no
8146  // parameter, so std::regex is only worth building for the patterns that
8147  // actually need it
8148  if (pattern.find_first_of(".^$|()[]{}*+?\\") == std::string::npos) {
8149    return detail::make_unique<detail::PathParamsMatcher>(pattern);
8150  }
8151
8152  return detail::make_unique<detail::RegexMatcher>(pattern);
8153}
8154
8155Server &Server::Get(const std::string &pattern, Handler handler) {
8156  return add_handler(get_handlers_, pattern, std::move(handler));
8157}
8158
8159Server &Server::Post(const std::string &pattern, Handler handler) {
8160  return add_handler(post_handlers_, pattern, std::move(handler));
8161}
8162
8163Server &Server::Post(const std::string &pattern,
8164                            HandlerWithContentReader handler) {
8165  return add_handler(post_handlers_for_content_reader_, pattern,
8166                     std::move(handler));
8167}
8168
8169Server &Server::Put(const std::string &pattern, Handler handler) {
8170  return add_handler(put_handlers_, pattern, std::move(handler));
8171}
8172
8173Server &Server::Put(const std::string &pattern,
8174                           HandlerWithContentReader handler) {
8175  return add_handler(put_handlers_for_content_reader_, pattern,
8176                     std::move(handler));
8177}
8178
8179Server &Server::Patch(const std::string &pattern, Handler handler) {
8180  return add_handler(patch_handlers_, pattern, std::move(handler));
8181}
8182
8183Server &Server::Patch(const std::string &pattern,
8184                             HandlerWithContentReader handler) {
8185  return add_handler(patch_handlers_for_content_reader_, pattern,
8186                     std::move(handler));
8187}
8188
8189Server &Server::Delete(const std::string &pattern, Handler handler) {
8190  return add_handler(delete_handlers_, pattern, std::move(handler));
8191}
8192
8193Server &Server::Delete(const std::string &pattern,
8194                              HandlerWithContentReader handler) {
8195  return add_handler(delete_handlers_for_content_reader_, pattern,
8196                     std::move(handler));
8197}
8198
8199Server &Server::Options(const std::string &pattern, Handler handler) {
8200  return add_handler(options_handlers_, pattern, std::move(handler));
8201}
8202
8203const std::set<std::string> &Server::builtin_methods() {
8204  thread_local const std::set<std::string> methods{
8205      "GET",     "HEAD",    "POST",  "PUT",   "DELETE",
8206      "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"};
8207  return methods;
8208}
8209
8210Server::CustomHandlerEntry *
8211Server::custom_entry_for_registration(const std::string &method) {
8212  // Built-in methods are refused for two different reasons. GET, HEAD, POST,
8213  // PUT, DELETE, OPTIONS and PATCH are dispatched by the if/else chain in
8214  // routing() before the custom tables are consulted, so a route registered
8215  // for one of them could never fire. CONNECT, TRACE and PRI have no branch
8216  // there and would be reachable, but they carry protocol-level meaning
8217  // (tunnel setup, request echo, the HTTP/2 connection preface) that this
8218  // library does not route.
8219  if (!detail::fields::is_token(method) || builtin_methods().count(method)) {
8220    output_error_log(Error::InvalidHTTPMethod, nullptr);
8221    has_invalid_registration_ = true;
8222    return nullptr;
8223  }
8224  return &custom_handlers_[method];
8225}
8226
8227Server &Server::CustomRoute(const std::string &method,
8228                                   const std::string &pattern,
8229                                   Handler handler) {
8230  auto *entry = custom_entry_for_registration(method);
8231  if (!entry) { return *this; }
8232  return add_handler(entry->handlers, pattern, std::move(handler));
8233}
8234
8235Server &Server::CustomRoute(const std::string &method,
8236                                   const std::string &pattern,
8237                                   HandlerWithContentReader handler) {
8238  auto *entry = custom_entry_for_registration(method);
8239  if (!entry) { return *this; }
8240  return add_handler(entry->handlers_for_content_reader, pattern,
8241                     std::move(handler));
8242}
8243
8244const Server::CustomHandlerEntry *
8245Server::find_custom_entry(const std::string &method) const {
8246  // find() alone would be correct here. The empty() check is what keeps the
8247  // per-request cost off servers that never call CustomRoute(), which is the
8248  // overwhelmingly common case; keep it rather than walking into the tree.
8249  if (custom_handlers_.empty()) { return nullptr; }
8250  auto it = custom_handlers_.find(method);
8251  return it == custom_handlers_.end() ? nullptr : &it->second;
8252}
8253
8254Server &Server::WebSocket(const std::string &pattern,
8255                                 WebSocketHandler handler) {
8256  websocket_handlers_.push_back(
8257      {make_matcher(pattern), std::move(handler), nullptr});
8258  return *this;
8259}
8260
8261Server &Server::WebSocket(const std::string &pattern,
8262                                 WebSocketHandler handler,
8263                                 SubProtocolSelector sub_protocol_selector) {
8264  websocket_handlers_.push_back({make_matcher(pattern), std::move(handler),
8265                                 std::move(sub_protocol_selector)});
8266  return *this;
8267}
8268
8269bool Server::set_base_dir(const std::string &dir,
8270                                 const std::string &mount_point) {
8271  return set_mount_point(mount_point, dir);
8272}
8273
8274bool Server::set_mount_point(const std::string &mount_point,
8275                                    const std::string &dir, Headers headers) {
8276  detail::FileStat stat(dir);
8277  if (stat.is_dir()) {
8278    std::string mnt = !mount_point.empty() ? mount_point : "/";
8279    if (!mnt.empty() && mnt[0] == '/') {
8280      std::string resolved_base;
8281      if (detail::canonicalize_path(dir.c_str(), resolved_base)) {
8282#if defined(_WIN32)
8283        if (resolved_base.back() != '\\' && resolved_base.back() != '/') {
8284          resolved_base += '\\';
8285        }
8286#else
8287        if (resolved_base.back() != '/') { resolved_base += '/'; }
8288#endif
8289      }
8290      base_dirs_.push_back(
8291          {std::move(mnt), dir, std::move(resolved_base), std::move(headers)});
8292      return true;
8293    }
8294  }
8295  return false;
8296}
8297
8298bool Server::remove_mount_point(const std::string &mount_point) {
8299  for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) {
8300    if (it->mount_point == mount_point) {
8301      base_dirs_.erase(it);
8302      return true;
8303    }
8304  }
8305  return false;
8306}
8307
8308Server &
8309Server::set_file_extension_and_mimetype_mapping(const std::string &ext,
8310                                                const std::string &mime) {
8311  file_extension_and_mimetype_map_[ext] = mime;
8312  return *this;
8313}
8314
8315Server &Server::set_default_file_mimetype(const std::string &mime) {
8316  default_file_mimetype_ = mime;
8317  return *this;
8318}
8319
8320Server &Server::set_file_request_handler(Handler handler) {
8321  file_request_handler_ = std::move(handler);
8322  return *this;
8323}
8324
8325Server &Server::set_error_handler_core(HandlerWithResponse handler,
8326                                              std::true_type) {
8327  error_handler_ = std::move(handler);
8328  return *this;
8329}
8330
8331Server &Server::set_error_handler_core(Handler handler,
8332                                              std::false_type) {
8333  error_handler_ = [handler](const Request &req, Response &res) {
8334    handler(req, res);
8335    return HandlerResponse::Handled;
8336  };
8337  return *this;
8338}
8339
8340Server &Server::set_exception_handler(ExceptionHandler handler) {
8341  exception_handler_ = std::move(handler);
8342  return *this;
8343}
8344
8345Server &Server::set_pre_routing_handler(HandlerWithResponse handler) {
8346  pre_routing_handler_ = std::move(handler);
8347  return *this;
8348}
8349
8350Server &Server::set_post_routing_handler(Handler handler) {
8351  post_routing_handler_ = std::move(handler);
8352  return *this;
8353}
8354
8355Server &Server::set_pre_request_handler(HandlerWithResponse handler) {
8356  pre_request_handler_ = std::move(handler);
8357  return *this;
8358}
8359
8360Server &Server::set_logger(Logger logger) {
8361  logger_ = std::move(logger);
8362  return *this;
8363}
8364
8365Server &Server::set_error_logger(ErrorLogger error_logger) {
8366  error_logger_ = std::move(error_logger);
8367  return *this;
8368}
8369
8370Server &Server::set_pre_compression_logger(Logger logger) {
8371  pre_compression_logger_ = std::move(logger);
8372  return *this;
8373}
8374
8375Server &
8376Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
8377  expect_100_continue_handler_ = std::move(handler);
8378  return *this;
8379}
8380
8381Server &Server::set_start_handler(StartHandler handler) {
8382  start_handler_ = std::move(handler);
8383  return *this;
8384}
8385
8386Server &Server::set_address_family(int family) {
8387  address_family_ = family;
8388  return *this;
8389}
8390
8391Server &Server::set_tcp_nodelay(bool on) {
8392  tcp_nodelay_ = on;
8393  return *this;
8394}
8395
8396Server &Server::set_ipv6_v6only(bool on) {
8397  ipv6_v6only_ = on;
8398  return *this;
8399}
8400
8401Server &Server::set_socket_options(SocketOptions socket_options) {
8402  socket_options_ = std::move(socket_options);
8403  return *this;
8404}
8405
8406Server &Server::set_default_headers(Headers headers) {
8407  default_headers_ = std::move(headers);
8408  return *this;
8409}
8410
8411Server &Server::set_header_writer(
8412    std::function<ssize_t(Stream &, Headers &)> const &writer) {
8413  header_writer_ = writer;
8414  return *this;
8415}
8416
8417Server &
8418Server::set_trusted_proxies(const std::vector<std::string> &proxies) {
8419  trusted_proxies_ = proxies;
8420  return *this;
8421}
8422
8423Server &Server::set_keep_alive_max_count(size_t count) {
8424  keep_alive_max_count_ = count;
8425  return *this;
8426}
8427
8428Server &Server::set_keep_alive_timeout(time_t sec) {
8429  keep_alive_timeout_sec_ = sec;
8430  return *this;
8431}
8432
8433template <class Rep, class Period>
8434Server &Server::set_keep_alive_timeout(
8435    const std::chrono::duration<Rep, Period> &duration) {
8436  detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t /*usec*/) {
8437    set_keep_alive_timeout(sec);
8438  });
8439  return *this;
8440}
8441
8442Server &Server::set_read_timeout(time_t sec, time_t usec) {
8443  read_timeout_sec_ = sec;
8444  read_timeout_usec_ = usec;
8445  return *this;
8446}
8447
8448Server &Server::set_write_timeout(time_t sec, time_t usec) {
8449  write_timeout_sec_ = sec;
8450  write_timeout_usec_ = usec;
8451  return *this;
8452}
8453
8454Server &Server::set_idle_interval(time_t sec, time_t usec) {
8455  idle_interval_sec_ = sec;
8456  idle_interval_usec_ = usec;
8457  return *this;
8458}
8459
8460Server &Server::set_payload_max_length(size_t length) {
8461  payload_max_length_ = length;
8462  return *this;
8463}
8464
8465Server &Server::set_static_file_compression(bool on) {
8466  static_file_compression_ = on;
8467  return *this;
8468}
8469
8470Server &Server::set_static_file_compression_min_length(size_t length) {
8471  static_file_compression_min_length_ = length;
8472  return *this;
8473}
8474
8475Server &Server::set_static_file_compression_max_length(size_t length) {
8476  static_file_compression_max_length_ = length;
8477  return *this;
8478}
8479
8480Server &Server::set_websocket_max_missed_pongs(int count) {
8481  websocket_max_missed_pongs_ = count;
8482  return *this;
8483}
8484
8485Server &Server::set_websocket_ping_interval(time_t sec) {
8486  websocket_ping_interval_sec_ = sec;
8487  return *this;
8488}
8489
8490template <class Rep, class Period>
8491Server &Server::set_websocket_ping_interval(
8492    const std::chrono::duration<Rep, Period> &duration) {
8493  detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t /*usec*/) {
8494    set_websocket_ping_interval(sec);
8495  });
8496  return *this;
8497}
8498
8499bool Server::bind_to_port(const std::string &host, int port,
8500                                 int socket_flags) {
8501  auto ret = bind_internal(host, port, socket_flags);
8502  if (ret == -1) { is_decommissioned = true; }
8503  return ret >= 0;
8504}
8505int Server::bind_to_any_port(const std::string &host, int socket_flags) {
8506  auto ret = bind_internal(host, 0, socket_flags);
8507  if (ret == -1) { is_decommissioned = true; }
8508  return ret;
8509}
8510
8511bool Server::listen_after_bind() { return listen_internal(); }
8512
8513bool Server::listen(const std::string &host, int port,
8514                           int socket_flags) {
8515  return bind_to_port(host, port, socket_flags) && listen_internal();
8516}
8517
8518bool Server::is_running() const { return is_running_; }
8519
8520void Server::wait_until_ready() const {
8521  while (!is_running_ && !is_decommissioned) {
8522    std::this_thread::sleep_for(std::chrono::milliseconds{1});
8523  }
8524}
8525
8526void Server::stop() noexcept {
8527  // Release the listening socket whether or not the accept loop is running:
8528  // bind_to_port() without listen_after_bind() still owns the descriptor. The
8529  // exchange is what makes this safe to call concurrently with the accept loop.
8530  socket_t sock = svr_sock_.exchange(INVALID_SOCKET);
8531  if (sock != INVALID_SOCKET) {
8532    detail::shutdown_socket(sock);
8533    detail::close_socket(sock);
8534  }
8535  is_decommissioned = false;
8536}
8537
8538void Server::decommission() { is_decommissioned = true; }
8539
8540bool Server::parse_request_line(const char *s, Request &req) const {
8541  auto len = strlen(s);
8542  if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; }
8543  len -= 2;
8544
8545  {
8546    size_t count = 0;
8547
8548    detail::split(s, s + len, ' ', [&](const char *b, const char *e) {
8549      switch (count) {
8550      case 0: req.method = std::string(b, e); break;
8551      case 1: req.target = std::string(b, e); break;
8552      case 2: req.version = std::string(b, e); break;
8553      default: break;
8554      }
8555      count++;
8556    });
8557
8558    if (count != 3) { return false; }
8559  }
8560
8561  // A method outside the built-in set is accepted only when a handler has been
8562  // registered for it with CustomRoute().
8563  const auto &methods = builtin_methods();
8564
8565  if (methods.find(req.method) == methods.end() &&
8566      !find_custom_entry(req.method)) {
8567    output_error_log(Error::InvalidHTTPMethod, &req);
8568    return false;
8569  }
8570
8571  if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") {
8572    output_error_log(Error::InvalidHTTPVersion, &req);
8573    return false;
8574  }
8575
8576  {
8577    // Skip URL fragment
8578    for (size_t i = 0; i < req.target.size(); i++) {
8579      if (req.target[i] == '#') {
8580        req.target.erase(i);
8581        break;
8582      }
8583    }
8584
8585    detail::divide(req.target, '?',
8586                   [&](const char *lhs_data, std::size_t lhs_size,
8587                       const char *rhs_data, std::size_t rhs_size) {
8588                     req.path =
8589                         decode_path_component(std::string(lhs_data, lhs_size));
8590                     detail::parse_query_text(rhs_data, rhs_size, req.params);
8591                   });
8592  }
8593
8594  return true;
8595}
8596
8597bool Server::write_response(Stream &strm, bool close_connection,
8598                                   Request &req, Response &res) {
8599  // NOTE: `req.ranges` should be empty, otherwise it will be applied
8600  // incorrectly to the error content.
8601  req.ranges.clear();
8602  return write_response_core(strm, close_connection, req, res, false);
8603}
8604
8605bool Server::write_response_with_content(Stream &strm,
8606                                                bool close_connection,
8607                                                const Request &req,
8608                                                Response &res) {
8609  return write_response_core(strm, close_connection, req, res, true);
8610}
8611
8612bool Server::write_response_core(Stream &strm, bool close_connection,
8613                                        const Request &req, Response &res,
8614                                        bool need_apply_ranges) {
8615  assert(res.status != -1);
8616
8617  if (400 <= res.status && error_handler_ &&
8618      error_handler_(req, res) == HandlerResponse::Handled) {
8619    need_apply_ranges = true;
8620  }
8621
8622  std::string content_type;
8623  std::string boundary;
8624  if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); }
8625
8626  // Prepare additional headers
8627  if (close_connection ||
8628      detail::has_header_token(req.headers, "Connection", "close") ||
8629      400 <= res.status) { // Don't leave connections open after errors
8630    res.set_header("Connection", "close");
8631  } else {
8632    std::string s = "timeout=";
8633    s += std::to_string(keep_alive_timeout_sec_);
8634    s += ", max=";
8635    s += std::to_string(keep_alive_max_count_);
8636    res.set_header("Keep-Alive", s);
8637  }
8638
8639  if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) &&
8640      !res.has_header("Content-Type")) {
8641    res.set_header("Content-Type", "text/plain");
8642  }
8643
8644  if (res.body.empty() && !res.content_length_ && !res.content_provider_ &&
8645      !res.has_header("Content-Length")) {
8646    res.set_header("Content-Length", "0");
8647  }
8648
8649  if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) {
8650    res.set_header("Accept-Ranges", "bytes");
8651  }
8652
8653  if (post_routing_handler_) { post_routing_handler_(req, res); }
8654
8655  // Response line and headers
8656  detail::BufferStream bstrm;
8657  if (!detail::write_response_line(bstrm, res.status)) { return false; }
8658  if (header_writer_(bstrm, res.headers) <= 0) { return false; }
8659
8660  // Combine small body with headers to reduce write syscalls
8661  if (req.method != "HEAD" && !res.body.empty() && !res.content_provider_) {
8662    bstrm.write(res.body.data(), res.body.size());
8663  }
8664
8665  // Log before writing to avoid race condition with client-side code that
8666  // accesses logger-captured data immediately after receiving the response.
8667  output_log(req, res);
8668
8669  // Flush buffer
8670  auto &data = bstrm.get_buffer();
8671  if (!detail::write_data(strm, data.data(), data.size())) { return false; }
8672
8673  // Streaming body
8674  auto ret = true;
8675  if (req.method != "HEAD" && res.content_provider_) {
8676    if (write_content_with_provider(strm, req, res, boundary, content_type)) {
8677      res.content_provider_success_ = true;
8678    } else {
8679      ret = false;
8680    }
8681  }
8682
8683  return ret;
8684}
8685
8686bool
8687Server::write_content_with_provider(Stream &strm, const Request &req,
8688                                    Response &res, const std::string &boundary,
8689                                    const std::string &content_type) {
8690  auto is_shutting_down = [this]() {
8691    return this->svr_sock_ == INVALID_SOCKET;
8692  };
8693
8694  if (res.content_length_ > 0) {
8695    // Only a 206 response is served as a partial representation, matching the
8696    // condition `apply_ranges()` used to decide the Content-Length and the
8697    // multipart boundary. Since `detail::range_error()` validates `req.ranges`
8698    // only for a 2xx status, slicing under any other status would write a body
8699    // that disagrees with the header already sent, from an unchecked offset.
8700    auto is_partial =
8701        !req.ranges.empty() && res.status == StatusCode::PartialContent_206;
8702
8703    if (!is_partial) {
8704      return detail::write_content(strm, res.content_provider_, 0,
8705                                   res.content_length_, is_shutting_down);
8706    } else if (req.ranges.size() == 1) {
8707      auto offset_and_length = detail::get_range_offset_and_length(
8708          req.ranges[0], res.content_length_);
8709
8710      return detail::write_content(strm, res.content_provider_,
8711                                   offset_and_length.first,
8712                                   offset_and_length.second, is_shutting_down);
8713    } else {
8714      return detail::write_multipart_ranges_data(
8715          strm, req, res, boundary, content_type, res.content_length_,
8716          is_shutting_down);
8717    }
8718  } else {
8719    if (res.is_chunked_content_provider_) {
8720      // Use the coding `apply_ranges()` chose when it wrote the headers;
8721      // re-negotiating here would disagree with them, e.g. once a handler's
8722      // own Content-Encoding header suppresses the negotiation.
8723      auto compressor = detail::make_compressor(res.content_coding_);
8724      if (!compressor) {
8725        compressor = detail::make_unique<detail::nocompressor>();
8726      }
8727
8728      return detail::write_content_chunked(strm, res.content_provider_,
8729                                           is_shutting_down, *compressor);
8730    } else {
8731      return detail::write_content_without_length(strm, res.content_provider_,
8732                                                  is_shutting_down);
8733    }
8734  }
8735}
8736
8737bool Server::read_content(Stream &strm, Request &req, Response &res) {
8738  FormFields::iterator cur_field;
8739  FormFiles::iterator cur_file;
8740  auto is_text_field = false;
8741  size_t count = 0;
8742  if (read_content_core(
8743          strm, req, res,
8744          // Regular
8745          [&](const char *buf, size_t n) {
8746            // Prevent arithmetic overflow when checking sizes.
8747            // Avoid computing (req.body.size() + n) directly because
8748            // adding two unsigned `size_t` values can wrap around and
8749            // produce a small result instead of indicating overflow.
8750            // Instead, check using subtraction: ensure `n` does not
8751            // exceed the remaining capacity `max_size() - size()`.
8752            if (req.body.size() >= req.body.max_size() ||
8753                n > req.body.max_size() - req.body.size()) {
8754              return false;
8755            }
8756
8757            // Limit decompressed body size to payload_max_length_ to protect
8758            // against "zip bomb" attacks where a small compressed payload
8759            // decompresses to a massive size.
8760            if (payload_max_length_ > 0 &&
8761                (req.body.size() >= payload_max_length_ ||
8762                 n > payload_max_length_ - req.body.size())) {
8763              return false;
8764            }
8765
8766            req.body.append(buf, n);
8767            return true;
8768          },
8769          // Multipart FormData
8770          [&](const FormData &file) {
8771            if (count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) {
8772              output_error_log(Error::TooManyFormDataFiles, &req);
8773              return false;
8774            }
8775
8776            if (file.filename.empty()) {
8777              cur_field = req.form.fields.emplace(
8778                  file.name, FormField{file.name, file.content, file.headers});
8779              is_text_field = true;
8780            } else {
8781              cur_file = req.form.files.emplace(file.name, file);
8782              is_text_field = false;
8783            }
8784            return true;
8785          },
8786          [&](const char *buf, size_t n) {
8787            if (is_text_field) {
8788              auto &content = cur_field->second.content;
8789              if (content.size() + n > content.max_size()) { return false; }
8790              content.append(buf, n);
8791            } else {
8792              auto &content = cur_file->second.content;
8793              if (content.size() + n > content.max_size()) { return false; }
8794              content.append(buf, n);
8795            }
8796            return true;
8797          })) {
8798    const auto &content_type = req.get_header_value("Content-Type");
8799    if (detail::extract_media_type(content_type) ==
8800        "application/x-www-form-urlencoded") {
8801      if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
8802        res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414?
8803        output_error_log(Error::ExceedMaxPayloadSize, &req);
8804        return false;
8805      }
8806      detail::parse_query_text(req.body, req.params);
8807    }
8808    return true;
8809  }
8810  return false;
8811}
8812
8813bool Server::read_content_with_content_receiver(
8814    Stream &strm, Request &req, Response &res, ContentReceiver receiver,
8815    FormDataHeader multipart_header, ContentReceiver multipart_receiver) {
8816  return read_content_core(strm, req, res, std::move(receiver),
8817                           std::move(multipart_header),
8818                           std::move(multipart_receiver));
8819}
8820
8821bool Server::read_content_core(
8822    Stream &strm, Request &req, Response &res, ContentReceiver receiver,
8823    FormDataHeader multipart_header, ContentReceiver multipart_receiver) const {
8824  detail::FormDataParser multipart_form_data_parser;
8825  ContentReceiverWithProgress out;
8826
8827  if (req.is_multipart_form_data()) {
8828    const auto &content_type = req.get_header_value("Content-Type");
8829    std::string boundary;
8830    if (!detail::parse_multipart_boundary(content_type, boundary)) {
8831      res.status = StatusCode::BadRequest_400;
8832      output_error_log(Error::MultipartParsing, &req);
8833      return false;
8834    }
8835
8836    multipart_form_data_parser.set_boundary(std::move(boundary));
8837    out = [&](const char *buf, size_t n, size_t /*off*/, size_t /*len*/) {
8838      return multipart_form_data_parser.parse(buf, n, multipart_header,
8839                                              multipart_receiver);
8840    };
8841  } else {
8842    out = [receiver](const char *buf, size_t n, size_t /*off*/,
8843                     size_t /*len*/) { return receiver(buf, n); };
8844  }
8845
8846  // RFC 9112 §6: no Transfer-Encoding and no Content-Length means no body.
8847  // For non-SSL builds we still scan non-persistent connections for stray
8848  // body bytes so the payload limit is enforced (413). On keep-alive,
8849  // pending bytes may be the next request (issue #2450), so skip.
8850#if !defined(CPPHTTPLIB_SSL_ENABLED)
8851  if (!req.has_header("Content-Length") &&
8852      !detail::is_chunked_transfer_encoding(req.headers)) {
8853    if (!detail::is_connection_persistent(req) && payload_max_length_ > 0 &&
8854        payload_max_length_ < (std::numeric_limits<size_t>::max)()) {
8855      auto has_data = strm.is_readable();
8856      if (!has_data) {
8857        auto s = strm.socket();
8858        if (s != INVALID_SOCKET) {
8859          has_data = detail::select_read(s, 0, 0) > 0;
8860        }
8861      }
8862      if (has_data) {
8863        // Route through the same decompressing reader used by the
8864        // length-framed and chunked paths below, so payload_max_length_ is
8865        // enforced on the decompressed size here too instead of only on the
8866        // compressed wire bytes.
8867        return detail::read_content(strm, req, payload_max_length_, res.status,
8868                                    nullptr, out, true);
8869      }
8870    }
8871    return true;
8872  }
8873#else
8874  if (!req.has_header("Content-Length") &&
8875      !detail::is_chunked_transfer_encoding(req.headers)) {
8876    return true;
8877  }
8878#endif
8879
8880  if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr,
8881                            out, true)) {
8882    return false;
8883  }
8884
8885  req.body_consumed_ = true;
8886
8887  if (req.is_multipart_form_data()) {
8888    if (!multipart_form_data_parser.is_valid()) {
8889      res.status = StatusCode::BadRequest_400;
8890      output_error_log(Error::MultipartParsing, &req);
8891      return false;
8892    }
8893  }
8894
8895  return true;
8896}
8897
8898bool Server::handle_file_request(Request &req, Response &res) {
8899  for (const auto &entry : base_dirs_) {
8900    // Prefix match, on a path segment boundary. A mount point of "/mount"
8901    // covers "/mount" and "/mount/...", but must not swallow "/mountdir/...".
8902    // One that already ends in '/' (the root mount among them) carries its own
8903    // boundary; set_mount_point() guarantees the mount point is not empty.
8904    if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point) &&
8905        (entry.mount_point.back() == '/' ||
8906         req.path.size() == entry.mount_point.size() ||
8907         req.path[entry.mount_point.size()] == '/')) {
8908      std::string sub_path = "/" + req.path.substr(entry.mount_point.size());
8909      if (detail::is_valid_path(sub_path)) {
8910        auto path = entry.base_dir + sub_path;
8911        if (path.back() == '/') { path += "index.html"; }
8912
8913        // Defense-in-depth: is_valid_path blocks ".." traversal in the URL,
8914        // but symlinks/junctions can still escape the base directory.
8915        if (!entry.resolved_base_dir.empty()) {
8916          std::string resolved_path;
8917          if (detail::canonicalize_path(path.c_str(), resolved_path) &&
8918              !detail::is_path_within_base(resolved_path,
8919                                           entry.resolved_base_dir)) {
8920            res.status = StatusCode::Forbidden_403;
8921            return true;
8922          }
8923        }
8924
8925        detail::FileStat stat(path);
8926
8927        if (stat.is_dir()) {
8928          res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301);
8929          return true;
8930        }
8931
8932        if (stat.is_file()) {
8933          for (const auto &kv : entry.headers) {
8934            res.set_header(kv.first, kv.second);
8935          }
8936
8937          auto content_type_of = [&]() {
8938            return detail::find_content_type(
8939                path, file_extension_and_mimetype_map_, default_file_mimetype_);
8940          };
8941
8942          // Only the ETag needs the content type this early, and only to name
8943          // the coding. Deciding it here would otherwise put a regex in front
8944          // of the 304 below, which serving a file never used to pay for.
8945          std::string content_type;
8946          auto encoding = detail::EncodingType::None;
8947          if (static_file_compression_) {
8948            content_type = content_type_of();
8949            encoding =
8950                static_file_encoding(req, res, content_type, stat.size());
8951          }
8952
8953          // The ETag names the representation actually sent, so a client that
8954          // cached the compressed form revalidates against the compressed ETag
8955          // and still gets a 304, while one that took identity keeps the plain
8956          // ETag.
8957          auto etag = detail::compute_etag(
8958              stat, encoding == detail::EncodingType::None
8959                        ? std::string()
8960                        : std::string("-") + detail::encoding_name(encoding));
8961          if (!etag.empty()) { res.set_header("ETag", etag); }
8962
8963          auto mtime = stat.mtime();
8964
8965          auto last_modified = detail::file_mtime_to_http_date(mtime);
8966          if (!last_modified.empty()) {
8967            res.set_header("Last-Modified", last_modified);
8968          }
8969
8970          if (check_if_not_modified(req, res, etag, mtime)) { return true; }
8971
8972          check_if_range(req, etag, mtime);
8973
8974          auto mm = std::make_shared<detail::mmap>(path.c_str());
8975          if (!mm->is_open()) {
8976            output_error_log(Error::OpenFile, &req);
8977            return false;
8978          }
8979
8980          if (!static_file_compression_) { content_type = content_type_of(); }
8981
8982          detail::set_file_content_provider(res, mm, content_type, encoding);
8983
8984          if (req.method != "HEAD" && file_request_handler_) {
8985            file_request_handler_(req, res);
8986          }
8987
8988          return true;
8989        } else {
8990          output_error_log(Error::OpenFile, &req);
8991        }
8992      }
8993    }
8994  }
8995  return false;
8996}
8997
8998bool Server::check_if_not_modified(const Request &req, Response &res,
8999                                          const std::string &etag,
9000                                          time_t mtime) const {
9001  // Handle conditional GET:
9002  // 1. If-None-Match takes precedence (RFC 9110 Section 13.1.2)
9003  // 2. If-Modified-Since is checked only when If-None-Match is absent
9004  if (req.has_header("If-None-Match")) {
9005    if (!etag.empty()) {
9006      auto val =
9007          detail::get_combined_header_value(req.headers, "If-None-Match");
9008
9009      // NOTE: We use exact string matching here. This works correctly
9010      // because our server always generates weak ETags (W/"..."), and
9011      // clients typically send back the same ETag they received.
9012      // RFC 9110 Section 8.8.3.2 allows weak comparison for
9013      // If-None-Match, where W/"x" and "x" would match, but this
9014      // simplified implementation requires exact matches.
9015      auto ret = detail::split_find(val.data(), val.data() + val.size(), ',',
9016                                    [&](const char *b, const char *e) {
9017                                      auto seg_len = static_cast<size_t>(e - b);
9018                                      return (seg_len == 1 && *b == '*') ||
9019                                             (seg_len == etag.size() &&
9020                                              std::equal(b, e, etag.begin()));
9021                                    });
9022
9023      if (ret) {
9024        res.status = StatusCode::NotModified_304;
9025        return true;
9026      }
9027    }
9028  } else if (req.has_header("If-Modified-Since")) {
9029    auto val = req.get_header_value("If-Modified-Since");
9030    auto t = detail::parse_http_date(val);
9031
9032    if (t != static_cast<time_t>(-1) && mtime <= t) {
9033      res.status = StatusCode::NotModified_304;
9034      return true;
9035    }
9036  }
9037  return false;
9038}
9039
9040bool Server::check_if_range(Request &req, const std::string &etag,
9041                                   time_t mtime) const {
9042  // Handle If-Range for partial content requests (RFC 9110
9043  // Section 13.1.5). If-Range is only evaluated when Range header is
9044  // present. If the validator matches, serve partial content; otherwise
9045  // serve full content.
9046  if (!req.ranges.empty() && req.has_header("If-Range")) {
9047    auto val = req.get_header_value("If-Range");
9048
9049    auto is_valid_range = [&]() {
9050      if (detail::is_strong_etag(val)) {
9051        // RFC 9110 Section 13.1.5: If-Range requires strong ETag
9052        // comparison.
9053        return (!etag.empty() && val == etag);
9054      } else if (detail::is_weak_etag(val)) {
9055        // Weak ETags are not valid for If-Range (RFC 9110 Section 13.1.5)
9056        return false;
9057      } else {
9058        // HTTP-date comparison
9059        auto t = detail::parse_http_date(val);
9060        return (t != static_cast<time_t>(-1) && mtime <= t);
9061      }
9062    };
9063
9064    if (!is_valid_range()) {
9065      // Validator doesn't match: ignore Range and serve full content
9066      req.ranges.clear();
9067      return false;
9068    }
9069  }
9070
9071  return true;
9072}
9073
9074socket_t
9075Server::create_server_socket(const std::string &host, int port,
9076                             int socket_flags,
9077                             SocketOptions socket_options) const {
9078  return detail::create_socket(
9079      host, std::string(), port, address_family_, socket_flags, tcp_nodelay_,
9080      ipv6_v6only_, std::move(socket_options),
9081      [&](socket_t sock, struct addrinfo &ai, bool & /*quit*/) -> bool {
9082        if (::bind(sock, ai.ai_addr, static_cast<socklen_t>(ai.ai_addrlen))) {
9083          output_error_log(Error::BindIPAddress, nullptr);
9084          return false;
9085        }
9086        if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) {
9087          output_error_log(Error::Listen, nullptr);
9088          return false;
9089        }
9090        return true;
9091      });
9092}
9093
9094int Server::bind_internal(const std::string &host, int port,
9095                                 int socket_flags) {
9096  if (is_decommissioned) { return -1; }
9097
9098  if (!is_valid()) { return -1; }
9099
9100  svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_);
9101  if (svr_sock_ == INVALID_SOCKET) { return -1; }
9102
9103  if (port == 0) {
9104    struct sockaddr_storage addr;
9105    socklen_t addr_len = sizeof(addr);
9106    if (getsockname(svr_sock_, reinterpret_cast<struct sockaddr *>(&addr),
9107                    &addr_len) == -1) {
9108      output_error_log(Error::GetSockName, nullptr);
9109      return -1;
9110    }
9111    if (addr.ss_family == AF_INET) {
9112      return ntohs(reinterpret_cast<struct sockaddr_in *>(&addr)->sin_port);
9113    } else if (addr.ss_family == AF_INET6) {
9114      return ntohs(reinterpret_cast<struct sockaddr_in6 *>(&addr)->sin6_port);
9115    } else {
9116      output_error_log(Error::UnsupportedAddressFamily, nullptr);
9117      return -1;
9118    }
9119  } else {
9120    return port;
9121  }
9122}
9123
9124bool Server::listen_internal() {
9125  // A stop() between bind and listen leaves nothing to accept on. Report
9126  // failure instead of returning success without ever serving, and mark the
9127  // server decommissioned the way any failed listen does so that a concurrent
9128  // wait_until_ready() wakes up instead of spinning forever.
9129  if (is_decommissioned || svr_sock_ == INVALID_SOCKET) {
9130    is_decommissioned = true;
9131    return false;
9132  }
9133
9134  auto ret = true;
9135  is_running_ = true;
9136  auto se = detail::scope_exit([&]() { is_running_ = false; });
9137
9138  if (start_handler_) { start_handler_(); }
9139
9140  {
9141    std::unique_ptr<TaskQueue> task_queue(new_task_queue());
9142
9143    while (svr_sock_ != INVALID_SOCKET) {
9144#ifndef _WIN32
9145      if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) {
9146#endif
9147        auto val = detail::select_read(svr_sock_, idle_interval_sec_,
9148                                       idle_interval_usec_);
9149        if (val == 0) { // Timeout
9150          task_queue->on_idle();
9151          continue;
9152        }
9153#ifndef _WIN32
9154      }
9155#endif
9156
9157#if defined _WIN32
9158      // sockets connected via WASAccept inherit flags NO_HANDLE_INHERIT,
9159      // OVERLAPPED
9160      socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0);
9161#elif defined SOCK_CLOEXEC
9162      socket_t sock = accept4(svr_sock_, nullptr, nullptr, SOCK_CLOEXEC);
9163#else
9164      socket_t sock = accept(svr_sock_, nullptr, nullptr);
9165#endif
9166
9167      if (sock == INVALID_SOCKET) {
9168        // NOTE: Winsock reports failures through WSAGetLastError() and never
9169        // touches the CRT errno, so the two have to be asked platform by
9170        // platform rather than by testing errno here.
9171        if (detail::is_accept_resource_error()) {
9172          // The per-process descriptor limit or the network stack's buffer
9173          // space has been reached. Try to accept new connections after a
9174          // short sleep.
9175          std::this_thread::sleep_for(std::chrono::microseconds{1});
9176          continue;
9177        } else if (detail::is_accept_transient_error()) {
9178          continue;
9179        }
9180        // Take the descriptor out of svr_sock_ before closing it: a later
9181        // stop() would otherwise shutdown()/close() a value the OS may have
9182        // reused, and keep_alive() watches svr_sock_ to notice the server is
9183        // gone. The exchange also settles the race with a concurrent stop(),
9184        // since whichever side takes the descriptor closes it exactly once.
9185        auto listen_sock = svr_sock_.exchange(INVALID_SOCKET);
9186        if (listen_sock != INVALID_SOCKET) {
9187          detail::close_socket(listen_sock);
9188          ret = false;
9189          output_error_log(Error::Connection, nullptr);
9190        } else {
9191          ; // The server socket was closed by user.
9192        }
9193        break;
9194      }
9195
9196      detail::set_socket_opt_time(sock, SOL_SOCKET, SO_RCVTIMEO,
9197                                  read_timeout_sec_, read_timeout_usec_);
9198      detail::set_socket_opt_time(sock, SOL_SOCKET, SO_SNDTIMEO,
9199                                  write_timeout_sec_, write_timeout_usec_);
9200
9201      if (tcp_nodelay_) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); }
9202
9203      if (!task_queue->enqueue(
9204              [this, sock]() { process_and_close_socket(sock); })) {
9205        output_error_log(Error::ResourceExhaustion, nullptr);
9206        detail::shutdown_socket(sock);
9207        detail::close_socket(sock);
9208      }
9209    }
9210
9211    task_queue->shutdown();
9212  }
9213
9214  is_decommissioned = !ret;
9215  return ret;
9216}
9217
9218bool Server::routing(Request &req, Response &res, Stream &strm) {
9219  if (pre_routing_handler_ &&
9220      pre_routing_handler_(req, res) == HandlerResponse::Handled) {
9221    return true;
9222  }
9223
9224  // File handler
9225  if ((req.method == "GET" || req.method == "HEAD") &&
9226      handle_file_request(req, res)) {
9227    return true;
9228  }
9229
9230  const auto *custom = find_custom_entry(req.method);
9231
9232  // The second clause mirrors what expect_content() does unconditionally for
9233  // POST/PUT/PATCH/DELETE: a content reader route fires even when the request
9234  // carries no body. Without it a body-less PROPFIND (RFC 4918 treats one as
9235  // `allprop`) would skip its handler and fall through to 404.
9236  if (detail::expect_content(req) ||
9237      (custom && !custom->handlers_for_content_reader.empty())) {
9238    // Content reader handler
9239    {
9240      // Track whether the ContentReader was aborted due to the decompressed
9241      // payload exceeding `payload_max_length_`.
9242      // The user handler runs after the lambda returns, so we must restore the
9243      // 413 status if the handler overwrites it.
9244      bool content_reader_payload_too_large = false;
9245
9246      ContentReader reader(
9247          [&](ContentReceiver receiver) {
9248            auto result = read_content_with_content_receiver(
9249                strm, req, res, std::move(receiver), nullptr, nullptr);
9250            if (!result) {
9251              output_error_log(Error::Read, &req);
9252              if (res.status == StatusCode::PayloadTooLarge_413) {
9253                content_reader_payload_too_large = true;
9254              }
9255            }
9256            return result;
9257          },
9258          [&](FormDataHeader header, ContentReceiver receiver) {
9259            auto result = read_content_with_content_receiver(
9260                strm, req, res, nullptr, std::move(header),
9261                std::move(receiver));
9262            if (!result) {
9263              output_error_log(Error::Read, &req);
9264              if (res.status == StatusCode::PayloadTooLarge_413) {
9265                content_reader_payload_too_large = true;
9266              }
9267            }
9268            return result;
9269          });
9270
9271      bool dispatched = false;
9272      if (req.method == "POST") {
9273        dispatched = dispatch_request_for_content_reader(
9274            req, res, std::move(reader), post_handlers_for_content_reader_);
9275      } else if (req.method == "PUT") {
9276        dispatched = dispatch_request_for_content_reader(
9277            req, res, std::move(reader), put_handlers_for_content_reader_);
9278      } else if (req.method == "PATCH") {
9279        dispatched = dispatch_request_for_content_reader(
9280            req, res, std::move(reader), patch_handlers_for_content_reader_);
9281      } else if (req.method == "DELETE") {
9282        dispatched = dispatch_request_for_content_reader(
9283            req, res, std::move(reader), delete_handlers_for_content_reader_);
9284      } else if (custom) {
9285        dispatched = dispatch_request_for_content_reader(
9286            req, res, std::move(reader), custom->handlers_for_content_reader);
9287      }
9288
9289      if (dispatched) {
9290        if (content_reader_payload_too_large) {
9291          // Enforce the limit: override any status the handler may have set
9292          // and return false so the error path sends a plain 413 response.
9293          res.status = StatusCode::PayloadTooLarge_413;
9294          res.body.clear();
9295          res.content_length_ = 0;
9296          res.content_provider_ = nullptr;
9297          return false;
9298        }
9299        return true;
9300      }
9301    }
9302
9303    // NOTE: `req.body` is not read here. For a regular handler the body is
9304    // read inside dispatch_request(), after the route has matched and the
9305    // pre-request handler has approved the request, so that a rejected
9306    // request (e.g. failed authentication) never forces us to buffer a
9307    // potentially large body.
9308  }
9309
9310  // Regular handler
9311  if (req.method == "GET" || req.method == "HEAD") {
9312    return dispatch_request(req, res, get_handlers_, strm);
9313  } else if (req.method == "POST") {
9314    return dispatch_request(req, res, post_handlers_, strm);
9315  } else if (req.method == "PUT") {
9316    return dispatch_request(req, res, put_handlers_, strm);
9317  } else if (req.method == "DELETE") {
9318    return dispatch_request(req, res, delete_handlers_, strm);
9319  } else if (req.method == "OPTIONS") {
9320    return dispatch_request(req, res, options_handlers_, strm);
9321  } else if (req.method == "PATCH") {
9322    return dispatch_request(req, res, patch_handlers_, strm);
9323  } else if (custom) {
9324    return dispatch_request(req, res, custom->handlers, strm);
9325  }
9326
9327  res.status = StatusCode::BadRequest_400;
9328  return false;
9329}
9330
9331bool Server::dispatch_request(Request &req, Response &res,
9332                                     const Handlers &handlers, Stream &strm) {
9333  for (const auto &x : handlers) {
9334    const auto &matcher = x.first;
9335    const auto &handler = x.second;
9336
9337    if (matcher->match(req)) {
9338      req.matched_route = matcher->pattern();
9339
9340      // Run the pre-request handler before reading the body so a rejected
9341      // request (e.g. failed authentication) never forces us to buffer a
9342      // potentially large body. `req.matched_route` is available here.
9343      if (pre_request_handler_ &&
9344          pre_request_handler_(req, res) == HandlerResponse::Handled) {
9345        return true;
9346      }
9347
9348      // The route matched and the request was approved; read the body now.
9349      if (detail::expect_content(req) && !read_content(strm, req, res)) {
9350        output_error_log(Error::Read, &req);
9351        return false;
9352      }
9353
9354      handler(req, res);
9355      return true;
9356    }
9357  }
9358  return false;
9359}
9360
9361// Decides the content coding for a response served straight from a file. Both
9362// the ETag, which has to name the representation actually sent, and
9363// `apply_static_file_compression()` go through this, so the two cannot drift
9364// apart.
9365detail::EncodingType
9366Server::static_file_encoding(const Request &req, const Response &res,
9367                             const std::string &content_type,
9368                             size_t length) const {
9369  if (!static_file_compression_) { return detail::EncodingType::None; }
9370
9371  // Nothing to compress, and an empty file already answers with
9372  // `Content-Length: 0`. Checked on its own so that a zero floor still cannot
9373  // turn an empty body into a 20-byte gzip stream.
9374  if (length == 0) { return detail::EncodingType::None; }
9375
9376  // A file that already fits in a single packet gains nothing from being made
9377  // smaller, since it still travels in that one segment, and a file of a few
9378  // bytes comes out larger than it went in.
9379  if (length < static_file_compression_min_length_) {
9380    return detail::EncodingType::None;
9381  }
9382
9383  // RFC 9110 applies Range to the representation after content coding, so a
9384  // compressed 206 would mean compressing the whole file and then slicing it.
9385  // Serve ranges from the identity representation instead.
9386  if (!req.ranges.empty()) { return detail::EncodingType::None; }
9387
9388  if (static_file_compression_max_length_ > 0 &&
9389      length > static_file_compression_max_length_) {
9390    return detail::EncodingType::None;
9391  }
9392
9393  return detail::encoding_type(req, res, content_type);
9394}
9395
9396// Compresses a file-backed content provider into `res.body` and takes over the
9397// framing headers. Returns false when the response is left untouched.
9398bool Server::apply_static_file_compression(const Request &req,
9399                                                  Response &res) const {
9400  auto type = res.content_coding_;
9401  if (type == detail::EncodingType::None || !res.content_provider_) {
9402    return false;
9403  }
9404
9405  auto compressor = detail::make_compressor(type);
9406  if (!compressor) { return false; }
9407
9408  output_pre_compression_log(req, res);
9409
9410  std::string compressed;
9411  if (!detail::compress_content_provider(res.content_provider_,
9412                                         res.content_length_, *compressor,
9413                                         compressed)) {
9414    return false;
9415  }
9416
9417  res.body.swap(compressed);
9418
9419  // The provider was consumed in full, so a resource releaser registered with
9420  // it should hear about a success when the response goes away.
9421  res.content_provider_success_ = true;
9422  res.content_provider_ = nullptr;
9423  res.content_length_ = 0;
9424  res.content_coding_ = detail::EncodingType::None;
9425
9426  res.set_header("Content-Encoding", detail::encoding_name(type));
9427  res.set_header("Vary", "Accept-Encoding");
9428  res.set_header("Content-Length", std::to_string(res.body.size()));
9429
9430  return true;
9431}
9432
9433void Server::apply_ranges(const Request &req, Response &res,
9434                                 std::string &content_type,
9435                                 std::string &boundary) const {
9436  // A known-length content provider leaves `res.body` empty, so the compressor
9437  // at the end of this function never runs for one (issue #2545). A file-backed
9438  // provider is fully readable right here, so compress it and answer with an
9439  // ordinary body: `Content-Length` and HEAD keep working, and the response
9440  // takes the same path as `set_content()` from here on. Range requests never
9441  // get a content coding, so `Content-Range` still names identity bytes and
9442  // none of the framing below applies.
9443  if (apply_static_file_compression(req, res)) { return; }
9444
9445  if (req.ranges.size() > 1 && res.status == StatusCode::PartialContent_206) {
9446    auto it = res.headers.find("Content-Type");
9447    if (it != res.headers.end()) {
9448      content_type = it->second;
9449      res.headers.erase(it);
9450    }
9451
9452    boundary = detail::make_multipart_data_boundary();
9453
9454    res.set_header("Content-Type",
9455                   "multipart/byteranges; boundary=" + boundary);
9456  }
9457
9458  auto type = detail::encoding_type(req, res);
9459
9460  if (res.body.empty()) {
9461    if (res.content_length_ > 0) {
9462      size_t length = 0;
9463      if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
9464        length = res.content_length_;
9465      } else if (req.ranges.size() == 1) {
9466        auto offset_and_length = detail::get_range_offset_and_length(
9467            req.ranges[0], res.content_length_);
9468
9469        length = offset_and_length.second;
9470
9471        auto content_range = detail::make_content_range_header_field(
9472            offset_and_length, res.content_length_);
9473        res.set_header("Content-Range", content_range);
9474      } else {
9475        length = detail::get_multipart_ranges_data_length(
9476            req, boundary, content_type, res.content_length_);
9477      }
9478      res.set_header("Content-Length", std::to_string(length));
9479    } else {
9480      if (res.content_provider_) {
9481        if (res.is_chunked_content_provider_) {
9482          res.set_header("Transfer-Encoding", "chunked");
9483          res.content_coding_ = type;
9484          if (type != detail::EncodingType::None) {
9485            res.set_header("Content-Encoding", detail::encoding_name(type));
9486            res.set_header("Vary", "Accept-Encoding");
9487          }
9488        }
9489      }
9490    }
9491  } else {
9492    if (req.ranges.empty() || res.status != StatusCode::PartialContent_206) {
9493      ;
9494    } else if (req.ranges.size() == 1) {
9495      auto offset_and_length =
9496          detail::get_range_offset_and_length(req.ranges[0], res.body.size());
9497      auto offset = offset_and_length.first;
9498      auto length = offset_and_length.second;
9499
9500      auto content_range = detail::make_content_range_header_field(
9501          offset_and_length, res.body.size());
9502      res.set_header("Content-Range", content_range);
9503
9504      assert(offset + length <= res.body.size());
9505      res.body = res.body.substr(offset, length);
9506    } else {
9507      std::string data;
9508      detail::make_multipart_ranges_data(req, res, boundary, content_type,
9509                                         res.body.size(), data);
9510      res.body.swap(data);
9511    }
9512
9513    if (type != detail::EncodingType::None) {
9514      output_pre_compression_log(req, res);
9515
9516      if (auto compressor = detail::make_compressor(type)) {
9517        std::string compressed;
9518        if (compressor->compress(res.body.data(), res.body.size(), true,
9519                                 [&](const char *data, size_t data_len) {
9520                                   compressed.append(data, data_len);
9521                                   return true;
9522                                 })) {
9523          res.body.swap(compressed);
9524          res.set_header("Content-Encoding", detail::encoding_name(type));
9525          res.set_header("Vary", "Accept-Encoding");
9526        }
9527      }
9528    }
9529
9530    res.content_length_ = res.body.size();
9531    res.set_header("Content-Length", std::to_string(res.content_length_));
9532  }
9533}
9534
9535bool Server::dispatch_request_for_content_reader(
9536    Request &req, Response &res, ContentReader content_reader,
9537    const HandlersForContentReader &handlers) const {
9538  for (const auto &x : handlers) {
9539    const auto &matcher = x.first;
9540    const auto &handler = x.second;
9541
9542    if (matcher->match(req)) {
9543      req.matched_route = matcher->pattern();
9544      if (!pre_request_handler_ ||
9545          pre_request_handler_(req, res) != HandlerResponse::Handled) {
9546        handler(req, res, content_reader);
9547      }
9548      return true;
9549    }
9550  }
9551  return false;
9552}
9553
9554std::string
9555get_client_ip(const std::string &x_forwarded_for,
9556              const std::vector<std::string> &trusted_proxies) {
9557  // X-Forwarded-For is a comma-separated list per RFC 7239
9558  std::vector<std::string> ip_list;
9559  detail::split(x_forwarded_for.data(),
9560                x_forwarded_for.data() + x_forwarded_for.size(), ',',
9561                [&](const char *b, const char *e) {
9562                  auto r = detail::trim(b, e, 0, static_cast<size_t>(e - b));
9563                  ip_list.emplace_back(std::string(b + r.first, b + r.second));
9564                });
9565
9566  // A malformed X-Forwarded-For (empty, comma-only, whitespace-only) yields
9567  // no segments. Signal "no client IP derived" with an empty string so the
9568  // caller can fall back to the connection-level remote address.
9569  if (ip_list.empty()) { return std::string(); }
9570
9571  // Each hop appends the address it received the request from, so the rightmost
9572  // entries are the ones written by our own infrastructure while the leftmost
9573  // are whatever the original client chose to send. Walk from the right and
9574  // skip trusted proxies; the first address that is not a trusted proxy is the
9575  // furthest point still attributable to a real hop, i.e. the client. Scanning
9576  // from the left instead lets a client forge an arbitrary address by following
9577  // it with a trusted proxy's address, which the left-to-right scan then
9578  // returned as the client.
9579  for (size_t i = ip_list.size(); i-- > 0;) {
9580    const auto &ip = ip_list[i];
9581
9582    auto is_trusted_proxy =
9583        std::any_of(trusted_proxies.begin(), trusted_proxies.end(),
9584                    [&](const std::string &proxy) { return ip == proxy; });
9585
9586    if (!is_trusted_proxy) { return ip; }
9587  }
9588
9589  // Every hop was a trusted proxy; fall back to the first entry.
9590  return ip_list.front();
9591}
9592
9593bool
9594Server::process_request(Stream &strm, const std::string &remote_addr,
9595                        int remote_port, const std::string &local_addr,
9596                        int local_port, bool close_connection,
9597                        bool &connection_closed,
9598                        const std::function<void(Request &)> &setup_request,
9599                        bool *websocket_upgraded) {
9600  std::array<char, 2048> buf{};
9601
9602  detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
9603
9604  // Connection has been closed on client
9605  if (!line_reader.getline()) { return false; }
9606
9607  Request req;
9608  req.start_time_ = std::chrono::steady_clock::now();
9609  req.remote_addr = remote_addr;
9610  req.remote_port = remote_port;
9611  req.local_addr = local_addr;
9612  req.local_port = local_port;
9613
9614  Response res;
9615  res.version = "HTTP/1.1";
9616  res.headers = default_headers_;
9617
9618  // Request line and headers
9619  if (!parse_request_line(line_reader.ptr(), req)) {
9620    res.status = StatusCode::BadRequest_400;
9621    output_error_log(Error::InvalidRequestLine, &req);
9622    return write_response(strm, close_connection, req, res);
9623  }
9624
9625  // Request headers
9626  if (!detail::read_headers(strm, req.headers)) {
9627    res.status = StatusCode::BadRequest_400;
9628    output_error_log(Error::InvalidHeaders, &req);
9629    return write_response(strm, close_connection, req, res);
9630  }
9631
9632  // RFC 9112 §6.3: Reject requests whose framing is ambiguous, which would
9633  // otherwise let an intermediary and this parser disagree on where the body
9634  // ends and enable request smuggling. Two cases: a non-zero Content-Length
9635  // alongside any Transfer-Encoding (Content-Length: 0 is tolerated for
9636  // compatibility with existing clients), and a Transfer-Encoding whose final
9637  // coding is not chunked, which leaves the body length undeterminable. The
9638  // latter must not fall through to the "no body" path, or the body bytes are
9639  // parsed as the next request on a persistent connection.
9640  if (detail::has_conflicting_content_length(req.headers) ||
9641      (req.has_header("Transfer-Encoding") &&
9642       !detail::is_chunked_transfer_encoding(req.headers))) {
9643    connection_closed = true;
9644    res.status = StatusCode::BadRequest_400;
9645    return write_response(strm, close_connection, req, res);
9646  }
9647
9648  // Check if the request URI doesn't exceed the limit
9649  if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
9650    connection_closed = true;
9651    res.status = StatusCode::UriTooLong_414;
9652    output_error_log(Error::ExceedUriMaxLength, &req);
9653    return write_response(strm, close_connection, req, res);
9654  }
9655
9656  if (detail::has_header_token(req.headers, "Connection", "close")) {
9657    connection_closed = true;
9658  }
9659
9660  if (req.version == "HTTP/1.0" &&
9661      !detail::has_header_token(req.headers, "Connection", "keep-alive")) {
9662    connection_closed = true;
9663  }
9664
9665  // Only honor X-Forwarded-For if the peer on the actual TCP connection is
9666  // itself a trusted proxy. Otherwise any direct client could spoof
9667  // remote_addr simply by sending an arbitrary X-Forwarded-For header.
9668  auto is_trusted_peer = std::any_of(
9669      trusted_proxies_.begin(), trusted_proxies_.end(),
9670      [&](const std::string &proxy) { return proxy == remote_addr; });
9671
9672  if (is_trusted_peer && req.has_header("X-Forwarded-For")) {
9673    // Some proxies append the address they observed as a separate
9674    // X-Forwarded-For field line instead of extending the one the client sent
9675    // (e.g. HAProxy's "option forwardfor"), so the whole combined value has to
9676    // be scanned. Reading only the first occurrence would hand back the
9677    // client-supplied, and therefore forgeable, value.
9678    auto x_forwarded_for =
9679        detail::get_combined_header_value(req.headers, "X-Forwarded-For");
9680    auto derived = get_client_ip(x_forwarded_for, trusted_proxies_);
9681    req.remote_addr = derived.empty() ? remote_addr : derived;
9682  } else {
9683    req.remote_addr = remote_addr;
9684  }
9685  req.remote_port = remote_port;
9686
9687  req.local_addr = local_addr;
9688  req.local_port = local_port;
9689
9690  if (req.has_header("Accept")) {
9691    auto accept_header =
9692        detail::get_combined_header_value(req.headers, "Accept");
9693    if (!detail::parse_accept_header(accept_header, req.accept_content_types)) {
9694      connection_closed = true;
9695      res.status = StatusCode::BadRequest_400;
9696      output_error_log(Error::HTTPParsing, &req);
9697      return write_response(strm, close_connection, req, res);
9698    }
9699  }
9700
9701  if (req.has_header("Range")) {
9702    const auto &range_header_value = req.get_header_value("Range");
9703    if (!detail::parse_range_header(range_header_value, req.ranges)) {
9704      connection_closed = true;
9705      res.status = StatusCode::RangeNotSatisfiable_416;
9706      output_error_log(Error::InvalidRangeHeader, &req);
9707      return write_response(strm, close_connection, req, res);
9708    }
9709  }
9710
9711  if (setup_request) { setup_request(req); }
9712
9713  // RFC 9110 10.1.1: Expect is a comma-separated list whose value is
9714  // case-insensitive, and a 100-continue expectation in an HTTP/1.0 request
9715  // must be ignored. An expectation we do not recognize is left alone; the
9716  // 417 the section allows for one is a MAY, not a requirement.
9717  if (req.version != "HTTP/1.0" &&
9718      detail::has_header_token(req.headers, "Expect", "100-continue")) {
9719    int status = StatusCode::Continue_100;
9720    if (expect_100_continue_handler_) {
9721      status = expect_100_continue_handler_(req, res);
9722    }
9723    switch (status) {
9724    case StatusCode::Continue_100:
9725    case StatusCode::ExpectationFailed_417:
9726      detail::write_response_line(strm, status);
9727      strm.write("\r\n");
9728      break;
9729    default:
9730      connection_closed = true;
9731      return write_response(strm, true, req, res);
9732    }
9733  }
9734
9735  // Setup `is_connection_closed` method
9736  auto sock = strm.socket();
9737  req.is_connection_closed = [sock]() {
9738    return !detail::is_socket_alive(sock);
9739  };
9740
9741  // WebSocket upgrade
9742  // Check pre_routing_handler_ before upgrading so that authentication
9743  // and other middleware can reject the request with an HTTP response
9744  // (e.g., 401) before the protocol switches.
9745  if (detail::is_websocket_upgrade(req)) {
9746    if (pre_routing_handler_ &&
9747        pre_routing_handler_(req, res) == HandlerResponse::Handled) {
9748      if (res.status == -1) { res.status = StatusCode::OK_200; }
9749      return write_response(strm, close_connection, req, res);
9750    }
9751    // Find matching WebSocket handler
9752    for (const auto &entry : websocket_handlers_) {
9753      if (entry.matcher->match(req)) {
9754        // Compute accept key
9755        auto client_key = req.get_header_value("Sec-WebSocket-Key");
9756        auto accept_key = detail::websocket_accept_key(client_key);
9757
9758        // Negotiate subprotocol
9759        std::string selected_subprotocol;
9760        if (entry.sub_protocol_selector) {
9761          auto protocol_header = detail::get_combined_header_value(
9762              req.headers, "Sec-WebSocket-Protocol");
9763          if (!protocol_header.empty()) {
9764            std::vector<std::string> protocols;
9765            detail::split(protocol_header.data(),
9766                          protocol_header.data() + protocol_header.size(), ',',
9767                          [&](const char *b, const char *e) {
9768                            protocols.emplace_back(b, e);
9769                          });
9770            selected_subprotocol = entry.sub_protocol_selector(protocols);
9771          }
9772        }
9773
9774        // Send 101 Switching Protocols
9775        std::string handshake_response = "HTTP/1.1 101 Switching Protocols\r\n"
9776                                         "Upgrade: websocket\r\n"
9777                                         "Connection: Upgrade\r\n"
9778                                         "Sec-WebSocket-Accept: " +
9779                                         accept_key + "\r\n";
9780        if (!selected_subprotocol.empty()) {
9781          if (!detail::fields::is_field_value(selected_subprotocol)) {
9782            return false;
9783          }
9784          handshake_response +=
9785              "Sec-WebSocket-Protocol: " + selected_subprotocol + "\r\n";
9786        }
9787        handshake_response += "\r\n";
9788        if (strm.write(handshake_response.data(), handshake_response.size()) <
9789            0) {
9790          return false;
9791        }
9792
9793        connection_closed = true;
9794        if (websocket_upgraded) { *websocket_upgraded = true; }
9795
9796        {
9797#ifdef CPPHTTPLIB_SSL_ENABLED
9798          if (req.ssl) {
9799            // wss: the heartbeat ping thread and the read path enter the same
9800            // TLS session from different threads. Hand the WebSocket a stream
9801            // that serializes every TLS call, so the shared SSLSocketStream on
9802            // the plain HTTP/HTTPS paths stays untouched.
9803            auto ws_strm =
9804                std::unique_ptr<Stream>(new detail::WebSocketSSLStream(
9805                    strm.socket(), const_cast<tls::session_t>(req.ssl),
9806                    CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND, 0,
9807                    write_timeout_sec_, write_timeout_usec_));
9808            ws::WebSocket ws(std::move(ws_strm), req, true,
9809                             websocket_ping_interval_sec_,
9810                             websocket_max_missed_pongs_);
9811            entry.handler(req, ws);
9812            return true;
9813          }
9814#endif
9815          // Use WebSocket-specific read timeout instead of HTTP timeout
9816          strm.set_read_timeout(CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND,
9817                                0);
9818          ws::WebSocket ws(strm, req, true, websocket_ping_interval_sec_,
9819                           websocket_max_missed_pongs_);
9820          entry.handler(req, ws);
9821        }
9822        return true;
9823      }
9824    }
9825    // No matching handler - fall through to 404
9826  }
9827
9828  // Routing
9829  auto routed = false;
9830#ifdef CPPHTTPLIB_NO_EXCEPTIONS
9831  routed = routing(req, res, strm);
9832#else
9833  try {
9834    routed = routing(req, res, strm);
9835  } catch (std::exception &) {
9836    if (exception_handler_) {
9837      auto ep = std::current_exception();
9838      exception_handler_(req, res, ep);
9839      routed = true;
9840    } else {
9841      res.status = StatusCode::InternalServerError_500;
9842    }
9843  } catch (...) {
9844    if (exception_handler_) {
9845      auto ep = std::current_exception();
9846      exception_handler_(req, res, ep);
9847      routed = true;
9848    } else {
9849      res.status = StatusCode::InternalServerError_500;
9850    }
9851  }
9852#endif
9853  auto ret = false;
9854  if (routed) {
9855    if (res.status == -1) {
9856      res.status = req.ranges.empty() ? StatusCode::OK_200
9857                                      : StatusCode::PartialContent_206;
9858    }
9859
9860    // Serve file content by using a content provider
9861    auto file_open_error = false;
9862    if (!res.file_content_path_.empty()) {
9863      const auto &path = res.file_content_path_;
9864      auto mm = std::make_shared<detail::mmap>(path.c_str());
9865      if (!mm->is_open()) {
9866        res.body.clear();
9867        res.content_length_ = 0;
9868        res.content_provider_ = nullptr;
9869        res.status = StatusCode::NotFound_404;
9870        output_error_log(Error::OpenFile, &req);
9871        file_open_error = true;
9872      } else {
9873        auto content_type = res.file_content_content_type_;
9874        if (content_type.empty()) {
9875          content_type = detail::find_content_type(
9876              path, file_extension_and_mimetype_map_, default_file_mimetype_);
9877        }
9878
9879        detail::set_file_content_provider(
9880            res, mm, content_type,
9881            static_file_encoding(req, res, content_type, mm->size()));
9882      }
9883    }
9884
9885    if (file_open_error) {
9886      ret = write_response(strm, close_connection, req, res);
9887    } else if (detail::range_error(req, res)) {
9888      res.body.clear();
9889      res.content_length_ = 0;
9890      res.content_provider_ = nullptr;
9891      res.status = StatusCode::RangeNotSatisfiable_416;
9892      ret = write_response(strm, close_connection, req, res);
9893    } else {
9894      ret = write_response_with_content(strm, close_connection, req, res);
9895    }
9896  } else {
9897    if (res.status == -1) { res.status = StatusCode::NotFound_404; }
9898    ret = write_response(strm, close_connection, req, res);
9899  }
9900
9901  // Drain any unconsumed framed body to prevent request smuggling on
9902  // keep-alive. Without framing there is no body to drain — reading would
9903  // consume the next request (issue #2450). If the response has committed the
9904  // connection to close, there is no next request to protect.
9905  if (!req.body_consumed_ && detail::has_framed_body(req)) {
9906    if (detail::has_header_token(res.headers, "Connection", "close")) {
9907      connection_closed = true;
9908    } else {
9909      int dummy_status;
9910      if (!detail::read_content(
9911              strm, req, payload_max_length_, dummy_status, nullptr,
9912              [](const char *, size_t, size_t, size_t) { return true; },
9913              false)) {
9914        connection_closed = true;
9915      }
9916    }
9917  }
9918
9919  return ret;
9920}
9921
9922bool Server::is_valid() const { return !has_invalid_registration_; }
9923
9924bool Server::process_and_close_socket(socket_t sock) {
9925  std::string remote_addr;
9926  int remote_port = 0;
9927  detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
9928
9929  std::string local_addr;
9930  int local_port = 0;
9931  detail::get_local_ip_and_port(sock, local_addr, local_port);
9932
9933  bool websocket_upgraded = false;
9934  auto ret = serve_guarded([&]() {
9935    return detail::process_server_socket(
9936        svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_,
9937        read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
9938        write_timeout_usec_,
9939        [&](Stream &strm, bool close_connection, bool &connection_closed) {
9940          return process_request(strm, remote_addr, remote_port, local_addr,
9941                                 local_port, close_connection,
9942                                 connection_closed, nullptr,
9943                                 &websocket_upgraded);
9944        });
9945  });
9946
9947  detail::drain_and_close_socket(sock);
9948  return ret;
9949}
9950
9951void Server::output_log(const Request &req, const Response &res) const {
9952  if (logger_) {
9953    std::lock_guard<std::mutex> guard(logger_mutex_);
9954    logger_(req, res);
9955  }
9956}
9957
9958void Server::output_pre_compression_log(const Request &req,
9959                                               const Response &res) const {
9960  if (pre_compression_logger_) {
9961    std::lock_guard<std::mutex> guard(logger_mutex_);
9962    pre_compression_logger_(req, res);
9963  }
9964}
9965
9966void Server::output_error_log(const Error &err,
9967                                     const Request *req) const {
9968  if (error_logger_) {
9969    std::lock_guard<std::mutex> guard(logger_mutex_);
9970    error_logger_(err, req);
9971  }
9972}
9973
9974/*
9975 * Group 5: ClientImpl and Client (Universal) implementation
9976 */
9977// HTTP client implementation
9978ClientImpl::ClientImpl(const std::string &host)
9979    : ClientImpl(host, 80, std::string(), std::string()) {}
9980
9981ClientImpl::ClientImpl(const std::string &host, int port)
9982    : ClientImpl(host, port, std::string(), std::string()) {}
9983
9984ClientImpl::ClientImpl(const std::string &host, int port,
9985                              const std::string &client_cert_path,
9986                              const std::string &client_key_path)
9987    : host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port),
9988      client_cert_path_(client_cert_path), client_key_path_(client_key_path) {}
9989
9990ClientImpl::~ClientImpl() {
9991  // Wait until all the requests in flight are handled.
9992  size_t retry_count = 10;
9993  while (retry_count-- > 0) {
9994    {
9995      std::lock_guard<std::mutex> guard(socket_mutex_);
9996      if (socket_requests_in_flight_ == 0) { break; }
9997    }
9998    std::this_thread::sleep_for(std::chrono::milliseconds{1});
9999  }
10000
10001  std::lock_guard<std::mutex> guard(socket_mutex_);
10002  shutdown_socket(socket_);
10003  close_socket(socket_);
10004}
10005
10006bool ClientImpl::is_valid() const { return true; }
10007
10008void ClientImpl::copy_settings(const ClientImpl &rhs) {
10009  client_cert_path_ = rhs.client_cert_path_;
10010  client_key_path_ = rhs.client_key_path_;
10011  connection_timeout_sec_ = rhs.connection_timeout_sec_;
10012  read_timeout_sec_ = rhs.read_timeout_sec_;
10013  read_timeout_usec_ = rhs.read_timeout_usec_;
10014  write_timeout_sec_ = rhs.write_timeout_sec_;
10015  write_timeout_usec_ = rhs.write_timeout_usec_;
10016  max_timeout_msec_ = rhs.max_timeout_msec_;
10017  basic_auth_username_ = rhs.basic_auth_username_;
10018  basic_auth_password_ = rhs.basic_auth_password_;
10019  bearer_token_auth_token_ = rhs.bearer_token_auth_token_;
10020  keep_alive_ = rhs.keep_alive_;
10021  follow_location_ = rhs.follow_location_;
10022  path_encode_ = rhs.path_encode_;
10023  address_family_ = rhs.address_family_;
10024  tcp_nodelay_ = rhs.tcp_nodelay_;
10025  ipv6_v6only_ = rhs.ipv6_v6only_;
10026  socket_options_ = rhs.socket_options_;
10027  compress_ = rhs.compress_;
10028  decompress_ = rhs.decompress_;
10029  payload_max_length_ = rhs.payload_max_length_;
10030  has_payload_max_length_ = rhs.has_payload_max_length_;
10031  interface_ = rhs.interface_;
10032  proxy_host_ = rhs.proxy_host_;
10033  proxy_port_ = rhs.proxy_port_;
10034  proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_;
10035  proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_;
10036  proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_;
10037  no_proxy_entries_ = rhs.no_proxy_entries_;
10038  logger_ = rhs.logger_;
10039  error_logger_ = rhs.error_logger_;
10040
10041#ifdef CPPHTTPLIB_SSL_ENABLED
10042  digest_auth_username_ = rhs.digest_auth_username_;
10043  digest_auth_password_ = rhs.digest_auth_password_;
10044  proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_;
10045  proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_;
10046  ca_cert_file_path_ = rhs.ca_cert_file_path_;
10047  ca_cert_dir_path_ = rhs.ca_cert_dir_path_;
10048  server_certificate_verification_ = rhs.server_certificate_verification_;
10049  server_hostname_verification_ = rhs.server_hostname_verification_;
10050  system_ca_mode_ = rhs.system_ca_mode_;
10051#endif
10052}
10053
10054bool
10055ClientImpl::is_proxy_enabled_for_host(const std::string &host) const {
10056  if (proxy_host_.empty() || proxy_port_ == -1) { return false; }
10057  if (no_proxy_entries_.empty()) { return true; }
10058  // host_ is const so its normalized form is invariant; cache it. The
10059  // cross-host path (setup_redirect_client passing next_host) re-normalizes.
10060  if (host == host_) {
10061    if (!host_normalized_valid_) {
10062      host_normalized_ = detail::normalize_target(host_);
10063      host_normalized_valid_ = true;
10064    }
10065    return !detail::host_matches_no_proxy(host_normalized_, no_proxy_entries_);
10066  }
10067  auto target = detail::normalize_target(host);
10068  return !detail::host_matches_no_proxy(target, no_proxy_entries_);
10069}
10070
10071socket_t ClientImpl::create_client_socket(Error &error) const {
10072  if (is_proxy_enabled_for_host(host_)) {
10073    return detail::create_client_socket(
10074        proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_,
10075        ipv6_v6only_, socket_options_, connection_timeout_sec_,
10076        connection_timeout_usec_, read_timeout_sec_, read_timeout_usec_,
10077        write_timeout_sec_, write_timeout_usec_, interface_, error);
10078  }
10079
10080  // Check is custom IP or hostname specified for host_
10081  std::string connect_host;
10082  std::string ip;
10083  detail::apply_addr_map(addr_map_, host_, connect_host, ip);
10084
10085  return detail::create_client_socket(
10086      connect_host, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
10087      socket_options_, connection_timeout_sec_, connection_timeout_usec_,
10088      read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
10089      write_timeout_usec_, interface_, error);
10090}
10091
10092bool ClientImpl::create_and_connect_socket(Socket &socket,
10093                                                  Error &error) {
10094  auto sock = create_client_socket(error);
10095  if (sock == INVALID_SOCKET) { return false; }
10096  socket.sock = sock;
10097  return true;
10098}
10099
10100bool ClientImpl::ensure_socket_connection(Socket &socket, Error &error) {
10101  return create_and_connect_socket(socket, error);
10102}
10103
10104bool ClientImpl::setup_proxy_connection(
10105    Socket & /*socket*/,
10106    std::chrono::time_point<std::chrono::steady_clock> /*start_time*/,
10107    Response & /*res*/, bool & /*success*/, Error & /*error*/) {
10108  return true;
10109}
10110
10111void ClientImpl::shutdown_ssl(Socket & /*socket*/,
10112                                     bool /*shutdown_gracefully*/) {
10113  // If there are any requests in flight from threads other than us, then it's
10114  // a thread-unsafe race because individual ssl* objects are not thread-safe.
10115  assert(socket_requests_in_flight_ == 0 ||
10116         socket_requests_are_from_thread_ == std::this_thread::get_id());
10117}
10118
10119void ClientImpl::shutdown_socket(Socket &socket) const {
10120  if (socket.sock == INVALID_SOCKET) { return; }
10121  detail::shutdown_socket(socket.sock);
10122}
10123
10124void ClientImpl::close_socket(Socket &socket) {
10125  // If there are requests in flight in another thread, usually closing
10126  // the socket will be fine and they will simply receive an error when
10127  // using the closed socket, but it is still a bug since rarely the OS
10128  // may reassign the socket id to be used for a new socket, and then
10129  // suddenly they will be operating on a live socket that is different
10130  // than the one they intended!
10131  assert(socket_requests_in_flight_ == 0 ||
10132         socket_requests_are_from_thread_ == std::this_thread::get_id());
10133
10134  // It is also a bug if this happens while SSL is still active
10135#ifdef CPPHTTPLIB_SSL_ENABLED
10136  assert(socket.ssl == nullptr);
10137#endif
10138
10139  if (socket.sock == INVALID_SOCKET) { return; }
10140  detail::close_socket(socket.sock);
10141  socket.sock = INVALID_SOCKET;
10142}
10143
10144void ClientImpl::disconnect(bool gracefully) {
10145  shutdown_ssl(socket_, gracefully);
10146  shutdown_socket(socket_);
10147  close_socket(socket_);
10148}
10149
10150bool ClientImpl::read_response_line(Stream &strm, const Request &req,
10151                                           Response &res,
10152                                           bool skip_100_continue) const {
10153  std::array<char, 2048> buf{};
10154
10155  detail::stream_line_reader line_reader(strm, buf.data(), buf.size());
10156
10157  if (!line_reader.getline()) { return false; }
10158
10159  if (!detail::parse_status_line(line_reader.ptr(), res.version, res.status,
10160                                 res.reason)) {
10161    return req.method == "CONNECT";
10162  }
10163
10164  // Ignore '100 Continue' (only when not using Expect: 100-continue explicitly)
10165  while (skip_100_continue && res.status == StatusCode::Continue_100) {
10166    if (!line_reader.getline()) { return false; } // CRLF
10167    if (!line_reader.getline()) { return false; } // next response line
10168
10169    if (!detail::parse_status_line(line_reader.ptr(), res.version, res.status,
10170                                   res.reason)) {
10171      return false;
10172    }
10173  }
10174
10175  return true;
10176}
10177
10178bool ClientImpl::send(Request &req, Response &res, Error &error) {
10179  std::lock_guard<std::recursive_mutex> request_mutex_guard(request_mutex_);
10180  auto ret = send_(req, res, error);
10181  if (error == Error::SSLPeerCouldBeClosed_) {
10182    assert(!ret);
10183    ret = send_(req, res, error);
10184    // If still failing with SSLPeerCouldBeClosed_, convert to Read error
10185    if (error == Error::SSLPeerCouldBeClosed_) { error = Error::Read; }
10186  }
10187  return ret;
10188}
10189
10190bool ClientImpl::send_(Request &req, Response &res, Error &error) {
10191  {
10192    std::lock_guard<std::mutex> guard(socket_mutex_);
10193
10194    // Set this to false immediately - if it ever gets set to true by the end
10195    // of the request, we know another thread instructed us to close the
10196    // socket.
10197    socket_should_be_closed_when_request_is_done_ = false;
10198
10199    auto is_alive = false;
10200    if (socket_.is_open()) {
10201      is_alive = detail::is_socket_alive(socket_.sock);
10202
10203#ifdef CPPHTTPLIB_SSL_ENABLED
10204      if (is_alive && is_ssl()) {
10205        if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
10206          is_alive = false;
10207        }
10208      }
10209#endif
10210
10211      if (!is_alive) {
10212        // Peer seems gone — non-graceful shutdown to avoid SIGPIPE.
10213        disconnect(/*gracefully=*/false);
10214      }
10215    }
10216
10217    if (!is_alive) {
10218      if (!ensure_socket_connection(socket_, error)) {
10219        output_error_log(error, &req);
10220        return false;
10221      }
10222
10223      {
10224        auto success = true;
10225        if (!setup_proxy_connection(socket_, req.start_time_, res, success,
10226                                    error)) {
10227          if (!success) { output_error_log(error, &req); }
10228          return success;
10229        }
10230      }
10231    }
10232
10233    // Mark the current socket as being in use so that it cannot be closed by
10234    // anyone else while this request is ongoing, even though we will be
10235    // releasing the mutex.
10236    if (socket_requests_in_flight_ > 1) {
10237      assert(socket_requests_are_from_thread_ == std::this_thread::get_id());
10238    }
10239    socket_requests_in_flight_ += 1;
10240    socket_requests_are_from_thread_ = std::this_thread::get_id();
10241  }
10242
10243  for (const auto &header : default_headers_) {
10244    if (req.headers.find(header.first) == req.headers.end()) {
10245      req.headers.insert(header);
10246    }
10247  }
10248
10249  auto ret = false;
10250  auto close_connection = !keep_alive_;
10251
10252  auto se = detail::scope_exit([&]() {
10253    // Briefly lock mutex in order to mark that a request is no longer ongoing
10254    std::lock_guard<std::mutex> guard(socket_mutex_);
10255    socket_requests_in_flight_ -= 1;
10256    if (socket_requests_in_flight_ <= 0) {
10257      assert(socket_requests_in_flight_ == 0);
10258      socket_requests_are_from_thread_ = std::thread::id();
10259    }
10260
10261    if (socket_should_be_closed_when_request_is_done_ || close_connection ||
10262        !ret) {
10263      disconnect(/*gracefully=*/true);
10264    }
10265  });
10266
10267  ret = process_socket(socket_, req.start_time_, [&](Stream &strm) {
10268    return handle_request(strm, req, res, close_connection, error);
10269  });
10270
10271  if (!ret) {
10272    if (error == Error::Success) {
10273      error = Error::Unknown;
10274      output_error_log(error, &req);
10275    }
10276  }
10277
10278  return ret;
10279}
10280
10281Result ClientImpl::send(const Request &req) {
10282  auto req2 = req;
10283  return send_(std::move(req2));
10284}
10285
10286Result ClientImpl::send_(Request &&req) {
10287  auto res = detail::make_unique<Response>();
10288  auto error = Error::Success;
10289  auto ret = send(req, *res, error);
10290#ifdef CPPHTTPLIB_SSL_ENABLED
10291  return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers),
10292                last_ssl_error_, last_backend_error_};
10293#else
10294  return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)};
10295#endif
10296}
10297
10298void ClientImpl::prepare_default_headers(Request &r, bool for_stream,
10299                                                const std::string &ct) {
10300  (void)for_stream;
10301  // Default headers are meant for the origin and may carry its credentials, so
10302  // keep them off the CONNECT request the proxy reads.
10303  if (r.method != "CONNECT") {
10304    for (const auto &header : default_headers_) {
10305      if (!r.has_header(header.first)) { r.headers.insert(header); }
10306    }
10307  }
10308
10309  // RFC 9110 5.3 recommends sending control data such as Host first, so
10310  // prepend it rather than appending it after the caller's own fields.
10311  if (!r.has_header("Host")) {
10312    r.headers.emplace_front(
10313        "Host", detail::make_default_host_header_value(host_, port_, is_ssl(),
10314                                                       address_family_));
10315  }
10316
10317  if (!r.has_header("Accept")) { r.headers.emplace("Accept", "*/*"); }
10318
10319  if (!r.content_receiver) {
10320    if (!r.has_header("Accept-Encoding")) {
10321      std::string accept_encoding;
10322#ifdef CPPHTTPLIB_BROTLI_SUPPORT
10323      accept_encoding = "br";
10324#endif
10325#ifdef CPPHTTPLIB_ZLIB_SUPPORT
10326      if (!accept_encoding.empty()) { accept_encoding += ", "; }
10327      accept_encoding += "gzip, deflate";
10328#endif
10329#ifdef CPPHTTPLIB_ZSTD_SUPPORT
10330      if (!accept_encoding.empty()) { accept_encoding += ", "; }
10331      accept_encoding += "zstd";
10332#endif
10333      r.set_header("Accept-Encoding", accept_encoding);
10334    }
10335
10336    detail::add_default_user_agent_header(r);
10337  }
10338
10339  if (!r.body.empty()) {
10340    if (!ct.empty() && !r.has_header("Content-Type")) {
10341      r.headers.emplace("Content-Type", ct);
10342    }
10343    if (!r.has_header("Content-Length")) {
10344      r.headers.emplace("Content-Length", std::to_string(r.body.size()));
10345    }
10346  }
10347}
10348
10349ClientImpl::StreamHandle
10350ClientImpl::open_stream(const std::string &method, const std::string &path,
10351                        const Params &params, const Headers &headers,
10352                        const std::string &body,
10353                        const std::string &content_type) {
10354  StreamHandle handle;
10355  handle.response = detail::make_unique<Response>();
10356  handle.error = Error::Success;
10357
10358  // Encode the target exactly like the buffered send path does, so that the
10359  // same `path` produces the same request line through either API.
10360  auto raw_query_path =
10361      params.empty() ? path : append_query_params(path, params);
10362  auto query_path = detail::encode_request_target(raw_query_path, path_encode_);
10363
10364  handle.connection_ = detail::make_unique<ClientConnection>();
10365
10366  {
10367    std::lock_guard<std::mutex> guard(socket_mutex_);
10368
10369    auto is_alive = false;
10370    if (socket_.is_open()) {
10371      is_alive = detail::is_socket_alive(socket_.sock);
10372#ifdef CPPHTTPLIB_SSL_ENABLED
10373      if (is_alive && is_ssl()) {
10374        if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
10375          is_alive = false;
10376        }
10377      }
10378#endif
10379      if (!is_alive) { disconnect(/*gracefully=*/false); }
10380    }
10381
10382    if (!is_alive) {
10383      if (!ensure_socket_connection(socket_, handle.error)) {
10384        handle.response.reset();
10385        return handle;
10386      }
10387
10388      {
10389        auto success = true;
10390        auto start_time = std::chrono::steady_clock::now();
10391        if (!setup_proxy_connection(socket_, start_time, *handle.response,
10392                                    success, handle.error)) {
10393          if (!success) { handle.response.reset(); }
10394          return handle;
10395        }
10396      }
10397    }
10398
10399    transfer_socket_ownership_to_handle(handle);
10400  }
10401
10402#ifdef CPPHTTPLIB_SSL_ENABLED
10403  if (is_ssl() && handle.connection_->session) {
10404    handle.socket_stream_ = detail::make_unique<detail::SSLSocketStream>(
10405        handle.connection_->sock, handle.connection_->session,
10406        read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
10407        write_timeout_usec_);
10408  } else {
10409    handle.socket_stream_ = detail::make_unique<detail::SocketStream>(
10410        handle.connection_->sock, read_timeout_sec_, read_timeout_usec_,
10411        write_timeout_sec_, write_timeout_usec_);
10412  }
10413#else
10414  handle.socket_stream_ = detail::make_unique<detail::SocketStream>(
10415      handle.connection_->sock, read_timeout_sec_, read_timeout_usec_,
10416      write_timeout_sec_, write_timeout_usec_);
10417#endif
10418  handle.stream_ = handle.socket_stream_.get();
10419
10420  Request req;
10421  req.method = method;
10422  req.path = query_path;
10423  req.headers = headers;
10424  req.body = body;
10425
10426  prepare_default_headers(req, true, content_type);
10427
10428  auto &strm = *handle.stream_;
10429  if (detail::write_request_line(strm, req.method, req.path) < 0) {
10430    handle.error = Error::Write;
10431    handle.response.reset();
10432    return handle;
10433  }
10434
10435  if (!detail::check_and_write_headers(strm, req.headers, header_writer_,
10436                                       handle.error)) {
10437    handle.response.reset();
10438    return handle;
10439  }
10440
10441  if (!body.empty()) {
10442    if (strm.write(body.data(), body.size()) < 0) {
10443      handle.error = Error::Write;
10444      handle.response.reset();
10445      return handle;
10446    }
10447  }
10448
10449  if (!read_response_line(strm, req, *handle.response) ||
10450      !detail::read_headers(strm, handle.response->headers)) {
10451    handle.error = Error::Read;
10452    handle.response.reset();
10453    return handle;
10454  }
10455
10456  // Same framing check as ClientImpl::process_request(). A HEAD or bodyless
10457  // (204/304) response legitimately carries framing headers with no body.
10458  if (method != "HEAD" &&
10459      handle.response->status != StatusCode::NoContent_204 &&
10460      handle.response->status != StatusCode::NotModified_304 &&
10461      detail::has_conflicting_content_length(handle.response->headers)) {
10462    handle.error = Error::Read;
10463    handle.response.reset();
10464    return handle;
10465  }
10466
10467  handle.body_reader_.stream = handle.stream_;
10468  handle.body_reader_.payload_max_length = payload_max_length_;
10469
10470  if (handle.response->has_header("Content-Length")) {
10471    bool is_invalid = false;
10472    auto content_length = detail::get_header_value_u64(
10473        handle.response->headers, "Content-Length", 0, 0, is_invalid);
10474    if (is_invalid) {
10475      handle.error = Error::Read;
10476      handle.response.reset();
10477      return handle;
10478    }
10479    handle.body_reader_.has_content_length = true;
10480    handle.body_reader_.content_length = content_length;
10481  }
10482
10483  handle.body_reader_.chunked =
10484      detail::is_chunked_transfer_encoding(handle.response->headers);
10485
10486  auto content_encoding = detail::get_combined_header_value(
10487      handle.response->headers, "Content-Encoding");
10488  if (!content_encoding.empty()) {
10489    // Same policy as prepare_content_receiver(): reject a coding we know about
10490    // but were not built with, pass an unrecognized one through as-is.
10491    handle.decompressor_ = detail::create_decompressor(content_encoding);
10492    if (!handle.decompressor_) {
10493      if (detail::is_known_content_encoding(content_encoding)) {
10494        handle.error = Error::UnsupportedContentEncoding;
10495        handle.response.reset();
10496        return handle;
10497      }
10498    } else if (!handle.decompressor_->is_valid()) {
10499      handle.error = Error::Compression;
10500      handle.response.reset();
10501      return handle;
10502    }
10503  }
10504
10505  return handle;
10506}
10507
10508ssize_t ClientImpl::StreamHandle::read(char *buf, size_t len) {
10509  if (!is_valid() || !response) { return -1; }
10510
10511  if (decompressor_) { return read_with_decompression(buf, len); }
10512  auto n = detail::read_body_content(stream_, body_reader_, buf, len);
10513
10514  if (n <= 0 && body_reader_.chunked && !trailers_parsed_ && stream_) {
10515    trailers_parsed_ = true;
10516    if (body_reader_.chunked_decoder) {
10517      if (!body_reader_.chunked_decoder->parse_trailers_into(
10518              response->trailers, response->headers)) {
10519        return n;
10520      }
10521    } else {
10522      detail::ChunkedDecoder dec(*stream_);
10523      if (!dec.parse_trailers_into(response->trailers, response->headers)) {
10524        return n;
10525      }
10526    }
10527  }
10528
10529  return n;
10530}
10531
10532ssize_t ClientImpl::StreamHandle::read_with_decompression(char *buf,
10533                                                                 size_t len) {
10534  if (decompress_offset_ < decompress_buffer_.size()) {
10535    auto available = decompress_buffer_.size() - decompress_offset_;
10536    auto to_copy = (std::min)(len, available);
10537    std::memcpy(buf, decompress_buffer_.data() + decompress_offset_, to_copy);
10538    decompress_offset_ += to_copy;
10539    decompressed_bytes_read_ += to_copy;
10540    return static_cast<ssize_t>(to_copy);
10541  }
10542
10543  decompress_buffer_.clear();
10544  decompress_offset_ = 0;
10545
10546  constexpr size_t kDecompressionBufferSize = 8192;
10547  char compressed_buf[kDecompressionBufferSize];
10548
10549  while (true) {
10550    auto n = detail::read_body_content(stream_, body_reader_, compressed_buf,
10551                                       sizeof(compressed_buf));
10552
10553    if (n <= 0) { return n; }
10554
10555    bool decompress_ok = decompressor_->decompress(
10556        compressed_buf, static_cast<size_t>(n),
10557        [this](const char *data, size_t data_len) {
10558          decompress_buffer_.append(data, data_len);
10559          auto limit = body_reader_.payload_max_length;
10560          if (decompressed_bytes_read_ + decompress_buffer_.size() > limit) {
10561            return false;
10562          }
10563          return true;
10564        });
10565
10566    if (!decompress_ok) {
10567      body_reader_.last_error = Error::Read;
10568      return -1;
10569    }
10570
10571    if (!decompress_buffer_.empty()) { break; }
10572  }
10573
10574  auto to_copy = (std::min)(len, decompress_buffer_.size());
10575  std::memcpy(buf, decompress_buffer_.data(), to_copy);
10576  decompress_offset_ = to_copy;
10577  decompressed_bytes_read_ += to_copy;
10578  return static_cast<ssize_t>(to_copy);
10579}
10580
10581void ClientImpl::StreamHandle::parse_trailers_if_needed() {
10582  if (!response || !stream_ || !body_reader_.chunked || trailers_parsed_) {
10583    return;
10584  }
10585
10586  trailers_parsed_ = true;
10587
10588  const auto bufsiz = 128;
10589  char line_buf[bufsiz];
10590  detail::stream_line_reader line_reader(*stream_, line_buf, bufsiz);
10591
10592  if (!line_reader.getline()) { return; }
10593
10594  if (!detail::parse_trailers(line_reader, response->trailers,
10595                              response->headers)) {
10596    return;
10597  }
10598}
10599
10600namespace detail {
10601
10602ChunkedDecoder::ChunkedDecoder(Stream &s) : strm(s) {}
10603
10604ssize_t ChunkedDecoder::read_payload(char *buf, size_t len,
10605                                            size_t &out_chunk_offset,
10606                                            size_t &out_chunk_total) {
10607  if (finished) { return 0; }
10608
10609  if (chunk_remaining == 0) {
10610    stream_line_reader lr(strm, line_buf, sizeof(line_buf));
10611    if (!lr.getline()) { return -1; }
10612
10613    // RFC 9112 §7.1: chunk-size = 1*HEXDIG
10614    const char *p = lr.ptr();
10615    int v = 0;
10616    if (!is_hex(*p, v)) { return -1; }
10617
10618    size_t chunk_len = 0;
10619    constexpr size_t chunk_len_max = (std::numeric_limits<size_t>::max)();
10620    for (; is_hex(*p, v); ++p) {
10621      if (chunk_len > (chunk_len_max >> 4)) { return -1; }
10622      chunk_len = (chunk_len << 4) | static_cast<size_t>(v);
10623    }
10624
10625    while (is_space_or_tab(*p)) {
10626      ++p;
10627    }
10628    if (*p != '\0' && *p != ';' && *p != '\r' && *p != '\n') { return -1; }
10629
10630    if (chunk_len == 0) {
10631      chunk_remaining = 0;
10632      finished = true;
10633      out_chunk_offset = 0;
10634      out_chunk_total = 0;
10635      return 0;
10636    }
10637
10638    chunk_remaining = chunk_len;
10639    last_chunk_total = chunk_remaining;
10640    last_chunk_offset = 0;
10641  }
10642
10643  auto to_read = (std::min)(chunk_remaining, len);
10644  auto n = strm.read(buf, to_read);
10645  if (n <= 0) { return -1; }
10646
10647  auto offset_before = last_chunk_offset;
10648  last_chunk_offset += static_cast<size_t>(n);
10649  chunk_remaining -= static_cast<size_t>(n);
10650
10651  out_chunk_offset = offset_before;
10652  out_chunk_total = last_chunk_total;
10653
10654  if (chunk_remaining == 0) {
10655    stream_line_reader lr(strm, line_buf, sizeof(line_buf));
10656    if (!lr.getline()) { return -1; }
10657    if (std::strcmp(lr.ptr(), "\r\n") != 0) { return -1; }
10658  }
10659
10660  return n;
10661}
10662
10663bool ChunkedDecoder::parse_trailers_into(Headers &dest,
10664                                                const Headers &src_headers) {
10665  stream_line_reader lr(strm, line_buf, sizeof(line_buf));
10666  if (!lr.getline()) { return false; }
10667  return parse_trailers(lr, dest, src_headers);
10668}
10669
10670} // namespace detail
10671
10672void
10673ClientImpl::transfer_socket_ownership_to_handle(StreamHandle &handle) {
10674  handle.connection_->sock = socket_.sock;
10675#ifdef CPPHTTPLIB_SSL_ENABLED
10676  handle.connection_->session = socket_.ssl;
10677  socket_.ssl = nullptr;
10678#endif
10679  socket_.sock = INVALID_SOCKET;
10680}
10681
10682bool ClientImpl::handle_request(Stream &strm, Request &req,
10683                                       Response &res, bool close_connection,
10684                                       Error &error) {
10685  if (req.path.empty()) {
10686    error = Error::Connection;
10687    output_error_log(error, &req);
10688    return false;
10689  }
10690
10691  auto req_save = req;
10692
10693  bool ret;
10694
10695  if (!is_ssl() && is_proxy_enabled_for_host(host_)) {
10696    auto req2 = req;
10697    req2.path = "http://" +
10698                detail::make_host_and_port_string(host_, port_, false) +
10699                req.path;
10700    ret = process_request(strm, req2, res, close_connection, error);
10701    req = std::move(req2);
10702    req.path = req_save.path;
10703  } else {
10704    ret = process_request(strm, req, res, close_connection, error);
10705  }
10706
10707  if (!ret) { return false; }
10708
10709  if (detail::has_header_token(res.headers, "Connection", "close") ||
10710      (res.version == "HTTP/1.0" && res.reason != "Connection established")) {
10711    // NOTE: this requires a not-entirely-obvious chain of calls to be correct
10712    // for this to be safe.
10713
10714    // This is safe to call because handle_request is only called by send_
10715    // which locks the request mutex during the process. It would be a bug
10716    // to call it from a different thread since it's a thread-safety issue
10717    // to do these things to the socket if another thread is using the socket.
10718    std::lock_guard<std::mutex> guard(socket_mutex_);
10719    disconnect(/*gracefully=*/true);
10720  }
10721
10722  if (300 < res.status && res.status < 400 && follow_location_) {
10723    req = std::move(req_save);
10724    ret = redirect(req, res, error);
10725  }
10726
10727#ifdef CPPHTTPLIB_SSL_ENABLED
10728  if ((res.status == StatusCode::Unauthorized_401 ||
10729       res.status == StatusCode::ProxyAuthenticationRequired_407) &&
10730      req.authorization_count_ < 5) {
10731    auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407;
10732
10733    // Only retry when the 407 actually came from a proxy hop: plain HTTP
10734    // through an enabled proxy. HTTPS via CONNECT tunnels the 407 from the
10735    // origin (#2457); direct/bypassed origins have no proxy hop at all.
10736    if (is_proxy && !(!is_ssl() && is_proxy_enabled_for_host(host_))) {
10737      return ret;
10738    }
10739
10740    const auto &username =
10741        is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
10742    const auto &password =
10743        is_proxy ? proxy_digest_auth_password_ : digest_auth_password_;
10744
10745    if (!username.empty() && !password.empty()) {
10746      std::map<std::string, std::string> auth;
10747      if (detail::parse_www_authenticate(res, auth, is_proxy)) {
10748        Request new_req = req;
10749        new_req.authorization_count_ += 1;
10750        new_req.headers.erase(is_proxy ? "Proxy-Authorization"
10751                                       : "Authorization");
10752        new_req.headers.insert(detail::make_digest_authentication_header(
10753            req, auth, new_req.authorization_count_, detail::random_string(10),
10754            username, password, is_proxy));
10755
10756        Response new_res;
10757
10758        ret = send(new_req, new_res, error);
10759        if (ret) { res = std::move(new_res); }
10760      }
10761    }
10762  }
10763#endif
10764
10765  return ret;
10766}
10767
10768bool ClientImpl::redirect(Request &req, Response &res, Error &error) {
10769  if (req.redirect_count_ == 0) {
10770    error = Error::ExceedRedirectCount;
10771    output_error_log(error, &req);
10772    return false;
10773  }
10774
10775  auto location = res.get_header_value("location");
10776  if (location.empty()) { return false; }
10777
10778  detail::UrlComponents uc;
10779  if (!detail::parse_url(location, uc)) { return false; }
10780
10781  // Only follow http/https redirects
10782  if (!uc.scheme.empty() && uc.scheme != "http" && uc.scheme != "https") {
10783    return false;
10784  }
10785
10786  auto scheme = is_ssl() ? "https" : "http";
10787
10788  auto next_scheme = std::move(uc.scheme);
10789  auto next_host = std::move(uc.host);
10790  auto port_str = std::move(uc.port);
10791  auto next_path = std::move(uc.path);
10792  auto next_query = std::move(uc.query);
10793
10794  auto next_port = port_;
10795  if (!port_str.empty()) {
10796    if (!detail::parse_port(port_str, next_port)) { return false; }
10797  } else if (!next_scheme.empty()) {
10798    next_port = next_scheme == "https" ? 443 : 80;
10799  }
10800
10801  if (next_scheme.empty()) { next_scheme = scheme; }
10802  if (next_host.empty()) { next_host = host_; }
10803  if (next_path.empty()) { next_path = "/"; }
10804
10805  auto path = decode_path_component(next_path) + next_query;
10806
10807  // Same host redirect - use current client
10808  if (next_scheme == scheme && next_host == host_ && next_port == port_) {
10809    return detail::redirect(*this, req, res, path, location, error);
10810  }
10811
10812  // Cross-host/scheme redirect - create new client with robust setup
10813  return create_redirect_client(next_scheme, next_host, next_port, req, res,
10814                                path, location, error);
10815}
10816
10817// New method for robust redirect client creation
10818bool ClientImpl::create_redirect_client(
10819    const std::string &scheme, const std::string &host, int port, Request &req,
10820    Response &res, const std::string &path, const std::string &location,
10821    Error &error) {
10822  // Determine if we need SSL
10823  auto need_ssl = (scheme == "https");
10824
10825  // Clean up request headers that are host/client specific
10826  // Remove headers that should not be carried over to new host
10827  auto headers_to_remove = std::vector<std::string>{
10828      "Host", "Proxy-Authorization", "Authorization", "Cookie", "Cookie2"};
10829
10830  for (const auto &header_name : headers_to_remove) {
10831    auto it = req.headers.find(header_name);
10832    while (it != req.headers.end()) {
10833      it = req.headers.erase(it);
10834      it = req.headers.find(header_name);
10835    }
10836  }
10837
10838  // Create appropriate client type and handle redirect
10839  if (need_ssl) {
10840#ifdef CPPHTTPLIB_SSL_ENABLED
10841    // Create SSL client for HTTPS redirect
10842    SSLClient redirect_client(host, port);
10843
10844    // Setup basic client configuration first
10845    setup_redirect_client(redirect_client);
10846
10847    redirect_client.enable_server_certificate_verification(
10848        server_certificate_verification_);
10849    redirect_client.enable_server_hostname_verification(
10850        server_hostname_verification_);
10851    redirect_client.system_ca_mode_ = system_ca_mode_;
10852
10853    // Transfer CA certificate to redirect client
10854    if (!ca_cert_pem_.empty()) {
10855      redirect_client.load_ca_cert_store(ca_cert_pem_.c_str(),
10856                                         ca_cert_pem_.size());
10857    }
10858    if (!ca_cert_file_path_.empty()) {
10859      redirect_client.set_ca_cert_path(ca_cert_file_path_, ca_cert_dir_path_);
10860    }
10861
10862    // Client certificates are set through constructor for SSLClient
10863    // NOTE: SSLClient constructor already takes client_cert_path and
10864    // client_key_path so we need to create it properly if client certs are
10865    // needed
10866
10867    // Execute the redirect
10868    return detail::redirect(redirect_client, req, res, path, location, error);
10869#else
10870    // SSL not supported - set appropriate error
10871    error = Error::SSLConnection;
10872    output_error_log(error, &req);
10873    return false;
10874#endif
10875  } else {
10876    // HTTP redirect
10877    ClientImpl redirect_client(host, port);
10878
10879    // Setup client with robust configuration
10880    setup_redirect_client(redirect_client);
10881
10882    // Execute the redirect
10883    return detail::redirect(redirect_client, req, res, path, location, error);
10884  }
10885}
10886
10887// New method for robust client setup (based on basic_manual_redirect.cpp
10888// logic)
10889template <typename ClientType>
10890void ClientImpl::setup_redirect_client(ClientType &client) {
10891  // Copy basic settings first
10892  client.set_connection_timeout(connection_timeout_sec_);
10893  client.set_read_timeout(read_timeout_sec_, read_timeout_usec_);
10894  client.set_write_timeout(write_timeout_sec_, write_timeout_usec_);
10895  client.set_keep_alive(keep_alive_);
10896  client.set_follow_location(
10897      true); // Enable redirects to handle multi-step redirects
10898  client.set_path_encode(path_encode_);
10899  client.set_compress(compress_);
10900  client.set_decompress(decompress_);
10901
10902  // NOTE: Authentication credentials (basic auth, bearer token, digest auth)
10903  // are intentionally NOT copied to the redirect client. Per RFC 9110 Section
10904  // 15.4, credentials must not be forwarded when redirecting to a different
10905  // host. This function is only called for cross-host redirects; same-host
10906  // redirects are handled directly in ClientImpl::redirect().
10907
10908  // Copy the proxy configuration unconditionally; the per-target bypass is
10909  // re-evaluated at send time, so a later hop to a non-bypassed host can
10910  // still use the proxy.
10911  client.no_proxy_entries_ = no_proxy_entries_;
10912  if (!proxy_host_.empty() && proxy_port_ != -1) {
10913    client.set_proxy(proxy_host_, proxy_port_);
10914
10915    if (!proxy_basic_auth_username_.empty()) {
10916      client.set_proxy_basic_auth(proxy_basic_auth_username_,
10917                                  proxy_basic_auth_password_);
10918    }
10919    if (!proxy_bearer_token_auth_token_.empty()) {
10920      client.set_proxy_bearer_token_auth(proxy_bearer_token_auth_token_);
10921    }
10922#ifdef CPPHTTPLIB_SSL_ENABLED
10923    if (!proxy_digest_auth_username_.empty()) {
10924      client.set_proxy_digest_auth(proxy_digest_auth_username_,
10925                                   proxy_digest_auth_password_);
10926    }
10927#endif
10928  }
10929
10930  // Copy network and socket settings
10931  client.set_address_family(address_family_);
10932  client.set_tcp_nodelay(tcp_nodelay_);
10933  client.set_ipv6_v6only(ipv6_v6only_);
10934  if (socket_options_) { client.set_socket_options(socket_options_); }
10935  if (!interface_.empty()) { client.set_interface(interface_); }
10936
10937  // Copy logging and headers
10938  if (logger_) { client.set_logger(logger_); }
10939  if (error_logger_) { client.set_error_logger(error_logger_); }
10940
10941  // NOTE: DO NOT copy default_headers_ as they may contain stale Host headers
10942  // Each new client should generate its own headers based on its target host
10943}
10944
10945bool ClientImpl::write_content_with_provider(Stream &strm,
10946                                                    const Request &req,
10947                                                    Error &error) const {
10948  auto is_shutting_down = []() { return false; };
10949
10950  if (req.is_chunked_content_provider_) {
10951    auto compressor = compress_ ? detail::create_compressor().first
10952                                : std::unique_ptr<detail::compressor>();
10953    if (!compressor) {
10954      compressor = detail::make_unique<detail::nocompressor>();
10955    }
10956
10957    return detail::write_content_chunked(strm, req.content_provider_,
10958                                         is_shutting_down, *compressor, error);
10959  } else {
10960    return detail::write_content_with_progress(
10961        strm, req.content_provider_, 0, req.content_length_, is_shutting_down,
10962        req.upload_progress, error);
10963  }
10964}
10965
10966bool ClientImpl::write_request(Stream &strm, Request &req,
10967                                      bool close_connection, Error &error,
10968                                      bool skip_body) {
10969  // Prepare additional headers
10970  if (close_connection) {
10971    if (!req.has_header("Connection")) {
10972      req.set_header("Connection", "close");
10973    }
10974  }
10975
10976  std::string ct_for_defaults;
10977  if (!req.has_header("Content-Type") && !req.body.empty()) {
10978    ct_for_defaults = "text/plain";
10979  }
10980  prepare_default_headers(req, false, ct_for_defaults);
10981
10982  if (req.body.empty()) {
10983    if (req.content_provider_) {
10984      if (!req.is_chunked_content_provider_) {
10985        if (!req.has_header("Content-Length")) {
10986          auto length = std::to_string(req.content_length_);
10987          req.set_header("Content-Length", length);
10988        }
10989      }
10990    } else {
10991      if (req.method == "POST" || req.method == "PUT" ||
10992          req.method == "PATCH") {
10993        req.set_header("Content-Length", "0");
10994      }
10995    }
10996  }
10997
10998  // A CONNECT request is read by the proxy; everything sent through the tunnel
10999  // it opens is read by the origin. Each credential goes only to its own hop.
11000  auto is_connect = req.method == "CONNECT";
11001
11002  if (!is_connect && !req.has_header("Authorization")) {
11003    if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) {
11004      req.headers.insert(make_basic_authentication_header(
11005          basic_auth_username_, basic_auth_password_, false));
11006    } else if (!bearer_token_auth_token_.empty()) {
11007      req.headers.insert(make_bearer_token_authentication_header(
11008          bearer_token_auth_token_, false));
11009    }
11010  }
11011
11012  // Proxy-Authorization is only sent when the proxy reads this message —
11013  // otherwise NO_PROXY-matched requests, and requests inside a TLS tunnel,
11014  // would leak proxy credentials to the destination server.
11015  if (is_proxy_enabled_for_host(host_) && (!is_ssl() || is_connect)) {
11016    if (!proxy_basic_auth_username_.empty() &&
11017        !proxy_basic_auth_password_.empty() &&
11018        !req.has_header("Proxy-Authorization")) {
11019      req.headers.insert(make_basic_authentication_header(
11020          proxy_basic_auth_username_, proxy_basic_auth_password_, true));
11021    }
11022    if (!proxy_bearer_token_auth_token_.empty() &&
11023        !req.has_header("Proxy-Authorization")) {
11024      req.headers.insert(make_bearer_token_authentication_header(
11025          proxy_bearer_token_auth_token_, true));
11026    }
11027  }
11028
11029  // Request line and headers
11030  {
11031    detail::BufferStream bstrm;
11032
11033    // Extract the query from req.path. The encoding itself is delegated to
11034    // `encode_request_target`; the raw query is still needed here to decide
11035    // between populating `req.params` from it and falling back to building a
11036    // query out of caller-supplied `req.params`.
11037    auto query_pos = req.path.find('?');
11038    auto query_part = query_pos == std::string::npos
11039                          ? std::string()
11040                          : req.path.substr(query_pos + 1);
11041
11042    auto path_with_query =
11043        detail::encode_request_target(req.path, path_encode_);
11044
11045    if (!query_part.empty()) {
11046      // The query already came in through `req.path`; still populate
11047      // `req.params` for handlers/users who read them.
11048      detail::parse_query_text(query_part, req.params);
11049    } else if (!req.params.empty()) {
11050      // No query in `req.path`; build one from `req.params` so existing
11051      // callers that pass `Params` separately continue to work.
11052      path_with_query = append_query_params(path_with_query, req.params);
11053    }
11054
11055    // Write request line and headers
11056    if (detail::write_request_line(bstrm, req.method, path_with_query) < 0) {
11057      // A rejected target (e.g. CR/LF smuggled in via a decoded redirect
11058      // Location under set_path_encode(false)) must fail the request cleanly
11059      // instead of emitting a request-line-less, header-injecting request.
11060      error = Error::Write;
11061      output_error_log(error, &req);
11062      return false;
11063    }
11064    if (!detail::check_and_write_headers(bstrm, req.headers, header_writer_,
11065                                         error)) {
11066      output_error_log(error, &req);
11067      return false;
11068    }
11069
11070    // Flush buffer
11071    auto &data = bstrm.get_buffer();
11072    if (!detail::write_data(strm, data.data(), data.size())) {
11073      error = Error::Write;
11074      output_error_log(error, &req);
11075      return false;
11076    }
11077  }
11078
11079  // After sending request line and headers, wait briefly for an early server
11080  // response (e.g. 4xx) and avoid sending a potentially large request body
11081  // unnecessarily. This workaround is only enabled on Windows because Unix
11082  // platforms surface write errors (EPIPE) earlier; on Windows kernel send
11083  // buffering can accept large writes even when the peer already responded.
11084  // Check the stream first (which covers SSL via `is_readable()`), then
11085  // fall back to select on the socket. Only perform the wait for very large
11086  // request bodies to avoid interfering with normal small requests and
11087  // reduce side-effects. Poll briefly (up to 50ms as default) for an early
11088  // response. Skip this check when using Expect: 100-continue, as the protocol
11089  // handles early responses properly.
11090#if defined(_WIN32)
11091  if (!skip_body &&
11092      req.body.size() > CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD &&
11093      req.path.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
11094    auto start = std::chrono::high_resolution_clock::now();
11095
11096    for (;;) {
11097      // Prefer socket-level readiness to avoid SSL_pending() false-positives
11098      // from SSL internals. If the underlying socket is readable, assume an
11099      // early response may be present.
11100      auto sock = strm.socket();
11101      if (sock != INVALID_SOCKET && detail::select_read(sock, 0, 0) > 0) {
11102        return false;
11103      }
11104
11105      // Fallback to stream-level check for non-socket streams or when the
11106      // socket isn't reporting readable. Avoid using `is_readable()` for
11107      // SSL, since `SSL_pending()` may report buffered records that do not
11108      // indicate a complete application-level response yet.
11109      if (!is_ssl() && strm.is_readable()) { return false; }
11110
11111      auto now = std::chrono::high_resolution_clock::now();
11112      auto elapsed =
11113          std::chrono::duration_cast<std::chrono::milliseconds>(now - start)
11114              .count();
11115      if (elapsed >= CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND) {
11116        break;
11117      }
11118
11119      std::this_thread::sleep_for(std::chrono::milliseconds(1));
11120    }
11121  }
11122#endif
11123
11124  // Body
11125  if (skip_body) { return true; }
11126
11127  return write_request_body(strm, req, error);
11128}
11129
11130bool ClientImpl::write_request_body(Stream &strm, Request &req,
11131                                           Error &error) {
11132  if (req.body.empty()) {
11133    return write_content_with_provider(strm, req, error);
11134  }
11135
11136  if (req.upload_progress) {
11137    auto body_size = req.body.size();
11138    size_t written = 0;
11139    auto data = req.body.data();
11140
11141    while (written < body_size) {
11142      size_t to_write = (std::min)(CPPHTTPLIB_SEND_BUFSIZ, body_size - written);
11143      if (!detail::write_data(strm, data + written, to_write)) {
11144        error = Error::Write;
11145        output_error_log(error, &req);
11146        return false;
11147      }
11148      written += to_write;
11149
11150      if (!req.upload_progress(written, body_size)) {
11151        error = Error::Canceled;
11152        output_error_log(error, &req);
11153        return false;
11154      }
11155    }
11156  } else {
11157    if (!detail::write_data(strm, req.body.data(), req.body.size())) {
11158      error = Error::Write;
11159      output_error_log(error, &req);
11160      return false;
11161    }
11162  }
11163
11164  return true;
11165}
11166
11167std::unique_ptr<Response>
11168ClientImpl::send_with_content_provider_and_receiver(
11169    Request &req, const char *body, size_t content_length,
11170    ContentProvider content_provider,
11171    ContentProviderWithoutLength content_provider_without_length,
11172    const std::string &content_type, ContentReceiver content_receiver,
11173    Error &error) {
11174  if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
11175
11176  auto enc = compress_
11177                 ? detail::create_compressor()
11178                 : std::pair<std::unique_ptr<detail::compressor>, const char *>(
11179                       nullptr, nullptr);
11180
11181  if (enc.second) { req.set_header("Content-Encoding", enc.second); }
11182
11183  if (enc.first && !content_provider_without_length) {
11184    auto &compressor = enc.first;
11185
11186    if (content_provider) {
11187      auto ok = true;
11188      auto finished = false;
11189      size_t offset = 0;
11190      DataSink data_sink;
11191
11192      data_sink.write = [&](const char *data, size_t data_len) -> bool {
11193        if (ok) {
11194          auto last = offset + data_len == content_length;
11195
11196          auto ret = compressor->compress(
11197              data, data_len, last,
11198              [&](const char *compressed_data, size_t compressed_data_len) {
11199                req.body.append(compressed_data, compressed_data_len);
11200                return true;
11201              });
11202
11203          if (ret) {
11204            offset += data_len;
11205          } else {
11206            ok = false;
11207          }
11208        }
11209        return ok;
11210      };
11211
11212      // As in detail::write_content_with_progress(): the body is framed by
11213      // content_length, so a provider that finishes early has truncated it.
11214      // Stop and report that instead of calling the provider forever.
11215      data_sink.done = [&]() { finished = true; };
11216
11217      while (ok && !finished && offset < content_length) {
11218        if (!content_provider(offset, content_length - offset, data_sink)) {
11219          error = Error::Canceled;
11220          output_error_log(error, &req);
11221          return nullptr;
11222        }
11223      }
11224
11225      // A short body here means either the provider stopped early or the
11226      // compressor gave up. The branch below reports a failing compressor as
11227      // Error::Compression, so keep the two distinguishable.
11228      if (offset < content_length) {
11229        error = ok ? Error::Write : Error::Compression;
11230        output_error_log(error, &req);
11231        return nullptr;
11232      }
11233    } else {
11234      if (!compressor->compress(body, content_length, true,
11235                                [&](const char *data, size_t data_len) {
11236                                  req.body.append(data, data_len);
11237                                  return true;
11238                                })) {
11239        error = Error::Compression;
11240        output_error_log(error, &req);
11241        return nullptr;
11242      }
11243    }
11244  } else {
11245    if (content_provider) {
11246      req.content_length_ = content_length;
11247      req.content_provider_ = std::move(content_provider);
11248      req.is_chunked_content_provider_ = false;
11249    } else if (content_provider_without_length) {
11250      req.content_length_ = 0;
11251      req.content_provider_ = detail::ContentProviderAdapter(
11252          std::move(content_provider_without_length));
11253      req.is_chunked_content_provider_ = true;
11254      req.set_header("Transfer-Encoding", "chunked");
11255    } else {
11256      req.body.assign(body, content_length);
11257    }
11258  }
11259
11260  if (content_receiver) {
11261    req.content_receiver =
11262        [content_receiver](const char *data, size_t data_length,
11263                           size_t /*offset*/, size_t /*total_length*/) {
11264          return content_receiver(data, data_length);
11265        };
11266  }
11267
11268  auto res = detail::make_unique<Response>();
11269  return send(req, *res, error) ? std::move(res) : nullptr;
11270}
11271
11272Result ClientImpl::send_with_content_provider_and_receiver(
11273    const std::string &method, const std::string &path, const Headers &headers,
11274    const char *body, size_t content_length, ContentProvider content_provider,
11275    ContentProviderWithoutLength content_provider_without_length,
11276    const std::string &content_type, ContentReceiver content_receiver,
11277    UploadProgress progress) {
11278  Request req;
11279  req.method = method;
11280  req.headers = headers;
11281  req.path = path;
11282  req.upload_progress = std::move(progress);
11283  if (max_timeout_msec_ > 0) {
11284    req.start_time_ = std::chrono::steady_clock::now();
11285  }
11286
11287  auto error = Error::Success;
11288
11289  auto res = send_with_content_provider_and_receiver(
11290      req, body, content_length, std::move(content_provider),
11291      std::move(content_provider_without_length), content_type,
11292      std::move(content_receiver), error);
11293
11294#ifdef CPPHTTPLIB_SSL_ENABLED
11295  return Result{std::move(res), error, std::move(req.headers), last_ssl_error_,
11296                last_backend_error_};
11297#else
11298  return Result{std::move(res), error, std::move(req.headers)};
11299#endif
11300}
11301
11302void ClientImpl::output_log(const Request &req,
11303                                   const Response &res) const {
11304  if (logger_) {
11305    std::lock_guard<std::mutex> guard(logger_mutex_);
11306    logger_(req, res);
11307  }
11308}
11309
11310void ClientImpl::output_error_log(const Error &err,
11311                                         const Request *req) const {
11312  if (error_logger_) {
11313    std::lock_guard<std::mutex> guard(logger_mutex_);
11314    error_logger_(err, req);
11315  }
11316}
11317
11318bool ClientImpl::process_request(Stream &strm, Request &req,
11319                                        Response &res, bool close_connection,
11320                                        Error &error) {
11321  // Auto-add Expect: 100-continue for large bodies
11322  if (CPPHTTPLIB_EXPECT_100_THRESHOLD > 0 && !req.has_header("Expect")) {
11323    auto body_size = req.body.empty() ? req.content_length_ : req.body.size();
11324    if (body_size >= CPPHTTPLIB_EXPECT_100_THRESHOLD) {
11325      req.set_header("Expect", "100-continue");
11326    }
11327  }
11328
11329  // Check for Expect: 100-continue
11330  auto expect_100_continue =
11331      detail::has_header_token(req.headers, "Expect", "100-continue");
11332
11333  // Send request (skip body if using Expect: 100-continue)
11334  auto write_request_success =
11335      write_request(strm, req, close_connection, error, expect_100_continue);
11336
11337#ifdef CPPHTTPLIB_SSL_ENABLED
11338  if (is_ssl() && !expect_100_continue) {
11339    auto is_proxy_enabled = is_proxy_enabled_for_host(host_);
11340    if (!is_proxy_enabled) {
11341      if (tls::is_peer_closed(socket_.ssl, socket_.sock)) {
11342        error = Error::SSLPeerCouldBeClosed_;
11343        output_error_log(error, &req);
11344        return false;
11345      }
11346    }
11347  }
11348#endif
11349
11350  // Handle Expect: 100-continue.
11351  //
11352  // Wait for an interim/early response by attempting to read the status line
11353  // under a short timeout, instead of trusting raw socket readability. Over
11354  // TLS, post-handshake records (e.g. session tickets) make the socket
11355  // readable without any HTTP response being available; relying on
11356  // `select_read` there caused the body to be withheld forever and the
11357  // request to fail with `Read` (#2458). If no status line arrives within the
11358  // timeout, send the body anyway (matching curl's behavior).
11359  auto status_line_read = false;
11360  if (expect_100_continue && write_request_success) {
11361    if (CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND > 0) {
11362      time_t sec = CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND / 1000;
11363      time_t usec = (CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND % 1000) * 1000;
11364      strm.set_read_timeout(sec, usec);
11365      status_line_read = read_response_line(strm, req, res, false);
11366      strm.set_read_timeout(read_timeout_sec_, read_timeout_usec_);
11367    }
11368
11369    if (!status_line_read) {
11370      // No interim response within the timeout: send the body and handle the
11371      // response as usual.
11372      if (!write_request_body(strm, req, error)) { return false; }
11373      expect_100_continue = false; // Switch to normal response handling
11374    }
11375  }
11376
11377  // Receive response and headers
11378  // When using Expect: 100-continue, don't auto-skip `100 Continue` response
11379  if ((!status_line_read &&
11380       !read_response_line(strm, req, res, !expect_100_continue)) ||
11381      !detail::read_headers(strm, res.headers)) {
11382    if (write_request_success) { error = Error::Read; }
11383    output_error_log(error, &req);
11384    return false;
11385  }
11386
11387  if (!write_request_success) { return false; }
11388
11389  // Handle Expect: 100-continue response
11390  if (expect_100_continue) {
11391    if (res.status == StatusCode::Continue_100) {
11392      // Server accepted, send the body
11393      if (!write_request_body(strm, req, error)) { return false; }
11394
11395      // Read the actual response
11396      res.headers.clear();
11397      res.body.clear();
11398      if (!read_response_line(strm, req, res) ||
11399          !detail::read_headers(strm, res.headers)) {
11400        error = Error::Read;
11401        output_error_log(error, &req);
11402        return false;
11403      }
11404    }
11405    // If not 100 Continue, server returned an error; proceed with that response
11406  }
11407
11408  // Body
11409  if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" &&
11410      req.method != "CONNECT") {
11411    // Reject ambiguous framing (RFC 9112 §6.3). Unlike a request, a response
11412    // whose final transfer coding is not chunked is not ambiguous: its body
11413    // runs until the server closes the connection, so it is not rejected.
11414    // HEAD/204 are excluded above and a 304 carries no body.
11415    if (res.status != StatusCode::NotModified_304 &&
11416        detail::has_conflicting_content_length(res.headers)) {
11417      error = Error::Read;
11418      output_error_log(error, &req);
11419      return false;
11420    }
11421
11422    auto redirect = 300 < res.status && res.status < 400 &&
11423                    res.status != StatusCode::NotModified_304 &&
11424                    follow_location_;
11425
11426    if (req.response_handler && !redirect) {
11427      if (!req.response_handler(res)) {
11428        error = Error::Canceled;
11429        output_error_log(error, &req);
11430        return false;
11431      }
11432    }
11433
11434    auto out =
11435        req.content_receiver
11436            ? static_cast<ContentReceiverWithProgress>(
11437                  [&](const char *buf, size_t n, size_t off, size_t len) {
11438                    if (redirect) { return true; }
11439                    auto ret = req.content_receiver(buf, n, off, len);
11440                    if (!ret) {
11441                      error = Error::Canceled;
11442                      output_error_log(error, &req);
11443                    }
11444                    return ret;
11445                  })
11446            : static_cast<ContentReceiverWithProgress>(
11447                  [&](const char *buf, size_t n, size_t /*off*/,
11448                      size_t /*len*/) {
11449                    assert(res.body.size() + n <= res.body.max_size());
11450                    if (payload_max_length_ > 0 &&
11451                        (res.body.size() >= payload_max_length_ ||
11452                         n > payload_max_length_ - res.body.size())) {
11453                      return false;
11454                    }
11455                    res.body.append(buf, n);
11456                    return true;
11457                  });
11458
11459    auto progress = [&](size_t current, size_t total) {
11460      if (!req.download_progress || redirect) { return true; }
11461      auto ret = req.download_progress(current, total);
11462      if (!ret) {
11463        error = Error::Canceled;
11464        output_error_log(error, &req);
11465      }
11466      return ret;
11467    };
11468
11469    if (res.has_header("Content-Length")) {
11470      if (!req.content_receiver) {
11471        auto len = res.get_header_value_u64("Content-Length");
11472        if (len > res.body.max_size()) {
11473          error = Error::Read;
11474          output_error_log(error, &req);
11475          return false;
11476        }
11477        // Cap the reservation by payload_max_length_ to avoid OOM when a
11478        // hostile or malformed server sends an enormous Content-Length.
11479        // The actual body read below is bounded by payload_max_length_,
11480        // so reserving more than that is never useful.
11481        auto reserve_len = static_cast<size_t>(len);
11482        if (payload_max_length_ > 0 && reserve_len > payload_max_length_) {
11483          reserve_len = payload_max_length_;
11484        }
11485        res.body.reserve(reserve_len);
11486      }
11487    }
11488
11489    if (res.status != StatusCode::NotModified_304) {
11490      auto content_status = 0;
11491      auto max_length = (!has_payload_max_length_ && req.content_receiver)
11492                            ? (std::numeric_limits<size_t>::max)()
11493                            : payload_max_length_;
11494      if (!detail::read_content(strm, res, max_length, content_status,
11495                                std::move(progress), std::move(out),
11496                                decompress_)) {
11497        if (error != Error::Canceled) {
11498          // Tell the caller apart from a plain read failure when the body could
11499          // not be decoded because of its Content-Encoding.
11500          switch (content_status) {
11501          case StatusCode::UnsupportedMediaType_415:
11502            error = Error::UnsupportedContentEncoding;
11503            break;
11504          case StatusCode::InternalServerError_500:
11505            error = Error::Compression;
11506            break;
11507          default: error = Error::Read; break;
11508          }
11509        }
11510        output_error_log(error, &req);
11511        return false;
11512      }
11513    }
11514  }
11515
11516  // Log
11517  output_log(req, res);
11518
11519  return true;
11520}
11521
11522ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
11523    const std::string &boundary, const UploadFormDataItems &items,
11524    const FormDataProviderItems &provider_items) const {
11525  size_t cur_item = 0;
11526  size_t cur_start = 0;
11527  // cur_item and cur_start are copied to within the std::function and
11528  // maintain state between successive calls
11529  return [&, cur_item, cur_start](size_t offset,
11530                                  DataSink &sink) mutable -> bool {
11531    if (!offset && !items.empty()) {
11532      sink.os << detail::serialize_multipart_formdata(items, boundary, false);
11533      return true;
11534    } else if (cur_item < provider_items.size()) {
11535      if (!cur_start) {
11536        const auto &begin = detail::serialize_multipart_formdata_item_begin(
11537            provider_items[cur_item], boundary);
11538        offset += begin.size();
11539        cur_start = offset;
11540        sink.os << begin;
11541      }
11542
11543      DataSink cur_sink;
11544      auto has_data = true;
11545      cur_sink.write = sink.write;
11546      // Forward is_writable so a provider item asking whether it may keep
11547      // going gets the outer sink's answer rather than the default `true`.
11548      cur_sink.is_writable = sink.is_writable;
11549      cur_sink.done = [&]() { has_data = false; };
11550
11551      if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) {
11552        return false;
11553      }
11554
11555      if (!has_data) {
11556        sink.os << detail::serialize_multipart_formdata_item_end();
11557        cur_item++;
11558        cur_start = 0;
11559      }
11560      return true;
11561    } else {
11562      sink.os << detail::serialize_multipart_formdata_finish(boundary);
11563      sink.done();
11564      return true;
11565    }
11566  };
11567}
11568
11569bool ClientImpl::process_socket(
11570    const Socket &socket,
11571    std::chrono::time_point<std::chrono::steady_clock> start_time,
11572    std::function<bool(Stream &strm)> callback) {
11573  return detail::process_client_socket(
11574      socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
11575      write_timeout_usec_, max_timeout_msec_, start_time, std::move(callback));
11576}
11577
11578bool ClientImpl::is_ssl() const { return false; }
11579
11580Result ClientImpl::Get(const std::string &path,
11581                              DownloadProgress progress) {
11582  return Get(path, Headers(), std::move(progress));
11583}
11584
11585Result ClientImpl::Get(const std::string &path, const Params &params,
11586                              DownloadProgress progress) {
11587  return Get(path, params, Headers(), std::move(progress));
11588}
11589
11590Result ClientImpl::Get(const std::string &path, const Params &params,
11591                              const Headers &headers,
11592                              DownloadProgress progress) {
11593  if (params.empty()) { return Get(path, headers); }
11594
11595  std::string path_with_query = append_query_params(path, params);
11596  return Get(path_with_query, headers, std::move(progress));
11597}
11598
11599Result ClientImpl::Get(const std::string &path, const Headers &headers,
11600                              DownloadProgress progress) {
11601  Request req;
11602  req.method = "GET";
11603  req.path = path;
11604  req.headers = headers;
11605  req.download_progress = std::move(progress);
11606  if (max_timeout_msec_ > 0) {
11607    req.start_time_ = std::chrono::steady_clock::now();
11608  }
11609
11610  return send_(std::move(req));
11611}
11612
11613Result ClientImpl::Get(const std::string &path,
11614                              ContentReceiver content_receiver,
11615                              DownloadProgress progress) {
11616  return Get(path, Headers(), nullptr, std::move(content_receiver),
11617             std::move(progress));
11618}
11619
11620Result ClientImpl::Get(const std::string &path, const Headers &headers,
11621                              ContentReceiver content_receiver,
11622                              DownloadProgress progress) {
11623  return Get(path, headers, nullptr, std::move(content_receiver),
11624             std::move(progress));
11625}
11626
11627Result ClientImpl::Get(const std::string &path,
11628                              ResponseHandler response_handler,
11629                              ContentReceiver content_receiver,
11630                              DownloadProgress progress) {
11631  return Get(path, Headers(), std::move(response_handler),
11632             std::move(content_receiver), std::move(progress));
11633}
11634
11635Result ClientImpl::Get(const std::string &path, const Headers &headers,
11636                              ResponseHandler response_handler,
11637                              ContentReceiver content_receiver,
11638                              DownloadProgress progress) {
11639  Request req;
11640  req.method = "GET";
11641  req.path = path;
11642  req.headers = headers;
11643  req.response_handler = std::move(response_handler);
11644  req.content_receiver =
11645      [content_receiver](const char *data, size_t data_length,
11646                         size_t /*offset*/, size_t /*total_length*/) {
11647        return content_receiver(data, data_length);
11648      };
11649  req.download_progress = std::move(progress);
11650  if (max_timeout_msec_ > 0) {
11651    req.start_time_ = std::chrono::steady_clock::now();
11652  }
11653
11654  return send_(std::move(req));
11655}
11656
11657Result ClientImpl::Get(const std::string &path, const Params &params,
11658                              const Headers &headers,
11659                              ContentReceiver content_receiver,
11660                              DownloadProgress progress) {
11661  return Get(path, params, headers, nullptr, std::move(content_receiver),
11662             std::move(progress));
11663}
11664
11665Result ClientImpl::Get(const std::string &path, const Params &params,
11666                              const Headers &headers,
11667                              ResponseHandler response_handler,
11668                              ContentReceiver content_receiver,
11669                              DownloadProgress progress) {
11670  if (params.empty()) {
11671    return Get(path, headers, std::move(response_handler),
11672               std::move(content_receiver), std::move(progress));
11673  }
11674
11675  std::string path_with_query = append_query_params(path, params);
11676  return Get(path_with_query, headers, std::move(response_handler),
11677             std::move(content_receiver), std::move(progress));
11678}
11679
11680Result ClientImpl::Head(const std::string &path) {
11681  return Head(path, Headers());
11682}
11683
11684Result ClientImpl::Head(const std::string &path,
11685                               const Headers &headers) {
11686  Request req;
11687  req.method = "HEAD";
11688  req.headers = headers;
11689  req.path = path;
11690  if (max_timeout_msec_ > 0) {
11691    req.start_time_ = std::chrono::steady_clock::now();
11692  }
11693
11694  return send_(std::move(req));
11695}
11696
11697Result ClientImpl::Post(const std::string &path) {
11698  return Post(path, std::string(), std::string());
11699}
11700
11701Result ClientImpl::Post(const std::string &path,
11702                               const Headers &headers) {
11703  return Post(path, headers, nullptr, 0, std::string());
11704}
11705
11706Result ClientImpl::Post(const std::string &path, const char *body,
11707                               size_t content_length,
11708                               const std::string &content_type,
11709                               UploadProgress progress) {
11710  return Post(path, Headers(), body, content_length, content_type, progress);
11711}
11712
11713Result ClientImpl::Post(const std::string &path, const std::string &body,
11714                               const std::string &content_type,
11715                               UploadProgress progress) {
11716  return Post(path, Headers(), body, content_type, progress);
11717}
11718
11719Result ClientImpl::Post(const std::string &path, const Params &params) {
11720  return Post(path, Headers(), params);
11721}
11722
11723Result ClientImpl::Post(const std::string &path, size_t content_length,
11724                               ContentProvider content_provider,
11725                               const std::string &content_type,
11726                               UploadProgress progress) {
11727  return Post(path, Headers(), content_length, std::move(content_provider),
11728              content_type, progress);
11729}
11730
11731Result ClientImpl::Post(const std::string &path, size_t content_length,
11732                               ContentProvider content_provider,
11733                               const std::string &content_type,
11734                               ContentReceiver content_receiver,
11735                               UploadProgress progress) {
11736  return Post(path, Headers(), content_length, std::move(content_provider),
11737              content_type, std::move(content_receiver), progress);
11738}
11739
11740Result ClientImpl::Post(const std::string &path,
11741                               ContentProviderWithoutLength content_provider,
11742                               const std::string &content_type,
11743                               UploadProgress progress) {
11744  return Post(path, Headers(), std::move(content_provider), content_type,
11745              progress);
11746}
11747
11748Result ClientImpl::Post(const std::string &path,
11749                               ContentProviderWithoutLength content_provider,
11750                               const std::string &content_type,
11751                               ContentReceiver content_receiver,
11752                               UploadProgress progress) {
11753  return Post(path, Headers(), std::move(content_provider), content_type,
11754              std::move(content_receiver), progress);
11755}
11756
11757Result ClientImpl::Post(const std::string &path, const Headers &headers,
11758                               const Params &params) {
11759  auto query = detail::params_to_query_str(params);
11760  return Post(path, headers, query, "application/x-www-form-urlencoded");
11761}
11762
11763Result ClientImpl::Post(const std::string &path,
11764                               const UploadFormDataItems &items,
11765                               UploadProgress progress) {
11766  return Post(path, Headers(), items, progress);
11767}
11768
11769Result ClientImpl::Post(const std::string &path, const Headers &headers,
11770                               const UploadFormDataItems &items,
11771                               UploadProgress progress) {
11772  const auto &boundary = detail::make_multipart_data_boundary();
11773  const auto &content_type =
11774      detail::serialize_multipart_formdata_get_content_type(boundary);
11775  auto content_length = detail::get_multipart_content_length(items, boundary);
11776  return Post(path, headers, content_length,
11777              detail::make_multipart_content_provider(items, boundary),
11778              content_type, progress);
11779}
11780
11781Result ClientImpl::Post(const std::string &path, const Headers &headers,
11782                               const UploadFormDataItems &items,
11783                               const std::string &boundary,
11784                               UploadProgress progress) {
11785  if (!detail::is_multipart_boundary_chars_valid(boundary)) {
11786    return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
11787  }
11788
11789  const auto &content_type =
11790      detail::serialize_multipart_formdata_get_content_type(boundary);
11791  auto content_length = detail::get_multipart_content_length(items, boundary);
11792  return Post(path, headers, content_length,
11793              detail::make_multipart_content_provider(items, boundary),
11794              content_type, progress);
11795}
11796
11797Result ClientImpl::Post(const std::string &path, const Headers &headers,
11798                               const char *body, size_t content_length,
11799                               const std::string &content_type,
11800                               UploadProgress progress) {
11801  return send_with_content_provider_and_receiver(
11802      "POST", path, headers, body, content_length, nullptr, nullptr,
11803      content_type, nullptr, progress);
11804}
11805
11806Result ClientImpl::Post(const std::string &path, const Headers &headers,
11807                               const std::string &body,
11808                               const std::string &content_type,
11809                               UploadProgress progress) {
11810  return send_with_content_provider_and_receiver(
11811      "POST", path, headers, body.data(), body.size(), nullptr, nullptr,
11812      content_type, nullptr, progress);
11813}
11814
11815Result ClientImpl::Post(const std::string &path, const Headers &headers,
11816                               size_t content_length,
11817                               ContentProvider content_provider,
11818                               const std::string &content_type,
11819                               UploadProgress progress) {
11820  return send_with_content_provider_and_receiver(
11821      "POST", path, headers, nullptr, content_length,
11822      std::move(content_provider), nullptr, content_type, nullptr, progress);
11823}
11824
11825Result ClientImpl::Post(const std::string &path, const Headers &headers,
11826                               size_t content_length,
11827                               ContentProvider content_provider,
11828                               const std::string &content_type,
11829                               ContentReceiver content_receiver,
11830                               DownloadProgress progress) {
11831  return send_with_content_provider_and_receiver(
11832      "POST", path, headers, nullptr, content_length,
11833      std::move(content_provider), nullptr, content_type,
11834      std::move(content_receiver), std::move(progress));
11835}
11836
11837Result ClientImpl::Post(const std::string &path, const Headers &headers,
11838                               ContentProviderWithoutLength content_provider,
11839                               const std::string &content_type,
11840                               UploadProgress progress) {
11841  return send_with_content_provider_and_receiver(
11842      "POST", path, headers, nullptr, 0, nullptr, std::move(content_provider),
11843      content_type, nullptr, progress);
11844}
11845
11846Result ClientImpl::Post(const std::string &path, const Headers &headers,
11847                               ContentProviderWithoutLength content_provider,
11848                               const std::string &content_type,
11849                               ContentReceiver content_receiver,
11850                               DownloadProgress progress) {
11851  return send_with_content_provider_and_receiver(
11852      "POST", path, headers, nullptr, 0, nullptr, std::move(content_provider),
11853      content_type, std::move(content_receiver), std::move(progress));
11854}
11855
11856Result ClientImpl::Post(const std::string &path, const Headers &headers,
11857                               const UploadFormDataItems &items,
11858                               const FormDataProviderItems &provider_items,
11859                               UploadProgress progress) {
11860  const auto &boundary = detail::make_multipart_data_boundary();
11861  const auto &content_type =
11862      detail::serialize_multipart_formdata_get_content_type(boundary);
11863  return send_with_content_provider_and_receiver(
11864      "POST", path, headers, nullptr, 0, nullptr,
11865      get_multipart_content_provider(boundary, items, provider_items),
11866      content_type, nullptr, progress);
11867}
11868
11869Result ClientImpl::Post(const std::string &path, const Headers &headers,
11870                               const std::string &body,
11871                               const std::string &content_type,
11872                               ContentReceiver content_receiver,
11873                               DownloadProgress progress) {
11874  Request req;
11875  req.method = "POST";
11876  req.path = path;
11877  req.headers = headers;
11878  req.body = body;
11879  req.content_receiver =
11880      [content_receiver](const char *data, size_t data_length,
11881                         size_t /*offset*/, size_t /*total_length*/) {
11882        return content_receiver(data, data_length);
11883      };
11884  req.download_progress = std::move(progress);
11885
11886  if (max_timeout_msec_ > 0) {
11887    req.start_time_ = std::chrono::steady_clock::now();
11888  }
11889
11890  if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
11891
11892  return send_(std::move(req));
11893}
11894
11895Result ClientImpl::Put(const std::string &path) {
11896  return Put(path, std::string(), std::string());
11897}
11898
11899Result ClientImpl::Put(const std::string &path, const Headers &headers) {
11900  return Put(path, headers, nullptr, 0, std::string());
11901}
11902
11903Result ClientImpl::Put(const std::string &path, const char *body,
11904                              size_t content_length,
11905                              const std::string &content_type,
11906                              UploadProgress progress) {
11907  return Put(path, Headers(), body, content_length, content_type, progress);
11908}
11909
11910Result ClientImpl::Put(const std::string &path, const std::string &body,
11911                              const std::string &content_type,
11912                              UploadProgress progress) {
11913  return Put(path, Headers(), body, content_type, progress);
11914}
11915
11916Result ClientImpl::Put(const std::string &path, const Params &params) {
11917  return Put(path, Headers(), params);
11918}
11919
11920Result ClientImpl::Put(const std::string &path, size_t content_length,
11921                              ContentProvider content_provider,
11922                              const std::string &content_type,
11923                              UploadProgress progress) {
11924  return Put(path, Headers(), content_length, std::move(content_provider),
11925             content_type, progress);
11926}
11927
11928Result ClientImpl::Put(const std::string &path, size_t content_length,
11929                              ContentProvider content_provider,
11930                              const std::string &content_type,
11931                              ContentReceiver content_receiver,
11932                              UploadProgress progress) {
11933  return Put(path, Headers(), content_length, std::move(content_provider),
11934             content_type, std::move(content_receiver), progress);
11935}
11936
11937Result ClientImpl::Put(const std::string &path,
11938                              ContentProviderWithoutLength content_provider,
11939                              const std::string &content_type,
11940                              UploadProgress progress) {
11941  return Put(path, Headers(), std::move(content_provider), content_type,
11942             progress);
11943}
11944
11945Result ClientImpl::Put(const std::string &path,
11946                              ContentProviderWithoutLength content_provider,
11947                              const std::string &content_type,
11948                              ContentReceiver content_receiver,
11949                              UploadProgress progress) {
11950  return Put(path, Headers(), std::move(content_provider), content_type,
11951             std::move(content_receiver), progress);
11952}
11953
11954Result ClientImpl::Put(const std::string &path, const Headers &headers,
11955                              const Params &params) {
11956  auto query = detail::params_to_query_str(params);
11957  return Put(path, headers, query, "application/x-www-form-urlencoded");
11958}
11959
11960Result ClientImpl::Put(const std::string &path,
11961                              const UploadFormDataItems &items,
11962                              UploadProgress progress) {
11963  return Put(path, Headers(), items, progress);
11964}
11965
11966Result ClientImpl::Put(const std::string &path, const Headers &headers,
11967                              const UploadFormDataItems &items,
11968                              UploadProgress progress) {
11969  const auto &boundary = detail::make_multipart_data_boundary();
11970  const auto &content_type =
11971      detail::serialize_multipart_formdata_get_content_type(boundary);
11972  auto content_length = detail::get_multipart_content_length(items, boundary);
11973  return Put(path, headers, content_length,
11974             detail::make_multipart_content_provider(items, boundary),
11975             content_type, progress);
11976}
11977
11978Result ClientImpl::Put(const std::string &path, const Headers &headers,
11979                              const UploadFormDataItems &items,
11980                              const std::string &boundary,
11981                              UploadProgress progress) {
11982  if (!detail::is_multipart_boundary_chars_valid(boundary)) {
11983    return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
11984  }
11985
11986  const auto &content_type =
11987      detail::serialize_multipart_formdata_get_content_type(boundary);
11988  auto content_length = detail::get_multipart_content_length(items, boundary);
11989  return Put(path, headers, content_length,
11990             detail::make_multipart_content_provider(items, boundary),
11991             content_type, progress);
11992}
11993
11994Result ClientImpl::Put(const std::string &path, const Headers &headers,
11995                              const char *body, size_t content_length,
11996                              const std::string &content_type,
11997                              UploadProgress progress) {
11998  return send_with_content_provider_and_receiver(
11999      "PUT", path, headers, body, content_length, nullptr, nullptr,
12000      content_type, nullptr, progress);
12001}
12002
12003Result ClientImpl::Put(const std::string &path, const Headers &headers,
12004                              const std::string &body,
12005                              const std::string &content_type,
12006                              UploadProgress progress) {
12007  return send_with_content_provider_and_receiver(
12008      "PUT", path, headers, body.data(), body.size(), nullptr, nullptr,
12009      content_type, nullptr, progress);
12010}
12011
12012Result ClientImpl::Put(const std::string &path, const Headers &headers,
12013                              size_t content_length,
12014                              ContentProvider content_provider,
12015                              const std::string &content_type,
12016                              UploadProgress progress) {
12017  return send_with_content_provider_and_receiver(
12018      "PUT", path, headers, nullptr, content_length,
12019      std::move(content_provider), nullptr, content_type, nullptr, progress);
12020}
12021
12022Result ClientImpl::Put(const std::string &path, const Headers &headers,
12023                              size_t content_length,
12024                              ContentProvider content_provider,
12025                              const std::string &content_type,
12026                              ContentReceiver content_receiver,
12027                              UploadProgress progress) {
12028  return send_with_content_provider_and_receiver(
12029      "PUT", path, headers, nullptr, content_length,
12030      std::move(content_provider), nullptr, content_type,
12031      std::move(content_receiver), progress);
12032}
12033
12034Result ClientImpl::Put(const std::string &path, const Headers &headers,
12035                              ContentProviderWithoutLength content_provider,
12036                              const std::string &content_type,
12037                              UploadProgress progress) {
12038  return send_with_content_provider_and_receiver(
12039      "PUT", path, headers, nullptr, 0, nullptr, std::move(content_provider),
12040      content_type, nullptr, progress);
12041}
12042
12043Result ClientImpl::Put(const std::string &path, const Headers &headers,
12044                              ContentProviderWithoutLength content_provider,
12045                              const std::string &content_type,
12046                              ContentReceiver content_receiver,
12047                              UploadProgress progress) {
12048  return send_with_content_provider_and_receiver(
12049      "PUT", path, headers, nullptr, 0, nullptr, std::move(content_provider),
12050      content_type, std::move(content_receiver), progress);
12051}
12052
12053Result ClientImpl::Put(const std::string &path, const Headers &headers,
12054                              const UploadFormDataItems &items,
12055                              const FormDataProviderItems &provider_items,
12056                              UploadProgress progress) {
12057  const auto &boundary = detail::make_multipart_data_boundary();
12058  const auto &content_type =
12059      detail::serialize_multipart_formdata_get_content_type(boundary);
12060  return send_with_content_provider_and_receiver(
12061      "PUT", path, headers, nullptr, 0, nullptr,
12062      get_multipart_content_provider(boundary, items, provider_items),
12063      content_type, nullptr, progress);
12064}
12065
12066Result ClientImpl::Put(const std::string &path, const Headers &headers,
12067                              const std::string &body,
12068                              const std::string &content_type,
12069                              ContentReceiver content_receiver,
12070                              DownloadProgress progress) {
12071  Request req;
12072  req.method = "PUT";
12073  req.path = path;
12074  req.headers = headers;
12075  req.body = body;
12076  req.content_receiver =
12077      [content_receiver](const char *data, size_t data_length,
12078                         size_t /*offset*/, size_t /*total_length*/) {
12079        return content_receiver(data, data_length);
12080      };
12081  req.download_progress = std::move(progress);
12082
12083  if (max_timeout_msec_ > 0) {
12084    req.start_time_ = std::chrono::steady_clock::now();
12085  }
12086
12087  if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
12088
12089  return send_(std::move(req));
12090}
12091
12092Result ClientImpl::Patch(const std::string &path) {
12093  return Patch(path, std::string(), std::string());
12094}
12095
12096Result ClientImpl::Patch(const std::string &path, const Headers &headers,
12097                                UploadProgress progress) {
12098  return Patch(path, headers, nullptr, 0, std::string(), progress);
12099}
12100
12101Result ClientImpl::Patch(const std::string &path, const char *body,
12102                                size_t content_length,
12103                                const std::string &content_type,
12104                                UploadProgress progress) {
12105  return Patch(path, Headers(), body, content_length, content_type, progress);
12106}
12107
12108Result ClientImpl::Patch(const std::string &path,
12109                                const std::string &body,
12110                                const std::string &content_type,
12111                                UploadProgress progress) {
12112  return Patch(path, Headers(), body, content_type, progress);
12113}
12114
12115Result ClientImpl::Patch(const std::string &path, const Params &params) {
12116  return Patch(path, Headers(), params);
12117}
12118
12119Result ClientImpl::Patch(const std::string &path, size_t content_length,
12120                                ContentProvider content_provider,
12121                                const std::string &content_type,
12122                                UploadProgress progress) {
12123  return Patch(path, Headers(), content_length, std::move(content_provider),
12124               content_type, progress);
12125}
12126
12127Result ClientImpl::Patch(const std::string &path, size_t content_length,
12128                                ContentProvider content_provider,
12129                                const std::string &content_type,
12130                                ContentReceiver content_receiver,
12131                                UploadProgress progress) {
12132  return Patch(path, Headers(), content_length, std::move(content_provider),
12133               content_type, std::move(content_receiver), progress);
12134}
12135
12136Result ClientImpl::Patch(const std::string &path,
12137                                ContentProviderWithoutLength content_provider,
12138                                const std::string &content_type,
12139                                UploadProgress progress) {
12140  return Patch(path, Headers(), std::move(content_provider), content_type,
12141               progress);
12142}
12143
12144Result ClientImpl::Patch(const std::string &path,
12145                                ContentProviderWithoutLength content_provider,
12146                                const std::string &content_type,
12147                                ContentReceiver content_receiver,
12148                                UploadProgress progress) {
12149  return Patch(path, Headers(), std::move(content_provider), content_type,
12150               std::move(content_receiver), progress);
12151}
12152
12153Result ClientImpl::Patch(const std::string &path, const Headers &headers,
12154                                const Params &params) {
12155  auto query = detail::params_to_query_str(params);
12156  return Patch(path, headers, query, "application/x-www-form-urlencoded");
12157}
12158
12159Result ClientImpl::Patch(const std::string &path,
12160                                const UploadFormDataItems &items,
12161                                UploadProgress progress) {
12162  return Patch(path, Headers(), items, progress);
12163}
12164
12165Result ClientImpl::Patch(const std::string &path, const Headers &headers,
12166                                const UploadFormDataItems &items,
12167                                UploadProgress progress) {
12168  const auto &boundary = detail::make_multipart_data_boundary();
12169  const auto &content_type =
12170      detail::serialize_multipart_formdata_get_content_type(boundary);
12171  auto content_length = detail::get_multipart_content_length(items, boundary);
12172  return Patch(path, headers, content_length,
12173               detail::make_multipart_content_provider(items, boundary),
12174               content_type, progress);
12175}
12176
12177Result ClientImpl::Patch(const std::string &path, const Headers &headers,
12178                                const UploadFormDataItems &items,
12179                                const std::string &boundary,
12180                                UploadProgress progress) {
12181  if (!detail::is_multipart_boundary_chars_valid(boundary)) {
12182    return Result{nullptr, Error::UnsupportedMultipartBoundaryChars};
12183  }
12184
12185  const auto &content_type =
12186      detail::serialize_multipart_formdata_get_content_type(boundary);
12187  auto content_length = detail::get_multipart_content_length(items, boundary);
12188  return Patch(path, headers, content_length,
12189               detail::make_multipart_content_provider(items, boundary),
12190               content_type, progress);
12191}
12192
12193Result ClientImpl::Patch(const std::string &path, const Headers &headers,
12194                                const char *body, size_t content_length,
12195                                const std::string &content_type,
12196                                UploadProgress progress) {
12197  return send_with_content_provider_and_receiver(
12198      "PATCH", path, headers, body, content_length, nullptr, nullptr,
12199      content_type, nullptr, progress);
12200}
12201
12202Result ClientImpl::Patch(const std::string &path, const Headers &headers,
12203                                const std::string &body,
12204                                const std::string &content_type,
12205                                UploadProgress progress) {
12206  return send_with_content_provider_and_receiver(
12207      "PATCH", path, headers, body.data(), body.size(), nullptr, nullptr,
12208      content_type, nullptr, progress);
12209}
12210
12211Result ClientImpl::Patch(const std::string &path, const Headers &headers,
12212                                size_t content_length,
12213                                ContentProvider content_provider,
12214                                const std::string &content_type,
12215                                UploadProgress progress) {
12216  return send_with_content_provider_and_receiver(
12217      "PATCH", path, headers, nullptr, content_length,
12218      std::move(content_provider), nullptr, content_type, nullptr, progress);
12219}
12220
12221Result ClientImpl::Patch(const std::string &path, const Headers &headers,
12222                                size_t content_length,
12223                                ContentProvider content_provider,
12224                                const std::string &content_type,
12225                                ContentReceiver content_receiver,
12226                                UploadProgress progress) {
12227  return send_with_content_provider_and_receiver(
12228      "PATCH", path, headers, nullptr, content_length,
12229      std::move(content_provider), nullptr, content_type,
12230      std::move(content_receiver), progress);
12231}
12232
12233Result ClientImpl::Patch(const std::string &path, const Headers &headers,
12234                                ContentProviderWithoutLength content_provider,
12235                                const std::string &content_type,
12236                                UploadProgress progress) {
12237  return send_with_content_provider_and_receiver(
12238      "PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider),
12239      content_type, nullptr, progress);
12240}
12241
12242Result ClientImpl::Patch(const std::string &path, const Headers &headers,
12243                                ContentProviderWithoutLength content_provider,
12244                                const std::string &content_type,
12245                                ContentReceiver content_receiver,
12246                                UploadProgress progress) {
12247  return send_with_content_provider_and_receiver(
12248      "PATCH", path, headers, nullptr, 0, nullptr, std::move(content_provider),
12249      content_type, std::move(content_receiver), progress);
12250}
12251
12252Result ClientImpl::Patch(const std::string &path, const Headers &headers,
12253                                const UploadFormDataItems &items,
12254                                const FormDataProviderItems &provider_items,
12255                                UploadProgress progress) {
12256  const auto &boundary = detail::make_multipart_data_boundary();
12257  const auto &content_type =
12258      detail::serialize_multipart_formdata_get_content_type(boundary);
12259  return send_with_content_provider_and_receiver(
12260      "PATCH", path, headers, nullptr, 0, nullptr,
12261      get_multipart_content_provider(boundary, items, provider_items),
12262      content_type, nullptr, progress);
12263}
12264
12265Result ClientImpl::Patch(const std::string &path, const Headers &headers,
12266                                const std::string &body,
12267                                const std::string &content_type,
12268                                ContentReceiver content_receiver,
12269                                DownloadProgress progress) {
12270  Request req;
12271  req.method = "PATCH";
12272  req.path = path;
12273  req.headers = headers;
12274  req.body = body;
12275  req.content_receiver =
12276      [content_receiver](const char *data, size_t data_length,
12277                         size_t /*offset*/, size_t /*total_length*/) {
12278        return content_receiver(data, data_length);
12279      };
12280  req.download_progress = std::move(progress);
12281
12282  if (max_timeout_msec_ > 0) {
12283    req.start_time_ = std::chrono::steady_clock::now();
12284  }
12285
12286  if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
12287
12288  return send_(std::move(req));
12289}
12290
12291Result ClientImpl::Delete(const std::string &path,
12292                                 DownloadProgress progress) {
12293  return Delete(path, Headers(), std::string(), std::string(), progress);
12294}
12295
12296Result ClientImpl::Delete(const std::string &path,
12297                                 const Headers &headers,
12298                                 DownloadProgress progress) {
12299  return Delete(path, headers, std::string(), std::string(), progress);
12300}
12301
12302Result ClientImpl::Delete(const std::string &path, const char *body,
12303                                 size_t content_length,
12304                                 const std::string &content_type,
12305                                 DownloadProgress progress) {
12306  return Delete(path, Headers(), body, content_length, content_type, progress);
12307}
12308
12309Result ClientImpl::Delete(const std::string &path,
12310                                 const std::string &body,
12311                                 const std::string &content_type,
12312                                 DownloadProgress progress) {
12313  return Delete(path, Headers(), body.data(), body.size(), content_type,
12314                progress);
12315}
12316
12317Result ClientImpl::Delete(const std::string &path,
12318                                 const Headers &headers,
12319                                 const std::string &body,
12320                                 const std::string &content_type,
12321                                 DownloadProgress progress) {
12322  return Delete(path, headers, body.data(), body.size(), content_type,
12323                progress);
12324}
12325
12326Result ClientImpl::Delete(const std::string &path, const Params &params,
12327                                 DownloadProgress progress) {
12328  return Delete(path, Headers(), params, progress);
12329}
12330
12331Result ClientImpl::Delete(const std::string &path,
12332                                 const Headers &headers, const Params &params,
12333                                 DownloadProgress progress) {
12334  auto query = detail::params_to_query_str(params);
12335  return Delete(path, headers, query, "application/x-www-form-urlencoded",
12336                progress);
12337}
12338
12339Result ClientImpl::Delete(const std::string &path,
12340                                 const Headers &headers, const char *body,
12341                                 size_t content_length,
12342                                 const std::string &content_type,
12343                                 DownloadProgress progress) {
12344  Request req;
12345  req.method = "DELETE";
12346  req.headers = headers;
12347  req.path = path;
12348  req.download_progress = std::move(progress);
12349  if (max_timeout_msec_ > 0) {
12350    req.start_time_ = std::chrono::steady_clock::now();
12351  }
12352
12353  if (!content_type.empty()) { req.set_header("Content-Type", content_type); }
12354  req.body.assign(body, content_length);
12355
12356  return send_(std::move(req));
12357}
12358
12359Result ClientImpl::Options(const std::string &path) {
12360  return Options(path, Headers());
12361}
12362
12363Result ClientImpl::Options(const std::string &path,
12364                                  const Headers &headers) {
12365  Request req;
12366  req.method = "OPTIONS";
12367  req.headers = headers;
12368  req.path = path;
12369  if (max_timeout_msec_ > 0) {
12370    req.start_time_ = std::chrono::steady_clock::now();
12371  }
12372
12373  return send_(std::move(req));
12374}
12375
12376void ClientImpl::stop() {
12377  std::lock_guard<std::mutex> guard(socket_mutex_);
12378
12379  // If there is anything ongoing right now, the ONLY thread-safe thing we can
12380  // do is to shutdown_socket, so that threads using this socket suddenly
12381  // discover they can't read/write any more and error out. Everything else
12382  // (closing the socket, shutting ssl down) is unsafe because these actions
12383  // are not thread-safe.
12384  if (socket_requests_in_flight_ > 0) {
12385    shutdown_socket(socket_);
12386
12387    // Aside from that, we set a flag for the socket to be closed when we're
12388    // done.
12389    socket_should_be_closed_when_request_is_done_ = true;
12390    return;
12391  }
12392
12393  disconnect(/*gracefully=*/true);
12394}
12395
12396std::string ClientImpl::host() const { return host_; }
12397
12398int ClientImpl::port() const { return port_; }
12399
12400size_t ClientImpl::is_socket_open() const {
12401  std::lock_guard<std::mutex> guard(socket_mutex_);
12402  return socket_.is_open();
12403}
12404
12405socket_t ClientImpl::socket() const { return socket_.sock; }
12406
12407void ClientImpl::set_connection_timeout(time_t sec, time_t usec) {
12408  connection_timeout_sec_ = sec;
12409  connection_timeout_usec_ = usec;
12410}
12411
12412void ClientImpl::set_read_timeout(time_t sec, time_t usec) {
12413  read_timeout_sec_ = sec;
12414  read_timeout_usec_ = usec;
12415}
12416
12417void ClientImpl::set_write_timeout(time_t sec, time_t usec) {
12418  write_timeout_sec_ = sec;
12419  write_timeout_usec_ = usec;
12420}
12421
12422void ClientImpl::set_max_timeout(time_t msec) {
12423  max_timeout_msec_ = msec;
12424}
12425
12426void ClientImpl::set_basic_auth(const std::string &username,
12427                                       const std::string &password) {
12428  basic_auth_username_ = username;
12429  basic_auth_password_ = password;
12430}
12431
12432void ClientImpl::set_bearer_token_auth(const std::string &token) {
12433  bearer_token_auth_token_ = token;
12434}
12435
12436void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; }
12437
12438void ClientImpl::set_follow_location(bool on) { follow_location_ = on; }
12439
12440void ClientImpl::set_path_encode(bool on) { path_encode_ = on; }
12441
12442void
12443ClientImpl::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
12444  addr_map_ = std::move(addr_map);
12445}
12446
12447void ClientImpl::set_default_headers(Headers headers) {
12448  default_headers_ = std::move(headers);
12449}
12450
12451void ClientImpl::set_header_writer(
12452    std::function<ssize_t(Stream &, Headers &)> const &writer) {
12453  header_writer_ = writer;
12454}
12455
12456void ClientImpl::set_address_family(int family) {
12457  address_family_ = family;
12458}
12459
12460void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
12461
12462void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
12463
12464void ClientImpl::set_socket_options(SocketOptions socket_options) {
12465  socket_options_ = std::move(socket_options);
12466}
12467
12468void ClientImpl::set_compress(bool on) { compress_ = on; }
12469
12470void ClientImpl::set_decompress(bool on) { decompress_ = on; }
12471
12472void ClientImpl::set_payload_max_length(size_t length) {
12473  payload_max_length_ = length;
12474  has_payload_max_length_ = true;
12475}
12476
12477void ClientImpl::set_interface(const std::string &intf) {
12478  interface_ = intf;
12479}
12480
12481void ClientImpl::set_proxy(const std::string &host, int port) {
12482  proxy_host_ = host;
12483  proxy_port_ = port;
12484  std::lock_guard<std::mutex> guard(socket_mutex_);
12485  disconnect(/*gracefully=*/true);
12486}
12487
12488void ClientImpl::set_proxy_basic_auth(const std::string &username,
12489                                             const std::string &password) {
12490  proxy_basic_auth_username_ = username;
12491  proxy_basic_auth_password_ = password;
12492}
12493
12494void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) {
12495  proxy_bearer_token_auth_token_ = token;
12496}
12497
12498void ClientImpl::set_no_proxy(const std::vector<std::string> &patterns) {
12499  std::vector<detail::NoProxyEntry> parsed;
12500  parsed.reserve(patterns.size());
12501  for (const auto &p : patterns) {
12502    auto trimmed = detail::trim_copy(p);
12503    if (trimmed.empty()) { continue; }
12504    detail::NoProxyEntry entry;
12505    if (detail::parse_no_proxy_entry(trimmed, entry)) {
12506      parsed.push_back(std::move(entry));
12507    }
12508  }
12509  no_proxy_entries_ = std::move(parsed);
12510  std::lock_guard<std::mutex> guard(socket_mutex_);
12511  disconnect(/*gracefully=*/true);
12512}
12513
12514#ifdef CPPHTTPLIB_SSL_ENABLED
12515void ClientImpl::set_digest_auth(const std::string &username,
12516                                        const std::string &password) {
12517  digest_auth_username_ = username;
12518  digest_auth_password_ = password;
12519}
12520
12521void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path,
12522                                         const std::string &ca_cert_dir_path) {
12523  ca_cert_file_path_ = ca_cert_file_path;
12524  ca_cert_dir_path_ = ca_cert_dir_path;
12525}
12526
12527void ClientImpl::set_proxy_digest_auth(const std::string &username,
12528                                              const std::string &password) {
12529  proxy_digest_auth_username_ = username;
12530  proxy_digest_auth_password_ = password;
12531}
12532
12533void ClientImpl::enable_server_certificate_verification(bool enabled) {
12534  server_certificate_verification_ = enabled;
12535}
12536
12537void ClientImpl::enable_server_hostname_verification(bool enabled) {
12538  server_hostname_verification_ = enabled;
12539}
12540
12541void ClientImpl::enable_system_ca(bool enabled) {
12542  system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled;
12543}
12544#endif
12545
12546void ClientImpl::set_logger(Logger logger) {
12547  logger_ = std::move(logger);
12548}
12549
12550void ClientImpl::set_error_logger(ErrorLogger error_logger) {
12551  error_logger_ = std::move(error_logger);
12552}
12553
12554/*
12555 * SSL/TLS Common Implementation
12556 */
12557
12558ClientConnection::~ClientConnection() {
12559#ifdef CPPHTTPLIB_SSL_ENABLED
12560  if (session) {
12561    tls::shutdown(session, true);
12562    tls::free_session(session);
12563    session = nullptr;
12564  }
12565#endif
12566
12567  if (sock != INVALID_SOCKET) {
12568    detail::close_socket(sock);
12569    sock = INVALID_SOCKET;
12570  }
12571}
12572
12573// Universal client implementation
12574Client::Client(const std::string &scheme_host_port)
12575    : Client(scheme_host_port, std::string(), std::string()) {}
12576
12577Client::Client(const std::string &scheme_host_port,
12578                      const std::string &client_cert_path,
12579                      const std::string &client_key_path) {
12580  detail::UrlComponents uc;
12581  if (detail::parse_url(scheme_host_port, uc) && !uc.host.empty()) {
12582    auto &scheme = uc.scheme;
12583
12584#ifdef CPPHTTPLIB_SSL_ENABLED
12585    if (!scheme.empty() && (scheme != "http" && scheme != "https")) {
12586#else
12587    if (!scheme.empty() && scheme != "http") {
12588#endif
12589#ifndef CPPHTTPLIB_NO_EXCEPTIONS
12590      std::string msg = "'" + scheme + "' scheme is not supported.";
12591      throw std::invalid_argument(msg);
12592#endif
12593      return;
12594    }
12595
12596    auto is_ssl = scheme == "https";
12597
12598    auto host = std::move(uc.host);
12599
12600    auto port = is_ssl ? 443 : 80;
12601    if (!uc.port.empty() && !detail::parse_port(uc.port, port)) { return; }
12602
12603    if (is_ssl) {
12604#ifdef CPPHTTPLIB_SSL_ENABLED
12605      cli_ = detail::make_unique<SSLClient>(host, port, client_cert_path,
12606                                            client_key_path);
12607      is_ssl_ = is_ssl;
12608#endif
12609    } else {
12610      cli_ = detail::make_unique<ClientImpl>(host, port, client_cert_path,
12611                                             client_key_path);
12612    }
12613  } else {
12614    // NOTE: Update TEST(UniversalClientImplTest, Ipv6LiteralAddress)
12615    // if port param below changes.
12616    cli_ = detail::make_unique<ClientImpl>(scheme_host_port, 80,
12617                                           client_cert_path, client_key_path);
12618  }
12619}
12620
12621Client::Client(const std::string &host, int port)
12622    : Client(host, port, std::string(), std::string()) {}
12623
12624Client::Client(const std::string &host, int port,
12625                      const std::string &client_cert_path,
12626                      const std::string &client_key_path)
12627    : cli_(detail::make_unique<ClientImpl>(host, port, client_cert_path,
12628                                           client_key_path)) {}
12629
12630Client::~Client() = default;
12631
12632bool Client::is_valid() const {
12633  return cli_ != nullptr && cli_->is_valid();
12634}
12635
12636Result Client::Get(const std::string &path, DownloadProgress progress) {
12637  return cli_->Get(path, std::move(progress));
12638}
12639Result Client::Get(const std::string &path, const Headers &headers,
12640                          DownloadProgress progress) {
12641  return cli_->Get(path, headers, std::move(progress));
12642}
12643Result Client::Get(const std::string &path,
12644                          ContentReceiver content_receiver,
12645                          DownloadProgress progress) {
12646  return cli_->Get(path, std::move(content_receiver), std::move(progress));
12647}
12648Result Client::Get(const std::string &path, const Headers &headers,
12649                          ContentReceiver content_receiver,
12650                          DownloadProgress progress) {
12651  return cli_->Get(path, headers, std::move(content_receiver),
12652                   std::move(progress));
12653}
12654Result Client::Get(const std::string &path,
12655                          ResponseHandler response_handler,
12656                          ContentReceiver content_receiver,
12657                          DownloadProgress progress) {
12658  return cli_->Get(path, std::move(response_handler),
12659                   std::move(content_receiver), std::move(progress));
12660}
12661Result Client::Get(const std::string &path, const Headers &headers,
12662                          ResponseHandler response_handler,
12663                          ContentReceiver content_receiver,
12664                          DownloadProgress progress) {
12665  return cli_->Get(path, headers, std::move(response_handler),
12666                   std::move(content_receiver), std::move(progress));
12667}
12668Result Client::Get(const std::string &path, const Params &params,
12669                          DownloadProgress progress) {
12670  return cli_->Get(path, params, std::move(progress));
12671}
12672Result Client::Get(const std::string &path, const Params &params,
12673                          const Headers &headers, DownloadProgress progress) {
12674  return cli_->Get(path, params, headers, std::move(progress));
12675}
12676Result Client::Get(const std::string &path, const Params &params,
12677                          const Headers &headers,
12678                          ContentReceiver content_receiver,
12679                          DownloadProgress progress) {
12680  return cli_->Get(path, params, headers, std::move(content_receiver),
12681                   std::move(progress));
12682}
12683Result Client::Get(const std::string &path, const Params &params,
12684                          const Headers &headers,
12685                          ResponseHandler response_handler,
12686                          ContentReceiver content_receiver,
12687                          DownloadProgress progress) {
12688  return cli_->Get(path, params, headers, std::move(response_handler),
12689                   std::move(content_receiver), std::move(progress));
12690}
12691
12692Result Client::Head(const std::string &path) { return cli_->Head(path); }
12693Result Client::Head(const std::string &path, const Headers &headers) {
12694  return cli_->Head(path, headers);
12695}
12696
12697Result Client::Post(const std::string &path) { return cli_->Post(path); }
12698Result Client::Post(const std::string &path, const Headers &headers) {
12699  return cli_->Post(path, headers);
12700}
12701Result Client::Post(const std::string &path, const char *body,
12702                           size_t content_length,
12703                           const std::string &content_type,
12704                           UploadProgress progress) {
12705  return cli_->Post(path, body, content_length, content_type, progress);
12706}
12707Result Client::Post(const std::string &path, const Headers &headers,
12708                           const char *body, size_t content_length,
12709                           const std::string &content_type,
12710                           UploadProgress progress) {
12711  return cli_->Post(path, headers, body, content_length, content_type,
12712                    progress);
12713}
12714Result Client::Post(const std::string &path, const std::string &body,
12715                           const std::string &content_type,
12716                           UploadProgress progress) {
12717  return cli_->Post(path, body, content_type, progress);
12718}
12719Result Client::Post(const std::string &path, const Headers &headers,
12720                           const std::string &body,
12721                           const std::string &content_type,
12722                           UploadProgress progress) {
12723  return cli_->Post(path, headers, body, content_type, progress);
12724}
12725Result Client::Post(const std::string &path, size_t content_length,
12726                           ContentProvider content_provider,
12727                           const std::string &content_type,
12728                           UploadProgress progress) {
12729  return cli_->Post(path, content_length, std::move(content_provider),
12730                    content_type, progress);
12731}
12732Result Client::Post(const std::string &path, size_t content_length,
12733                           ContentProvider content_provider,
12734                           const std::string &content_type,
12735                           ContentReceiver content_receiver,
12736                           UploadProgress progress) {
12737  return cli_->Post(path, content_length, std::move(content_provider),
12738                    content_type, std::move(content_receiver), progress);
12739}
12740Result Client::Post(const std::string &path,
12741                           ContentProviderWithoutLength content_provider,
12742                           const std::string &content_type,
12743                           UploadProgress progress) {
12744  return cli_->Post(path, std::move(content_provider), content_type, progress);
12745}
12746Result Client::Post(const std::string &path,
12747                           ContentProviderWithoutLength content_provider,
12748                           const std::string &content_type,
12749                           ContentReceiver content_receiver,
12750                           UploadProgress progress) {
12751  return cli_->Post(path, std::move(content_provider), content_type,
12752                    std::move(content_receiver), progress);
12753}
12754Result Client::Post(const std::string &path, const Headers &headers,
12755                           size_t content_length,
12756                           ContentProvider content_provider,
12757                           const std::string &content_type,
12758                           UploadProgress progress) {
12759  return cli_->Post(path, headers, content_length, std::move(content_provider),
12760                    content_type, progress);
12761}
12762Result Client::Post(const std::string &path, const Headers &headers,
12763                           size_t content_length,
12764                           ContentProvider content_provider,
12765                           const std::string &content_type,
12766                           ContentReceiver content_receiver,
12767                           DownloadProgress progress) {
12768  return cli_->Post(path, headers, content_length, std::move(content_provider),
12769                    content_type, std::move(content_receiver), progress);
12770}
12771Result Client::Post(const std::string &path, const Headers &headers,
12772                           ContentProviderWithoutLength content_provider,
12773                           const std::string &content_type,
12774                           UploadProgress progress) {
12775  return cli_->Post(path, headers, std::move(content_provider), content_type,
12776                    progress);
12777}
12778Result Client::Post(const std::string &path, const Headers &headers,
12779                           ContentProviderWithoutLength content_provider,
12780                           const std::string &content_type,
12781                           ContentReceiver content_receiver,
12782                           DownloadProgress progress) {
12783  return cli_->Post(path, headers, std::move(content_provider), content_type,
12784                    std::move(content_receiver), progress);
12785}
12786Result Client::Post(const std::string &path, const Params &params) {
12787  return cli_->Post(path, params);
12788}
12789Result Client::Post(const std::string &path, const Headers &headers,
12790                           const Params &params) {
12791  return cli_->Post(path, headers, params);
12792}
12793Result Client::Post(const std::string &path,
12794                           const UploadFormDataItems &items,
12795                           UploadProgress progress) {
12796  return cli_->Post(path, items, progress);
12797}
12798Result Client::Post(const std::string &path, const Headers &headers,
12799                           const UploadFormDataItems &items,
12800                           UploadProgress progress) {
12801  return cli_->Post(path, headers, items, progress);
12802}
12803Result Client::Post(const std::string &path, const Headers &headers,
12804                           const UploadFormDataItems &items,
12805                           const std::string &boundary,
12806                           UploadProgress progress) {
12807  return cli_->Post(path, headers, items, boundary, progress);
12808}
12809Result Client::Post(const std::string &path, const Headers &headers,
12810                           const UploadFormDataItems &items,
12811                           const FormDataProviderItems &provider_items,
12812                           UploadProgress progress) {
12813  return cli_->Post(path, headers, items, provider_items, progress);
12814}
12815Result Client::Post(const std::string &path, const Headers &headers,
12816                           const std::string &body,
12817                           const std::string &content_type,
12818                           ContentReceiver content_receiver,
12819                           DownloadProgress progress) {
12820  return cli_->Post(path, headers, body, content_type,
12821                    std::move(content_receiver), progress);
12822}
12823
12824Result Client::Put(const std::string &path) { return cli_->Put(path); }
12825Result Client::Put(const std::string &path, const Headers &headers) {
12826  return cli_->Put(path, headers);
12827}
12828Result Client::Put(const std::string &path, const char *body,
12829                          size_t content_length,
12830                          const std::string &content_type,
12831                          UploadProgress progress) {
12832  return cli_->Put(path, body, content_length, content_type, progress);
12833}
12834Result Client::Put(const std::string &path, const Headers &headers,
12835                          const char *body, size_t content_length,
12836                          const std::string &content_type,
12837                          UploadProgress progress) {
12838  return cli_->Put(path, headers, body, content_length, content_type, progress);
12839}
12840Result Client::Put(const std::string &path, const std::string &body,
12841                          const std::string &content_type,
12842                          UploadProgress progress) {
12843  return cli_->Put(path, body, content_type, progress);
12844}
12845Result Client::Put(const std::string &path, const Headers &headers,
12846                          const std::string &body,
12847                          const std::string &content_type,
12848                          UploadProgress progress) {
12849  return cli_->Put(path, headers, body, content_type, progress);
12850}
12851Result Client::Put(const std::string &path, size_t content_length,
12852                          ContentProvider content_provider,
12853                          const std::string &content_type,
12854                          UploadProgress progress) {
12855  return cli_->Put(path, content_length, std::move(content_provider),
12856                   content_type, progress);
12857}
12858Result Client::Put(const std::string &path, size_t content_length,
12859                          ContentProvider content_provider,
12860                          const std::string &content_type,
12861                          ContentReceiver content_receiver,
12862                          UploadProgress progress) {
12863  return cli_->Put(path, content_length, std::move(content_provider),
12864                   content_type, std::move(content_receiver), progress);
12865}
12866Result Client::Put(const std::string &path,
12867                          ContentProviderWithoutLength content_provider,
12868                          const std::string &content_type,
12869                          UploadProgress progress) {
12870  return cli_->Put(path, std::move(content_provider), content_type, progress);
12871}
12872Result Client::Put(const std::string &path,
12873                          ContentProviderWithoutLength content_provider,
12874                          const std::string &content_type,
12875                          ContentReceiver content_receiver,
12876                          UploadProgress progress) {
12877  return cli_->Put(path, std::move(content_provider), content_type,
12878                   std::move(content_receiver), progress);
12879}
12880Result Client::Put(const std::string &path, const Headers &headers,
12881                          size_t content_length,
12882                          ContentProvider content_provider,
12883                          const std::string &content_type,
12884                          UploadProgress progress) {
12885  return cli_->Put(path, headers, content_length, std::move(content_provider),
12886                   content_type, progress);
12887}
12888Result Client::Put(const std::string &path, const Headers &headers,
12889                          size_t content_length,
12890                          ContentProvider content_provider,
12891                          const std::string &content_type,
12892                          ContentReceiver content_receiver,
12893                          UploadProgress progress) {
12894  return cli_->Put(path, headers, content_length, std::move(content_provider),
12895                   content_type, std::move(content_receiver), progress);
12896}
12897Result Client::Put(const std::string &path, const Headers &headers,
12898                          ContentProviderWithoutLength content_provider,
12899                          const std::string &content_type,
12900                          UploadProgress progress) {
12901  return cli_->Put(path, headers, std::move(content_provider), content_type,
12902                   progress);
12903}
12904Result Client::Put(const std::string &path, const Headers &headers,
12905                          ContentProviderWithoutLength content_provider,
12906                          const std::string &content_type,
12907                          ContentReceiver content_receiver,
12908                          UploadProgress progress) {
12909  return cli_->Put(path, headers, std::move(content_provider), content_type,
12910                   std::move(content_receiver), progress);
12911}
12912Result Client::Put(const std::string &path, const Params &params) {
12913  return cli_->Put(path, params);
12914}
12915Result Client::Put(const std::string &path, const Headers &headers,
12916                          const Params &params) {
12917  return cli_->Put(path, headers, params);
12918}
12919Result Client::Put(const std::string &path,
12920                          const UploadFormDataItems &items,
12921                          UploadProgress progress) {
12922  return cli_->Put(path, items, progress);
12923}
12924Result Client::Put(const std::string &path, const Headers &headers,
12925                          const UploadFormDataItems &items,
12926                          UploadProgress progress) {
12927  return cli_->Put(path, headers, items, progress);
12928}
12929Result Client::Put(const std::string &path, const Headers &headers,
12930                          const UploadFormDataItems &items,
12931                          const std::string &boundary,
12932                          UploadProgress progress) {
12933  return cli_->Put(path, headers, items, boundary, progress);
12934}
12935Result Client::Put(const std::string &path, const Headers &headers,
12936                          const UploadFormDataItems &items,
12937                          const FormDataProviderItems &provider_items,
12938                          UploadProgress progress) {
12939  return cli_->Put(path, headers, items, provider_items, progress);
12940}
12941Result Client::Put(const std::string &path, const Headers &headers,
12942                          const std::string &body,
12943                          const std::string &content_type,
12944                          ContentReceiver content_receiver,
12945                          DownloadProgress progress) {
12946  return cli_->Put(path, headers, body, content_type, content_receiver,
12947                   progress);
12948}
12949
12950Result Client::Patch(const std::string &path) {
12951  return cli_->Patch(path);
12952}
12953Result Client::Patch(const std::string &path, const Headers &headers) {
12954  return cli_->Patch(path, headers);
12955}
12956Result Client::Patch(const std::string &path, const char *body,
12957                            size_t content_length,
12958                            const std::string &content_type,
12959                            UploadProgress progress) {
12960  return cli_->Patch(path, body, content_length, content_type, progress);
12961}
12962Result Client::Patch(const std::string &path, const Headers &headers,
12963                            const char *body, size_t content_length,
12964                            const std::string &content_type,
12965                            UploadProgress progress) {
12966  return cli_->Patch(path, headers, body, content_length, content_type,
12967                     progress);
12968}
12969Result Client::Patch(const std::string &path, const std::string &body,
12970                            const std::string &content_type,
12971                            UploadProgress progress) {
12972  return cli_->Patch(path, body, content_type, progress);
12973}
12974Result Client::Patch(const std::string &path, const Headers &headers,
12975                            const std::string &body,
12976                            const std::string &content_type,
12977                            UploadProgress progress) {
12978  return cli_->Patch(path, headers, body, content_type, progress);
12979}
12980Result Client::Patch(const std::string &path, size_t content_length,
12981                            ContentProvider content_provider,
12982                            const std::string &content_type,
12983                            UploadProgress progress) {
12984  return cli_->Patch(path, content_length, std::move(content_provider),
12985                     content_type, progress);
12986}
12987Result Client::Patch(const std::string &path, size_t content_length,
12988                            ContentProvider content_provider,
12989                            const std::string &content_type,
12990                            ContentReceiver content_receiver,
12991                            UploadProgress progress) {
12992  return cli_->Patch(path, content_length, std::move(content_provider),
12993                     content_type, std::move(content_receiver), progress);
12994}
12995Result Client::Patch(const std::string &path,
12996                            ContentProviderWithoutLength content_provider,
12997                            const std::string &content_type,
12998                            UploadProgress progress) {
12999  return cli_->Patch(path, std::move(content_provider), content_type, progress);
13000}
13001Result Client::Patch(const std::string &path,
13002                            ContentProviderWithoutLength content_provider,
13003                            const std::string &content_type,
13004                            ContentReceiver content_receiver,
13005                            UploadProgress progress) {
13006  return cli_->Patch(path, std::move(content_provider), content_type,
13007                     std::move(content_receiver), progress);
13008}
13009Result Client::Patch(const std::string &path, const Headers &headers,
13010                            size_t content_length,
13011                            ContentProvider content_provider,
13012                            const std::string &content_type,
13013                            UploadProgress progress) {
13014  return cli_->Patch(path, headers, content_length, std::move(content_provider),
13015                     content_type, progress);
13016}
13017Result Client::Patch(const std::string &path, const Headers &headers,
13018                            size_t content_length,
13019                            ContentProvider content_provider,
13020                            const std::string &content_type,
13021                            ContentReceiver content_receiver,
13022                            UploadProgress progress) {
13023  return cli_->Patch(path, headers, content_length, std::move(content_provider),
13024                     content_type, std::move(content_receiver), progress);
13025}
13026Result Client::Patch(const std::string &path, const Headers &headers,
13027                            ContentProviderWithoutLength content_provider,
13028                            const std::string &content_type,
13029                            UploadProgress progress) {
13030  return cli_->Patch(path, headers, std::move(content_provider), content_type,
13031                     progress);
13032}
13033Result Client::Patch(const std::string &path, const Headers &headers,
13034                            ContentProviderWithoutLength content_provider,
13035                            const std::string &content_type,
13036                            ContentReceiver content_receiver,
13037                            UploadProgress progress) {
13038  return cli_->Patch(path, headers, std::move(content_provider), content_type,
13039                     std::move(content_receiver), progress);
13040}
13041Result Client::Patch(const std::string &path, const Params &params) {
13042  return cli_->Patch(path, params);
13043}
13044Result Client::Patch(const std::string &path, const Headers &headers,
13045                            const Params &params) {
13046  return cli_->Patch(path, headers, params);
13047}
13048Result Client::Patch(const std::string &path,
13049                            const UploadFormDataItems &items,
13050                            UploadProgress progress) {
13051  return cli_->Patch(path, items, progress);
13052}
13053Result Client::Patch(const std::string &path, const Headers &headers,
13054                            const UploadFormDataItems &items,
13055                            UploadProgress progress) {
13056  return cli_->Patch(path, headers, items, progress);
13057}
13058Result Client::Patch(const std::string &path, const Headers &headers,
13059                            const UploadFormDataItems &items,
13060                            const std::string &boundary,
13061                            UploadProgress progress) {
13062  return cli_->Patch(path, headers, items, boundary, progress);
13063}
13064Result Client::Patch(const std::string &path, const Headers &headers,
13065                            const UploadFormDataItems &items,
13066                            const FormDataProviderItems &provider_items,
13067                            UploadProgress progress) {
13068  return cli_->Patch(path, headers, items, provider_items, progress);
13069}
13070Result Client::Patch(const std::string &path, const Headers &headers,
13071                            const std::string &body,
13072                            const std::string &content_type,
13073                            ContentReceiver content_receiver,
13074                            DownloadProgress progress) {
13075  return cli_->Patch(path, headers, body, content_type, content_receiver,
13076                     progress);
13077}
13078
13079Result Client::Delete(const std::string &path,
13080                             DownloadProgress progress) {
13081  return cli_->Delete(path, progress);
13082}
13083Result Client::Delete(const std::string &path, const Headers &headers,
13084                             DownloadProgress progress) {
13085  return cli_->Delete(path, headers, progress);
13086}
13087Result Client::Delete(const std::string &path, const char *body,
13088                             size_t content_length,
13089                             const std::string &content_type,
13090                             DownloadProgress progress) {
13091  return cli_->Delete(path, body, content_length, content_type, progress);
13092}
13093Result Client::Delete(const std::string &path, const Headers &headers,
13094                             const char *body, size_t content_length,
13095                             const std::string &content_type,
13096                             DownloadProgress progress) {
13097  return cli_->Delete(path, headers, body, content_length, content_type,
13098                      progress);
13099}
13100Result Client::Delete(const std::string &path, const std::string &body,
13101                             const std::string &content_type,
13102                             DownloadProgress progress) {
13103  return cli_->Delete(path, body, content_type, progress);
13104}
13105Result Client::Delete(const std::string &path, const Headers &headers,
13106                             const std::string &body,
13107                             const std::string &content_type,
13108                             DownloadProgress progress) {
13109  return cli_->Delete(path, headers, body, content_type, progress);
13110}
13111Result Client::Delete(const std::string &path, const Params &params,
13112                             DownloadProgress progress) {
13113  return cli_->Delete(path, params, progress);
13114}
13115Result Client::Delete(const std::string &path, const Headers &headers,
13116                             const Params &params, DownloadProgress progress) {
13117  return cli_->Delete(path, headers, params, progress);
13118}
13119
13120Result Client::Options(const std::string &path) {
13121  return cli_->Options(path);
13122}
13123Result Client::Options(const std::string &path, const Headers &headers) {
13124  return cli_->Options(path, headers);
13125}
13126
13127ClientImpl::StreamHandle
13128Client::open_stream(const std::string &method, const std::string &path,
13129                    const Params &params, const Headers &headers,
13130                    const std::string &body, const std::string &content_type) {
13131  return cli_->open_stream(method, path, params, headers, body, content_type);
13132}
13133
13134bool Client::send(Request &req, Response &res, Error &error) {
13135  return cli_->send(req, res, error);
13136}
13137
13138Result Client::send(const Request &req) { return cli_->send(req); }
13139
13140void Client::stop() { cli_->stop(); }
13141
13142std::string Client::host() const { return cli_->host(); }
13143
13144int Client::port() const { return cli_->port(); }
13145
13146size_t Client::is_socket_open() const { return cli_->is_socket_open(); }
13147
13148socket_t Client::socket() const { return cli_->socket(); }
13149
13150void
13151Client::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
13152  cli_->set_hostname_addr_map(std::move(addr_map));
13153}
13154
13155void Client::set_default_headers(Headers headers) {
13156  cli_->set_default_headers(std::move(headers));
13157}
13158
13159void Client::set_header_writer(
13160    std::function<ssize_t(Stream &, Headers &)> const &writer) {
13161  cli_->set_header_writer(writer);
13162}
13163
13164void Client::set_address_family(int family) {
13165  cli_->set_address_family(family);
13166}
13167
13168void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); }
13169
13170void Client::set_socket_options(SocketOptions socket_options) {
13171  cli_->set_socket_options(std::move(socket_options));
13172}
13173
13174void Client::set_connection_timeout(time_t sec, time_t usec) {
13175  cli_->set_connection_timeout(sec, usec);
13176}
13177
13178void Client::set_read_timeout(time_t sec, time_t usec) {
13179  cli_->set_read_timeout(sec, usec);
13180}
13181
13182void Client::set_write_timeout(time_t sec, time_t usec) {
13183  cli_->set_write_timeout(sec, usec);
13184}
13185
13186void Client::set_basic_auth(const std::string &username,
13187                                   const std::string &password) {
13188  cli_->set_basic_auth(username, password);
13189}
13190void Client::set_bearer_token_auth(const std::string &token) {
13191  cli_->set_bearer_token_auth(token);
13192}
13193
13194void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); }
13195void Client::set_follow_location(bool on) {
13196  cli_->set_follow_location(on);
13197}
13198
13199void Client::set_path_encode(bool on) { cli_->set_path_encode(on); }
13200
13201void Client::set_compress(bool on) { cli_->set_compress(on); }
13202
13203void Client::set_decompress(bool on) { cli_->set_decompress(on); }
13204
13205void Client::set_payload_max_length(size_t length) {
13206  cli_->set_payload_max_length(length);
13207}
13208
13209void Client::set_interface(const std::string &intf) {
13210  cli_->set_interface(intf);
13211}
13212
13213void Client::set_proxy(const std::string &host, int port) {
13214  cli_->set_proxy(host, port);
13215}
13216void Client::set_proxy_basic_auth(const std::string &username,
13217                                         const std::string &password) {
13218  cli_->set_proxy_basic_auth(username, password);
13219}
13220void Client::set_proxy_bearer_token_auth(const std::string &token) {
13221  cli_->set_proxy_bearer_token_auth(token);
13222}
13223void Client::set_no_proxy(const std::vector<std::string> &patterns) {
13224  cli_->set_no_proxy(patterns);
13225}
13226
13227void Client::set_logger(Logger logger) {
13228  cli_->set_logger(std::move(logger));
13229}
13230
13231void Client::set_error_logger(ErrorLogger error_logger) {
13232  cli_->set_error_logger(std::move(error_logger));
13233}
13234
13235/*
13236 * Group 6: SSL Server and Client implementation
13237 */
13238
13239#ifdef CPPHTTPLIB_SSL_ENABLED
13240
13241// SSL HTTP server implementation
13242SSLServer::SSLServer(const char *cert_path, const char *private_key_path,
13243                            const char *client_ca_cert_file_path,
13244                            const char *client_ca_cert_dir_path,
13245                            const char *private_key_password) {
13246  using namespace tls;
13247
13248  ctx_ = create_server_context();
13249  if (!ctx_) { return; }
13250
13251  // Load server certificate and private key
13252  if (!set_server_cert_file(ctx_, cert_path, private_key_path,
13253                            private_key_password)) {
13254    last_ssl_error_ = static_cast<int>(get_error());
13255    free_context(ctx_);
13256    ctx_ = nullptr;
13257    return;
13258  }
13259
13260  // Load client CA certificates for client authentication
13261  if (client_ca_cert_file_path || client_ca_cert_dir_path) {
13262    if (!set_client_ca_file(ctx_, client_ca_cert_file_path,
13263                            client_ca_cert_dir_path)) {
13264      last_ssl_error_ = static_cast<int>(get_error());
13265      free_context(ctx_);
13266      ctx_ = nullptr;
13267      return;
13268    }
13269    // Enable client certificate verification
13270    set_verify_client(ctx_, true);
13271  }
13272}
13273
13274SSLServer::SSLServer(const PemMemory &pem) {
13275  using namespace tls;
13276  ctx_ = create_server_context();
13277  if (ctx_) {
13278    if (!set_server_cert_pem(ctx_, pem.cert_pem, pem.key_pem,
13279                             pem.private_key_password)) {
13280      last_ssl_error_ = static_cast<int>(get_error());
13281      free_context(ctx_);
13282      ctx_ = nullptr;
13283    } else if (pem.client_ca_pem && pem.client_ca_pem_len > 0) {
13284      if (!load_ca_pem(ctx_, pem.client_ca_pem, pem.client_ca_pem_len)) {
13285        last_ssl_error_ = static_cast<int>(get_error());
13286        free_context(ctx_);
13287        ctx_ = nullptr;
13288      } else {
13289        set_verify_client(ctx_, true);
13290      }
13291    }
13292  }
13293}
13294
13295SSLServer::SSLServer(const tls::ContextSetupCallback &setup_callback) {
13296  using namespace tls;
13297  ctx_ = create_server_context();
13298  if (ctx_) {
13299    if (!setup_callback(ctx_)) {
13300      free_context(ctx_);
13301      ctx_ = nullptr;
13302    }
13303  }
13304}
13305
13306SSLServer::~SSLServer() {
13307  if (ctx_) { tls::free_context(ctx_); }
13308}
13309
13310bool SSLServer::is_valid() const {
13311  return ctx_ != nullptr && Server::is_valid();
13312}
13313
13314bool SSLServer::process_and_close_socket(socket_t sock) {
13315  using namespace tls;
13316
13317  // Create TLS session with mutex protection
13318  session_t session = nullptr;
13319  {
13320    std::lock_guard<std::mutex> guard(ctx_mutex_);
13321    session = create_session(static_cast<ctx_t>(ctx_), sock);
13322  }
13323
13324  if (!session) {
13325    last_ssl_error_ = static_cast<int>(get_error());
13326    detail::shutdown_socket(sock);
13327    detail::close_socket(sock);
13328    return false;
13329  }
13330
13331  // Use scope_exit to ensure cleanup on all paths (including exceptions)
13332  bool handshake_done = false;
13333  bool ret = false;
13334  bool websocket_upgraded = false;
13335  auto cleanup = detail::scope_exit([&] {
13336    if (handshake_done) { shutdown(session, !websocket_upgraded && ret); }
13337    free_session(session);
13338    detail::shutdown_socket(sock);
13339    detail::close_socket(sock);
13340  });
13341
13342  // Perform TLS accept handshake with timeout
13343  TlsError tls_err;
13344  if (!accept_nonblocking(session, sock, read_timeout_sec_, read_timeout_usec_,
13345                          &tls_err)) {
13346#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
13347    // Map TlsError to legacy ssl_error for backward compatibility
13348    if (tls_err.code == ErrorCode::WantRead) {
13349      last_ssl_error_ = SSL_ERROR_WANT_READ;
13350    } else if (tls_err.code == ErrorCode::WantWrite) {
13351      last_ssl_error_ = SSL_ERROR_WANT_WRITE;
13352    } else {
13353      last_ssl_error_ = SSL_ERROR_SSL;
13354    }
13355#else
13356    last_ssl_error_ = static_cast<int>(get_error());
13357#endif
13358    return false;
13359  }
13360
13361  handshake_done = true;
13362
13363  std::string remote_addr;
13364  int remote_port = 0;
13365  detail::get_remote_ip_and_port(sock, remote_addr, remote_port);
13366
13367  std::string local_addr;
13368  int local_port = 0;
13369  detail::get_local_ip_and_port(sock, local_addr, local_port);
13370
13371  ret = serve_guarded([&]() {
13372    return detail::process_server_socket_ssl(
13373        svr_sock_, session, sock, keep_alive_max_count_,
13374        keep_alive_timeout_sec_, read_timeout_sec_, read_timeout_usec_,
13375        write_timeout_sec_, write_timeout_usec_,
13376        [&](Stream &strm, bool close_connection, bool &connection_closed) {
13377          return process_request(
13378              strm, remote_addr, remote_port, local_addr, local_port,
13379              close_connection, connection_closed,
13380              [&](Request &req) { req.ssl = session; }, &websocket_upgraded);
13381        });
13382  });
13383
13384  return ret;
13385}
13386
13387bool SSLServer::update_certs_pem(const char *cert_pem,
13388                                        const char *key_pem,
13389                                        const char *client_ca_pem,
13390                                        const char *password) {
13391  if (!ctx_) { return false; }
13392  std::lock_guard<std::mutex> guard(ctx_mutex_);
13393  if (!tls::update_server_cert(ctx_, cert_pem, key_pem, password)) {
13394    return false;
13395  }
13396  if (client_ca_pem) {
13397    return tls::update_server_client_ca(ctx_, client_ca_pem);
13398  }
13399  return true;
13400}
13401
13402// SSL HTTP client implementation
13403SSLClient::~SSLClient() {
13404  // Make sure to shut down SSL since shutdown_ssl will resolve to the
13405  // base function rather than the derived function once we get to the
13406  // base class destructor, and won't free the SSL (causing a leak).
13407  // This must happen before the context is freed below: some backends
13408  // (e.g. mbedTLS) have the SSL session borrow a raw pointer into the
13409  // context, so freeing the context first leaves close_notify reading
13410  // freed memory.
13411  shutdown_ssl_impl(socket_, true);
13412  if (ctx_) {
13413    tls::free_context(ctx_);
13414    ctx_ = nullptr;
13415  }
13416}
13417
13418bool SSLClient::is_valid() const { return ctx_ != nullptr; }
13419
13420void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) {
13421  shutdown_ssl_impl(socket, shutdown_gracefully);
13422}
13423
13424void SSLClient::shutdown_ssl_impl(Socket &socket,
13425                                         bool shutdown_gracefully) {
13426  if (socket.sock == INVALID_SOCKET) {
13427    assert(socket.ssl == nullptr);
13428    return;
13429  }
13430  if (socket.ssl) {
13431    tls::shutdown(socket.ssl, shutdown_gracefully);
13432    {
13433      std::lock_guard<std::mutex> guard(ctx_mutex_);
13434      tls::free_session(socket.ssl);
13435    }
13436    socket.ssl = nullptr;
13437  }
13438  assert(socket.ssl == nullptr);
13439}
13440
13441bool SSLClient::process_socket(
13442    const Socket &socket,
13443    std::chrono::time_point<std::chrono::steady_clock> start_time,
13444    std::function<bool(Stream &strm)> callback) {
13445  assert(socket.ssl);
13446  return detail::process_client_socket_ssl(
13447      socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_,
13448      write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, start_time,
13449      std::move(callback));
13450}
13451
13452bool SSLClient::is_ssl() const { return true; }
13453
13454bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) {
13455  if (!is_valid()) {
13456    error = Error::SSLConnection;
13457    return false;
13458  }
13459  return ClientImpl::create_and_connect_socket(socket, error);
13460}
13461
13462bool SSLClient::setup_proxy_connection(
13463    Socket &socket,
13464    std::chrono::time_point<std::chrono::steady_clock> start_time,
13465    Response &res, bool &success, Error &error) {
13466  if (!is_proxy_enabled_for_host(host_)) { return true; }
13467
13468  if (!connect_with_proxy(socket, start_time, res, success, error)) {
13469    return false;
13470  }
13471
13472  if (!initialize_ssl(socket, error)) {
13473    success = false;
13474    return false;
13475  }
13476
13477  return true;
13478}
13479
13480// Assumes that socket_mutex_ is locked and that there are no requests in
13481// flight
13482bool SSLClient::connect_with_proxy(
13483    Socket &socket,
13484    std::chrono::time_point<std::chrono::steady_clock> start_time,
13485    Response &res, bool &success, Error &error) {
13486  success = true;
13487  Response proxy_res;
13488  if (!detail::process_client_socket(
13489          socket.sock, read_timeout_sec_, read_timeout_usec_,
13490          write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
13491          start_time, [&](Stream &strm) {
13492            Request req2;
13493            req2.method = "CONNECT";
13494            req2.path =
13495                detail::make_host_and_port_string_always_port(host_, port_);
13496            if (max_timeout_msec_ > 0) {
13497              req2.start_time_ = std::chrono::steady_clock::now();
13498            }
13499            return process_request(strm, req2, proxy_res, false, error);
13500          })) {
13501    // Thread-safe to close everything because we are assuming there are no
13502    // requests in flight
13503    shutdown_ssl(socket, true);
13504    shutdown_socket(socket);
13505    close_socket(socket);
13506    success = false;
13507    return false;
13508  }
13509
13510  if (proxy_res.status == StatusCode::ProxyAuthenticationRequired_407) {
13511    if (!proxy_digest_auth_username_.empty() &&
13512        !proxy_digest_auth_password_.empty()) {
13513      std::map<std::string, std::string> auth;
13514      if (detail::parse_www_authenticate(proxy_res, auth, true)) {
13515        // Close the current socket and create a new one for the authenticated
13516        // request
13517        shutdown_ssl(socket, true);
13518        shutdown_socket(socket);
13519        close_socket(socket);
13520
13521        // Create a new socket for the authenticated CONNECT request
13522        if (!ensure_socket_connection(socket, error)) {
13523          success = false;
13524          output_error_log(error, nullptr);
13525          return false;
13526        }
13527
13528        proxy_res = Response();
13529        if (!detail::process_client_socket(
13530                socket.sock, read_timeout_sec_, read_timeout_usec_,
13531                write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
13532                start_time, [&](Stream &strm) {
13533                  Request req3;
13534                  req3.method = "CONNECT";
13535                  req3.path = detail::make_host_and_port_string_always_port(
13536                      host_, port_);
13537                  req3.headers.insert(detail::make_digest_authentication_header(
13538                      req3, auth, 1, detail::random_string(10),
13539                      proxy_digest_auth_username_, proxy_digest_auth_password_,
13540                      true));
13541                  if (max_timeout_msec_ > 0) {
13542                    req3.start_time_ = std::chrono::steady_clock::now();
13543                  }
13544                  return process_request(strm, req3, proxy_res, false, error);
13545                })) {
13546          // Thread-safe to close everything because we are assuming there are
13547          // no requests in flight
13548          shutdown_ssl(socket, true);
13549          shutdown_socket(socket);
13550          close_socket(socket);
13551          success = false;
13552          return false;
13553        }
13554      }
13555    }
13556  }
13557
13558  // If status code is not 200, proxy request is failed.
13559  // Set error to ProxyConnection and return proxy response
13560  // as the response of the request
13561  if (proxy_res.status != StatusCode::OK_200) {
13562    error = Error::ProxyConnection;
13563    output_error_log(error, nullptr);
13564    res = std::move(proxy_res);
13565    // Thread-safe to close everything because we are assuming there are
13566    // no requests in flight
13567    shutdown_ssl(socket, true);
13568    shutdown_socket(socket);
13569    close_socket(socket);
13570    return false;
13571  }
13572
13573  return true;
13574}
13575
13576bool SSLClient::ensure_socket_connection(Socket &socket, Error &error) {
13577  if (!ClientImpl::ensure_socket_connection(socket, error)) { return false; }
13578
13579  if (is_proxy_enabled_for_host(host_)) { return true; }
13580
13581  if (!initialize_ssl(socket, error)) {
13582    shutdown_socket(socket);
13583    close_socket(socket);
13584    return false;
13585  }
13586
13587  return true;
13588}
13589
13590// SSL HTTP client implementation
13591SSLClient::SSLClient(const std::string &host)
13592    : SSLClient(host, 443, std::string(), std::string()) {}
13593
13594SSLClient::SSLClient(const std::string &host, int port)
13595    : SSLClient(host, port, std::string(), std::string()) {}
13596
13597void SSLClient::init_ctx() {
13598  ctx_ = tls::create_client_context();
13599  if (ctx_) { tls::set_min_version(ctx_, tls::Version::TLS1_2); }
13600}
13601
13602void SSLClient::reset_ctx_on_error() {
13603  last_backend_error_ = tls::get_error();
13604  tls::free_context(ctx_);
13605  ctx_ = nullptr;
13606}
13607
13608SSLClient::SSLClient(const std::string &host, int port,
13609                            const std::string &client_cert_path,
13610                            const std::string &client_key_path,
13611                            const std::string &private_key_password)
13612    : ClientImpl(host, port, client_cert_path, client_key_path) {
13613  init_ctx();
13614  if (!ctx_) { return; }
13615
13616  if (!client_cert_path.empty() && !client_key_path.empty()) {
13617    const char *password =
13618        private_key_password.empty() ? nullptr : private_key_password.c_str();
13619    if (!tls::set_client_cert_file(ctx_, client_cert_path.c_str(),
13620                                   client_key_path.c_str(), password)) {
13621      reset_ctx_on_error();
13622    }
13623  }
13624}
13625
13626SSLClient::SSLClient(const std::string &host, int port,
13627                            const PemMemory &pem)
13628    : ClientImpl(host, port) {
13629  init_ctx();
13630  if (!ctx_) { return; }
13631
13632  if (pem.cert_pem && pem.key_pem) {
13633    if (!tls::set_client_cert_pem(ctx_, pem.cert_pem, pem.key_pem,
13634                                  pem.private_key_password)) {
13635      reset_ctx_on_error();
13636    }
13637  }
13638}
13639
13640void SSLClient::set_ca_cert_store(tls::ca_store_t ca_cert_store) {
13641  if (ca_cert_store && ctx_) {
13642    // set_ca_store takes ownership of ca_cert_store
13643    tls::set_ca_store(ctx_, ca_cert_store);
13644    ca_cert_store_set_ = true;
13645  } else if (ca_cert_store) {
13646    tls::free_ca_store(ca_cert_store);
13647  }
13648}
13649
13650void
13651SSLClient::set_server_certificate_verifier(tls::VerifyCallback verifier) {
13652  if (!ctx_) { return; }
13653  tls::set_verify_callback(ctx_, verifier);
13654}
13655
13656void SSLClient::set_session_verifier(
13657    std::function<SSLVerifierResponse(tls::session_t)> verifier) {
13658  session_verifier_ = std::move(verifier);
13659}
13660
13661#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
13662void SSLClient::enable_windows_certificate_verification(bool enabled) {
13663  enable_windows_cert_verification_ = enabled;
13664}
13665#endif
13666
13667void SSLClient::load_ca_cert_store(const char *ca_cert,
13668                                          std::size_t size) {
13669  if (ctx_ && ca_cert && size > 0) {
13670    ca_cert_pem_.assign(ca_cert, size); // Store for redirect transfer
13671    tls::load_ca_pem(ctx_, ca_cert, size);
13672  }
13673}
13674
13675bool SSLClient::load_certs() {
13676  auto ret = true;
13677
13678  // call_once rather than the plain flag WebSocketClient::create_stream() uses:
13679  // one client is shared across concurrent requests here.
13680  std::call_once(initialize_cert_, [&]() {
13681    std::lock_guard<std::mutex> guard(ctx_mutex_);
13682
13683    ret = detail::load_client_ca_config(
13684        ctx_, ca_cert_file_path_, ca_cert_dir_path_,
13685        !ca_cert_pem_.empty() || ca_cert_store_set_, system_ca_mode_,
13686        last_backend_error_);
13687  });
13688
13689  return ret;
13690}
13691
13692bool SSLClient::initialize_ssl(Socket &socket, Error &error) {
13693  // Load CA certificates if server verification is enabled
13694  if (server_certificate_verification_) {
13695    if (!load_certs()) {
13696      error = Error::SSLLoadingCerts;
13697      output_error_log(error, nullptr);
13698      return false;
13699    }
13700  }
13701
13702  detail::ClientTlsSessionOptions options;
13703  options.server_hostname_verification = server_hostname_verification_;
13704  options.session_verifier = session_verifier_;
13705  options.ctx_mutex = &ctx_mutex_;
13706#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
13707  // Skip Schannel when a custom CA cert is specified, as the Windows
13708  // certificate store would not know about user-provided CA certificates.
13709  // Also skip when system CA trust is explicitly disabled.
13710  options.windows_cert_verification =
13711      enable_windows_cert_verification_ &&
13712      system_ca_mode_ != SystemCAMode::Disabled && ca_cert_file_path_.empty() &&
13713      ca_cert_dir_path_.empty() && ca_cert_pem_.empty() && !ca_cert_store_set_;
13714#endif
13715
13716  tls::session_t session = nullptr;
13717
13718  // Use scope_exit to ensure session is freed on error paths
13719  bool success = false;
13720  auto session_guard = detail::scope_exit([&] {
13721    if (!success) { tls::free_session(session); }
13722  });
13723
13724  detail::ClientTlsSessionError tls_error;
13725  if (!detail::setup_client_tls_session(
13726          host_, ctx_, session, socket.sock, server_certificate_verification_,
13727          connection_timeout_sec_, connection_timeout_usec_, &tls_error,
13728          options)) {
13729    error = tls_error.error;
13730    last_ssl_error_ = tls_error.ssl_error;
13731    last_backend_error_ = tls_error.backend_error;
13732    output_error_log(error, nullptr);
13733    return false;
13734  }
13735
13736  success = true;
13737  socket.ssl = session;
13738  return true;
13739}
13740
13741void Client::set_digest_auth(const std::string &username,
13742                                    const std::string &password) {
13743  cli_->set_digest_auth(username, password);
13744}
13745
13746void Client::set_proxy_digest_auth(const std::string &username,
13747                                          const std::string &password) {
13748  cli_->set_proxy_digest_auth(username, password);
13749}
13750
13751void Client::enable_server_certificate_verification(bool enabled) {
13752  cli_->enable_server_certificate_verification(enabled);
13753}
13754
13755void Client::enable_server_hostname_verification(bool enabled) {
13756  cli_->enable_server_hostname_verification(enabled);
13757}
13758
13759void Client::enable_system_ca(bool enabled) {
13760  cli_->enable_system_ca(enabled);
13761}
13762
13763#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
13764void Client::enable_windows_certificate_verification(bool enabled) {
13765  if (is_ssl_) {
13766    static_cast<SSLClient &>(*cli_).enable_windows_certificate_verification(
13767        enabled);
13768  }
13769}
13770#endif
13771
13772void Client::set_ca_cert_path(const std::string &ca_cert_file_path,
13773                                     const std::string &ca_cert_dir_path) {
13774  cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path);
13775}
13776
13777void Client::set_ca_cert_store(tls::ca_store_t ca_cert_store) {
13778  if (is_ssl_) {
13779    static_cast<SSLClient &>(*cli_).set_ca_cert_store(ca_cert_store);
13780  } else if (ca_cert_store) {
13781    tls::free_ca_store(ca_cert_store);
13782  }
13783}
13784
13785void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) {
13786  if (is_ssl_) {
13787    // Use the PEM-based path so the CA data is retained for redirect transfer
13788    static_cast<SSLClient &>(*cli_).load_ca_cert_store(ca_cert, size);
13789  }
13790}
13791
13792void
13793Client::set_server_certificate_verifier(tls::VerifyCallback verifier) {
13794  if (is_ssl_) {
13795    static_cast<SSLClient &>(*cli_).set_server_certificate_verifier(
13796        std::move(verifier));
13797  }
13798}
13799
13800void Client::set_session_verifier(
13801    std::function<SSLVerifierResponse(tls::session_t)> verifier) {
13802  if (is_ssl_) {
13803    static_cast<SSLClient &>(*cli_).set_session_verifier(std::move(verifier));
13804  }
13805}
13806
13807tls::ctx_t Client::tls_context() const {
13808  if (is_ssl_) { return static_cast<SSLClient &>(*cli_).tls_context(); }
13809  return nullptr;
13810}
13811
13812#endif // CPPHTTPLIB_SSL_ENABLED
13813
13814/*
13815 * Group 7: TLS abstraction layer - Common API
13816 */
13817
13818#ifdef CPPHTTPLIB_SSL_ENABLED
13819
13820namespace tls {
13821
13822// Helper for PeerCert construction
13823PeerCert get_peer_cert_from_session(const_session_t session) {
13824  return PeerCert(get_peer_cert(session));
13825}
13826
13827namespace impl {
13828
13829VerifyCallback &get_verify_callback() {
13830  static thread_local VerifyCallback callback;
13831  return callback;
13832}
13833
13834VerifyCallback &get_mbedtls_verify_callback() {
13835  static thread_local VerifyCallback callback;
13836  return callback;
13837}
13838
13839// Check if a string is an IPv4 address
13840bool is_ipv4_address(const std::string &str) {
13841  int dots = 0;
13842  for (char c : str) {
13843    if (c == '.') {
13844      dots++;
13845    } else if (!detail::is_ascii_digit(c)) {
13846      return false;
13847    }
13848  }
13849  return dots == 3;
13850}
13851
13852// Parse IPv4 address string to bytes
13853bool parse_ipv4(const std::string &str, unsigned char *out) {
13854  const char *p = str.c_str();
13855  for (int i = 0; i < 4; i++) {
13856    if (i > 0) {
13857      if (*p != '.') { return false; }
13858      p++;
13859    }
13860    int val = 0;
13861    int digits = 0;
13862    while (detail::is_ascii_digit(*p)) {
13863      val = val * 10 + (*p - '0');
13864      if (val > 255) { return false; }
13865      p++;
13866      digits++;
13867    }
13868    if (digits == 0) { return false; }
13869    // Reject leading zeros (e.g., "01.002.03.04") to prevent ambiguity
13870    if (digits > 1 && *(p - digits) == '0') { return false; }
13871    out[i] = static_cast<unsigned char>(val);
13872  }
13873  return *p == '\0';
13874}
13875
13876// Parse an IP literal (IPv4 or IPv6) into raw network-order bytes.
13877// `out` must have room for at least 16 bytes. Returns the address length
13878// (4 for IPv4, 16 for IPv6) on success, or 0 if the string is not an IP
13879// literal. Used to match a host against iPAddress SANs the same way the
13880// OpenSSL backend does via X509_check_ip.
13881size_t parse_ip_address(const std::string &str, unsigned char *out) {
13882  if (is_ipv4_address(str)) { return parse_ipv4(str, out) ? 4 : 0; }
13883  struct in6_addr addr6 = {};
13884  if (inet_pton(AF_INET6, str.c_str(), &addr6) == 1) {
13885    memcpy(out, &addr6, 16);
13886    return 16;
13887  }
13888  return 0;
13889}
13890
13891#ifdef _WIN32
13892// Enumerate Windows system certificates and call callback with DER data
13893template <typename Callback>
13894bool enumerate_windows_system_certs(Callback cb) {
13895  bool loaded = false;
13896  static const wchar_t *store_names[] = {L"ROOT", L"CA"};
13897  for (auto store_name : store_names) {
13898    HCERTSTORE hStore = CertOpenSystemStoreW(0, store_name);
13899    if (hStore) {
13900      PCCERT_CONTEXT pContext = nullptr;
13901      while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
13902             nullptr) {
13903        if (cb(pContext->pbCertEncoded, pContext->cbCertEncoded)) {
13904          loaded = true;
13905        }
13906      }
13907      CertCloseStore(hStore, 0);
13908    }
13909  }
13910  return loaded;
13911}
13912#endif
13913
13914#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
13915// Enumerate macOS Keychain certificates and call callback with DER data
13916template <typename Callback>
13917bool enumerate_macos_keychain_certs(Callback cb) {
13918  bool loaded = false;
13919  const SecTrustSettingsDomain domains[] = {
13920      kSecTrustSettingsDomainSystem,
13921      kSecTrustSettingsDomainAdmin,
13922      kSecTrustSettingsDomainUser,
13923  };
13924  for (auto domain : domains) {
13925    CFArrayRef certs = nullptr;
13926    OSStatus status = SecTrustSettingsCopyCertificates(domain, &certs);
13927    if (status != errSecSuccess || !certs) {
13928      if (certs) CFRelease(certs);
13929      continue;
13930    }
13931    CFIndex count = CFArrayGetCount(certs);
13932    for (CFIndex i = 0; i < count; i++) {
13933      SecCertificateRef cert =
13934          (SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
13935      CFDataRef data = SecCertificateCopyData(cert);
13936      if (data) {
13937        if (cb(CFDataGetBytePtr(data),
13938               static_cast<size_t>(CFDataGetLength(data)))) {
13939          loaded = true;
13940        }
13941        CFRelease(data);
13942      }
13943    }
13944    CFRelease(certs);
13945  }
13946  return loaded;
13947}
13948#endif
13949
13950#if !defined(_WIN32) && !(defined(__APPLE__) &&                                \
13951                          defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN))
13952// Common CA certificate file paths on Linux/Unix
13953const char **system_ca_paths() {
13954  static const char *paths[] = {
13955      "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu
13956      "/etc/pki/tls/certs/ca-bundle.crt",   // RHEL/CentOS
13957      "/etc/ssl/ca-bundle.pem",             // OpenSUSE
13958      "/etc/pki/tls/cacert.pem",            // OpenELEC
13959      "/etc/ssl/cert.pem",                  // Alpine, FreeBSD
13960      nullptr};
13961  return paths;
13962}
13963
13964// Common CA certificate directory paths on Linux/Unix
13965const char **system_ca_dirs() {
13966  static const char *dirs[] = {"/etc/ssl/certs",             // Debian/Ubuntu
13967                               "/etc/pki/tls/certs",         // RHEL/CentOS
13968                               "/usr/share/ca-certificates", // Other
13969                               nullptr};
13970  return dirs;
13971}
13972#endif
13973
13974} // namespace impl
13975
13976bool set_client_ca_file(ctx_t ctx, const char *ca_file,
13977                               const char *ca_dir) {
13978  if (!ctx) { return false; }
13979
13980  bool success = true;
13981  if (ca_file && *ca_file) {
13982    if (!load_ca_file(ctx, ca_file)) { success = false; }
13983  }
13984  if (ca_dir && *ca_dir) {
13985    if (!load_ca_dir(ctx, ca_dir)) { success = false; }
13986  }
13987
13988#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
13989  // Set CA list for client certificate request (CertificateRequest message)
13990  if (ca_file && *ca_file) {
13991    auto list = SSL_load_client_CA_file(ca_file);
13992    if (list) { SSL_CTX_set_client_CA_list(static_cast<SSL_CTX *>(ctx), list); }
13993  }
13994#endif
13995
13996  return success;
13997}
13998
13999bool set_server_cert_pem(ctx_t ctx, const char *cert, const char *key,
14000                                const char *password) {
14001  return set_client_cert_pem(ctx, cert, key, password);
14002}
14003
14004bool set_server_cert_file(ctx_t ctx, const char *cert_path,
14005                                 const char *key_path, const char *password) {
14006  return set_client_cert_file(ctx, cert_path, key_path, password);
14007}
14008
14009// PeerCert implementation
14010PeerCert::PeerCert() = default;
14011
14012PeerCert::PeerCert(cert_t cert) : cert_(cert) {}
14013
14014PeerCert::PeerCert(PeerCert &&other) noexcept : cert_(other.cert_) {
14015  other.cert_ = nullptr;
14016}
14017
14018PeerCert &PeerCert::operator=(PeerCert &&other) noexcept {
14019  if (this != &other) {
14020    if (cert_) { free_cert(cert_); }
14021    cert_ = other.cert_;
14022    other.cert_ = nullptr;
14023  }
14024  return *this;
14025}
14026
14027PeerCert::~PeerCert() {
14028  if (cert_) { free_cert(cert_); }
14029}
14030
14031PeerCert::operator bool() const { return cert_ != nullptr; }
14032
14033std::string PeerCert::subject_cn() const {
14034  return cert_ ? get_cert_subject_cn(cert_) : std::string();
14035}
14036
14037std::string PeerCert::issuer_name() const {
14038  return cert_ ? get_cert_issuer_name(cert_) : std::string();
14039}
14040
14041bool PeerCert::check_hostname(const char *hostname) const {
14042  return cert_ ? verify_hostname(cert_, hostname) : false;
14043}
14044
14045std::vector<SanEntry> PeerCert::sans() const {
14046  std::vector<SanEntry> result;
14047  if (cert_) { get_cert_sans(cert_, result); }
14048  return result;
14049}
14050
14051bool PeerCert::validity(time_t &not_before, time_t &not_after) const {
14052  return cert_ ? get_cert_validity(cert_, not_before, not_after) : false;
14053}
14054
14055std::string PeerCert::serial() const {
14056  return cert_ ? get_cert_serial(cert_) : std::string();
14057}
14058
14059// VerifyContext method implementations
14060std::string VerifyContext::subject_cn() const {
14061  return cert ? get_cert_subject_cn(cert) : std::string();
14062}
14063
14064std::string VerifyContext::issuer_name() const {
14065  return cert ? get_cert_issuer_name(cert) : std::string();
14066}
14067
14068bool VerifyContext::check_hostname(const char *hostname) const {
14069  return cert ? verify_hostname(cert, hostname) : false;
14070}
14071
14072std::vector<SanEntry> VerifyContext::sans() const {
14073  std::vector<SanEntry> result;
14074  if (cert) { get_cert_sans(cert, result); }
14075  return result;
14076}
14077
14078bool VerifyContext::validity(time_t &not_before,
14079                                    time_t &not_after) const {
14080  return cert ? get_cert_validity(cert, not_before, not_after) : false;
14081}
14082
14083std::string VerifyContext::serial() const {
14084  return cert ? get_cert_serial(cert) : std::string();
14085}
14086
14087// TlsError static method implementation
14088std::string TlsError::verify_error_to_string(long error_code) {
14089  return verify_error_string(error_code);
14090}
14091
14092} // namespace tls
14093
14094// Request::peer_cert() implementation
14095tls::PeerCert Request::peer_cert() const {
14096  return tls::get_peer_cert_from_session(ssl);
14097}
14098
14099// Request::sni() implementation
14100std::string Request::sni() const {
14101  if (!ssl) { return std::string(); }
14102  const char *s = tls::get_sni(ssl);
14103  return s ? std::string(s) : std::string();
14104}
14105
14106#endif // CPPHTTPLIB_SSL_ENABLED
14107
14108/*
14109 * Group 8: TLS abstraction layer - OpenSSL backend
14110 */
14111
14112/*
14113 * OpenSSL Backend Implementation
14114 */
14115
14116#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
14117namespace tls {
14118
14119namespace impl {
14120
14121// Helper to map OpenSSL SSL_get_error to ErrorCode
14122ErrorCode map_ssl_error(int ssl_error, int &out_errno) {
14123  switch (ssl_error) {
14124  case SSL_ERROR_NONE: return ErrorCode::Success;
14125  case SSL_ERROR_WANT_READ: return ErrorCode::WantRead;
14126  case SSL_ERROR_WANT_WRITE: return ErrorCode::WantWrite;
14127  case SSL_ERROR_ZERO_RETURN: return ErrorCode::PeerClosed;
14128  case SSL_ERROR_SYSCALL: out_errno = errno; return ErrorCode::SyscallError;
14129  case SSL_ERROR_SSL:
14130  default: return ErrorCode::Fatal;
14131  }
14132}
14133
14134// Helper: Create client CA list from PEM string
14135// Returns a new STACK_OF(X509_NAME)* or nullptr on failure
14136// Caller takes ownership of returned list
14137STACK_OF(X509_NAME) *
14138    create_client_ca_list_from_pem(const char *ca_pem) {
14139  if (!ca_pem) { return nullptr; }
14140
14141  auto ca_list = sk_X509_NAME_new_null();
14142  if (!ca_list) { return nullptr; }
14143
14144  BIO *bio = BIO_new_mem_buf(ca_pem, -1);
14145  if (!bio) {
14146    sk_X509_NAME_pop_free(ca_list, X509_NAME_free);
14147    return nullptr;
14148  }
14149
14150  X509 *cert = nullptr;
14151  while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) !=
14152         nullptr) {
14153    const X509_NAME *name = X509_get_subject_name(cert);
14154    if (name) {
14155      sk_X509_NAME_push(ca_list, X509_NAME_dup(const_cast<X509_NAME *>(name)));
14156    }
14157    X509_free(cert);
14158  }
14159  BIO_free(bio);
14160
14161  return ca_list;
14162}
14163
14164// OpenSSL verify callback wrapper
14165int openssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
14166  auto &callback = get_verify_callback();
14167  if (!callback) { return preverify_ok; }
14168
14169  // Get SSL object from X509_STORE_CTX
14170  auto ssl = static_cast<SSL *>(
14171      X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
14172  if (!ssl) { return preverify_ok; }
14173
14174  // Get current certificate and depth
14175  auto cert = X509_STORE_CTX_get_current_cert(ctx);
14176  int depth = X509_STORE_CTX_get_error_depth(ctx);
14177  int error = X509_STORE_CTX_get_error(ctx);
14178
14179  // Build context
14180  VerifyContext verify_ctx;
14181  verify_ctx.session = static_cast<session_t>(ssl);
14182  verify_ctx.cert = static_cast<cert_t>(cert);
14183  verify_ctx.depth = depth;
14184  verify_ctx.preverify_ok = (preverify_ok != 0);
14185  verify_ctx.error_code = error;
14186  verify_ctx.error_string =
14187      (error != X509_V_OK) ? X509_verify_cert_error_string(error) : nullptr;
14188
14189  return callback(verify_ctx) ? 1 : 0;
14190}
14191
14192// X509_STORE_get0_objects is deprecated since OpenSSL 4.0 because it is not
14193// thread-safe; X509_STORE_get1_objects (OpenSSL 3.3+) returns a snapshot
14194// that must be released with release_store_objects
14195#if !defined(OPENSSL_IS_BORINGSSL) && !defined(LIBRESSL_VERSION_NUMBER) &&     \
14196    OPENSSL_VERSION_NUMBER >= 0x30300000L
14197#define CPPHTTPLIB_HAS_X509_STORE_GET1_OBJECTS
14198#endif
14199
14200STACK_OF(X509_OBJECT) * get_store_objects(X509_STORE *store) {
14201#ifdef CPPHTTPLIB_HAS_X509_STORE_GET1_OBJECTS
14202  return X509_STORE_get1_objects(store);
14203#else
14204  return X509_STORE_get0_objects(store);
14205#endif
14206}
14207
14208void release_store_objects(STACK_OF(X509_OBJECT) * objs) {
14209#ifdef CPPHTTPLIB_HAS_X509_STORE_GET1_OBJECTS
14210  sk_X509_OBJECT_pop_free(objs, X509_OBJECT_free);
14211#else
14212  (void)objs; // get0 variant returns an internal pointer; nothing to free
14213#endif
14214}
14215
14216} // namespace impl
14217
14218ctx_t create_client_context() {
14219  SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
14220  if (ctx) {
14221    // Disable auto-retry to properly handle non-blocking I/O
14222    SSL_CTX_clear_mode(ctx, SSL_MODE_AUTO_RETRY);
14223    // Set minimum TLS version
14224    SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
14225  }
14226  return static_cast<ctx_t>(ctx);
14227}
14228
14229void free_context(ctx_t ctx) {
14230  if (ctx) { SSL_CTX_free(static_cast<SSL_CTX *>(ctx)); }
14231}
14232
14233bool set_min_version(ctx_t ctx, Version version) {
14234  if (!ctx) return false;
14235  return SSL_CTX_set_min_proto_version(static_cast<SSL_CTX *>(ctx),
14236                                       static_cast<int>(version)) == 1;
14237}
14238
14239bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) {
14240  if (!ctx || !pem || len == 0) return false;
14241
14242  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
14243  auto store = SSL_CTX_get_cert_store(ssl_ctx);
14244  if (!store) return false;
14245
14246  auto bio = BIO_new_mem_buf(pem, static_cast<int>(len));
14247  if (!bio) return false;
14248
14249  bool ok = true;
14250  X509 *cert = nullptr;
14251  while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) !=
14252         nullptr) {
14253    if (X509_STORE_add_cert(store, cert) != 1) {
14254      // Ignore duplicate errors
14255      auto err = ERR_peek_last_error();
14256      if (ERR_GET_REASON(err) != X509_R_CERT_ALREADY_IN_HASH_TABLE) {
14257        ok = false;
14258      }
14259    }
14260    X509_free(cert);
14261    if (!ok) break;
14262  }
14263  BIO_free(bio);
14264
14265  // Clear any "no more certificates" errors
14266  ERR_clear_error();
14267  return ok;
14268}
14269
14270bool load_ca_file(ctx_t ctx, const char *file_path) {
14271  if (!ctx || !file_path) return false;
14272  return SSL_CTX_load_verify_locations(static_cast<SSL_CTX *>(ctx), file_path,
14273                                       nullptr) == 1;
14274}
14275
14276bool load_ca_dir(ctx_t ctx, const char *dir_path) {
14277  if (!ctx || !dir_path) return false;
14278  return SSL_CTX_load_verify_locations(static_cast<SSL_CTX *>(ctx), nullptr,
14279                                       dir_path) == 1;
14280}
14281
14282bool load_system_certs(ctx_t ctx) {
14283  if (!ctx) return false;
14284  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
14285
14286#ifdef _WIN32
14287  // Windows: Load from system certificate store (ROOT and CA)
14288  auto store = SSL_CTX_get_cert_store(ssl_ctx);
14289  if (!store) return false;
14290
14291  bool loaded_any = false;
14292  static const wchar_t *store_names[] = {L"ROOT", L"CA"};
14293  for (auto store_name : store_names) {
14294    auto hStore = CertOpenSystemStoreW(NULL, store_name);
14295    if (!hStore) continue;
14296
14297    PCCERT_CONTEXT pContext = nullptr;
14298    while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) !=
14299           nullptr) {
14300      const unsigned char *data = pContext->pbCertEncoded;
14301      auto x509 = d2i_X509(nullptr, &data, pContext->cbCertEncoded);
14302      if (x509) {
14303        if (X509_STORE_add_cert(store, x509) == 1) { loaded_any = true; }
14304        X509_free(x509);
14305      }
14306    }
14307    CertCloseStore(hStore, 0);
14308  }
14309  return loaded_any;
14310
14311#elif defined(__APPLE__)
14312#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
14313  // macOS: Load from Keychain
14314  auto store = SSL_CTX_get_cert_store(ssl_ctx);
14315  if (!store) return false;
14316
14317  bool loaded_any = false;
14318  const SecTrustSettingsDomain domains[] = {
14319      kSecTrustSettingsDomainSystem,
14320      kSecTrustSettingsDomainAdmin,
14321      kSecTrustSettingsDomainUser,
14322  };
14323  for (auto domain : domains) {
14324    CFArrayRef certs = nullptr;
14325    if (SecTrustSettingsCopyCertificates(domain, &certs) != errSecSuccess ||
14326        !certs) {
14327      if (certs) CFRelease(certs);
14328      continue;
14329    }
14330    auto count = CFArrayGetCount(certs);
14331    for (CFIndex i = 0; i < count; i++) {
14332      auto cert = reinterpret_cast<SecCertificateRef>(
14333          const_cast<void *>(CFArrayGetValueAtIndex(certs, i)));
14334      CFDataRef der = SecCertificateCopyData(cert);
14335      if (der) {
14336        const unsigned char *data = CFDataGetBytePtr(der);
14337        auto x509 = d2i_X509(nullptr, &data, CFDataGetLength(der));
14338        if (x509) {
14339          if (X509_STORE_add_cert(store, x509) == 1) { loaded_any = true; }
14340          X509_free(x509);
14341        }
14342        CFRelease(der);
14343      }
14344    }
14345    CFRelease(certs);
14346  }
14347  return loaded_any || SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
14348#else
14349  return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
14350#endif
14351
14352#else
14353  // Other Unix: use default verify paths
14354  return SSL_CTX_set_default_verify_paths(ssl_ctx) == 1;
14355#endif
14356}
14357
14358bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
14359                                const char *password) {
14360  if (!ctx || !cert || !key) return false;
14361
14362  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
14363
14364  // Load certificate
14365  auto cert_bio = BIO_new_mem_buf(cert, -1);
14366  if (!cert_bio) return false;
14367
14368  auto x509 = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr);
14369  BIO_free(cert_bio);
14370  if (!x509) return false;
14371
14372  auto cert_ok = SSL_CTX_use_certificate(ssl_ctx, x509) == 1;
14373  X509_free(x509);
14374  if (!cert_ok) return false;
14375
14376  // Load private key
14377  auto key_bio = BIO_new_mem_buf(key, -1);
14378  if (!key_bio) return false;
14379
14380  auto pkey = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr,
14381                                      password ? const_cast<char *>(password)
14382                                               : nullptr);
14383  BIO_free(key_bio);
14384  if (!pkey) return false;
14385
14386  auto key_ok = SSL_CTX_use_PrivateKey(ssl_ctx, pkey) == 1;
14387  EVP_PKEY_free(pkey);
14388
14389  return key_ok && SSL_CTX_check_private_key(ssl_ctx) == 1;
14390}
14391
14392bool set_client_cert_file(ctx_t ctx, const char *cert_path,
14393                                 const char *key_path, const char *password) {
14394  if (!ctx || !cert_path || !key_path) return false;
14395
14396  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
14397
14398  if (password && password[0] != '\0') {
14399    SSL_CTX_set_default_passwd_cb_userdata(
14400        ssl_ctx, reinterpret_cast<void *>(const_cast<char *>(password)));
14401  }
14402
14403  return SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_path) == 1 &&
14404         SSL_CTX_use_PrivateKey_file(ssl_ctx, key_path, SSL_FILETYPE_PEM) == 1;
14405}
14406
14407ctx_t create_server_context() {
14408  SSL_CTX *ctx = SSL_CTX_new(TLS_server_method());
14409  if (ctx) {
14410    SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION |
14411                                 SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
14412    SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
14413  }
14414  return static_cast<ctx_t>(ctx);
14415}
14416
14417void set_verify_client(ctx_t ctx, bool require) {
14418  if (!ctx) return;
14419  SSL_CTX_set_verify(static_cast<SSL_CTX *>(ctx),
14420                     require
14421                         ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
14422                         : SSL_VERIFY_NONE,
14423                     nullptr);
14424}
14425
14426session_t create_session(ctx_t ctx, socket_t sock) {
14427  if (!ctx || sock == INVALID_SOCKET) return nullptr;
14428
14429  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
14430  SSL *ssl = SSL_new(ssl_ctx);
14431  if (!ssl) return nullptr;
14432
14433  // Disable auto-retry for proper non-blocking I/O handling
14434  SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY);
14435
14436  auto bio = BIO_new_socket(static_cast<int>(sock), BIO_NOCLOSE);
14437  if (!bio) {
14438    SSL_free(ssl);
14439    return nullptr;
14440  }
14441
14442  SSL_set_bio(ssl, bio, bio);
14443  return static_cast<session_t>(ssl);
14444}
14445
14446void free_session(session_t session) {
14447  if (session) { SSL_free(static_cast<SSL *>(session)); }
14448}
14449
14450bool set_sni(session_t session, const char *hostname,
14451                    bool /*verify_hostname*/) {
14452  if (!session || !hostname) return false;
14453
14454  auto ssl = static_cast<SSL *>(session);
14455
14456  // Set SNI (Server Name Indication) only - does not enable verification.
14457  // OpenSSL never binds identity checking to SNI (that happens post-
14458  // handshake in setup_client_tls_session()), so verify_hostname is unused.
14459#if defined(OPENSSL_IS_BORINGSSL)
14460  return SSL_set_tlsext_host_name(ssl, hostname) == 1;
14461#else
14462  // Direct call instead of macro to suppress -Wold-style-cast warning
14463  return SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name,
14464                  static_cast<void *>(const_cast<char *>(hostname))) == 1;
14465#endif
14466}
14467
14468TlsError connect(session_t session) {
14469  if (!session) { return TlsError(); }
14470
14471  auto ssl = static_cast<SSL *>(session);
14472  auto ret = SSL_connect(ssl);
14473
14474  TlsError err;
14475  if (ret == 1) {
14476    err.code = ErrorCode::Success;
14477  } else {
14478    auto ssl_err = SSL_get_error(ssl, ret);
14479    err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
14480    err.backend_code = ERR_get_error();
14481  }
14482  return err;
14483}
14484
14485TlsError accept(session_t session) {
14486  if (!session) { return TlsError(); }
14487
14488  auto ssl = static_cast<SSL *>(session);
14489  auto ret = SSL_accept(ssl);
14490
14491  TlsError err;
14492  if (ret == 1) {
14493    err.code = ErrorCode::Success;
14494  } else {
14495    auto ssl_err = SSL_get_error(ssl, ret);
14496    err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
14497    err.backend_code = ERR_get_error();
14498  }
14499  return err;
14500}
14501
14502bool connect_nonblocking(session_t session, socket_t sock,
14503                                time_t timeout_sec, time_t timeout_usec,
14504                                TlsError *err) {
14505  if (!session) {
14506    if (err) { err->code = ErrorCode::Fatal; }
14507    return false;
14508  }
14509
14510  auto ssl = static_cast<SSL *>(session);
14511  auto bio = SSL_get_rbio(ssl);
14512
14513  // Set non-blocking mode for handshake
14514  detail::set_nonblocking(sock, true);
14515  if (bio) { BIO_set_nbio(bio, 1); }
14516
14517  auto cleanup = detail::scope_exit([&]() {
14518    // Restore blocking mode after handshake
14519    if (bio) { BIO_set_nbio(bio, 0); }
14520    detail::set_nonblocking(sock, false);
14521  });
14522
14523  auto res = 0;
14524  while ((res = SSL_connect(ssl)) != 1) {
14525    auto ssl_err = SSL_get_error(ssl, res);
14526    switch (ssl_err) {
14527    case SSL_ERROR_WANT_READ:
14528      if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
14529        continue;
14530      }
14531      break;
14532    case SSL_ERROR_WANT_WRITE:
14533      if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
14534        continue;
14535      }
14536      break;
14537    default: break;
14538    }
14539    if (err) {
14540      err->code = impl::map_ssl_error(ssl_err, err->sys_errno);
14541      err->backend_code = ERR_get_error();
14542    }
14543    return false;
14544  }
14545  if (err) { err->code = ErrorCode::Success; }
14546  return true;
14547}
14548
14549bool accept_nonblocking(session_t session, socket_t sock,
14550                               time_t timeout_sec, time_t timeout_usec,
14551                               TlsError *err) {
14552  if (!session) {
14553    if (err) { err->code = ErrorCode::Fatal; }
14554    return false;
14555  }
14556
14557  auto ssl = static_cast<SSL *>(session);
14558  auto bio = SSL_get_rbio(ssl);
14559
14560  // Set non-blocking mode for handshake
14561  detail::set_nonblocking(sock, true);
14562  if (bio) { BIO_set_nbio(bio, 1); }
14563
14564  auto cleanup = detail::scope_exit([&]() {
14565    // Restore blocking mode after handshake
14566    if (bio) { BIO_set_nbio(bio, 0); }
14567    detail::set_nonblocking(sock, false);
14568  });
14569
14570  auto res = 0;
14571  while ((res = SSL_accept(ssl)) != 1) {
14572    auto ssl_err = SSL_get_error(ssl, res);
14573    switch (ssl_err) {
14574    case SSL_ERROR_WANT_READ:
14575      if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
14576        continue;
14577      }
14578      break;
14579    case SSL_ERROR_WANT_WRITE:
14580      if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
14581        continue;
14582      }
14583      break;
14584    default: break;
14585    }
14586    if (err) {
14587      err->code = impl::map_ssl_error(ssl_err, err->sys_errno);
14588      err->backend_code = ERR_get_error();
14589    }
14590    return false;
14591  }
14592  if (err) { err->code = ErrorCode::Success; }
14593  return true;
14594}
14595
14596ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
14597  if (!session || !buf) {
14598    err.code = ErrorCode::Fatal;
14599    return -1;
14600  }
14601
14602  auto ssl = static_cast<SSL *>(session);
14603  constexpr auto max_len =
14604      static_cast<size_t>((std::numeric_limits<int>::max)());
14605  if (len > max_len) { len = max_len; }
14606  auto ret = SSL_read(ssl, buf, static_cast<int>(len));
14607
14608  if (ret > 0) {
14609    err.code = ErrorCode::Success;
14610    return ret;
14611  }
14612
14613  auto ssl_err = SSL_get_error(ssl, ret);
14614  err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
14615  if (err.code == ErrorCode::PeerClosed) {
14616    return 0;
14617  } // Gracefully handle the peer closed state.
14618  if (err.code == ErrorCode::Fatal) { err.backend_code = ERR_get_error(); }
14619  return -1;
14620}
14621
14622ssize_t write(session_t session, const void *buf, size_t len,
14623                     TlsError &err) {
14624  if (!session || !buf) {
14625    err.code = ErrorCode::Fatal;
14626    return -1;
14627  }
14628
14629  auto ssl = static_cast<SSL *>(session);
14630  auto ret = SSL_write(ssl, buf, static_cast<int>(len));
14631
14632  if (ret > 0) {
14633    err.code = ErrorCode::Success;
14634    return ret;
14635  }
14636
14637  auto ssl_err = SSL_get_error(ssl, ret);
14638  err.code = impl::map_ssl_error(ssl_err, err.sys_errno);
14639  if (err.code == ErrorCode::Fatal) { err.backend_code = ERR_get_error(); }
14640  return -1;
14641}
14642
14643int pending(const_session_t session) {
14644  if (!session) return 0;
14645  return SSL_pending(static_cast<SSL *>(const_cast<void *>(session)));
14646}
14647
14648void shutdown(session_t session, bool graceful) {
14649  if (!session) return;
14650
14651  auto ssl = static_cast<SSL *>(session);
14652  if (graceful) {
14653    // First call sends close_notify
14654    if (SSL_shutdown(ssl) == 0) {
14655      // Second call waits for peer's close_notify
14656      SSL_shutdown(ssl);
14657    }
14658  }
14659}
14660
14661bool is_peer_closed(session_t session, socket_t sock) {
14662  if (!session) return true;
14663
14664  // Temporarily set socket to non-blocking to avoid blocking on SSL_peek
14665  detail::set_nonblocking(sock, true);
14666  auto se = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
14667
14668  auto ssl = static_cast<SSL *>(session);
14669  char buf;
14670  auto ret = SSL_peek(ssl, &buf, 1);
14671  if (ret > 0) return false;
14672
14673  auto err = SSL_get_error(ssl, ret);
14674  return err == SSL_ERROR_ZERO_RETURN;
14675}
14676
14677cert_t get_peer_cert(const_session_t session) {
14678  if (!session) return nullptr;
14679  return static_cast<cert_t>(SSL_get1_peer_certificate(
14680      static_cast<SSL *>(const_cast<void *>(session))));
14681}
14682
14683void free_cert(cert_t cert) {
14684  if (cert) { X509_free(static_cast<X509 *>(cert)); }
14685}
14686
14687bool verify_hostname(cert_t cert, const char *hostname) {
14688  if (!cert || !hostname) return false;
14689
14690  auto x509 = static_cast<X509 *>(cert);
14691
14692  // Use X509_check_ip_asc for IP addresses, X509_check_host for DNS names
14693  if (detail::is_ip_address(hostname)) {
14694    return X509_check_ip_asc(x509, hostname, 0) == 1;
14695  }
14696  return X509_check_host(x509, hostname, strlen(hostname), 0, nullptr) == 1;
14697}
14698
14699uint64_t hostname_mismatch_code() {
14700  return static_cast<uint64_t>(X509_V_ERR_HOSTNAME_MISMATCH);
14701}
14702
14703long get_verify_result(const_session_t session) {
14704  if (!session) return X509_V_ERR_UNSPECIFIED;
14705  return SSL_get_verify_result(static_cast<SSL *>(const_cast<void *>(session)));
14706}
14707
14708std::string get_cert_subject_cn(cert_t cert) {
14709  if (!cert) return "";
14710  auto x509 = static_cast<X509 *>(cert);
14711  auto subject_name = X509_get_subject_name(x509);
14712  if (!subject_name) return "";
14713
14714  // X509_NAME_get_text_by_NID is deprecated since OpenSSL 4.0
14715  auto idx = X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1);
14716  if (idx < 0) return "";
14717
14718  auto entry = X509_NAME_get_entry(subject_name, idx);
14719  if (!entry) return "";
14720
14721  auto data = X509_NAME_ENTRY_get_data(entry);
14722  if (!data) return "";
14723
14724  return std::string(
14725      reinterpret_cast<const char *>(ASN1_STRING_get0_data(data)),
14726      static_cast<size_t>(ASN1_STRING_length(data)));
14727}
14728
14729std::string get_cert_issuer_name(cert_t cert) {
14730  if (!cert) return "";
14731  auto x509 = static_cast<X509 *>(cert);
14732  auto issuer_name = X509_get_issuer_name(x509);
14733  if (!issuer_name) return "";
14734
14735  char buf[256];
14736  X509_NAME_oneline(issuer_name, buf, sizeof(buf));
14737  return std::string(buf);
14738}
14739
14740bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
14741  sans.clear();
14742  if (!cert) return false;
14743  auto x509 = static_cast<X509 *>(cert);
14744
14745  auto names = static_cast<GENERAL_NAMES *>(
14746      X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
14747  if (!names) return true; // No SANs is valid
14748
14749  auto count = sk_GENERAL_NAME_num(names);
14750  for (decltype(count) i = 0; i < count; i++) {
14751    auto gen = sk_GENERAL_NAME_value(names, i);
14752    if (!gen) continue;
14753
14754    SanEntry entry;
14755    switch (gen->type) {
14756    case GEN_DNS:
14757      entry.type = SanType::DNS;
14758      if (gen->d.dNSName) {
14759        entry.value = std::string(
14760            reinterpret_cast<const char *>(
14761                ASN1_STRING_get0_data(gen->d.dNSName)),
14762            static_cast<size_t>(ASN1_STRING_length(gen->d.dNSName)));
14763      }
14764      break;
14765    case GEN_IPADD:
14766      entry.type = SanType::IP;
14767      if (gen->d.iPAddress) {
14768        auto data = ASN1_STRING_get0_data(gen->d.iPAddress);
14769        auto len = ASN1_STRING_length(gen->d.iPAddress);
14770        if (len == 4) {
14771          // IPv4
14772          char buf[INET_ADDRSTRLEN];
14773          inet_ntop(AF_INET, data, buf, sizeof(buf));
14774          entry.value = buf;
14775        } else if (len == 16) {
14776          // IPv6
14777          char buf[INET6_ADDRSTRLEN];
14778          inet_ntop(AF_INET6, data, buf, sizeof(buf));
14779          entry.value = buf;
14780        }
14781      }
14782      break;
14783    case GEN_EMAIL:
14784      entry.type = SanType::EMAIL;
14785      if (gen->d.rfc822Name) {
14786        entry.value = std::string(
14787            reinterpret_cast<const char *>(
14788                ASN1_STRING_get0_data(gen->d.rfc822Name)),
14789            static_cast<size_t>(ASN1_STRING_length(gen->d.rfc822Name)));
14790      }
14791      break;
14792    case GEN_URI:
14793      entry.type = SanType::URI;
14794      if (gen->d.uniformResourceIdentifier) {
14795        entry.value = std::string(
14796            reinterpret_cast<const char *>(
14797                ASN1_STRING_get0_data(gen->d.uniformResourceIdentifier)),
14798            static_cast<size_t>(
14799                ASN1_STRING_length(gen->d.uniformResourceIdentifier)));
14800      }
14801      break;
14802    default: entry.type = SanType::OTHER; break;
14803    }
14804
14805    if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
14806  }
14807
14808  GENERAL_NAMES_free(names);
14809  return true;
14810}
14811
14812bool get_cert_validity(cert_t cert, time_t &not_before,
14813                              time_t &not_after) {
14814  if (!cert) return false;
14815  auto x509 = static_cast<X509 *>(cert);
14816
14817  auto nb = X509_get0_notBefore(x509);
14818  auto na = X509_get0_notAfter(x509);
14819  if (!nb || !na) return false;
14820
14821  ASN1_TIME *epoch = ASN1_TIME_new();
14822  if (!epoch) return false;
14823  auto se = detail::scope_exit([&] { ASN1_TIME_free(epoch); });
14824
14825  if (!ASN1_TIME_set(epoch, 0)) return false;
14826
14827  int pday, psec;
14828
14829  if (!ASN1_TIME_diff(&pday, &psec, epoch, nb)) return false;
14830  not_before = 86400 * (time_t)pday + psec;
14831
14832  if (!ASN1_TIME_diff(&pday, &psec, epoch, na)) return false;
14833  not_after = 86400 * (time_t)pday + psec;
14834
14835  return true;
14836}
14837
14838std::string get_cert_serial(cert_t cert) {
14839  if (!cert) return "";
14840  auto x509 = static_cast<X509 *>(cert);
14841
14842  auto serial = X509_get_serialNumber(x509);
14843  if (!serial) return "";
14844
14845  auto bn = ASN1_INTEGER_to_BN(serial, nullptr);
14846  if (!bn) return "";
14847
14848  auto hex = BN_bn2hex(bn);
14849  BN_free(bn);
14850  if (!hex) return "";
14851
14852  std::string result(hex);
14853  OPENSSL_free(hex);
14854  return result;
14855}
14856
14857bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
14858  if (!cert) return false;
14859  auto x509 = static_cast<X509 *>(cert);
14860  auto len = i2d_X509(x509, nullptr);
14861  if (len < 0) return false;
14862  der.resize(static_cast<size_t>(len));
14863  auto p = der.data();
14864  i2d_X509(x509, &p);
14865  return true;
14866}
14867
14868const char *get_sni(const_session_t session) {
14869  if (!session) return nullptr;
14870  auto ssl = static_cast<SSL *>(const_cast<void *>(session));
14871  return SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
14872}
14873
14874uint64_t peek_error() { return ERR_peek_last_error(); }
14875
14876uint64_t get_error() { return ERR_get_error(); }
14877
14878std::string error_string(uint64_t code) {
14879  char buf[256];
14880  ERR_error_string_n(static_cast<unsigned long>(code), buf, sizeof(buf));
14881  return std::string(buf);
14882}
14883
14884ca_store_t create_ca_store(const char *pem, size_t len) {
14885  auto mem = BIO_new_mem_buf(pem, static_cast<int>(len));
14886  if (!mem) { return nullptr; }
14887  auto mem_guard = detail::scope_exit([&] { BIO_free_all(mem); });
14888
14889  auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr);
14890  if (!inf) { return nullptr; }
14891
14892  auto store = X509_STORE_new();
14893  if (store) {
14894    for (auto i = 0; i < static_cast<int>(sk_X509_INFO_num(inf)); i++) {
14895      auto itmp = sk_X509_INFO_value(inf, i);
14896      if (!itmp) { continue; }
14897      if (itmp->x509) { X509_STORE_add_cert(store, itmp->x509); }
14898      if (itmp->crl) { X509_STORE_add_crl(store, itmp->crl); }
14899    }
14900  }
14901
14902  sk_X509_INFO_pop_free(inf, X509_INFO_free);
14903  return static_cast<ca_store_t>(store);
14904}
14905
14906void free_ca_store(ca_store_t store) {
14907  if (store) { X509_STORE_free(static_cast<X509_STORE *>(store)); }
14908}
14909
14910bool set_ca_store(ctx_t ctx, ca_store_t store) {
14911  if (!ctx || !store) { return false; }
14912  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
14913  auto x509_store = static_cast<X509_STORE *>(store);
14914
14915  // Check if same store is already set
14916  if (SSL_CTX_get_cert_store(ssl_ctx) == x509_store) { return true; }
14917
14918  // SSL_CTX_set_cert_store takes ownership and frees the old store
14919  SSL_CTX_set_cert_store(ssl_ctx, x509_store);
14920  return true;
14921}
14922
14923size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
14924  certs.clear();
14925  if (!ctx) { return 0; }
14926  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
14927
14928  auto store = SSL_CTX_get_cert_store(ssl_ctx);
14929  if (!store) { return 0; }
14930
14931  auto objs = impl::get_store_objects(store);
14932  if (!objs) { return 0; }
14933  auto se = detail::scope_exit([&] { impl::release_store_objects(objs); });
14934
14935  auto count = sk_X509_OBJECT_num(objs);
14936  for (decltype(count) i = 0; i < count; i++) {
14937    auto obj = sk_X509_OBJECT_value(objs, i);
14938    if (!obj) { continue; }
14939    if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
14940      auto x509 = X509_OBJECT_get0_X509(obj);
14941      if (x509) {
14942        // Increment reference count so caller can free it
14943        X509_up_ref(x509);
14944        certs.push_back(static_cast<cert_t>(x509));
14945      }
14946    }
14947  }
14948  return certs.size();
14949}
14950
14951std::vector<std::string> get_ca_names(ctx_t ctx) {
14952  std::vector<std::string> names;
14953  if (!ctx) { return names; }
14954  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
14955
14956  auto store = SSL_CTX_get_cert_store(ssl_ctx);
14957  if (!store) { return names; }
14958
14959  auto objs = impl::get_store_objects(store);
14960  if (!objs) { return names; }
14961  auto se = detail::scope_exit([&] { impl::release_store_objects(objs); });
14962
14963  auto count = sk_X509_OBJECT_num(objs);
14964  for (decltype(count) i = 0; i < count; i++) {
14965    auto obj = sk_X509_OBJECT_value(objs, i);
14966    if (!obj) { continue; }
14967    if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
14968      auto x509 = X509_OBJECT_get0_X509(obj);
14969      if (x509) {
14970        auto subject = X509_get_subject_name(x509);
14971        if (subject) {
14972          char buf[512];
14973          X509_NAME_oneline(subject, buf, sizeof(buf));
14974          names.push_back(buf);
14975        }
14976      }
14977    }
14978  }
14979  return names;
14980}
14981
14982bool update_server_cert(ctx_t ctx, const char *cert_pem,
14983                               const char *key_pem, const char *password) {
14984  if (!ctx || !cert_pem || !key_pem) { return false; }
14985  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
14986
14987  // Load certificate from PEM
14988  auto cert_bio = BIO_new_mem_buf(cert_pem, -1);
14989  if (!cert_bio) { return false; }
14990  auto cert = PEM_read_bio_X509(cert_bio, nullptr, nullptr, nullptr);
14991  BIO_free(cert_bio);
14992  if (!cert) { return false; }
14993
14994  // Load private key from PEM
14995  auto key_bio = BIO_new_mem_buf(key_pem, -1);
14996  if (!key_bio) {
14997    X509_free(cert);
14998    return false;
14999  }
15000  auto key = PEM_read_bio_PrivateKey(key_bio, nullptr, nullptr,
15001                                     password ? const_cast<char *>(password)
15002                                              : nullptr);
15003  BIO_free(key_bio);
15004  if (!key) {
15005    X509_free(cert);
15006    return false;
15007  }
15008
15009  // Update certificate and key
15010  auto ret = SSL_CTX_use_certificate(ssl_ctx, cert) == 1 &&
15011             SSL_CTX_use_PrivateKey(ssl_ctx, key) == 1;
15012
15013  X509_free(cert);
15014  EVP_PKEY_free(key);
15015  return ret;
15016}
15017
15018bool update_server_client_ca(ctx_t ctx, const char *ca_pem) {
15019  if (!ctx || !ca_pem) { return false; }
15020  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
15021
15022  // Create new X509_STORE from PEM
15023  auto store = create_ca_store(ca_pem, strlen(ca_pem));
15024  if (!store) { return false; }
15025
15026  // SSL_CTX_set_cert_store takes ownership
15027  SSL_CTX_set_cert_store(ssl_ctx, static_cast<X509_STORE *>(store));
15028
15029  // Set client CA list for client certificate request
15030  auto ca_list = impl::create_client_ca_list_from_pem(ca_pem);
15031  if (ca_list) {
15032    // SSL_CTX_set_client_CA_list takes ownership of ca_list
15033    SSL_CTX_set_client_CA_list(ssl_ctx, ca_list);
15034  }
15035
15036  return true;
15037}
15038
15039bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
15040  if (!ctx) { return false; }
15041  auto ssl_ctx = static_cast<SSL_CTX *>(ctx);
15042
15043  impl::get_verify_callback() = std::move(callback);
15044
15045  if (impl::get_verify_callback()) {
15046    SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, impl::openssl_verify_callback);
15047  } else {
15048    SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, nullptr);
15049  }
15050  return true;
15051}
15052
15053long get_verify_error(const_session_t session) {
15054  if (!session) { return -1; }
15055  auto ssl = static_cast<SSL *>(const_cast<void *>(session));
15056  return SSL_get_verify_result(ssl);
15057}
15058
15059std::string verify_error_string(long error_code) {
15060  if (error_code == X509_V_OK) { return ""; }
15061  const char *str = X509_verify_cert_error_string(static_cast<int>(error_code));
15062  return str ? str : "unknown error";
15063}
15064
15065} // namespace tls
15066
15067#endif // CPPHTTPLIB_OPENSSL_SUPPORT
15068
15069/*
15070 * Group 9: TLS abstraction layer - Mbed TLS backend
15071 */
15072
15073/*
15074 * Mbed TLS Backend Implementation
15075 */
15076
15077#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
15078namespace tls {
15079
15080namespace impl {
15081
15082// Mbed TLS session wrapper
15083struct MbedTlsSession {
15084  mbedtls_ssl_context ssl;
15085  socket_t sock = INVALID_SOCKET;
15086  std::string hostname;     // For client: set via set_sni
15087  std::string sni_hostname; // For server: received from client via SNI callback
15088
15089  // Mbed TLS has no SSL_peek() equivalent, so is_peer_closed() must probe with
15090  // a real 1-byte mbedtls_ssl_read(). If that probe lands on application data
15091  // (e.g. a response that arrived while this side was still in its post-write
15092  // check), the byte is pushed back here and served by the next read().
15093  unsigned char peeked_byte = 0;
15094  bool has_peeked_byte = false;
15095
15096  // Set by set_sni() when the caller disabled hostname verification, so the
15097  // verify callback can clear the CN/SAN mismatch flag while still enforcing
15098  // the rest of the chain (Mbed TLS ties SNI and identity checking together;
15099  // OpenSSL and wolfSSL keep them independent).
15100  bool suppress_hostname_mismatch = false;
15101
15102  // Copied from the owning MbedTlsContext at creation. set_sni() uses this to
15103  // decide which verify callback to install when hostname verification is
15104  // disabled: mbedtls_verify_callback() when a user callback is genuinely
15105  // wired for this context, or a self-contained one otherwise, so a session
15106  // that never opted into a callback never consults the process-wide
15107  // set_verify_callback() slot (which some other, unrelated client may have
15108  // populated).
15109  bool has_verify_callback = false;
15110
15111  MbedTlsSession() { mbedtls_ssl_init(&ssl); }
15112
15113  ~MbedTlsSession() { mbedtls_ssl_free(&ssl); }
15114
15115  MbedTlsSession(const MbedTlsSession &) = delete;
15116  MbedTlsSession &operator=(const MbedTlsSession &) = delete;
15117};
15118
15119// Thread-local error code accessor for Mbed TLS (since it doesn't have an error
15120// queue)
15121int &mbedtls_last_error() {
15122  static thread_local int err = 0;
15123  return err;
15124}
15125
15126// Helper to map Mbed TLS error to ErrorCode
15127ErrorCode map_mbedtls_error(int ret, int &out_errno,
15128                                   uint32_t verify_flags) {
15129  if (ret == 0) { return ErrorCode::Success; }
15130  if (ret == MBEDTLS_ERR_SSL_WANT_READ) { return ErrorCode::WantRead; }
15131  if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) { return ErrorCode::WantWrite; }
15132  if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
15133    return ErrorCode::PeerClosed;
15134  }
15135  if (ret == MBEDTLS_ERR_NET_CONN_RESET || ret == MBEDTLS_ERR_NET_SEND_FAILED ||
15136      ret == MBEDTLS_ERR_NET_RECV_FAILED) {
15137    out_errno = errno;
15138    return ErrorCode::SyscallError;
15139  }
15140  if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) {
15141    // Unlike OpenSSL/wolfSSL, Mbed TLS folds the CN/SAN identity check into
15142    // the handshake's chain verification (see set_sni()); a mismatch there
15143    // is reported the same way as any other verify_flags bit. Report it as
15144    // HostnameMismatch, matching the other backends and the post-handshake
15145    // identity check below, but only when naming is the sole problem -
15146    // if the chain itself is also untrusted/expired/etc., that takes
15147    // priority over the naming detail.
15148    if (verify_flags == static_cast<uint32_t>(hostname_mismatch_code())) {
15149      return ErrorCode::HostnameMismatch;
15150    }
15151    return ErrorCode::CertVerifyFailed;
15152  }
15153  return ErrorCode::Fatal;
15154}
15155
15156// Populates a TlsError from a failed (non-zero) mbedtls_ssl_handshake()
15157// return value, including the verify-flags-dependent HostnameMismatch
15158// mapping; shared by connect() and connect_nonblocking() so the
15159// backend_code policy for that mapping only lives in one place.
15160void fill_mbedtls_tls_error(TlsError &err, mbedtls_ssl_context &ssl,
15161                                   int ret) {
15162  auto verify_flags = mbedtls_ssl_get_verify_result(&ssl);
15163  err.code = map_mbedtls_error(ret, err.sys_errno, verify_flags);
15164  err.backend_code = err.code == ErrorCode::HostnameMismatch
15165                         ? static_cast<uint64_t>(verify_flags)
15166                         : static_cast<uint64_t>(-ret);
15167}
15168
15169// A TLS 1.3 NewSessionTicket (signaled by default on Mbed TLS 4.x) is a
15170// non-fatal notification delivered between records, not an error and not
15171// application data, so I/O calls that see it should just be retried. Kept in
15172// one helper so the retry loops keep an intact "do { } while (...)" instead of
15173// splitting the closing brace across an #if.
15174bool mbedtls_is_session_ticket(int ret) {
15175#if defined(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET)
15176  return ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET;
15177#else
15178  (void)ret;
15179  return false;
15180#endif
15181}
15182
15183// BIO-like send callback for Mbed TLS
15184int mbedtls_net_send_cb(void *ctx, const unsigned char *buf,
15185                               size_t len) {
15186  auto sock = *static_cast<socket_t *>(ctx);
15187#ifdef _WIN32
15188  auto ret =
15189      send(sock, reinterpret_cast<const char *>(buf), static_cast<int>(len), 0);
15190  if (ret == SOCKET_ERROR) {
15191    int err = WSAGetLastError();
15192    if (err == WSAEWOULDBLOCK) { return MBEDTLS_ERR_SSL_WANT_WRITE; }
15193    return MBEDTLS_ERR_NET_SEND_FAILED;
15194  }
15195#else
15196  auto ret = send(sock, buf, len, 0);
15197  if (ret < 0) {
15198    if (errno == EAGAIN || errno == EWOULDBLOCK) {
15199      return MBEDTLS_ERR_SSL_WANT_WRITE;
15200    }
15201    return MBEDTLS_ERR_NET_SEND_FAILED;
15202  }
15203#endif
15204  return static_cast<int>(ret);
15205}
15206
15207// BIO-like recv callback for Mbed TLS
15208int mbedtls_net_recv_cb(void *ctx, unsigned char *buf, size_t len) {
15209  auto sock = *static_cast<socket_t *>(ctx);
15210#ifdef _WIN32
15211  auto ret =
15212      recv(sock, reinterpret_cast<char *>(buf), static_cast<int>(len), 0);
15213  if (ret == SOCKET_ERROR) {
15214    int err = WSAGetLastError();
15215    if (err == WSAEWOULDBLOCK) { return MBEDTLS_ERR_SSL_WANT_READ; }
15216    return MBEDTLS_ERR_NET_RECV_FAILED;
15217  }
15218#else
15219  auto ret = recv(sock, buf, len, 0);
15220  if (ret < 0) {
15221    if (errno == EAGAIN || errno == EWOULDBLOCK) {
15222      return MBEDTLS_ERR_SSL_WANT_READ;
15223    }
15224    return MBEDTLS_ERR_NET_RECV_FAILED;
15225  }
15226#endif
15227  if (ret == 0) { return MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY; }
15228  return static_cast<int>(ret);
15229}
15230
15231// MbedTlsContext constructor/destructor implementations
15232MbedTlsContext::MbedTlsContext() {
15233  mbedtls_ssl_config_init(&conf);
15234#ifndef CPPHTTPLIB_MBEDTLS_V4
15235  mbedtls_entropy_init(&entropy);
15236  mbedtls_ctr_drbg_init(&ctr_drbg);
15237#endif
15238  mbedtls_x509_crt_init(&ca_chain);
15239  mbedtls_x509_crt_init(&own_cert);
15240  mbedtls_pk_init(&own_key);
15241}
15242
15243MbedTlsContext::~MbedTlsContext() {
15244  mbedtls_pk_free(&own_key);
15245  mbedtls_x509_crt_free(&own_cert);
15246  mbedtls_x509_crt_free(&ca_chain);
15247#ifndef CPPHTTPLIB_MBEDTLS_V4
15248  mbedtls_ctr_drbg_free(&ctr_drbg);
15249  mbedtls_entropy_free(&entropy);
15250#endif
15251  mbedtls_ssl_config_free(&conf);
15252}
15253
15254// Thread-local storage for SNI captured during handshake
15255// This is needed because the SNI callback doesn't have a way to pass
15256// session-specific data before the session is fully set up
15257std::string &mbedpending_sni() {
15258  static thread_local std::string sni;
15259  return sni;
15260}
15261
15262// SNI callback for Mbed TLS server to capture client's SNI hostname
15263int mbedtls_sni_callback(void *p_ctx, mbedtls_ssl_context *ssl,
15264                                const unsigned char *name, size_t name_len) {
15265  (void)p_ctx;
15266  (void)ssl;
15267
15268  // Store SNI name in thread-local storage
15269  // It will be retrieved and stored in the session after handshake
15270  if (name && name_len > 0) {
15271    mbedpending_sni().assign(reinterpret_cast<const char *>(name), name_len);
15272  } else {
15273    mbedpending_sni().clear();
15274  }
15275  return 0; // Accept any SNI
15276}
15277
15278void mbedtls_clear_cn_mismatch(uint32_t *flags) {
15279  *flags &= ~static_cast<uint32_t>(hostname_mismatch_code());
15280}
15281
15282// Verify callback used when hostname verification is disabled for a session
15283// that has no user-supplied verify callback of its own (MbedTlsSession::
15284// has_verify_callback is false). Deliberately does not consult
15285// get_verify_callback(): that slot is process-wide, so reading it here would
15286// pick up whatever another, unrelated client last installed there.
15287int mbedtls_mask_hostname_mismatch_callback(void *data,
15288                                                   mbedtls_x509_crt *, int,
15289                                                   uint32_t *flags) {
15290  (void)data;
15291  mbedtls_clear_cn_mismatch(flags);
15292  return 0;
15293}
15294
15295int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt,
15296                                   int cert_depth, uint32_t *flags);
15297
15298// MbedTLS verify callback wrapper
15299int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt,
15300                                   int cert_depth, uint32_t *flags) {
15301  // data points to the MbedTlsSession
15302  auto *session = static_cast<MbedTlsSession *>(data);
15303
15304  // set_sni() disabled hostname verification for this session: drop the
15305  // CN/SAN mismatch flag so it doesn't fail the chain check below, mirroring
15306  // the OpenSSL/wolfSSL backends where identity checking is independent of
15307  // SNI. The final pass/fail decision still comes from the remaining flags
15308  // (or, below, from the user's own verify callback).
15309  if (session && session->suppress_hostname_mismatch) {
15310    mbedtls_clear_cn_mismatch(flags);
15311  }
15312
15313  auto &callback = get_verify_callback();
15314  if (!callback) { return 0; } // Continue with default verification
15315
15316  // Build context
15317  VerifyContext verify_ctx;
15318  verify_ctx.session = static_cast<session_t>(session);
15319  verify_ctx.cert = static_cast<cert_t>(crt);
15320  verify_ctx.depth = cert_depth;
15321  verify_ctx.preverify_ok = (*flags == 0);
15322  verify_ctx.error_code = static_cast<long>(*flags);
15323
15324  // Convert Mbed TLS flags to error string
15325  static thread_local char error_buf[256];
15326  if (*flags != 0) {
15327    mbedtls_x509_crt_verify_info(error_buf, sizeof(error_buf), "", *flags);
15328    verify_ctx.error_string = error_buf;
15329  } else {
15330    verify_ctx.error_string = nullptr;
15331  }
15332
15333  bool accepted = callback(verify_ctx);
15334
15335  if (accepted) {
15336    *flags = 0; // Clear all error flags
15337    return 0;
15338  }
15339  return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
15340}
15341
15342} // namespace impl
15343
15344ctx_t create_client_context() {
15345  auto ctx = new (std::nothrow) impl::MbedTlsContext();
15346  if (!ctx) { return nullptr; }
15347
15348  ctx->is_server = false;
15349
15350#ifdef CPPHTTPLIB_MBEDTLS_V4
15351  // Mbed TLS 4.x draws randomness from PSA Crypto; just ensure it is ready.
15352  if (!detail::ensure_mbedtls_psa_crypto()) {
15353    delete ctx;
15354    return nullptr;
15355  }
15356  int ret;
15357#else
15358  // Seed the random number generator
15359  const char *pers = "httplib_client";
15360  int ret = mbedtls_ctr_drbg_seed(
15361      &ctx->ctr_drbg, mbedtls_entropy_func, &ctx->entropy,
15362      reinterpret_cast<const unsigned char *>(pers), strlen(pers));
15363  if (ret != 0) {
15364    impl::mbedtls_last_error() = ret;
15365    delete ctx;
15366    return nullptr;
15367  }
15368#endif
15369
15370  // Set up SSL config for client
15371  ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_CLIENT,
15372                                    MBEDTLS_SSL_TRANSPORT_STREAM,
15373                                    MBEDTLS_SSL_PRESET_DEFAULT);
15374  if (ret != 0) {
15375    impl::mbedtls_last_error() = ret;
15376    delete ctx;
15377    return nullptr;
15378  }
15379
15380#ifndef CPPHTTPLIB_MBEDTLS_V4
15381  // Set random number generator (Mbed TLS 4.x uses the PSA RNG implicitly)
15382  mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
15383#endif
15384
15385  // Default: verify peer certificate
15386  mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
15387
15388  // Set minimum TLS version to 1.2
15389#ifdef CPPHTTPLIB_MBEDTLS_V3
15390  mbedtls_ssl_conf_min_tls_version(&ctx->conf, MBEDTLS_SSL_VERSION_TLS1_2);
15391#else
15392  mbedtls_ssl_conf_min_version(&ctx->conf, MBEDTLS_SSL_MAJOR_VERSION_3,
15393                               MBEDTLS_SSL_MINOR_VERSION_3);
15394#endif
15395
15396  return static_cast<ctx_t>(ctx);
15397}
15398
15399ctx_t create_server_context() {
15400  auto ctx = new (std::nothrow) impl::MbedTlsContext();
15401  if (!ctx) { return nullptr; }
15402
15403  ctx->is_server = true;
15404
15405#ifdef CPPHTTPLIB_MBEDTLS_V4
15406  // Mbed TLS 4.x draws randomness from PSA Crypto; just ensure it is ready.
15407  if (!detail::ensure_mbedtls_psa_crypto()) {
15408    delete ctx;
15409    return nullptr;
15410  }
15411  int ret;
15412#else
15413  // Seed the random number generator
15414  const char *pers = "httplib_server";
15415  int ret = mbedtls_ctr_drbg_seed(
15416      &ctx->ctr_drbg, mbedtls_entropy_func, &ctx->entropy,
15417      reinterpret_cast<const unsigned char *>(pers), strlen(pers));
15418  if (ret != 0) {
15419    impl::mbedtls_last_error() = ret;
15420    delete ctx;
15421    return nullptr;
15422  }
15423#endif
15424
15425  // Set up SSL config for server
15426  ret = mbedtls_ssl_config_defaults(&ctx->conf, MBEDTLS_SSL_IS_SERVER,
15427                                    MBEDTLS_SSL_TRANSPORT_STREAM,
15428                                    MBEDTLS_SSL_PRESET_DEFAULT);
15429  if (ret != 0) {
15430    impl::mbedtls_last_error() = ret;
15431    delete ctx;
15432    return nullptr;
15433  }
15434
15435#ifndef CPPHTTPLIB_MBEDTLS_V4
15436  // Set random number generator (Mbed TLS 4.x uses the PSA RNG implicitly)
15437  mbedtls_ssl_conf_rng(&ctx->conf, mbedtls_ctr_drbg_random, &ctx->ctr_drbg);
15438#endif
15439
15440  // Default: don't verify client
15441  mbedtls_ssl_conf_authmode(&ctx->conf, MBEDTLS_SSL_VERIFY_NONE);
15442
15443  // Set minimum TLS version to 1.2
15444#ifdef CPPHTTPLIB_MBEDTLS_V3
15445  mbedtls_ssl_conf_min_tls_version(&ctx->conf, MBEDTLS_SSL_VERSION_TLS1_2);
15446#else
15447  mbedtls_ssl_conf_min_version(&ctx->conf, MBEDTLS_SSL_MAJOR_VERSION_3,
15448                               MBEDTLS_SSL_MINOR_VERSION_3);
15449#endif
15450
15451  // Set SNI callback to capture client's SNI hostname
15452  mbedtls_ssl_conf_sni(&ctx->conf, impl::mbedtls_sni_callback, nullptr);
15453
15454  return static_cast<ctx_t>(ctx);
15455}
15456
15457void free_context(ctx_t ctx) {
15458  if (ctx) { delete static_cast<impl::MbedTlsContext *>(ctx); }
15459}
15460
15461bool set_min_version(ctx_t ctx, Version version) {
15462  if (!ctx) { return false; }
15463  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
15464
15465#ifdef CPPHTTPLIB_MBEDTLS_V3
15466  // Mbed TLS 3.x uses mbedtls_ssl_protocol_version enum
15467  mbedtls_ssl_protocol_version min_ver = MBEDTLS_SSL_VERSION_TLS1_2;
15468  if (version >= Version::TLS1_3) {
15469#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
15470    min_ver = MBEDTLS_SSL_VERSION_TLS1_3;
15471#endif
15472  }
15473  mbedtls_ssl_conf_min_tls_version(&mctx->conf, min_ver);
15474#else
15475  // Mbed TLS 2.x uses major/minor version numbers
15476  int major = MBEDTLS_SSL_MAJOR_VERSION_3;
15477  int minor = MBEDTLS_SSL_MINOR_VERSION_3; // TLS 1.2
15478  if (version >= Version::TLS1_3) {
15479#if defined(MBEDTLS_SSL_PROTO_TLS1_3)
15480    minor = MBEDTLS_SSL_MINOR_VERSION_4; // TLS 1.3
15481#else
15482    minor = MBEDTLS_SSL_MINOR_VERSION_3; // Fall back to TLS 1.2
15483#endif
15484  }
15485  mbedtls_ssl_conf_min_version(&mctx->conf, major, minor);
15486#endif
15487  return true;
15488}
15489
15490bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) {
15491  if (!ctx || !pem) { return false; }
15492  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
15493
15494  // mbedtls_x509_crt_parse expects null-terminated string for PEM
15495  // Add null terminator if not present
15496  std::string pem_str(pem, len);
15497  int ret = mbedtls_x509_crt_parse(
15498      &mctx->ca_chain, reinterpret_cast<const unsigned char *>(pem_str.c_str()),
15499      pem_str.size() + 1);
15500  if (ret != 0) {
15501    impl::mbedtls_last_error() = ret;
15502    return false;
15503  }
15504
15505  mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
15506  return true;
15507}
15508
15509bool load_ca_file(ctx_t ctx, const char *file_path) {
15510  if (!ctx || !file_path) { return false; }
15511  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
15512
15513  int ret = mbedtls_x509_crt_parse_file(&mctx->ca_chain, file_path);
15514  if (ret != 0) {
15515    impl::mbedtls_last_error() = ret;
15516    return false;
15517  }
15518
15519  mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
15520  return true;
15521}
15522
15523bool load_ca_dir(ctx_t ctx, const char *dir_path) {
15524  if (!ctx || !dir_path) { return false; }
15525  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
15526
15527  int ret = mbedtls_x509_crt_parse_path(&mctx->ca_chain, dir_path);
15528  if (ret < 0) { // Returns number of certs on success, negative on error
15529    impl::mbedtls_last_error() = ret;
15530    return false;
15531  }
15532
15533  mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
15534  return true;
15535}
15536
15537bool load_system_certs(ctx_t ctx) {
15538  if (!ctx) { return false; }
15539  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
15540  bool loaded = false;
15541
15542#ifdef _WIN32
15543  loaded = impl::enumerate_windows_system_certs(
15544      [&](const unsigned char *data, size_t len) {
15545        return mbedtls_x509_crt_parse_der(&mctx->ca_chain, data, len) == 0;
15546      });
15547#elif defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
15548  loaded = impl::enumerate_macos_keychain_certs(
15549      [&](const unsigned char *data, size_t len) {
15550        return mbedtls_x509_crt_parse_der(&mctx->ca_chain, data, len) == 0;
15551      });
15552#else
15553  for (auto path = impl::system_ca_paths(); *path; ++path) {
15554    if (mbedtls_x509_crt_parse_file(&mctx->ca_chain, *path) >= 0) {
15555      loaded = true;
15556      break;
15557    }
15558  }
15559
15560  if (!loaded) {
15561    for (auto dir = impl::system_ca_dirs(); *dir; ++dir) {
15562      if (mbedtls_x509_crt_parse_path(&mctx->ca_chain, *dir) >= 0) {
15563        loaded = true;
15564        break;
15565      }
15566    }
15567  }
15568#endif
15569
15570  if (loaded) {
15571    mbedtls_ssl_conf_ca_chain(&mctx->conf, &mctx->ca_chain, nullptr);
15572  }
15573  return loaded;
15574}
15575
15576bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
15577                                const char *password) {
15578  if (!ctx || !cert || !key) { return false; }
15579  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
15580
15581  // Parse certificate
15582  std::string cert_str(cert);
15583  int ret = mbedtls_x509_crt_parse(
15584      &mctx->own_cert,
15585      reinterpret_cast<const unsigned char *>(cert_str.c_str()),
15586      cert_str.size() + 1);
15587  if (ret != 0) {
15588    impl::mbedtls_last_error() = ret;
15589    return false;
15590  }
15591
15592  // Parse private key
15593  std::string key_str(key);
15594  const unsigned char *pwd =
15595      password ? reinterpret_cast<const unsigned char *>(password) : nullptr;
15596  size_t pwd_len = password ? strlen(password) : 0;
15597
15598#if defined(CPPHTTPLIB_MBEDTLS_V3) && !defined(CPPHTTPLIB_MBEDTLS_V4)
15599  ret = mbedtls_pk_parse_key(
15600      &mctx->own_key, reinterpret_cast<const unsigned char *>(key_str.c_str()),
15601      key_str.size() + 1, pwd, pwd_len, mbedtls_ctr_drbg_random,
15602      &mctx->ctr_drbg);
15603#else
15604  ret = mbedtls_pk_parse_key(
15605      &mctx->own_key, reinterpret_cast<const unsigned char *>(key_str.c_str()),
15606      key_str.size() + 1, pwd, pwd_len);
15607#endif
15608  if (ret != 0) {
15609    impl::mbedtls_last_error() = ret;
15610    return false;
15611  }
15612
15613  // Verify that the certificate and private key match.
15614  // Mbed TLS 4.x: mbedtls_pk_check_pair() reports a spurious mismatch for
15615  // PSA-backed keys, so skip it and let the handshake surface a real mismatch.
15616#ifndef CPPHTTPLIB_MBEDTLS_V4
15617#ifdef CPPHTTPLIB_MBEDTLS_V3
15618  ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key,
15619                              mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
15620#else
15621  ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key);
15622#endif
15623  if (ret != 0) {
15624    impl::mbedtls_last_error() = ret;
15625    return false;
15626  }
15627#endif
15628
15629  ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key);
15630  if (ret != 0) {
15631    impl::mbedtls_last_error() = ret;
15632    return false;
15633  }
15634
15635  return true;
15636}
15637
15638bool set_client_cert_file(ctx_t ctx, const char *cert_path,
15639                                 const char *key_path, const char *password) {
15640  if (!ctx || !cert_path || !key_path) { return false; }
15641  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
15642
15643  // Parse certificate file
15644  int ret = mbedtls_x509_crt_parse_file(&mctx->own_cert, cert_path);
15645  if (ret != 0) {
15646    impl::mbedtls_last_error() = ret;
15647    return false;
15648  }
15649
15650  // Parse private key file
15651#if defined(CPPHTTPLIB_MBEDTLS_V3) && !defined(CPPHTTPLIB_MBEDTLS_V4)
15652  ret = mbedtls_pk_parse_keyfile(&mctx->own_key, key_path, password,
15653                                 mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
15654#else
15655  ret = mbedtls_pk_parse_keyfile(&mctx->own_key, key_path, password);
15656#endif
15657  if (ret != 0) {
15658    impl::mbedtls_last_error() = ret;
15659    return false;
15660  }
15661
15662  // Verify that the certificate and private key match.
15663  // Mbed TLS 4.x: see set_client_cert() — skip the spurious check_pair.
15664#ifndef CPPHTTPLIB_MBEDTLS_V4
15665#ifdef CPPHTTPLIB_MBEDTLS_V3
15666  ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key,
15667                              mbedtls_ctr_drbg_random, &mctx->ctr_drbg);
15668#else
15669  ret = mbedtls_pk_check_pair(&mctx->own_cert.pk, &mctx->own_key);
15670#endif
15671  if (ret != 0) {
15672    impl::mbedtls_last_error() = ret;
15673    return false;
15674  }
15675#endif
15676
15677  ret = mbedtls_ssl_conf_own_cert(&mctx->conf, &mctx->own_cert, &mctx->own_key);
15678  if (ret != 0) {
15679    impl::mbedtls_last_error() = ret;
15680    return false;
15681  }
15682
15683  return true;
15684}
15685
15686void set_verify_client(ctx_t ctx, bool require) {
15687  if (!ctx) { return; }
15688  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
15689  mctx->verify_client = require;
15690  if (require) {
15691    mbedtls_ssl_conf_authmode(&mctx->conf, MBEDTLS_SSL_VERIFY_REQUIRED);
15692  } else {
15693    // If a verify callback is set, use OPTIONAL mode to ensure the callback
15694    // is called (matching OpenSSL behavior). Otherwise use NONE.
15695    mbedtls_ssl_conf_authmode(&mctx->conf, mctx->has_verify_callback
15696                                               ? MBEDTLS_SSL_VERIFY_OPTIONAL
15697                                               : MBEDTLS_SSL_VERIFY_NONE);
15698  }
15699}
15700
15701session_t create_session(ctx_t ctx, socket_t sock) {
15702  if (!ctx || sock == INVALID_SOCKET) { return nullptr; }
15703  auto mctx = static_cast<impl::MbedTlsContext *>(ctx);
15704
15705  auto session = new (std::nothrow) impl::MbedTlsSession();
15706  if (!session) { return nullptr; }
15707
15708  session->sock = sock;
15709
15710  int ret = mbedtls_ssl_setup(&session->ssl, &mctx->conf);
15711  if (ret != 0) {
15712    impl::mbedtls_last_error() = ret;
15713    delete session;
15714    return nullptr;
15715  }
15716
15717  // Explicitly opt out of in-handshake hostname verification by default;
15718  // since Mbed TLS 3.6.4 a client handshake with certificate verification
15719  // fails outright when no hostname was set. set_sni() installs the real
15720  // hostname for DNS hosts; for IP hosts (where SNI must not be set) the
15721  // caller verifies the certificate identity post-handshake via
15722  // verify_hostname().
15723  mbedtls_ssl_set_hostname(&session->ssl, nullptr);
15724
15725  // Set BIO callbacks
15726  mbedtls_ssl_set_bio(&session->ssl, &session->sock, impl::mbedtls_net_send_cb,
15727                      impl::mbedtls_net_recv_cb, nullptr);
15728
15729  // Set per-session verify callback with session pointer if callback is
15730  // registered
15731  session->has_verify_callback = mctx->has_verify_callback;
15732  if (mctx->has_verify_callback) {
15733    mbedtls_ssl_set_verify(&session->ssl, impl::mbedtls_verify_callback,
15734                           session);
15735  }
15736
15737  return static_cast<session_t>(session);
15738}
15739
15740void free_session(session_t session) {
15741  if (session) { delete static_cast<impl::MbedTlsSession *>(session); }
15742}
15743
15744bool set_sni(session_t session, const char *hostname,
15745                    bool verify_hostname) {
15746  if (!session || !hostname) { return false; }
15747  auto msession = static_cast<impl::MbedTlsSession *>(session);
15748
15749  // mbedtls_ssl_set_hostname() both sends the SNI extension and binds the
15750  // handshake-time CN/SAN check to `hostname`; the two can't be requested
15751  // independently, so a disabled hostname check is handled below by masking
15752  // the resulting mismatch flag instead of skipping this call.
15753  int ret = mbedtls_ssl_set_hostname(&msession->ssl, hostname);
15754  if (ret != 0) {
15755    impl::mbedtls_last_error() = ret;
15756    return false;
15757  }
15758
15759  msession->hostname = hostname;
15760
15761  if (!verify_hostname) {
15762    msession->suppress_hostname_mismatch = true;
15763    // If a user verify callback is already wired for this session,
15764    // mbedtls_verify_callback() masks the mismatch flag itself before
15765    // consulting it (see suppress_hostname_mismatch above) - reinstalling it
15766    // here would be redundant. Otherwise install the self-contained masking
15767    // callback, which never touches the process-wide callback slot.
15768    if (!msession->has_verify_callback) {
15769      mbedtls_ssl_set_verify(&msession->ssl,
15770                             impl::mbedtls_mask_hostname_mismatch_callback,
15771                             msession);
15772    }
15773  }
15774
15775  return true;
15776}
15777
15778TlsError connect(session_t session) {
15779  TlsError err;
15780  if (!session) {
15781    err.code = ErrorCode::Fatal;
15782    return err;
15783  }
15784
15785  auto msession = static_cast<impl::MbedTlsSession *>(session);
15786  int ret;
15787  do {
15788    ret = mbedtls_ssl_handshake(&msession->ssl);
15789  } while (impl::mbedtls_is_session_ticket(ret));
15790
15791  if (ret == 0) {
15792    err.code = ErrorCode::Success;
15793  } else {
15794    impl::fill_mbedtls_tls_error(err, msession->ssl, ret);
15795    impl::mbedtls_last_error() = ret;
15796  }
15797
15798  return err;
15799}
15800
15801TlsError accept(session_t session) {
15802  // Same as connect for Mbed TLS - handshake works for both client and server
15803  auto result = connect(session);
15804
15805  // After successful handshake, capture SNI from thread-local storage
15806  if (result.code == ErrorCode::Success && session) {
15807    auto msession = static_cast<impl::MbedTlsSession *>(session);
15808    msession->sni_hostname = std::move(impl::mbedpending_sni());
15809    impl::mbedpending_sni().clear();
15810  }
15811
15812  return result;
15813}
15814
15815bool connect_nonblocking(session_t session, socket_t sock,
15816                                time_t timeout_sec, time_t timeout_usec,
15817                                TlsError *err) {
15818  if (!session) {
15819    if (err) { err->code = ErrorCode::Fatal; }
15820    return false;
15821  }
15822
15823  auto msession = static_cast<impl::MbedTlsSession *>(session);
15824
15825  // Set socket to non-blocking mode
15826  detail::set_nonblocking(sock, true);
15827  auto cleanup =
15828      detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
15829
15830  int ret;
15831  while ((ret = mbedtls_ssl_handshake(&msession->ssl)) != 0) {
15832    // Non-fatal TLS 1.3 ticket; retry immediately.
15833    if (impl::mbedtls_is_session_ticket(ret)) { continue; }
15834    if (ret == MBEDTLS_ERR_SSL_WANT_READ) {
15835      if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
15836        continue;
15837      }
15838    } else if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
15839      if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
15840        continue;
15841      }
15842    }
15843
15844    // TlsError or timeout
15845    if (err) { impl::fill_mbedtls_tls_error(*err, msession->ssl, ret); }
15846    impl::mbedtls_last_error() = ret;
15847    return false;
15848  }
15849
15850  if (err) { err->code = ErrorCode::Success; }
15851  return true;
15852}
15853
15854bool accept_nonblocking(session_t session, socket_t sock,
15855                               time_t timeout_sec, time_t timeout_usec,
15856                               TlsError *err) {
15857  // Same implementation as connect for Mbed TLS
15858  bool result =
15859      connect_nonblocking(session, sock, timeout_sec, timeout_usec, err);
15860
15861  // After successful handshake, capture SNI from thread-local storage
15862  if (result && session) {
15863    auto msession = static_cast<impl::MbedTlsSession *>(session);
15864    msession->sni_hostname = std::move(impl::mbedpending_sni());
15865    impl::mbedpending_sni().clear();
15866  }
15867
15868  return result;
15869}
15870
15871ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
15872  if (!session || !buf) {
15873    err.code = ErrorCode::Fatal;
15874    return -1;
15875  }
15876
15877  auto msession = static_cast<impl::MbedTlsSession *>(session);
15878
15879  // Serve a byte consumed by the is_peer_closed() probe before reading more.
15880  if (msession->has_peeked_byte) {
15881    if (len == 0) { return 0; }
15882    auto p = static_cast<unsigned char *>(buf);
15883    p[0] = msession->peeked_byte;
15884    msession->has_peeked_byte = false;
15885    size_t n = 1;
15886    // Top up with any already-decrypted bytes without risking a block.
15887    if (len > 1 && mbedtls_ssl_get_bytes_avail(&msession->ssl) > 0) {
15888      int extra = mbedtls_ssl_read(&msession->ssl, p + 1, len - 1);
15889      if (extra > 0) { n += static_cast<size_t>(extra); }
15890    }
15891    err.code = ErrorCode::Success;
15892    return static_cast<ssize_t>(n);
15893  }
15894
15895  int ret;
15896  do {
15897    ret = mbedtls_ssl_read(&msession->ssl, static_cast<unsigned char *>(buf),
15898                           len);
15899  } while (impl::mbedtls_is_session_ticket(ret));
15900
15901  if (ret > 0) {
15902    err.code = ErrorCode::Success;
15903    return static_cast<ssize_t>(ret);
15904  }
15905
15906  if (ret == 0) {
15907    err.code = ErrorCode::PeerClosed;
15908    return 0;
15909  }
15910
15911  err.code = impl::map_mbedtls_error(ret, err.sys_errno, 0);
15912  err.backend_code = static_cast<uint64_t>(-ret);
15913  impl::mbedtls_last_error() = ret;
15914  // mbedTLS signals a clean close_notify via a negative error code rather
15915  // than 0; surface it as a clean EOF the way OpenSSL/wolfSSL do.
15916  if (err.code == ErrorCode::PeerClosed) { return 0; }
15917  return -1;
15918}
15919
15920ssize_t write(session_t session, const void *buf, size_t len,
15921                     TlsError &err) {
15922  if (!session || !buf) {
15923    err.code = ErrorCode::Fatal;
15924    return -1;
15925  }
15926
15927  auto msession = static_cast<impl::MbedTlsSession *>(session);
15928  int ret;
15929  do {
15930    ret = mbedtls_ssl_write(&msession->ssl,
15931                            static_cast<const unsigned char *>(buf), len);
15932  } while (impl::mbedtls_is_session_ticket(ret));
15933
15934  if (ret > 0) {
15935    err.code = ErrorCode::Success;
15936    return static_cast<ssize_t>(ret);
15937  }
15938
15939  if (ret == 0) {
15940    err.code = ErrorCode::PeerClosed;
15941    return 0;
15942  }
15943
15944  err.code = impl::map_mbedtls_error(ret, err.sys_errno, 0);
15945  err.backend_code = static_cast<uint64_t>(-ret);
15946  impl::mbedtls_last_error() = ret;
15947  return -1;
15948}
15949
15950int pending(const_session_t session) {
15951  if (!session) { return 0; }
15952  auto msession =
15953      static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
15954  return static_cast<int>(mbedtls_ssl_get_bytes_avail(&msession->ssl)) +
15955         (msession->has_peeked_byte ? 1 : 0);
15956}
15957
15958void shutdown(session_t session, bool graceful) {
15959  if (!session) { return; }
15960  auto msession = static_cast<impl::MbedTlsSession *>(session);
15961
15962  if (graceful) {
15963    // Try to send close_notify, but don't block forever
15964    int ret;
15965    int attempts = 0;
15966    while ((ret = mbedtls_ssl_close_notify(&msession->ssl)) != 0 &&
15967           attempts < 3) {
15968      if (ret != MBEDTLS_ERR_SSL_WANT_READ &&
15969          ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
15970        break;
15971      }
15972      attempts++;
15973    }
15974  }
15975}
15976
15977bool is_peer_closed(session_t session, socket_t sock) {
15978  if (!session || sock == INVALID_SOCKET) { return true; }
15979  auto msession = static_cast<impl::MbedTlsSession *>(session);
15980
15981  // Check if there's already decrypted or pushed-back data available.
15982  // If so, the connection is definitely alive.
15983  if (msession->has_peeked_byte ||
15984      mbedtls_ssl_get_bytes_avail(&msession->ssl) > 0) {
15985    return false;
15986  }
15987
15988  // Set socket to non-blocking to avoid blocking on read
15989  detail::set_nonblocking(sock, true);
15990  auto cleanup =
15991      detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
15992
15993  // Probe with a 1-byte read (Mbed TLS has no peek API). If the probe lands
15994  // on application data — e.g. a response that already arrived — push the
15995  // byte back so the next read() delivers it instead of losing it.
15996  unsigned char buf;
15997  int ret;
15998  do {
15999    ret = mbedtls_ssl_read(&msession->ssl, &buf, 1);
16000  } while (impl::mbedtls_is_session_ticket(ret));
16001
16002  // If we got data or WANT_READ (would block), connection is alive
16003  if (ret > 0) {
16004    msession->peeked_byte = buf;
16005    msession->has_peeked_byte = true;
16006    return false;
16007  }
16008  if (ret == MBEDTLS_ERR_SSL_WANT_READ) { return false; }
16009
16010  // If we get a peer close notify or a connection reset, the peer is closed
16011  return ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY ||
16012         ret == MBEDTLS_ERR_NET_CONN_RESET || ret == 0;
16013}
16014
16015cert_t get_peer_cert(const_session_t session) {
16016  if (!session) { return nullptr; }
16017  auto msession =
16018      static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
16019
16020  // Mbed TLS returns a pointer to the internal peer cert chain.
16021  // WARNING: This pointer is only valid while the session is active.
16022  // Do not use the certificate after calling free_session().
16023  const mbedtls_x509_crt *cert = mbedtls_ssl_get_peer_cert(&msession->ssl);
16024  return const_cast<mbedtls_x509_crt *>(cert);
16025}
16026
16027void free_cert(cert_t cert) {
16028  // Mbed TLS: peer certificate is owned by the SSL context.
16029  // No-op here, but callers should still call this for cross-backend
16030  // portability.
16031  (void)cert;
16032}
16033
16034bool verify_hostname(cert_t cert, const char *hostname) {
16035  if (!cert || !hostname) { return false; }
16036  auto mcert = static_cast<const mbedtls_x509_crt *>(cert);
16037  std::string host_str(hostname);
16038
16039  // Check if hostname is an IP address (IPv4 or IPv6)
16040  unsigned char ip_bytes[16];
16041  auto ip_len = impl::parse_ip_address(host_str, ip_bytes);
16042  auto is_ip = ip_len > 0;
16043
16044  // Check Subject Alternative Names (SAN)
16045  // In Mbed TLS 3.x, subject_alt_names contains raw values without ASN.1 tags
16046  // - DNS names: raw string bytes
16047  // - IP addresses: raw IP bytes (4 for IPv4, 16 for IPv6)
16048  const mbedtls_x509_sequence *san = &mcert->subject_alt_names;
16049  while (san != nullptr && san->buf.p != nullptr && san->buf.len > 0) {
16050    const unsigned char *p = san->buf.p;
16051    size_t len = san->buf.len;
16052
16053    if (is_ip) {
16054      // For an IP host, only a matching iPAddress SAN of the same family
16055      // (4 bytes for IPv4, 16 bytes for IPv6) may authenticate it.
16056      if (len == ip_len && memcmp(p, ip_bytes, ip_len) == 0) { return true; }
16057    } else {
16058      // Check if this SAN is a DNS name (printable ASCII string)
16059      bool is_dns = len > 0;
16060      for (size_t i = 0; i < len && is_dns; i++) {
16061        if (p[i] < 32 || p[i] > 126) { is_dns = false; }
16062      }
16063      if (is_dns) {
16064        std::string san_name(reinterpret_cast<const char *>(p), len);
16065        if (detail::match_hostname(san_name, host_str)) { return true; }
16066      }
16067    }
16068    san = san->next;
16069  }
16070
16071  // Fallback: Check Common Name (CN) in subject. Skipped for IP-literal hosts:
16072  // an IP identity is only valid via an iPAddress SAN, never the CN (RFC 9110;
16073  // the OpenSSL backend's X509_check_ip behaves the same way).
16074  if (!is_ip) {
16075    char cn[256];
16076    int ret = mbedtls_x509_dn_gets(cn, sizeof(cn), &mcert->subject);
16077    if (ret > 0) {
16078      std::string cn_str(cn);
16079
16080      // Look for "CN=" in the DN string
16081      size_t cn_pos = cn_str.find("CN=");
16082      if (cn_pos != std::string::npos) {
16083        size_t start = cn_pos + 3;
16084        size_t end = cn_str.find(',', start);
16085        std::string cn_value =
16086            cn_str.substr(start, end == std::string::npos ? end : end - start);
16087
16088        if (detail::match_hostname(cn_value, host_str)) { return true; }
16089      }
16090    }
16091  }
16092
16093  return false;
16094}
16095
16096uint64_t hostname_mismatch_code() {
16097  return static_cast<uint64_t>(MBEDTLS_X509_BADCERT_CN_MISMATCH);
16098}
16099
16100long get_verify_result(const_session_t session) {
16101  if (!session) { return -1; }
16102  auto msession =
16103      static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
16104  uint32_t flags = mbedtls_ssl_get_verify_result(&msession->ssl);
16105  // Return 0 (X509_V_OK equivalent) if verification passed
16106  return flags == 0 ? 0 : static_cast<long>(flags);
16107}
16108
16109std::string get_cert_subject_cn(cert_t cert) {
16110  if (!cert) return "";
16111  auto x509 = static_cast<mbedtls_x509_crt *>(cert);
16112
16113  // Find the CN in the subject
16114  const mbedtls_x509_name *name = &x509->subject;
16115  while (name != nullptr) {
16116    if (MBEDTLS_OID_CMP(MBEDTLS_OID_AT_CN, &name->oid) == 0) {
16117      return std::string(reinterpret_cast<const char *>(name->val.p),
16118                         name->val.len);
16119    }
16120    name = name->next;
16121  }
16122  return "";
16123}
16124
16125std::string get_cert_issuer_name(cert_t cert) {
16126  if (!cert) return "";
16127  auto x509 = static_cast<mbedtls_x509_crt *>(cert);
16128
16129  // Build a human-readable issuer name string
16130  char buf[512];
16131  int ret = mbedtls_x509_dn_gets(buf, sizeof(buf), &x509->issuer);
16132  if (ret < 0) return "";
16133  return std::string(buf);
16134}
16135
16136bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
16137  sans.clear();
16138  if (!cert) return false;
16139  auto x509 = static_cast<mbedtls_x509_crt *>(cert);
16140
16141  // Parse the Subject Alternative Name extension
16142  const mbedtls_x509_sequence *cur = &x509->subject_alt_names;
16143  while (cur != nullptr) {
16144    if (cur->buf.len > 0) {
16145      // Mbed TLS stores SAN as ASN.1 sequences
16146      // The tag byte indicates the type
16147      const unsigned char *p = cur->buf.p;
16148      size_t len = cur->buf.len;
16149
16150      // First byte is the tag
16151      unsigned char tag = *p;
16152      p++;
16153      len--;
16154
16155      // Parse length (simple single-byte length assumed)
16156      if (len > 0 && *p < 0x80) {
16157        size_t value_len = *p;
16158        p++;
16159        len--;
16160
16161        if (value_len <= len) {
16162          SanEntry entry;
16163          // ASN.1 context tags for GeneralName
16164          switch (tag & 0x1F) {
16165          case 2: // dNSName
16166            entry.type = SanType::DNS;
16167            entry.value =
16168                std::string(reinterpret_cast<const char *>(p), value_len);
16169            break;
16170          case 7: // iPAddress
16171            entry.type = SanType::IP;
16172            if (value_len == 4) {
16173              // IPv4
16174              char buf[16];
16175              snprintf(buf, sizeof(buf), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
16176              entry.value = buf;
16177            } else if (value_len == 16) {
16178              // IPv6
16179              char buf[64];
16180              snprintf(buf, sizeof(buf),
16181                       "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
16182                       "%02x%02x:%02x%02x:%02x%02x:%02x%02x",
16183                       p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8],
16184                       p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
16185              entry.value = buf;
16186            }
16187            break;
16188          case 1: // rfc822Name (email)
16189            entry.type = SanType::EMAIL;
16190            entry.value =
16191                std::string(reinterpret_cast<const char *>(p), value_len);
16192            break;
16193          case 6: // uniformResourceIdentifier
16194            entry.type = SanType::URI;
16195            entry.value =
16196                std::string(reinterpret_cast<const char *>(p), value_len);
16197            break;
16198          default: entry.type = SanType::OTHER; break;
16199          }
16200
16201          if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
16202        }
16203      }
16204    }
16205    cur = cur->next;
16206  }
16207  return true;
16208}
16209
16210bool get_cert_validity(cert_t cert, time_t &not_before,
16211                              time_t &not_after) {
16212  if (!cert) return false;
16213  auto x509 = static_cast<mbedtls_x509_crt *>(cert);
16214
16215  // Convert mbedtls_x509_time to time_t
16216  auto to_time_t = [](const mbedtls_x509_time &t) -> time_t {
16217    struct tm tm_time = {};
16218    tm_time.tm_year = t.year - 1900;
16219    tm_time.tm_mon = t.mon - 1;
16220    tm_time.tm_mday = t.day;
16221    tm_time.tm_hour = t.hour;
16222    tm_time.tm_min = t.min;
16223    tm_time.tm_sec = t.sec;
16224#ifdef _WIN32
16225    return _mkgmtime(&tm_time);
16226#else
16227    return timegm(&tm_time);
16228#endif
16229  };
16230
16231  not_before = to_time_t(x509->valid_from);
16232  not_after = to_time_t(x509->valid_to);
16233  return true;
16234}
16235
16236std::string get_cert_serial(cert_t cert) {
16237  if (!cert) return "";
16238  auto x509 = static_cast<mbedtls_x509_crt *>(cert);
16239
16240  // Convert serial number to hex string
16241  std::string result;
16242  result.reserve(x509->serial.len * 2);
16243  for (size_t i = 0; i < x509->serial.len; i++) {
16244    char hex[3];
16245    snprintf(hex, sizeof(hex), "%02X", x509->serial.p[i]);
16246    result += hex;
16247  }
16248  return result;
16249}
16250
16251bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
16252  if (!cert) return false;
16253  auto crt = static_cast<mbedtls_x509_crt *>(cert);
16254  if (!crt->raw.p || crt->raw.len == 0) return false;
16255  der.assign(crt->raw.p, crt->raw.p + crt->raw.len);
16256  return true;
16257}
16258
16259const char *get_sni(const_session_t session) {
16260  if (!session) return nullptr;
16261  auto msession = static_cast<const impl::MbedTlsSession *>(session);
16262
16263  // For server: return SNI received from client during handshake
16264  if (!msession->sni_hostname.empty()) {
16265    return msession->sni_hostname.c_str();
16266  }
16267
16268  // For client: return the hostname set via set_sni
16269  if (!msession->hostname.empty()) { return msession->hostname.c_str(); }
16270
16271  return nullptr;
16272}
16273
16274uint64_t peek_error() {
16275  // Mbed TLS doesn't have an error queue, return the last error
16276  return static_cast<uint64_t>(-impl::mbedtls_last_error());
16277}
16278
16279uint64_t get_error() {
16280  // Mbed TLS doesn't have an error queue, return and clear the last error
16281  uint64_t err = static_cast<uint64_t>(-impl::mbedtls_last_error());
16282  impl::mbedtls_last_error() = 0;
16283  return err;
16284}
16285
16286std::string error_string(uint64_t code) {
16287  char buf[256];
16288  mbedtls_strerror(-static_cast<int>(code), buf, sizeof(buf));
16289  return std::string(buf);
16290}
16291
16292ca_store_t create_ca_store(const char *pem, size_t len) {
16293  auto *ca_chain = new (std::nothrow) mbedtls_x509_crt;
16294  if (!ca_chain) { return nullptr; }
16295
16296  mbedtls_x509_crt_init(ca_chain);
16297
16298  // mbedtls_x509_crt_parse expects null-terminated PEM
16299  int ret = mbedtls_x509_crt_parse(ca_chain,
16300                                   reinterpret_cast<const unsigned char *>(pem),
16301                                   len + 1); // +1 for null terminator
16302  if (ret != 0) {
16303    // Try without +1 in case PEM is already null-terminated
16304    ret = mbedtls_x509_crt_parse(
16305        ca_chain, reinterpret_cast<const unsigned char *>(pem), len);
16306    if (ret != 0) {
16307      mbedtls_x509_crt_free(ca_chain);
16308      delete ca_chain;
16309      return nullptr;
16310    }
16311  }
16312
16313  return static_cast<ca_store_t>(ca_chain);
16314}
16315
16316void free_ca_store(ca_store_t store) {
16317  if (store) {
16318    auto *ca_chain = static_cast<mbedtls_x509_crt *>(store);
16319    mbedtls_x509_crt_free(ca_chain);
16320    delete ca_chain;
16321  }
16322}
16323
16324bool set_ca_store(ctx_t ctx, ca_store_t store) {
16325  if (!ctx || !store) { return false; }
16326  auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
16327  auto *ca_chain = static_cast<mbedtls_x509_crt *>(store);
16328
16329  // Free existing CA chain
16330  mbedtls_x509_crt_free(&mbed_ctx->ca_chain);
16331  mbedtls_x509_crt_init(&mbed_ctx->ca_chain);
16332
16333  // Copy the CA chain (deep copy)
16334  // Parse from the raw data of the source cert
16335  mbedtls_x509_crt *src = ca_chain;
16336  while (src != nullptr) {
16337    int ret = mbedtls_x509_crt_parse_der(&mbed_ctx->ca_chain, src->raw.p,
16338                                         src->raw.len);
16339    if (ret != 0) {
16340      free_ca_store(store);
16341      return false;
16342    }
16343    src = src->next;
16344  }
16345
16346  // This function takes ownership of the store; the chain was deep-copied
16347  // above, so release the source
16348  free_ca_store(store);
16349
16350  // Update the SSL config to use the new CA chain
16351  mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr);
16352  return true;
16353}
16354
16355size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
16356  certs.clear();
16357  if (!ctx) { return 0; }
16358  auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
16359
16360  // Iterate through the CA chain
16361  mbedtls_x509_crt *cert = &mbed_ctx->ca_chain;
16362  while (cert != nullptr && cert->raw.len > 0) {
16363    // Create a copy of the certificate for the caller
16364    auto *copy = new mbedtls_x509_crt;
16365    mbedtls_x509_crt_init(copy);
16366    int ret = mbedtls_x509_crt_parse_der(copy, cert->raw.p, cert->raw.len);
16367    if (ret == 0) {
16368      certs.push_back(static_cast<cert_t>(copy));
16369    } else {
16370      mbedtls_x509_crt_free(copy);
16371      delete copy;
16372    }
16373    cert = cert->next;
16374  }
16375  return certs.size();
16376}
16377
16378std::vector<std::string> get_ca_names(ctx_t ctx) {
16379  std::vector<std::string> names;
16380  if (!ctx) { return names; }
16381  auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
16382
16383  // Iterate through the CA chain
16384  mbedtls_x509_crt *cert = &mbed_ctx->ca_chain;
16385  while (cert != nullptr && cert->raw.len > 0) {
16386    char buf[512];
16387    int ret = mbedtls_x509_dn_gets(buf, sizeof(buf), &cert->subject);
16388    if (ret > 0) { names.push_back(buf); }
16389    cert = cert->next;
16390  }
16391  return names;
16392}
16393
16394bool update_server_cert(ctx_t ctx, const char *cert_pem,
16395                               const char *key_pem, const char *password) {
16396  if (!ctx || !cert_pem || !key_pem) { return false; }
16397  auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
16398
16399  // Free existing certificate and key
16400  mbedtls_x509_crt_free(&mbed_ctx->own_cert);
16401  mbedtls_pk_free(&mbed_ctx->own_key);
16402  mbedtls_x509_crt_init(&mbed_ctx->own_cert);
16403  mbedtls_pk_init(&mbed_ctx->own_key);
16404
16405  // Parse certificate PEM
16406  int ret = mbedtls_x509_crt_parse(
16407      &mbed_ctx->own_cert, reinterpret_cast<const unsigned char *>(cert_pem),
16408      strlen(cert_pem) + 1);
16409  if (ret != 0) {
16410    impl::mbedtls_last_error() = ret;
16411    return false;
16412  }
16413
16414  // Parse private key PEM
16415#if defined(CPPHTTPLIB_MBEDTLS_V3) && !defined(CPPHTTPLIB_MBEDTLS_V4)
16416  ret = mbedtls_pk_parse_key(
16417      &mbed_ctx->own_key, reinterpret_cast<const unsigned char *>(key_pem),
16418      strlen(key_pem) + 1,
16419      password ? reinterpret_cast<const unsigned char *>(password) : nullptr,
16420      password ? strlen(password) : 0, mbedtls_ctr_drbg_random,
16421      &mbed_ctx->ctr_drbg);
16422#else
16423  ret = mbedtls_pk_parse_key(
16424      &mbed_ctx->own_key, reinterpret_cast<const unsigned char *>(key_pem),
16425      strlen(key_pem) + 1,
16426      password ? reinterpret_cast<const unsigned char *>(password) : nullptr,
16427      password ? strlen(password) : 0);
16428#endif
16429  if (ret != 0) {
16430    impl::mbedtls_last_error() = ret;
16431    return false;
16432  }
16433
16434  // Configure SSL to use the new certificate and key
16435  ret = mbedtls_ssl_conf_own_cert(&mbed_ctx->conf, &mbed_ctx->own_cert,
16436                                  &mbed_ctx->own_key);
16437  if (ret != 0) {
16438    impl::mbedtls_last_error() = ret;
16439    return false;
16440  }
16441
16442  return true;
16443}
16444
16445bool update_server_client_ca(ctx_t ctx, const char *ca_pem) {
16446  if (!ctx || !ca_pem) { return false; }
16447  auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
16448
16449  // Free existing CA chain
16450  mbedtls_x509_crt_free(&mbed_ctx->ca_chain);
16451  mbedtls_x509_crt_init(&mbed_ctx->ca_chain);
16452
16453  // Parse CA PEM
16454  int ret = mbedtls_x509_crt_parse(
16455      &mbed_ctx->ca_chain, reinterpret_cast<const unsigned char *>(ca_pem),
16456      strlen(ca_pem) + 1);
16457  if (ret != 0) {
16458    impl::mbedtls_last_error() = ret;
16459    return false;
16460  }
16461
16462  // Update SSL config to use new CA chain
16463  mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr);
16464  return true;
16465}
16466
16467bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
16468  if (!ctx) { return false; }
16469  auto *mbed_ctx = static_cast<impl::MbedTlsContext *>(ctx);
16470
16471  impl::get_verify_callback() = std::move(callback);
16472  mbed_ctx->has_verify_callback =
16473      static_cast<bool>(impl::get_verify_callback());
16474
16475  if (mbed_ctx->has_verify_callback) {
16476    // Set OPTIONAL mode to ensure callback is called even when verification
16477    // is disabled (matching OpenSSL behavior where SSL_VERIFY_PEER is set)
16478    mbedtls_ssl_conf_authmode(&mbed_ctx->conf, MBEDTLS_SSL_VERIFY_OPTIONAL);
16479    mbedtls_ssl_conf_verify(&mbed_ctx->conf, impl::mbedtls_verify_callback,
16480                            nullptr);
16481  } else {
16482    mbedtls_ssl_conf_verify(&mbed_ctx->conf, nullptr, nullptr);
16483  }
16484  return true;
16485}
16486
16487long get_verify_error(const_session_t session) {
16488  if (!session) { return -1; }
16489  auto *msession =
16490      static_cast<impl::MbedTlsSession *>(const_cast<void *>(session));
16491  return static_cast<long>(mbedtls_ssl_get_verify_result(&msession->ssl));
16492}
16493
16494std::string verify_error_string(long error_code) {
16495  if (error_code == 0) { return ""; }
16496  char buf[256];
16497  mbedtls_x509_crt_verify_info(buf, sizeof(buf), "",
16498                               static_cast<uint32_t>(error_code));
16499  // Remove trailing newline if present
16500  std::string result(buf);
16501  while (!result.empty() && (result.back() == '\n' || result.back() == ' ')) {
16502    result.pop_back();
16503  }
16504  return result;
16505}
16506
16507} // namespace tls
16508
16509#endif // CPPHTTPLIB_MBEDTLS_SUPPORT
16510
16511/*
16512 * Group 10: TLS abstraction layer - wolfSSL backend
16513 */
16514
16515/*
16516 * wolfSSL Backend Implementation
16517 */
16518
16519#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT
16520namespace tls {
16521
16522namespace impl {
16523
16524// wolfSSL session wrapper
16525struct WolfSSLSession {
16526  WOLFSSL *ssl = nullptr;
16527  socket_t sock = INVALID_SOCKET;
16528  std::string hostname;     // For client: set via set_sni
16529  std::string sni_hostname; // For server: received from client via SNI callback
16530
16531  WolfSSLSession() = default;
16532
16533  ~WolfSSLSession() {
16534    if (ssl) { wolfSSL_free(ssl); }
16535  }
16536
16537  WolfSSLSession(const WolfSSLSession &) = delete;
16538  WolfSSLSession &operator=(const WolfSSLSession &) = delete;
16539};
16540
16541// Thread-local error code accessor for wolfSSL
16542uint64_t &wolfssl_last_error() {
16543  static thread_local uint64_t err = 0;
16544  return err;
16545}
16546
16547// Helper to map wolfSSL error to ErrorCode.
16548// ssl_error is the value from wolfSSL_get_error().
16549// raw_ret is the raw return value from the wolfSSL call (for low-level error).
16550ErrorCode map_wolfssl_error(WOLFSSL *ssl, int ssl_error,
16551                                   int &out_errno) {
16552  switch (ssl_error) {
16553  case SSL_ERROR_NONE: return ErrorCode::Success;
16554  case SSL_ERROR_WANT_READ: return ErrorCode::WantRead;
16555  case SSL_ERROR_WANT_WRITE: return ErrorCode::WantWrite;
16556  case SSL_ERROR_ZERO_RETURN: return ErrorCode::PeerClosed;
16557  case SSL_ERROR_SYSCALL: out_errno = errno; return ErrorCode::SyscallError;
16558  default:
16559    if (ssl) {
16560      // wolfSSL stores the low-level error code as a negative value.
16561      // DOMAIN_NAME_MISMATCH (-322) indicates hostname verification failure.
16562      int low_err = ssl_error; // wolfSSL_get_error returns the low-level code
16563      if (low_err == DOMAIN_NAME_MISMATCH) {
16564        return ErrorCode::HostnameMismatch;
16565      }
16566      // Check verify result to distinguish cert verification from generic SSL
16567      // errors.
16568      long vr = wolfSSL_get_verify_result(ssl);
16569      if (vr != 0) { return ErrorCode::CertVerifyFailed; }
16570    }
16571    return ErrorCode::Fatal;
16572  }
16573}
16574
16575// WolfSSLContext constructor/destructor implementations
16576WolfSSLContext::WolfSSLContext() { wolfSSL_Init(); }
16577
16578WolfSSLContext::~WolfSSLContext() {
16579  if (ctx) { wolfSSL_CTX_free(ctx); }
16580}
16581
16582// Thread-local storage for SNI captured during handshake
16583std::string &wolfssl_pending_sni() {
16584  static thread_local std::string sni;
16585  return sni;
16586}
16587
16588// SNI callback for wolfSSL server to capture client's SNI hostname
16589int wolfssl_sni_callback(WOLFSSL *ssl, int *ret, void *exArg) {
16590  (void)ret;
16591  (void)exArg;
16592
16593  void *name_data = nullptr;
16594  unsigned short name_len =
16595      wolfSSL_SNI_GetRequest(ssl, WOLFSSL_SNI_HOST_NAME, &name_data);
16596
16597  if (name_data && name_len > 0) {
16598    wolfssl_pending_sni().assign(static_cast<const char *>(name_data),
16599                                 name_len);
16600  } else {
16601    wolfssl_pending_sni().clear();
16602  }
16603  return 0; // Continue regardless
16604}
16605
16606// wolfSSL verify callback wrapper
16607int wolfssl_verify_callback(int preverify_ok,
16608                                   WOLFSSL_X509_STORE_CTX *x509_ctx) {
16609  auto &callback = get_verify_callback();
16610  if (!callback) { return preverify_ok; }
16611
16612  WOLFSSL_X509 *cert = wolfSSL_X509_STORE_CTX_get_current_cert(x509_ctx);
16613  int depth = wolfSSL_X509_STORE_CTX_get_error_depth(x509_ctx);
16614  int err = wolfSSL_X509_STORE_CTX_get_error(x509_ctx);
16615
16616  // Get the WOLFSSL object from the X509_STORE_CTX
16617  WOLFSSL *ssl = static_cast<WOLFSSL *>(wolfSSL_X509_STORE_CTX_get_ex_data(
16618      x509_ctx, wolfSSL_get_ex_data_X509_STORE_CTX_idx()));
16619
16620  VerifyContext verify_ctx;
16621  verify_ctx.session = static_cast<session_t>(ssl);
16622  verify_ctx.cert = static_cast<cert_t>(cert);
16623  verify_ctx.depth = depth;
16624  verify_ctx.preverify_ok = (preverify_ok != 0);
16625  verify_ctx.error_code = static_cast<long>(err);
16626
16627  if (err != 0) {
16628    verify_ctx.error_string = wolfSSL_X509_verify_cert_error_string(err);
16629  } else {
16630    verify_ctx.error_string = nullptr;
16631  }
16632
16633  bool accepted = callback(verify_ctx);
16634  return accepted ? 1 : 0;
16635}
16636
16637void set_wolfssl_password_cb(WOLFSSL_CTX *ctx, const char *password) {
16638  wolfSSL_CTX_set_default_passwd_cb_userdata(ctx, const_cast<char *>(password));
16639  wolfSSL_CTX_set_default_passwd_cb(
16640      ctx, [](char *buf, int size, int /*rwflag*/, void *userdata) -> int {
16641        auto *pwd = static_cast<const char *>(userdata);
16642        if (!pwd) return 0;
16643        auto len = static_cast<int>(strlen(pwd));
16644        if (len > size) len = size;
16645        memcpy(buf, pwd, static_cast<size_t>(len));
16646        return len;
16647      });
16648}
16649
16650} // namespace impl
16651
16652ctx_t create_client_context() {
16653  auto ctx = new (std::nothrow) impl::WolfSSLContext();
16654  if (!ctx) { return nullptr; }
16655
16656  ctx->is_server = false;
16657
16658  WOLFSSL_METHOD *method = wolfTLSv1_2_client_method();
16659  if (!method) {
16660    delete ctx;
16661    return nullptr;
16662  }
16663
16664  ctx->ctx = wolfSSL_CTX_new(method);
16665  if (!ctx->ctx) {
16666    delete ctx;
16667    return nullptr;
16668  }
16669
16670  // Default: verify peer certificate
16671  wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_PEER, nullptr);
16672
16673  return static_cast<ctx_t>(ctx);
16674}
16675
16676ctx_t create_server_context() {
16677  auto ctx = new (std::nothrow) impl::WolfSSLContext();
16678  if (!ctx) { return nullptr; }
16679
16680  ctx->is_server = true;
16681
16682  WOLFSSL_METHOD *method = wolfTLSv1_2_server_method();
16683  if (!method) {
16684    delete ctx;
16685    return nullptr;
16686  }
16687
16688  ctx->ctx = wolfSSL_CTX_new(method);
16689  if (!ctx->ctx) {
16690    delete ctx;
16691    return nullptr;
16692  }
16693
16694  // Default: don't verify client
16695  wolfSSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_NONE, nullptr);
16696
16697  // Enable SNI on server
16698  wolfSSL_CTX_SNI_SetOptions(ctx->ctx, WOLFSSL_SNI_HOST_NAME,
16699                             WOLFSSL_SNI_CONTINUE_ON_MISMATCH);
16700  wolfSSL_CTX_set_servername_callback(ctx->ctx, impl::wolfssl_sni_callback);
16701
16702  return static_cast<ctx_t>(ctx);
16703}
16704
16705void free_context(ctx_t ctx) {
16706  if (ctx) { delete static_cast<impl::WolfSSLContext *>(ctx); }
16707}
16708
16709bool set_min_version(ctx_t ctx, Version version) {
16710  if (!ctx) { return false; }
16711  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
16712
16713  int min_ver = WOLFSSL_TLSV1_2;
16714  if (version >= Version::TLS1_3) { min_ver = WOLFSSL_TLSV1_3; }
16715
16716  return wolfSSL_CTX_SetMinVersion(wctx->ctx, min_ver) == WOLFSSL_SUCCESS;
16717}
16718
16719bool load_ca_pem(ctx_t ctx, const char *pem, size_t len) {
16720  if (!ctx || !pem) { return false; }
16721  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
16722
16723  int ret = wolfSSL_CTX_load_verify_buffer(
16724      wctx->ctx, reinterpret_cast<const unsigned char *>(pem),
16725      static_cast<long>(len), SSL_FILETYPE_PEM);
16726  if (ret != SSL_SUCCESS) {
16727    impl::wolfssl_last_error() =
16728        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
16729    return false;
16730  }
16731  wctx->ca_pem_data_.append(pem, len);
16732  return true;
16733}
16734
16735bool load_ca_file(ctx_t ctx, const char *file_path) {
16736  if (!ctx || !file_path) { return false; }
16737  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
16738
16739  int ret = wolfSSL_CTX_load_verify_locations(wctx->ctx, file_path, nullptr);
16740  if (ret != SSL_SUCCESS) {
16741    impl::wolfssl_last_error() =
16742        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
16743    return false;
16744  }
16745  return true;
16746}
16747
16748bool load_ca_dir(ctx_t ctx, const char *dir_path) {
16749  if (!ctx || !dir_path) { return false; }
16750  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
16751
16752  int ret = wolfSSL_CTX_load_verify_locations(wctx->ctx, nullptr, dir_path);
16753  // wolfSSL may fail if the directory doesn't contain properly hashed certs.
16754  // Unlike OpenSSL which lazily loads certs from directories, wolfSSL scans
16755  // immediately. Return true even on failure since the CA file may have
16756  // already been loaded, matching OpenSSL's lenient behavior.
16757  (void)ret;
16758  return true;
16759}
16760
16761bool load_system_certs(ctx_t ctx) {
16762  if (!ctx) { return false; }
16763  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
16764  bool loaded = false;
16765
16766#ifdef _WIN32
16767  loaded = impl::enumerate_windows_system_certs(
16768      [&](const unsigned char *data, size_t len) {
16769        return wolfSSL_CTX_load_verify_buffer(wctx->ctx, data,
16770                                              static_cast<long>(len),
16771                                              SSL_FILETYPE_ASN1) == SSL_SUCCESS;
16772      });
16773#elif defined(__APPLE__) && defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
16774  loaded = impl::enumerate_macos_keychain_certs(
16775      [&](const unsigned char *data, size_t len) {
16776        return wolfSSL_CTX_load_verify_buffer(wctx->ctx, data,
16777                                              static_cast<long>(len),
16778                                              SSL_FILETYPE_ASN1) == SSL_SUCCESS;
16779      });
16780#else
16781  for (auto path = impl::system_ca_paths(); *path; ++path) {
16782    if (wolfSSL_CTX_load_verify_locations(wctx->ctx, *path, nullptr) ==
16783        SSL_SUCCESS) {
16784      loaded = true;
16785      break;
16786    }
16787  }
16788
16789  if (!loaded) {
16790    for (auto dir = impl::system_ca_dirs(); *dir; ++dir) {
16791      if (wolfSSL_CTX_load_verify_locations(wctx->ctx, nullptr, *dir) ==
16792          SSL_SUCCESS) {
16793        loaded = true;
16794        break;
16795      }
16796    }
16797  }
16798#endif
16799
16800  return loaded;
16801}
16802
16803bool set_client_cert_pem(ctx_t ctx, const char *cert, const char *key,
16804                                const char *password) {
16805  if (!ctx || !cert || !key) { return false; }
16806  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
16807
16808  // Load certificate
16809  int ret = wolfSSL_CTX_use_certificate_buffer(
16810      wctx->ctx, reinterpret_cast<const unsigned char *>(cert),
16811      static_cast<long>(strlen(cert)), SSL_FILETYPE_PEM);
16812  if (ret != SSL_SUCCESS) {
16813    impl::wolfssl_last_error() =
16814        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
16815    return false;
16816  }
16817
16818  // Set password callback if password is provided
16819  if (password) { impl::set_wolfssl_password_cb(wctx->ctx, password); }
16820
16821  // Load private key
16822  ret = wolfSSL_CTX_use_PrivateKey_buffer(
16823      wctx->ctx, reinterpret_cast<const unsigned char *>(key),
16824      static_cast<long>(strlen(key)), SSL_FILETYPE_PEM);
16825  if (ret != SSL_SUCCESS) {
16826    impl::wolfssl_last_error() =
16827        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
16828    return false;
16829  }
16830
16831  // Verify that the certificate and private key match
16832  return wolfSSL_CTX_check_private_key(wctx->ctx) == SSL_SUCCESS;
16833}
16834
16835bool set_client_cert_file(ctx_t ctx, const char *cert_path,
16836                                 const char *key_path, const char *password) {
16837  if (!ctx || !cert_path || !key_path) { return false; }
16838  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
16839
16840  // Load certificate file
16841  int ret =
16842      wolfSSL_CTX_use_certificate_file(wctx->ctx, cert_path, SSL_FILETYPE_PEM);
16843  if (ret != SSL_SUCCESS) {
16844    impl::wolfssl_last_error() =
16845        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
16846    return false;
16847  }
16848
16849  // Set password callback if password is provided
16850  if (password) { impl::set_wolfssl_password_cb(wctx->ctx, password); }
16851
16852  // Load private key file
16853  ret = wolfSSL_CTX_use_PrivateKey_file(wctx->ctx, key_path, SSL_FILETYPE_PEM);
16854  if (ret != SSL_SUCCESS) {
16855    impl::wolfssl_last_error() =
16856        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
16857    return false;
16858  }
16859
16860  // Verify that the certificate and private key match
16861  return wolfSSL_CTX_check_private_key(wctx->ctx) == SSL_SUCCESS;
16862}
16863
16864void set_verify_client(ctx_t ctx, bool require) {
16865  if (!ctx) { return; }
16866  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
16867  wctx->verify_client = require;
16868  if (require) {
16869    wolfSSL_CTX_set_verify(
16870        wctx->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
16871        wctx->has_verify_callback ? impl::wolfssl_verify_callback : nullptr);
16872  } else {
16873    if (wctx->has_verify_callback) {
16874      wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
16875                             impl::wolfssl_verify_callback);
16876    } else {
16877      wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_NONE, nullptr);
16878    }
16879  }
16880}
16881
16882session_t create_session(ctx_t ctx, socket_t sock) {
16883  if (!ctx || sock == INVALID_SOCKET) { return nullptr; }
16884  auto wctx = static_cast<impl::WolfSSLContext *>(ctx);
16885
16886  auto session = new (std::nothrow) impl::WolfSSLSession();
16887  if (!session) { return nullptr; }
16888
16889  session->sock = sock;
16890  session->ssl = wolfSSL_new(wctx->ctx);
16891  if (!session->ssl) {
16892    impl::wolfssl_last_error() =
16893        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
16894    delete session;
16895    return nullptr;
16896  }
16897
16898  wolfSSL_set_fd(session->ssl, static_cast<int>(sock));
16899
16900  return static_cast<session_t>(session);
16901}
16902
16903void free_session(session_t session) {
16904  if (session) { delete static_cast<impl::WolfSSLSession *>(session); }
16905}
16906
16907bool set_sni(session_t session, const char *hostname,
16908                    bool verify_hostname) {
16909  if (!session || !hostname) { return false; }
16910  auto wsession = static_cast<impl::WolfSSLSession *>(session);
16911
16912  int ret = wolfSSL_UseSNI(wsession->ssl, WOLFSSL_SNI_HOST_NAME, hostname,
16913                           static_cast<word16>(strlen(hostname)));
16914  if (ret != WOLFSSL_SUCCESS) {
16915    impl::wolfssl_last_error() =
16916        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
16917    return false;
16918  }
16919
16920  // wolfSSL_check_domain_name binds identity checking to the handshake,
16921  // separately from the SNI extension sent above; skip it when hostname
16922  // verification is disabled so only the chain is checked, matching OpenSSL.
16923  if (verify_hostname) { wolfSSL_check_domain_name(wsession->ssl, hostname); }
16924
16925  wsession->hostname = hostname;
16926  return true;
16927}
16928
16929TlsError connect(session_t session) {
16930  TlsError err;
16931  if (!session) {
16932    err.code = ErrorCode::Fatal;
16933    return err;
16934  }
16935
16936  auto wsession = static_cast<impl::WolfSSLSession *>(session);
16937  int ret = wolfSSL_connect(wsession->ssl);
16938
16939  if (ret == SSL_SUCCESS) {
16940    err.code = ErrorCode::Success;
16941  } else {
16942    int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
16943    err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
16944    err.backend_code = static_cast<uint64_t>(ssl_error);
16945    impl::wolfssl_last_error() = err.backend_code;
16946  }
16947
16948  return err;
16949}
16950
16951TlsError accept(session_t session) {
16952  TlsError err;
16953  if (!session) {
16954    err.code = ErrorCode::Fatal;
16955    return err;
16956  }
16957
16958  auto wsession = static_cast<impl::WolfSSLSession *>(session);
16959  int ret = wolfSSL_accept(wsession->ssl);
16960
16961  if (ret == SSL_SUCCESS) {
16962    err.code = ErrorCode::Success;
16963    // Capture SNI from thread-local storage after successful handshake
16964    wsession->sni_hostname = std::move(impl::wolfssl_pending_sni());
16965    impl::wolfssl_pending_sni().clear();
16966  } else {
16967    int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
16968    err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
16969    err.backend_code = static_cast<uint64_t>(ssl_error);
16970    impl::wolfssl_last_error() = err.backend_code;
16971  }
16972
16973  return err;
16974}
16975
16976bool connect_nonblocking(session_t session, socket_t sock,
16977                                time_t timeout_sec, time_t timeout_usec,
16978                                TlsError *err) {
16979  if (!session) {
16980    if (err) { err->code = ErrorCode::Fatal; }
16981    return false;
16982  }
16983
16984  auto wsession = static_cast<impl::WolfSSLSession *>(session);
16985
16986  // Set socket to non-blocking mode
16987  detail::set_nonblocking(sock, true);
16988  auto cleanup =
16989      detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
16990
16991  int ret;
16992  while ((ret = wolfSSL_connect(wsession->ssl)) != SSL_SUCCESS) {
16993    int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
16994    if (ssl_error == SSL_ERROR_WANT_READ) {
16995      if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
16996        continue;
16997      }
16998    } else if (ssl_error == SSL_ERROR_WANT_WRITE) {
16999      if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
17000        continue;
17001      }
17002    }
17003
17004    // Error or timeout
17005    if (err) {
17006      err->code =
17007          impl::map_wolfssl_error(wsession->ssl, ssl_error, err->sys_errno);
17008      err->backend_code = static_cast<uint64_t>(ssl_error);
17009    }
17010    impl::wolfssl_last_error() = static_cast<uint64_t>(ssl_error);
17011    return false;
17012  }
17013
17014  if (err) { err->code = ErrorCode::Success; }
17015  return true;
17016}
17017
17018bool accept_nonblocking(session_t session, socket_t sock,
17019                               time_t timeout_sec, time_t timeout_usec,
17020                               TlsError *err) {
17021  if (!session) {
17022    if (err) { err->code = ErrorCode::Fatal; }
17023    return false;
17024  }
17025
17026  auto wsession = static_cast<impl::WolfSSLSession *>(session);
17027
17028  // Set socket to non-blocking mode
17029  detail::set_nonblocking(sock, true);
17030  auto cleanup =
17031      detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
17032
17033  int ret;
17034  while ((ret = wolfSSL_accept(wsession->ssl)) != SSL_SUCCESS) {
17035    int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
17036    if (ssl_error == SSL_ERROR_WANT_READ) {
17037      if (detail::select_read(sock, timeout_sec, timeout_usec) > 0) {
17038        continue;
17039      }
17040    } else if (ssl_error == SSL_ERROR_WANT_WRITE) {
17041      if (detail::select_write(sock, timeout_sec, timeout_usec) > 0) {
17042        continue;
17043      }
17044    }
17045
17046    // Error or timeout
17047    if (err) {
17048      err->code =
17049          impl::map_wolfssl_error(wsession->ssl, ssl_error, err->sys_errno);
17050      err->backend_code = static_cast<uint64_t>(ssl_error);
17051    }
17052    impl::wolfssl_last_error() = static_cast<uint64_t>(ssl_error);
17053    return false;
17054  }
17055
17056  if (err) { err->code = ErrorCode::Success; }
17057
17058  // Capture SNI from thread-local storage after successful handshake
17059  wsession->sni_hostname = std::move(impl::wolfssl_pending_sni());
17060  impl::wolfssl_pending_sni().clear();
17061
17062  return true;
17063}
17064
17065ssize_t read(session_t session, void *buf, size_t len, TlsError &err) {
17066  if (!session || !buf) {
17067    err.code = ErrorCode::Fatal;
17068    return -1;
17069  }
17070
17071  auto wsession = static_cast<impl::WolfSSLSession *>(session);
17072  int ret = wolfSSL_read(wsession->ssl, buf, static_cast<int>(len));
17073
17074  if (ret > 0) {
17075    err.code = ErrorCode::Success;
17076    return static_cast<ssize_t>(ret);
17077  }
17078
17079  if (ret == 0) {
17080    err.code = ErrorCode::PeerClosed;
17081    return 0;
17082  }
17083
17084  int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
17085  err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
17086  err.backend_code = static_cast<uint64_t>(ssl_error);
17087  impl::wolfssl_last_error() = err.backend_code;
17088  return -1;
17089}
17090
17091ssize_t write(session_t session, const void *buf, size_t len,
17092                     TlsError &err) {
17093  if (!session || !buf) {
17094    err.code = ErrorCode::Fatal;
17095    return -1;
17096  }
17097
17098  auto wsession = static_cast<impl::WolfSSLSession *>(session);
17099  int ret = wolfSSL_write(wsession->ssl, buf, static_cast<int>(len));
17100
17101  if (ret > 0) {
17102    err.code = ErrorCode::Success;
17103    return static_cast<ssize_t>(ret);
17104  }
17105
17106  // wolfSSL_write returns 0 when the peer has sent a close_notify.
17107  // Treat this as an error (return -1) so callers don't spin in a
17108  // write loop adding zero to the offset.
17109  if (ret == 0) {
17110    err.code = ErrorCode::PeerClosed;
17111    return -1;
17112  }
17113
17114  int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
17115  err.code = impl::map_wolfssl_error(wsession->ssl, ssl_error, err.sys_errno);
17116  err.backend_code = static_cast<uint64_t>(ssl_error);
17117  impl::wolfssl_last_error() = err.backend_code;
17118  return -1;
17119}
17120
17121int pending(const_session_t session) {
17122  if (!session) { return 0; }
17123  auto wsession =
17124      static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
17125  return wolfSSL_pending(wsession->ssl);
17126}
17127
17128void shutdown(session_t session, bool graceful) {
17129  if (!session) { return; }
17130  auto wsession = static_cast<impl::WolfSSLSession *>(session);
17131
17132  if (graceful) {
17133    int ret;
17134    int attempts = 0;
17135    while ((ret = wolfSSL_shutdown(wsession->ssl)) != SSL_SUCCESS &&
17136           attempts < 3) {
17137      int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
17138      if (ssl_error != SSL_ERROR_WANT_READ &&
17139          ssl_error != SSL_ERROR_WANT_WRITE) {
17140        break;
17141      }
17142      attempts++;
17143    }
17144  } else {
17145    wolfSSL_shutdown(wsession->ssl);
17146  }
17147}
17148
17149bool is_peer_closed(session_t session, socket_t sock) {
17150  if (!session || sock == INVALID_SOCKET) { return true; }
17151  auto wsession = static_cast<impl::WolfSSLSession *>(session);
17152
17153  // Check if there's already decrypted data available
17154  if (wolfSSL_pending(wsession->ssl) > 0) { return false; }
17155
17156  // Set socket to non-blocking to avoid blocking on read
17157  detail::set_nonblocking(sock, true);
17158  auto cleanup =
17159      detail::scope_exit([&]() { detail::set_nonblocking(sock, false); });
17160
17161  // Peek 1 byte to check connection status without consuming data
17162  unsigned char buf;
17163  int ret = wolfSSL_peek(wsession->ssl, &buf, 1);
17164
17165  // If we got data or WANT_READ (would block), connection is alive
17166  if (ret > 0) { return false; }
17167
17168  int ssl_error = wolfSSL_get_error(wsession->ssl, ret);
17169  if (ssl_error == SSL_ERROR_WANT_READ) { return false; }
17170
17171  return ssl_error == SSL_ERROR_ZERO_RETURN || ssl_error == SSL_ERROR_SYSCALL ||
17172         ret == 0;
17173}
17174
17175cert_t get_peer_cert(const_session_t session) {
17176  if (!session) { return nullptr; }
17177  auto wsession =
17178      static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
17179
17180  WOLFSSL_X509 *cert = wolfSSL_get_peer_certificate(wsession->ssl);
17181  return static_cast<cert_t>(cert);
17182}
17183
17184void free_cert(cert_t cert) {
17185  if (cert) { wolfSSL_X509_free(static_cast<WOLFSSL_X509 *>(cert)); }
17186}
17187
17188bool verify_hostname(cert_t cert, const char *hostname) {
17189  if (!cert || !hostname) { return false; }
17190  auto x509 = static_cast<WOLFSSL_X509 *>(cert);
17191  std::string host_str(hostname);
17192
17193  // Check if hostname is an IP address (IPv4 or IPv6)
17194  unsigned char ip_bytes[16];
17195  auto ip_len = impl::parse_ip_address(host_str, ip_bytes);
17196  auto is_ip = ip_len > 0;
17197
17198  // Check Subject Alternative Names
17199  auto *san_names = static_cast<WOLF_STACK_OF(WOLFSSL_GENERAL_NAME) *>(
17200      wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
17201
17202  if (san_names) {
17203    int san_count = wolfSSL_sk_num(san_names);
17204    for (int i = 0; i < san_count; i++) {
17205      auto *names =
17206          static_cast<WOLFSSL_GENERAL_NAME *>(wolfSSL_sk_value(san_names, i));
17207      if (!names) continue;
17208
17209      if (!is_ip && names->type == WOLFSSL_GEN_DNS) {
17210        // DNS name
17211        unsigned char *dns_name = nullptr;
17212        int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, names->d.dNSName);
17213        if (dns_name && dns_len > 0) {
17214          std::string san_name(reinterpret_cast<char *>(dns_name),
17215                               static_cast<size_t>(dns_len));
17216          XFREE(dns_name, nullptr, DYNAMIC_TYPE_OPENSSL);
17217          if (detail::match_hostname(san_name, host_str)) {
17218            wolfSSL_sk_free(san_names);
17219            return true;
17220          }
17221        }
17222      } else if (is_ip && names->type == WOLFSSL_GEN_IPADD) {
17223        // IP address: only an iPAddress SAN of the same family (4 bytes for
17224        // IPv4, 16 bytes for IPv6) may authenticate the host.
17225        unsigned char *ip_data = wolfSSL_ASN1_STRING_data(names->d.iPAddress);
17226        auto san_ip_len = wolfSSL_ASN1_STRING_length(names->d.iPAddress);
17227        if (ip_data && san_ip_len == static_cast<int>(ip_len) &&
17228            memcmp(ip_data, ip_bytes, ip_len) == 0) {
17229          wolfSSL_sk_free(san_names);
17230          return true;
17231        }
17232      }
17233    }
17234    wolfSSL_sk_free(san_names);
17235  }
17236
17237  // Fallback: Check Common Name (CN) in subject. Skipped for IP-literal hosts:
17238  // an IP identity is only valid via an iPAddress SAN, never the CN (RFC 9110;
17239  // the OpenSSL backend's X509_check_ip behaves the same way).
17240  auto subject = is_ip ? nullptr : wolfSSL_X509_get_subject_name(x509);
17241  if (subject) {
17242    char cn[256] = {};
17243    int cn_len = wolfSSL_X509_NAME_get_text_by_NID(subject, NID_commonName, cn,
17244                                                   sizeof(cn));
17245    if (cn_len > 0) {
17246      std::string cn_str(cn, static_cast<size_t>(cn_len));
17247      if (detail::match_hostname(cn_str, host_str)) { return true; }
17248    }
17249  }
17250
17251  return false;
17252}
17253
17254uint64_t hostname_mismatch_code() {
17255  return static_cast<uint64_t>(DOMAIN_NAME_MISMATCH);
17256}
17257
17258long get_verify_result(const_session_t session) {
17259  if (!session) { return -1; }
17260  auto wsession =
17261      static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
17262  long result = wolfSSL_get_verify_result(wsession->ssl);
17263  return result;
17264}
17265
17266std::string get_cert_subject_cn(cert_t cert) {
17267  if (!cert) return "";
17268  auto x509 = static_cast<WOLFSSL_X509 *>(cert);
17269
17270  WOLFSSL_X509_NAME *subject = wolfSSL_X509_get_subject_name(x509);
17271  if (!subject) return "";
17272
17273  char cn[256] = {};
17274  int cn_len = wolfSSL_X509_NAME_get_text_by_NID(subject, NID_commonName, cn,
17275                                                 sizeof(cn));
17276  if (cn_len <= 0) return "";
17277  return std::string(cn, static_cast<size_t>(cn_len));
17278}
17279
17280std::string get_cert_issuer_name(cert_t cert) {
17281  if (!cert) return "";
17282  auto x509 = static_cast<WOLFSSL_X509 *>(cert);
17283
17284  WOLFSSL_X509_NAME *issuer = wolfSSL_X509_get_issuer_name(x509);
17285  if (!issuer) return "";
17286
17287  char *name_str = wolfSSL_X509_NAME_oneline(issuer, nullptr, 0);
17288  if (!name_str) return "";
17289
17290  std::string result(name_str);
17291  XFREE(name_str, nullptr, DYNAMIC_TYPE_OPENSSL);
17292  return result;
17293}
17294
17295bool get_cert_sans(cert_t cert, std::vector<SanEntry> &sans) {
17296  sans.clear();
17297  if (!cert) return false;
17298  auto x509 = static_cast<WOLFSSL_X509 *>(cert);
17299
17300  auto *san_names = static_cast<WOLF_STACK_OF(WOLFSSL_GENERAL_NAME) *>(
17301      wolfSSL_X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr));
17302  if (!san_names) return true; // No SANs is not an error
17303
17304  int count = wolfSSL_sk_num(san_names);
17305  for (int i = 0; i < count; i++) {
17306    auto *name =
17307        static_cast<WOLFSSL_GENERAL_NAME *>(wolfSSL_sk_value(san_names, i));
17308    if (!name) continue;
17309
17310    SanEntry entry;
17311    switch (name->type) {
17312    case WOLFSSL_GEN_DNS: {
17313      entry.type = SanType::DNS;
17314      unsigned char *dns_name = nullptr;
17315      int dns_len = wolfSSL_ASN1_STRING_to_UTF8(&dns_name, name->d.dNSName);
17316      if (dns_name && dns_len > 0) {
17317        entry.value = std::string(reinterpret_cast<char *>(dns_name),
17318                                  static_cast<size_t>(dns_len));
17319        XFREE(dns_name, nullptr, DYNAMIC_TYPE_OPENSSL);
17320      }
17321      break;
17322    }
17323    case WOLFSSL_GEN_IPADD: {
17324      entry.type = SanType::IP;
17325      unsigned char *ip_data = wolfSSL_ASN1_STRING_data(name->d.iPAddress);
17326      int ip_len = wolfSSL_ASN1_STRING_length(name->d.iPAddress);
17327      if (ip_data && ip_len == 4) {
17328        char buf[16];
17329        snprintf(buf, sizeof(buf), "%d.%d.%d.%d", ip_data[0], ip_data[1],
17330                 ip_data[2], ip_data[3]);
17331        entry.value = buf;
17332      } else if (ip_data && ip_len == 16) {
17333        char buf[64];
17334        snprintf(buf, sizeof(buf),
17335                 "%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
17336                 "%02x%02x:%02x%02x:%02x%02x:%02x%02x",
17337                 ip_data[0], ip_data[1], ip_data[2], ip_data[3], ip_data[4],
17338                 ip_data[5], ip_data[6], ip_data[7], ip_data[8], ip_data[9],
17339                 ip_data[10], ip_data[11], ip_data[12], ip_data[13],
17340                 ip_data[14], ip_data[15]);
17341        entry.value = buf;
17342      }
17343      break;
17344    }
17345    case WOLFSSL_GEN_EMAIL:
17346      entry.type = SanType::EMAIL;
17347      {
17348        unsigned char *email = nullptr;
17349        int email_len = wolfSSL_ASN1_STRING_to_UTF8(&email, name->d.rfc822Name);
17350        if (email && email_len > 0) {
17351          entry.value = std::string(reinterpret_cast<char *>(email),
17352                                    static_cast<size_t>(email_len));
17353          XFREE(email, nullptr, DYNAMIC_TYPE_OPENSSL);
17354        }
17355      }
17356      break;
17357    case WOLFSSL_GEN_URI:
17358      entry.type = SanType::URI;
17359      {
17360        unsigned char *uri = nullptr;
17361        int uri_len = wolfSSL_ASN1_STRING_to_UTF8(
17362            &uri, name->d.uniformResourceIdentifier);
17363        if (uri && uri_len > 0) {
17364          entry.value = std::string(reinterpret_cast<char *>(uri),
17365                                    static_cast<size_t>(uri_len));
17366          XFREE(uri, nullptr, DYNAMIC_TYPE_OPENSSL);
17367        }
17368      }
17369      break;
17370    default: entry.type = SanType::OTHER; break;
17371    }
17372
17373    if (!entry.value.empty()) { sans.push_back(std::move(entry)); }
17374  }
17375  wolfSSL_sk_free(san_names);
17376  return true;
17377}
17378
17379bool get_cert_validity(cert_t cert, time_t &not_before,
17380                              time_t &not_after) {
17381  if (!cert) return false;
17382  auto x509 = static_cast<WOLFSSL_X509 *>(cert);
17383
17384  const WOLFSSL_ASN1_TIME *nb = wolfSSL_X509_get_notBefore(x509);
17385  const WOLFSSL_ASN1_TIME *na = wolfSSL_X509_get_notAfter(x509);
17386
17387  if (!nb || !na) return false;
17388
17389  // wolfSSL_ASN1_TIME_to_tm is available
17390  struct tm tm_nb = {}, tm_na = {};
17391  if (wolfSSL_ASN1_TIME_to_tm(nb, &tm_nb) != WOLFSSL_SUCCESS) return false;
17392  if (wolfSSL_ASN1_TIME_to_tm(na, &tm_na) != WOLFSSL_SUCCESS) return false;
17393
17394#ifdef _WIN32
17395  not_before = _mkgmtime(&tm_nb);
17396  not_after = _mkgmtime(&tm_na);
17397#else
17398  not_before = timegm(&tm_nb);
17399  not_after = timegm(&tm_na);
17400#endif
17401  return true;
17402}
17403
17404std::string get_cert_serial(cert_t cert) {
17405  if (!cert) return "";
17406  auto x509 = static_cast<WOLFSSL_X509 *>(cert);
17407
17408  WOLFSSL_ASN1_INTEGER *serial_asn1 = wolfSSL_X509_get_serialNumber(x509);
17409  if (!serial_asn1) return "";
17410
17411  // Get the serial number data
17412  int len = serial_asn1->length;
17413  unsigned char *data = serial_asn1->data;
17414  if (!data || len <= 0) return "";
17415
17416  std::string result;
17417  result.reserve(static_cast<size_t>(len) * 2);
17418  for (int i = 0; i < len; i++) {
17419    char hex[3];
17420    snprintf(hex, sizeof(hex), "%02X", data[i]);
17421    result += hex;
17422  }
17423  return result;
17424}
17425
17426bool get_cert_der(cert_t cert, std::vector<unsigned char> &der) {
17427  if (!cert) return false;
17428  auto x509 = static_cast<WOLFSSL_X509 *>(cert);
17429
17430  int der_len = 0;
17431  const unsigned char *der_data = wolfSSL_X509_get_der(x509, &der_len);
17432  if (!der_data || der_len <= 0) return false;
17433
17434  der.assign(der_data, der_data + der_len);
17435  return true;
17436}
17437
17438const char *get_sni(const_session_t session) {
17439  if (!session) return nullptr;
17440  auto wsession = static_cast<const impl::WolfSSLSession *>(session);
17441
17442  // For server: return SNI received from client during handshake
17443  if (!wsession->sni_hostname.empty()) {
17444    return wsession->sni_hostname.c_str();
17445  }
17446
17447  // For client: return the hostname set via set_sni
17448  if (!wsession->hostname.empty()) { return wsession->hostname.c_str(); }
17449
17450  return nullptr;
17451}
17452
17453uint64_t peek_error() {
17454  return static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
17455}
17456
17457uint64_t get_error() {
17458  uint64_t err = impl::wolfssl_last_error();
17459  impl::wolfssl_last_error() = 0;
17460  return err;
17461}
17462
17463std::string error_string(uint64_t code) {
17464  char buf[256];
17465  wolfSSL_ERR_error_string(static_cast<unsigned long>(code), buf);
17466  return std::string(buf);
17467}
17468
17469ca_store_t create_ca_store(const char *pem, size_t len) {
17470  if (!pem || len == 0) { return nullptr; }
17471  // Validate by attempting to load into a temporary ctx
17472  WOLFSSL_CTX *tmp_ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method());
17473  if (!tmp_ctx) { return nullptr; }
17474  int ret = wolfSSL_CTX_load_verify_buffer(
17475      tmp_ctx, reinterpret_cast<const unsigned char *>(pem),
17476      static_cast<long>(len), SSL_FILETYPE_PEM);
17477  wolfSSL_CTX_free(tmp_ctx);
17478  if (ret != SSL_SUCCESS) { return nullptr; }
17479  return static_cast<ca_store_t>(
17480      new impl::WolfSSLCAStore{std::string(pem, len)});
17481}
17482
17483void free_ca_store(ca_store_t store) {
17484  delete static_cast<impl::WolfSSLCAStore *>(store);
17485}
17486
17487bool set_ca_store(ctx_t ctx, ca_store_t store) {
17488  if (!ctx || !store) { return false; }
17489  auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
17490  auto *ca = static_cast<impl::WolfSSLCAStore *>(store);
17491  int ret = wolfSSL_CTX_load_verify_buffer(
17492      wctx->ctx, reinterpret_cast<const unsigned char *>(ca->pem_data.data()),
17493      static_cast<long>(ca->pem_data.size()), SSL_FILETYPE_PEM);
17494  if (ret == SSL_SUCCESS) { wctx->ca_pem_data_ += ca->pem_data; }
17495  // This function takes ownership of the store; the PEM data was copied into
17496  // the context, so release the source
17497  free_ca_store(store);
17498  return ret == SSL_SUCCESS;
17499}
17500
17501size_t get_ca_certs(ctx_t ctx, std::vector<cert_t> &certs) {
17502  certs.clear();
17503  if (!ctx) { return 0; }
17504  auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
17505  if (wctx->ca_pem_data_.empty()) { return 0; }
17506
17507  const std::string &pem = wctx->ca_pem_data_;
17508  const std::string begin_marker = "-----BEGIN CERTIFICATE-----";
17509  const std::string end_marker = "-----END CERTIFICATE-----";
17510  size_t pos = 0;
17511  while ((pos = pem.find(begin_marker, pos)) != std::string::npos) {
17512    size_t end_pos = pem.find(end_marker, pos);
17513    if (end_pos == std::string::npos) { break; }
17514    end_pos += end_marker.size();
17515    std::string cert_pem = pem.substr(pos, end_pos - pos);
17516    WOLFSSL_X509 *x509 = wolfSSL_X509_load_certificate_buffer(
17517        reinterpret_cast<const unsigned char *>(cert_pem.data()),
17518        static_cast<int>(cert_pem.size()), WOLFSSL_FILETYPE_PEM);
17519    if (x509) { certs.push_back(static_cast<cert_t>(x509)); }
17520    pos = end_pos;
17521  }
17522  return certs.size();
17523}
17524
17525std::vector<std::string> get_ca_names(ctx_t ctx) {
17526  std::vector<std::string> names;
17527  if (!ctx) { return names; }
17528  auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
17529  if (wctx->ca_pem_data_.empty()) { return names; }
17530
17531  const std::string &pem = wctx->ca_pem_data_;
17532  const std::string begin_marker = "-----BEGIN CERTIFICATE-----";
17533  const std::string end_marker = "-----END CERTIFICATE-----";
17534  size_t pos = 0;
17535  while ((pos = pem.find(begin_marker, pos)) != std::string::npos) {
17536    size_t end_pos = pem.find(end_marker, pos);
17537    if (end_pos == std::string::npos) { break; }
17538    end_pos += end_marker.size();
17539    std::string cert_pem = pem.substr(pos, end_pos - pos);
17540    WOLFSSL_X509 *x509 = wolfSSL_X509_load_certificate_buffer(
17541        reinterpret_cast<const unsigned char *>(cert_pem.data()),
17542        static_cast<int>(cert_pem.size()), WOLFSSL_FILETYPE_PEM);
17543    if (x509) {
17544      WOLFSSL_X509_NAME *subject = wolfSSL_X509_get_subject_name(x509);
17545      if (subject) {
17546        char *name_str = wolfSSL_X509_NAME_oneline(subject, nullptr, 0);
17547        if (name_str) {
17548          names.push_back(name_str);
17549          XFREE(name_str, nullptr, DYNAMIC_TYPE_OPENSSL);
17550        }
17551      }
17552      wolfSSL_X509_free(x509);
17553    }
17554    pos = end_pos;
17555  }
17556  return names;
17557}
17558
17559bool update_server_cert(ctx_t ctx, const char *cert_pem,
17560                               const char *key_pem, const char *password) {
17561  if (!ctx || !cert_pem || !key_pem) { return false; }
17562  auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
17563
17564  // Load new certificate
17565  int ret = wolfSSL_CTX_use_certificate_buffer(
17566      wctx->ctx, reinterpret_cast<const unsigned char *>(cert_pem),
17567      static_cast<long>(strlen(cert_pem)), SSL_FILETYPE_PEM);
17568  if (ret != SSL_SUCCESS) {
17569    impl::wolfssl_last_error() =
17570        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
17571    return false;
17572  }
17573
17574  // Set password if provided
17575  if (password) { impl::set_wolfssl_password_cb(wctx->ctx, password); }
17576
17577  // Load new private key
17578  ret = wolfSSL_CTX_use_PrivateKey_buffer(
17579      wctx->ctx, reinterpret_cast<const unsigned char *>(key_pem),
17580      static_cast<long>(strlen(key_pem)), SSL_FILETYPE_PEM);
17581  if (ret != SSL_SUCCESS) {
17582    impl::wolfssl_last_error() =
17583        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
17584    return false;
17585  }
17586
17587  return true;
17588}
17589
17590bool update_server_client_ca(ctx_t ctx, const char *ca_pem) {
17591  if (!ctx || !ca_pem) { return false; }
17592  auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
17593
17594  int ret = wolfSSL_CTX_load_verify_buffer(
17595      wctx->ctx, reinterpret_cast<const unsigned char *>(ca_pem),
17596      static_cast<long>(strlen(ca_pem)), SSL_FILETYPE_PEM);
17597  if (ret != SSL_SUCCESS) {
17598    impl::wolfssl_last_error() =
17599        static_cast<uint64_t>(wolfSSL_ERR_peek_last_error());
17600    return false;
17601  }
17602  return true;
17603}
17604
17605bool set_verify_callback(ctx_t ctx, VerifyCallback callback) {
17606  if (!ctx) { return false; }
17607  auto *wctx = static_cast<impl::WolfSSLContext *>(ctx);
17608
17609  impl::get_verify_callback() = std::move(callback);
17610  wctx->has_verify_callback = static_cast<bool>(impl::get_verify_callback());
17611
17612  if (wctx->has_verify_callback) {
17613    wolfSSL_CTX_set_verify(wctx->ctx, SSL_VERIFY_PEER,
17614                           impl::wolfssl_verify_callback);
17615  } else {
17616    wolfSSL_CTX_set_verify(
17617        wctx->ctx,
17618        wctx->verify_client
17619            ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
17620            : SSL_VERIFY_NONE,
17621        nullptr);
17622  }
17623  return true;
17624}
17625
17626long get_verify_error(const_session_t session) {
17627  if (!session) { return -1; }
17628  auto *wsession =
17629      static_cast<impl::WolfSSLSession *>(const_cast<void *>(session));
17630  return wolfSSL_get_verify_result(wsession->ssl);
17631}
17632
17633std::string verify_error_string(long error_code) {
17634  if (error_code == 0) { return ""; }
17635  const char *str =
17636      wolfSSL_X509_verify_cert_error_string(static_cast<int>(error_code));
17637  return str ? std::string(str) : std::string();
17638}
17639
17640} // namespace tls
17641
17642#endif // CPPHTTPLIB_WOLFSSL_SUPPORT
17643
17644// WebSocket implementation
17645namespace ws {
17646
17647bool WebSocket::send_frame(Opcode op, const char *data, size_t len,
17648                                  bool fin) {
17649  std::lock_guard<std::mutex> lock(write_mutex_);
17650  if (closed_) { return false; }
17651  return detail::write_websocket_frame(strm_, op, data, len, fin, !is_server_);
17652}
17653
17654ReadResult WebSocket::read(std::string &msg) {
17655  std::unique_lock<std::mutex> read_lock(read_mutex_);
17656  while (!closed_) {
17657    Opcode opcode;
17658    std::string payload;
17659    bool fin;
17660
17661    impl::FrameRead r =
17662        impl::read_websocket_frame(strm_, opcode, payload, fin, is_server_,
17663                                   CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH);
17664    // A timeout landed on a frame boundary: the connection is untouched and
17665    // still usable, so hand control back without closing it. That is only
17666    // useful to a caller who asked for the timeout; the compile-time default
17667    // is a backstop against a peer gone quiet, and elapsing it closes the
17668    // connection so a plain `while (ws.read(msg))` loop ends.
17669    if (r == impl::FrameRead::Timeout && read_timeout_set_) { return Timeout; }
17670    if (r != impl::FrameRead::Ok) {
17671      closed_ = true;
17672      return Fail;
17673    }
17674
17675    switch (opcode) {
17676    case Opcode::Ping: {
17677      std::lock_guard<std::mutex> lock(write_mutex_);
17678      detail::write_websocket_frame(strm_, Opcode::Pong, payload.data(),
17679                                    payload.size(), true, !is_server_);
17680      continue;
17681    }
17682    case Opcode::Pong: {
17683      std::lock_guard<std::mutex> lock(ping_mutex_);
17684      unacked_pings_ = 0;
17685      continue;
17686    }
17687    case Opcode::Close: {
17688      if (!closed_.exchange(true)) {
17689        // Echo close frame back
17690        std::lock_guard<std::mutex> lock(write_mutex_);
17691        detail::write_websocket_frame(strm_, Opcode::Close, payload.data(),
17692                                      payload.size(), true, !is_server_);
17693      }
17694      return Fail;
17695    }
17696    case Opcode::Text:
17697    case Opcode::Binary: {
17698      auto result = opcode == Opcode::Text ? Text : Binary;
17699      msg = std::move(payload);
17700
17701      // Handle fragmentation
17702      if (!fin) {
17703        while (true) {
17704          Opcode cont_opcode;
17705          std::string cont_payload;
17706          bool cont_fin;
17707          // A timeout is not reportable here: half of a fragmented message is
17708          // already in `msg` and read() has no way to resume it, so it is a
17709          // failure like any other. Timeouts are only ever seen on a message
17710          // boundary.
17711          if (impl::read_websocket_frame(
17712                  strm_, cont_opcode, cont_payload, cont_fin, is_server_,
17713                  CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH) !=
17714              impl::FrameRead::Ok) {
17715            closed_ = true;
17716            return Fail;
17717          }
17718          if (cont_opcode == Opcode::Ping) {
17719            std::lock_guard<std::mutex> lock(write_mutex_);
17720            detail::write_websocket_frame(
17721                strm_, Opcode::Pong, cont_payload.data(), cont_payload.size(),
17722                true, !is_server_);
17723            continue;
17724          }
17725          if (cont_opcode == Opcode::Pong) {
17726            std::lock_guard<std::mutex> lock(ping_mutex_);
17727            unacked_pings_ = 0;
17728            continue;
17729          }
17730          if (cont_opcode == Opcode::Close) {
17731            if (!closed_.exchange(true)) {
17732              std::lock_guard<std::mutex> lock(write_mutex_);
17733              detail::write_websocket_frame(
17734                  strm_, Opcode::Close, cont_payload.data(),
17735                  cont_payload.size(), true, !is_server_);
17736            }
17737            return Fail;
17738          }
17739          // RFC 6455: continuation frames must use opcode 0x0
17740          if (cont_opcode != Opcode::Continuation) {
17741            closed_ = true;
17742            return Fail;
17743          }
17744          msg += cont_payload;
17745          if (msg.size() > CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH) {
17746            closed_ = true;
17747            return Fail;
17748          }
17749          if (cont_fin) { break; }
17750        }
17751      }
17752      // RFC 6455 Section 5.6: text frames must contain valid UTF-8
17753      if (result == Text && !impl::is_valid_utf8(msg)) {
17754        // close() takes the read lock to wait for the peer's Close reply, so
17755        // it must not run while this thread still holds it.
17756        read_lock.unlock();
17757        close(CloseStatus::InvalidPayload, "invalid UTF-8");
17758        return Fail;
17759      }
17760      return result;
17761    }
17762    default: closed_ = true; return Fail;
17763    }
17764  }
17765  return Fail;
17766}
17767
17768bool WebSocket::send(const std::string &data) {
17769  return send_frame(Opcode::Text, data.data(), data.size());
17770}
17771
17772bool WebSocket::send(const char *data, size_t len) {
17773  return send_frame(Opcode::Binary, data, len);
17774}
17775
17776void WebSocket::close(CloseStatus status, const std::string &reason) {
17777  if (closed_.exchange(true)) { return; }
17778  ping_cv_.notify_all();
17779  std::string payload;
17780  auto code = static_cast<uint16_t>(status);
17781  payload.push_back(static_cast<char>((code >> 8) & 0xFF));
17782  payload.push_back(static_cast<char>(code & 0xFF));
17783  // RFC 6455 Section 5.5: control frame payload must not exceed 125 bytes
17784  // Close frame has 2-byte status code, so reason is limited to 123 bytes
17785  payload += reason.substr(0, 123);
17786  {
17787    std::lock_guard<std::mutex> lock(write_mutex_);
17788    detail::write_websocket_frame(strm_, Opcode::Close, payload.data(),
17789                                  payload.size(), true, !is_server_);
17790  }
17791
17792  // RFC 6455 Section 7.1.1: after sending a Close frame, wait for the peer's
17793  // Close response before closing the TCP connection.
17794  //
17795  // Wait only when no other thread is parsing frames. When one is, it is the
17796  // thread positioned to see the peer's reply, and reading here would take
17797  // bytes out of the message it is assembling. Bailing out also leaves the
17798  // stream, including its read timeout, entirely to that thread.
17799  std::unique_lock<std::mutex> read_lock(read_mutex_, std::try_to_lock);
17800  if (!read_lock.owns_lock()) { return; }
17801
17802  // Use a short timeout to avoid hanging if the peer doesn't respond.
17803  strm_.set_read_timeout(CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND, 0);
17804
17805  Opcode op;
17806  std::string resp;
17807  bool fin;
17808  while (impl::read_websocket_frame(strm_, op, resp, fin, is_server_, 125) ==
17809         impl::FrameRead::Ok) {
17810    if (op == Opcode::Close) { break; }
17811  }
17812}
17813
17814WebSocket::~WebSocket() {
17815  {
17816    std::lock_guard<std::mutex> lock(ping_mutex_);
17817    closed_ = true;
17818  }
17819  ping_cv_.notify_all();
17820  if (ping_thread_.joinable()) { ping_thread_.join(); }
17821}
17822
17823void WebSocket::start_heartbeat() {
17824  if (ping_interval_sec_ == 0) { return; }
17825  ping_thread_ = std::thread([this]() {
17826    std::unique_lock<std::mutex> lock(ping_mutex_);
17827    while (!closed_) {
17828      ping_cv_.wait_for(lock, std::chrono::seconds(ping_interval_sec_));
17829      if (closed_) { break; }
17830      // If the peer has failed to respond to the previous pings, give up.
17831      // RFC 6455 does not define a pong-timeout mechanism; this is an
17832      // opt-in liveness check controlled by max_missed_pongs_.
17833      if (max_missed_pongs_ > 0 && unacked_pings_ >= max_missed_pongs_) {
17834        lock.unlock();
17835        close(CloseStatus::GoingAway, "pong timeout");
17836        return;
17837      }
17838      lock.unlock();
17839      if (!send_frame(Opcode::Ping, nullptr, 0)) {
17840        lock.lock();
17841        closed_ = true;
17842        break;
17843      }
17844      lock.lock();
17845      unacked_pings_++;
17846    }
17847  });
17848}
17849
17850const Request &WebSocket::request() const { return req_; }
17851
17852bool WebSocket::is_open() const { return !closed_; }
17853
17854void WebSocket::set_read_timeout(time_t sec, time_t usec) {
17855  // 0 waits forever here, as it does for SO_RCVTIMEO. The stream waits with
17856  // poll(), where 0 would instead mean "return immediately", so hand it the
17857  // negative poll uses for an unbounded wait.
17858  if (sec == 0 && usec == 0) { sec = -1; }
17859  strm_.set_read_timeout(sec, usec);
17860  read_timeout_set_ = true;
17861}
17862
17863// WebSocketClient implementation
17864WebSocketClient::WebSocketClient(
17865    const std::string &scheme_host_port_path, const Headers &headers)
17866    : headers_(headers) {
17867  detail::UrlComponents uc;
17868  if (detail::parse_url(scheme_host_port_path, uc) && !uc.scheme.empty() &&
17869      !uc.host.empty() && !uc.path.empty()) {
17870    auto &scheme = uc.scheme;
17871
17872#ifdef CPPHTTPLIB_SSL_ENABLED
17873    if (scheme != "ws" && scheme != "wss") {
17874#else
17875    if (scheme != "ws") {
17876#endif
17877#ifndef CPPHTTPLIB_NO_EXCEPTIONS
17878      std::string msg = "'" + scheme + "' scheme is not supported.";
17879      throw std::invalid_argument(msg);
17880#endif
17881      return;
17882    }
17883
17884    auto is_ssl = scheme == "wss";
17885
17886    host_ = std::move(uc.host);
17887
17888    port_ = is_ssl ? 443 : 80;
17889    if (!uc.port.empty() && !detail::parse_port(uc.port, port_)) { return; }
17890
17891    path_ = std::move(uc.path);
17892    if (!uc.query.empty()) { path_ += uc.query; }
17893
17894#ifdef CPPHTTPLIB_SSL_ENABLED
17895    is_ssl_ = is_ssl;
17896    if (is_ssl_) {
17897      // The context lives as long as the client so that CA configuration
17898      // survives reconnects; sessions are created per connection.
17899      tls_ctx_ = tls::create_client_context();
17900      if (!tls_ctx_) { return; }
17901    }
17902#else
17903    if (is_ssl) { return; }
17904#endif
17905
17906    is_valid_ = true;
17907  }
17908}
17909
17910#ifdef CPPHTTPLIB_SSL_ENABLED
17911WebSocketClient::WebSocketClient(
17912    const std::string &scheme_host_port_path, const PemMemory &pem,
17913    const Headers &headers)
17914    : WebSocketClient(scheme_host_port_path, headers) {
17915  // For ws:// URLs the client certificate is silently ignored, consistent
17916  // with the TLS-only setters such as set_ca_cert_path().
17917  if (is_valid_ && is_ssl_ && pem.cert_pem && pem.key_pem) {
17918    if (!tls::set_client_cert_pem(tls_ctx_, pem.cert_pem, pem.key_pem,
17919                                  pem.private_key_password)) {
17920      tls::free_context(tls_ctx_);
17921      tls_ctx_ = nullptr;
17922      is_valid_ = false;
17923    }
17924  }
17925}
17926#endif
17927
17928WebSocketClient::~WebSocketClient() {
17929  shutdown_and_close();
17930#ifdef CPPHTTPLIB_SSL_ENABLED
17931  if (tls_ctx_) {
17932    tls::free_context(tls_ctx_);
17933    tls_ctx_ = nullptr;
17934  }
17935#endif
17936}
17937
17938bool WebSocketClient::is_valid() const { return is_valid_; }
17939
17940void WebSocketClient::shutdown_and_close() {
17941  // Send the close frame while the TLS session is still alive: ws_ holds an
17942  // SSLSocketStream that keeps a raw pointer to tls_session_, so the session
17943  // must outlive ws_->close() and ws_.reset() to avoid a use-after-free.
17944  if (ws_ && ws_->is_open()) { ws_->close(); }
17945  ws_.reset();
17946#ifdef CPPHTTPLIB_SSL_ENABLED
17947  if (is_ssl_) {
17948    if (tls_session_) {
17949      tls::shutdown(tls_session_, true);
17950      tls::free_session(tls_session_);
17951      tls_session_ = nullptr;
17952    }
17953  }
17954#endif
17955  if (sock_ != INVALID_SOCKET) {
17956    detail::shutdown_socket(sock_);
17957    detail::close_socket(sock_);
17958    sock_ = INVALID_SOCKET;
17959  }
17960}
17961
17962bool WebSocketClient::create_stream(std::unique_ptr<Stream> &strm,
17963                                           Error &error, int &ssl_error,
17964                                           uint64_t &ssl_backend_error) {
17965  // A read timeout of 0 means "wait forever", the way SO_RCVTIMEO reads it.
17966  // The streams wait with poll(), where 0 instead means "return immediately",
17967  // so they are given the negative poll uses for an unbounded wait.
17968  auto unbounded = read_timeout_sec_ == 0 && read_timeout_usec_ == 0;
17969  time_t strm_read_sec = unbounded ? -1 : read_timeout_sec_;
17970  time_t strm_read_usec = unbounded ? 0 : read_timeout_usec_;
17971  // The handshake belongs to establishing the connection, so an unset read
17972  // timeout leaves it bounded by the connection timeout instead of forever.
17973  time_t hs_sec = unbounded ? connection_timeout_sec_ : read_timeout_sec_;
17974  time_t hs_usec = unbounded ? connection_timeout_usec_ : read_timeout_usec_;
17975#ifdef CPPHTTPLIB_SSL_ENABLED
17976  if (is_ssl_) {
17977    // A plain flag rather than SSLClient::load_certs()'s call_once: connect()
17978    // is not safe to call concurrently on one client to begin with, since
17979    // nothing else here is guarded either.
17980    if (server_certificate_verification_ && !certs_loaded_) {
17981      uint64_t backend_error = 0;
17982      detail::load_client_ca_config(tls_ctx_, ca_cert_file_path_,
17983                                    ca_cert_dir_path_, custom_ca_loaded_,
17984                                    system_ca_mode_, backend_error);
17985      certs_loaded_ = true;
17986    }
17987
17988    detail::ClientTlsSessionOptions options;
17989    options.server_hostname_verification = server_hostname_verification_;
17990
17991    detail::ClientTlsSessionError tls_error;
17992    if (!detail::setup_client_tls_session(host_, tls_ctx_, tls_session_, sock_,
17993                                          server_certificate_verification_,
17994                                          hs_sec, hs_usec, &tls_error,
17995                                          options)) {
17996      error = tls_error.error;
17997      ssl_error = tls_error.ssl_error;
17998      ssl_backend_error = tls_error.backend_error;
17999      return false;
18000    }
18001
18002    strm = std::unique_ptr<Stream>(new detail::WebSocketSSLStream(
18003        sock_, tls_session_, strm_read_sec, strm_read_usec, write_timeout_sec_,
18004        write_timeout_usec_));
18005    return true;
18006  }
18007#else
18008  (void)error;
18009  (void)ssl_error;
18010  (void)ssl_backend_error;
18011  (void)hs_sec;
18012  (void)hs_usec;
18013#endif
18014  strm = std::unique_ptr<Stream>(
18015      new detail::SocketStream(sock_, strm_read_sec, strm_read_usec,
18016                               write_timeout_sec_, write_timeout_usec_));
18017  return true;
18018}
18019
18020void WebSocketClient::prepare_default_headers(Request &req) {
18021#ifdef CPPHTTPLIB_SSL_ENABLED
18022  auto is_ssl = is_ssl_;
18023#else
18024  auto is_ssl = false;
18025#endif
18026
18027  if (!req.has_header("Host")) {
18028    req.headers.emplace("Host", detail::make_default_host_header_value(
18029                                    host_, port_, is_ssl, address_family_));
18030  }
18031
18032  detail::add_default_user_agent_header(req);
18033}
18034
18035Result WebSocketClient::connect() {
18036  if (!is_valid_) { return Result{Error::Connection, -1, Headers{}}; }
18037  shutdown_and_close();
18038
18039  // Check is custom IP or hostname specified for host_
18040  std::string connect_host;
18041  std::string ip;
18042  detail::apply_addr_map(addr_map_, host_, connect_host, ip);
18043
18044  auto error = Error::Success;
18045  sock_ = detail::create_client_socket(
18046      connect_host, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
18047      socket_options_, connection_timeout_sec_, connection_timeout_usec_,
18048      read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
18049      write_timeout_usec_, interface_, error);
18050
18051  if (sock_ == INVALID_SOCKET) {
18052    if (error == Error::Success) { error = Error::Connection; }
18053    return Result{error, -1, Headers{}};
18054  }
18055
18056  std::unique_ptr<Stream> strm;
18057  auto stream_error = Error::SSLConnection;
18058  int ssl_error = 0;
18059  uint64_t ssl_backend_error = 0;
18060  if (!create_stream(strm, stream_error, ssl_error, ssl_backend_error)) {
18061    shutdown_and_close();
18062#ifdef CPPHTTPLIB_SSL_ENABLED
18063    return Result{stream_error, -1, Headers{}, ssl_error, ssl_backend_error};
18064#else
18065    return Result{stream_error, -1, Headers{}};
18066#endif
18067  }
18068
18069  Request req;
18070  req.method = "GET";
18071  req.path = path_;
18072  req.headers = headers_;
18073  prepare_default_headers(req);
18074
18075  detail::WebSocketUpgradeResponse upgrade;
18076  if (!detail::perform_websocket_handshake(*strm, req, upgrade)) {
18077    shutdown_and_close();
18078    return Result{upgrade.error, upgrade.status, std::move(upgrade.headers)};
18079  }
18080  subprotocol_ = std::move(upgrade.selected_subprotocol);
18081
18082  ws_ = std::unique_ptr<WebSocket>(new WebSocket(std::move(strm), req, false,
18083                                                 websocket_ping_interval_sec_,
18084                                                 websocket_max_missed_pongs_));
18085  // The stream was created with the timeout already; tell the WebSocket
18086  // whether it came from the caller, so read() knows to report it as Timeout.
18087  ws_->read_timeout_set_ = read_timeout_set_;
18088  return Result{Error::Success, upgrade.status, std::move(upgrade.headers)};
18089}
18090
18091ReadResult WebSocketClient::read(std::string &msg) {
18092  if (!ws_) { return Fail; }
18093  return ws_->read(msg);
18094}
18095
18096bool WebSocketClient::send(const std::string &data) {
18097  if (!ws_) { return false; }
18098  return ws_->send(data);
18099}
18100
18101bool WebSocketClient::send(const char *data, size_t len) {
18102  if (!ws_) { return false; }
18103  return ws_->send(data, len);
18104}
18105
18106void WebSocketClient::close(CloseStatus status,
18107                                   const std::string &reason) {
18108  if (ws_) { ws_->close(status, reason); }
18109}
18110
18111bool WebSocketClient::is_open() const { return ws_ && ws_->is_open(); }
18112
18113const std::string &WebSocketClient::subprotocol() const {
18114  return subprotocol_;
18115}
18116
18117void WebSocketClient::set_read_timeout(time_t sec, time_t usec) {
18118  read_timeout_sec_ = sec;
18119  read_timeout_usec_ = usec;
18120  read_timeout_set_ = true;
18121  // The members above only seed the next connect(); read() consults the
18122  // stream, so an already-open connection has to be told directly.
18123  if (ws_) { ws_->set_read_timeout(sec, usec); }
18124}
18125
18126void WebSocketClient::set_write_timeout(time_t sec, time_t usec) {
18127  write_timeout_sec_ = sec;
18128  write_timeout_usec_ = usec;
18129}
18130
18131void WebSocketClient::set_websocket_ping_interval(time_t sec) {
18132  websocket_ping_interval_sec_ = sec;
18133}
18134
18135void WebSocketClient::set_websocket_max_missed_pongs(int count) {
18136  websocket_max_missed_pongs_ = count;
18137}
18138
18139void WebSocketClient::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; }
18140
18141void WebSocketClient::set_address_family(int family) {
18142  address_family_ = family;
18143}
18144
18145void WebSocketClient::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; }
18146
18147void WebSocketClient::set_socket_options(SocketOptions socket_options) {
18148  socket_options_ = std::move(socket_options);
18149}
18150
18151void WebSocketClient::set_connection_timeout(time_t sec, time_t usec) {
18152  connection_timeout_sec_ = sec;
18153  connection_timeout_usec_ = usec;
18154}
18155
18156void WebSocketClient::set_interface(const std::string &intf) {
18157  interface_ = intf;
18158}
18159
18160void WebSocketClient::set_hostname_addr_map(
18161    std::map<std::string, std::string> addr_map) {
18162  addr_map_ = std::move(addr_map);
18163}
18164
18165#ifdef CPPHTTPLIB_SSL_ENABLED
18166
18167void
18168WebSocketClient::set_ca_cert_path(const std::string &ca_cert_file_path,
18169                                  const std::string &ca_cert_dir_path) {
18170  ca_cert_file_path_ = ca_cert_file_path;
18171  ca_cert_dir_path_ = ca_cert_dir_path;
18172}
18173
18174void WebSocketClient::set_ca_cert_store(tls::ca_store_t store) {
18175  if (store && tls_ctx_) {
18176    // set_ca_store takes ownership of store
18177    tls::set_ca_store(tls_ctx_, store);
18178    custom_ca_loaded_ = true;
18179  } else if (store) {
18180    tls::free_ca_store(store);
18181  }
18182}
18183
18184void WebSocketClient::load_ca_cert_store(const char *ca_cert,
18185                                                std::size_t size) {
18186  if (tls_ctx_ && ca_cert && size > 0) {
18187    tls::load_ca_pem(tls_ctx_, ca_cert, size);
18188    custom_ca_loaded_ = true;
18189  }
18190}
18191
18192void
18193WebSocketClient::enable_server_certificate_verification(bool enabled) {
18194  server_certificate_verification_ = enabled;
18195}
18196
18197void WebSocketClient::enable_server_hostname_verification(bool enabled) {
18198  server_hostname_verification_ = enabled;
18199}
18200
18201void WebSocketClient::enable_system_ca(bool enabled) {
18202  system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled;
18203}
18204
18205#endif // CPPHTTPLIB_SSL_ENABLED
18206
18207} // namespace ws
18208
18209} // namespace httplib