//
//  httplib.h
//
//  Copyright (c) 2026 Yuji Hirose. All rights reserved.
//  MIT License
//

#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H

#define CPPHTTPLIB_VERSION "0.50.1"
#define CPPHTTPLIB_VERSION_NUM "0x003201"

#ifdef _WIN32
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00
#error                                                                         \
    "cpp-httplib doesn't support Windows 8 or lower. Please use Windows 10 or later."
#endif
#endif

/*
 * Configuration
 */

#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
#endif

#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND 10000
#endif

#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100
#endif

#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300
#endif

#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND
#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0
#endif

#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND
#define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND 5
#endif

#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND
#define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND 0
#endif

#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND
#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND 5
#endif

#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND
#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND 0
#endif

#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND
#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND 300
#endif

#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND
#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND 0
#endif

#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND
#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND 5
#endif

#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND
#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0
#endif

#ifndef CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND
#define CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND 0
#endif

#ifndef CPPHTTPLIB_EXPECT_100_THRESHOLD
#define CPPHTTPLIB_EXPECT_100_THRESHOLD 1024
#endif

#ifndef CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND
#define CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND 1000
#endif

#ifndef CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD
#define CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD (1024 * 1024)
#endif

#ifndef CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND
#define CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND 50
#endif

#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND
#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0
#endif

#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND
#ifdef _WIN32
#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 1000
#else
#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0
#endif
#endif

#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH
#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
#endif

#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH
#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192
#endif

#ifndef CPPHTTPLIB_HEADER_MAX_COUNT
#define CPPHTTPLIB_HEADER_MAX_COUNT 100
#endif

#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT
#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
#endif

#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT
#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024
#endif

#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH
#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (100 * 1024 * 1024) // 100MB
#endif

#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH
#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192
#endif

#ifndef CPPHTTPLIB_RANGE_MAX_COUNT
#define CPPHTTPLIB_RANGE_MAX_COUNT 1024
#endif

#ifndef CPPHTTPLIB_TCP_NODELAY
#define CPPHTTPLIB_TCP_NODELAY false
#endif

#ifndef CPPHTTPLIB_IPV6_V6ONLY
#define CPPHTTPLIB_IPV6_V6ONLY false
#endif

#ifndef CPPHTTPLIB_RECV_BUFSIZ
#define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u)
#endif

#ifndef CPPHTTPLIB_SEND_BUFSIZ
#define CPPHTTPLIB_SEND_BUFSIZ size_t(16384u)
#endif

#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ
#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u)
#endif

#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
#define CPPHTTPLIB_THREAD_POOL_COUNT                                           \
  ((std::max)(8u, std::thread::hardware_concurrency() > 0                      \
                      ? std::thread::hardware_concurrency() - 1                \
                      : 0))
#endif

#ifndef CPPHTTPLIB_THREAD_POOL_MAX_COUNT
#define CPPHTTPLIB_THREAD_POOL_MAX_COUNT (CPPHTTPLIB_THREAD_POOL_COUNT * 4)
#endif

#ifndef CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT
#define CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT 3 // seconds
#endif

#ifndef CPPHTTPLIB_RECV_FLAGS
#define CPPHTTPLIB_RECV_FLAGS 0
#endif

#ifndef CPPHTTPLIB_SEND_FLAGS
#define CPPHTTPLIB_SEND_FLAGS 0
#endif

#ifndef CPPHTTPLIB_LISTEN_BACKLOG
#define CPPHTTPLIB_LISTEN_BACKLOG 5
#endif

#ifndef CPPHTTPLIB_MAX_LINE_LENGTH
#define CPPHTTPLIB_MAX_LINE_LENGTH 32768
#endif

#ifndef CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH
#define CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH 16777216
#endif

#ifndef CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND
#define CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND 300
#endif

#ifndef CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND
#define CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND 5
#endif

#ifndef CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND
#define CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND 30
#endif

#ifndef CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS
#define CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS 0
#endif

/*
 * Headers
 */

#ifdef _WIN32
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif //_CRT_SECURE_NO_WARNINGS

#ifndef _CRT_NONSTDC_NO_DEPRECATE
#define _CRT_NONSTDC_NO_DEPRECATE
#endif //_CRT_NONSTDC_NO_DEPRECATE

#if defined(_MSC_VER)
#if _MSC_VER < 1900
#error Sorry, Visual Studio versions prior to 2015 are not supported
#endif

#pragma comment(lib, "ws2_32.lib")

#ifndef _SSIZE_T_DEFINED
using ssize_t = __int64;
#define _SSIZE_T_DEFINED
#endif
#endif // _MSC_VER

#ifndef S_ISREG
#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG)
#endif // S_ISREG

#ifndef S_ISDIR
#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR)
#endif // S_ISDIR

#ifndef NOMINMAX
#define NOMINMAX
#endif // NOMINMAX

#include <io.h>
#include <winsock2.h>
#include <ws2tcpip.h>

#if defined(__has_include)
#if __has_include(<afunix.h>)
// afunix.h uses types declared in winsock2.h, so has to be included after it.
#include <afunix.h>
#define CPPHTTPLIB_HAVE_AFUNIX_H 1
#endif
#endif

#ifndef WSA_FLAG_NO_HANDLE_INHERIT
#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
#endif

using nfds_t = unsigned long;
using socket_t = SOCKET;
using socklen_t = int;

#else // not _WIN32

#include <arpa/inet.h>
#if !defined(_AIX) && !defined(__MVS__)
#include <ifaddrs.h>
#endif
#ifdef __MVS__
#include <strings.h>
#ifndef NI_MAXHOST
#define NI_MAXHOST 1025
#endif
#endif
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#ifdef __linux__
#include <resolv.h>
#undef _res // Undefine _res macro to avoid conflicts with user code (#2278)
#endif
#include <csignal>
#include <netinet/tcp.h>
#include <poll.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

using socket_t = int;
#ifndef INVALID_SOCKET
#define INVALID_SOCKET (-1)
#endif
#endif //_WIN32

#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif

#include <algorithm>
#include <array>
#include <atomic>
#include <cassert>
#include <chrono>
#include <climits>
#include <condition_variable>
#include <cstdlib>
#include <cstring>
#include <errno.h>
#include <exception>
#include <fcntl.h>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <random>
#include <regex>
#include <set>
#include <sstream>
#include <string>
#include <sys/stat.h>
#include <system_error>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <utility>

// On macOS with a TLS backend, enable Keychain root certificates by default
// unless the user explicitly opts out. Not enabled on iOS/tvOS/watchOS since
// the SecTrustSettings APIs used to enumerate anchor certificates are macOS
// only; on those platforms the user must provide a CA bundle explicitly.
#if defined(__APPLE__) && defined(__clang__) &&                                \
    !defined(CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES) &&         \
    (defined(CPPHTTPLIB_OPENSSL_SUPPORT) ||                                    \
     defined(CPPHTTPLIB_MBEDTLS_SUPPORT) ||                                    \
     defined(CPPHTTPLIB_WOLFSSL_SUPPORT))
#if TARGET_OS_OSX
#ifndef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
#define CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
#endif
#endif
#endif

#if defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) &&                      \
    defined(__APPLE__) && !TARGET_OS_OSX
#error                                                                         \
    "CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN is only supported on macOS. On iOS/tvOS/watchOS, supply a CA bundle via set_ca_cert_path()."
#endif

// On Windows, enable Schannel certificate verification by default
// unless the user explicitly opts out.
#if defined(_WIN32) &&                                                         \
    !defined(CPPHTTPLIB_DISABLE_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE)
#define CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
#endif

#if defined(CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO) ||                        \
    defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN)
#if TARGET_OS_MAC && defined(__clang__)
#include <CFNetwork/CFHost.h>
#include <CoreFoundation/CoreFoundation.h>
#endif
#endif

#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#ifdef _WIN32
#include <wincrypt.h>

// these are defined in wincrypt.h and it breaks compilation if BoringSSL is
// used
#undef X509_NAME
#undef X509_CERT_PAIR
#undef X509_EXTENSIONS
#undef PKCS7_SIGNER_INFO

#ifdef _MSC_VER
#pragma comment(lib, "crypt32.lib")
#endif
#endif // _WIN32

#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
#if TARGET_OS_OSX
#include <Security/Security.h>
#endif
#endif

#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>

#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK)
#include <openssl/applink.c>
#endif

#include <iostream>
#include <sstream>

#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER)
#if OPENSSL_VERSION_NUMBER < 0x1010107f
#error Please use OpenSSL or a current version of BoringSSL
#endif
#define SSL_get1_peer_certificate SSL_get_peer_certificate
#elif OPENSSL_VERSION_NUMBER < 0x30000000L
#error Sorry, OpenSSL versions prior to 3.0.0 are not supported
#endif

#endif // CPPHTTPLIB_OPENSSL_SUPPORT

#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/entropy.h>
#include <mbedtls/error.h>
#include <mbedtls/md5.h>
#include <mbedtls/net_sockets.h>
#include <mbedtls/oid.h>
#include <mbedtls/pk.h>
#include <mbedtls/sha1.h>
#include <mbedtls/sha256.h>
#include <mbedtls/sha512.h>
#include <mbedtls/ssl.h>
#include <mbedtls/x509_crt.h>
#ifdef _WIN32
#include <wincrypt.h>
#ifdef _MSC_VER
#pragma comment(lib, "crypt32.lib")
#endif
#endif // _WIN32
#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
#if TARGET_OS_OSX
#include <Security/Security.h>
#endif
#endif

// Mbed TLS 3.x API compatibility
#if MBEDTLS_VERSION_MAJOR >= 3
#define CPPHTTPLIB_MBEDTLS_V3
#endif

#endif // CPPHTTPLIB_MBEDTLS_SUPPORT

#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT
#include <wolfssl/options.h>

#include <wolfssl/openssl/x509v3.h>

// Fallback definitions for older wolfSSL versions (e.g., 5.6.6)
#ifndef WOLFSSL_GEN_EMAIL
#define WOLFSSL_GEN_EMAIL 1
#endif
#ifndef WOLFSSL_GEN_DNS
#define WOLFSSL_GEN_DNS 2
#endif
#ifndef WOLFSSL_GEN_URI
#define WOLFSSL_GEN_URI 6
#endif
#ifndef WOLFSSL_GEN_IPADD
#define WOLFSSL_GEN_IPADD 7
#endif

#include <wolfssl/ssl.h>
#include <wolfssl/wolfcrypt/hash.h>
#include <wolfssl/wolfcrypt/md5.h>
#include <wolfssl/wolfcrypt/sha256.h>
#include <wolfssl/wolfcrypt/sha512.h>
#ifdef _WIN32
#include <wincrypt.h>
#ifdef _MSC_VER
#pragma comment(lib, "crypt32.lib")
#endif
#endif // _WIN32
#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN
#if TARGET_OS_OSX
#include <Security/Security.h>
#endif
#endif
#endif // CPPHTTPLIB_WOLFSSL_SUPPORT

// Define CPPHTTPLIB_SSL_ENABLED if any SSL backend is available
#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) ||                                     \
    defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT)
#define CPPHTTPLIB_SSL_ENABLED
#endif

#ifdef CPPHTTPLIB_ZLIB_SUPPORT
#include <zlib.h>
#endif

#ifdef CPPHTTPLIB_BROTLI_SUPPORT
#include <brotli/decode.h>
#include <brotli/encode.h>
#endif

#ifdef CPPHTTPLIB_ZSTD_SUPPORT
#include <zstd.h>
#endif

/*
 * Declaration
 */
namespace httplib {

namespace ws {
class WebSocket;
} // namespace ws

namespace detail {

/*
 * Backport std::make_unique from C++14.
 *
 * NOTE: This code came up with the following stackoverflow post:
 * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique
 *
 */

template <class T, class... Args>
typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
make_unique(Args &&...args) {
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

template <class T>
typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type
make_unique(std::size_t n) {
  typedef typename std::remove_extent<T>::type RT;
  return std::unique_ptr<T>(new RT[n]);
}

// Locale-independent ASCII character classification. The <cctype>
// counterparts (std::isalnum, std::isdigit, ...) consult the global C locale,
// so e.g. std::isalnum(0xC5) can return true once an embedder calls
// setlocale(). HTTP grammars are defined over ASCII, so raw bytes must be
// classified without regard to the locale.
inline bool is_ascii_digit(char c) { return '0' <= c && c <= '9'; }

inline bool is_ascii_alpha(char c) {
  return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
}

inline bool is_ascii_alnum(char c) {
  return is_ascii_digit(c) || is_ascii_alpha(c);
}

namespace case_ignore {

inline unsigned char to_lower(int c) {
  const static unsigned char table[256] = {
      0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,
      15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,
      30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,
      45,  46,  47,  48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,
      60,  61,  62,  63,  64,  97,  98,  99,  100, 101, 102, 103, 104, 105, 106,
      107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
      122, 91,  92,  93,  94,  95,  96,  97,  98,  99,  100, 101, 102, 103, 104,
      105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
      120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
      135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
      150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
      165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
      180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 224, 225, 226,
      227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241,
      242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, 224,
      225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
      240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
      255,
  };
  return table[(unsigned char)(char)c];
}

inline std::string to_lower(const std::string &s) {
  std::string result = s;
  std::transform(
      result.begin(), result.end(), result.begin(),
      [](unsigned char c) { return static_cast<char>(to_lower(c)); });
  return result;
}

inline bool equal(const std::string &a, const std::string &b) {
  return a.size() == b.size() &&
         std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) {
           return to_lower(ca) == to_lower(cb);
         });
}

struct equal_to {
  bool operator()(const std::string &a, const std::string &b) const {
    return equal(a, b);
  }
};

struct hash {
  size_t operator()(const std::string &key) const {
    return hash_core(key.data(), key.size(), 0);
  }

  size_t hash_core(const char *s, size_t l, size_t h) const {
    return (l == 0) ? h
                    : hash_core(s + 1, l - 1,
                                // Unsets the 6 high bits of h, therefore no
                                // overflow happens
                                (((std::numeric_limits<size_t>::max)() >> 6) &
                                 h * 33) ^
                                    static_cast<unsigned char>(to_lower(*s)));
  }
};

template <typename T>
using unordered_set = std::unordered_set<T, detail::case_ignore::hash,
                                         detail::case_ignore::equal_to>;

} // namespace case_ignore

// This is based on
// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189".

struct scope_exit {
  explicit scope_exit(std::function<void(void)> &&f)
      : exit_function(std::move(f)), execute_on_destruction{true} {}

  scope_exit(scope_exit &&rhs) noexcept
      : exit_function(std::move(rhs.exit_function)),
        execute_on_destruction{rhs.execute_on_destruction} {
    rhs.release();
  }

  ~scope_exit() {
    if (execute_on_destruction) { this->exit_function(); }
  }

  void release() { this->execute_on_destruction = false; }

private:
  scope_exit(const scope_exit &) = delete;
  void operator=(const scope_exit &) = delete;
  scope_exit &operator=(scope_exit &&) = delete;

  std::function<void(void)> exit_function;
  bool execute_on_destruction;
};

// Simple from_chars implementation for integer and double types (C++17
// substitute)
template <typename T> struct from_chars_result {
  const char *ptr;
  std::errc ec;
};

template <typename T>
inline from_chars_result<T> from_chars(const char *first, const char *last,
                                       T &value, int base = 10) {
  value = 0;
  const char *p = first;
  bool negative = false;

  if (p != last && *p == '-') {
    negative = true;
    ++p;
  }
  if (p == last) { return {first, std::errc::invalid_argument}; }

  T result = 0;
  for (; p != last; ++p) {
    char c = *p;
    int digit = -1;
    if (is_ascii_digit(c)) {
      digit = c - '0';
    } else if ('a' <= c && c <= 'z') {
      digit = c - 'a' + 10;
    } else if ('A' <= c && c <= 'Z') {
      digit = c - 'A' + 10;
    } else {
      break;
    }

    if (digit < 0 || digit >= base) { break; }
    if (result > ((std::numeric_limits<T>::max)() - digit) / base) {
      return {p, std::errc::result_out_of_range};
    }
    result = result * base + digit;
  }

  if (p == first || (negative && p == first + 1)) {
    return {first, std::errc::invalid_argument};
  }

  value = negative ? -result : result;
  return {p, std::errc{}};
}

// from_chars for double (hand-written, locale-independent)
//
// The only double consumed by this library is the HTTP quality value, whose
// grammar is (RFC 9110 12.4.2):
//   qvalue = ( "0" [ "." 0*3DIGIT ] ) / ( "1" [ "." 0*3("0") ] )
// i.e. a non-negative decimal with no sign, exponent, "inf"/"nan", or wide
// magnitude. So this parser recognizes exactly  1*DIGIT [ "." *DIGIT ]  with
// '.' always the decimal separator (std::strtod would instead read it from the
// global C locale, mis-parsing q-values once an embedder calls
// setlocale(LC_ALL, "") into a comma-decimal locale). The caller range-checks
// the result to [0, 1], so inputs outside that range need not be distinguished
// here. Allocation-free, single pass, and free of the overflow/rounding edge
// cases that exponent and wide-range handling would introduce.
inline from_chars_result<double> from_chars(const char *first, const char *last,
                                            double &value) {
  value = 0.0;
  const char *p = first;

  // Each 1eN is exactly representable, so a single final division by the
  // matching entry yields a correctly-rounded result.
  static const double powers_of_ten[] = {
      1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,  1e8, 1e9,
      1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18};
  const int max_frac_digits =
      static_cast<int>(sizeof(powers_of_ten) / sizeof(powers_of_ten[0])) - 1;

  // Accumulate digits into a 64-bit integer and remember how many were
  // fractional. Two independent caps keep this bounded and safe:
  //   * accumulation saturates before mantissa could overflow uint64_t, and
  //   * frac_digits is capped at max_frac_digits so it is always a valid index
  //     into powers_of_ten (without this an input like "0.000...0" would never
  //     grow mantissa, so the saturation cap alone would not bound it).
  // Both caps only drop digits far beyond the precision a q-value needs; any
  // value they would change is well outside [0, 1] and rejected by the caller.
  uint64_t mantissa = 0;
  int frac_digits = 0;
  bool seen_digit = false;

  const uint64_t limit = ((std::numeric_limits<uint64_t>::max)() - 9) / 10;
  auto accumulate = [&](char c) {
    if (mantissa <= limit) {
      mantissa = mantissa * 10 + static_cast<uint64_t>(c - '0');
      return true;
    }
    return false;
  };

  for (; p != last && is_ascii_digit(*p); ++p) {
    seen_digit = true;
    accumulate(*p);
  }

  if (p != last && *p == '.') {
    ++p;
    for (; p != last && is_ascii_digit(*p); ++p) {
      seen_digit = true;
      if (frac_digits < max_frac_digits && accumulate(*p)) { ++frac_digits; }
    }
  }

  if (!seen_digit) { return {first, std::errc::invalid_argument}; }

  value = static_cast<double>(mantissa) / powers_of_ten[frac_digits];
  return {p, std::errc{}};
}

inline bool parse_port(const char *s, size_t len, int &port) {
  int val = 0;
  auto r = from_chars(s, s + len, val);
  if (r.ec != std::errc{} || val < 1 || val > 65535) { return false; }
  port = val;
  return true;
}

inline bool parse_port(const std::string &s, int &port) {
  return parse_port(s.data(), s.size(), port);
}

struct UrlComponents {
  std::string scheme;
  std::string host;
  std::string port;
  std::string path;
  std::string query;
};

inline bool parse_url(const std::string &url, UrlComponents &uc) {
  uc = {};
  size_t pos = 0;

  auto sep = url.find("://");
  if (sep != std::string::npos) {
    uc.scheme = url.substr(0, sep);

    // Scheme must be [a-z]+ only
    if (uc.scheme.empty()) { return false; }
    for (auto c : uc.scheme) {
      if (c < 'a' || c > 'z') { return false; }
    }

    pos = sep + 3;
  } else if (url.compare(0, 2, "//") == 0) {
    pos = 2;
  }

  auto has_authority_prefix = pos > 0;
  auto has_authority = has_authority_prefix || (!url.empty() && url[0] != '/' &&
                                                url[0] != '?' && url[0] != '#');
  if (has_authority) {
    if (pos < url.size() && url[pos] == '[') {
      auto close = url.find(']', pos);
      if (close == std::string::npos) { return false; }
      uc.host = url.substr(pos + 1, close - pos - 1);

      // IPv6 host must be [a-fA-F0-9:]+ only
      if (uc.host.empty()) { return false; }
      for (auto c : uc.host) {
        if (!(is_ascii_digit(c) || (c >= 'a' && c <= 'f') ||
              (c >= 'A' && c <= 'F') || c == ':')) {
          return false;
        }
      }

      pos = close + 1;
    } else {
      auto end = url.find_first_of(":/?#", pos);
      if (end == std::string::npos) { end = url.size(); }
      uc.host = url.substr(pos, end - pos);
      pos = end;
    }

    if (pos < url.size() && url[pos] == ':') {
      ++pos;
      auto end = url.find_first_of("/?#", pos);
      if (end == std::string::npos) { end = url.size(); }
      uc.port = url.substr(pos, end - pos);
      pos = end;
    }

    // Without :// or //, the entire input must be consumed as host[:port].
    // If there is leftover (path, query, etc.), this is not a valid
    // host[:port] string — clear and reparse as a plain path.
    if (!has_authority_prefix && pos < url.size()) {
      uc.host.clear();
      uc.port.clear();
      pos = 0;
    }
  }

  if (pos < url.size() && url[pos] != '?' && url[pos] != '#') {
    auto end = url.find_first_of("?#", pos);
    if (end == std::string::npos) { end = url.size(); }
    uc.path = url.substr(pos, end - pos);
    pos = end;
  }

  if (pos < url.size() && url[pos] == '?') {
    auto end = url.find('#', pos);
    if (end == std::string::npos) { end = url.size(); }
    uc.query = url.substr(pos, end - pos);
  }

  return true;
}

} // namespace detail

enum class SSLVerifierResponse {
  // no decision has been made, use the built-in certificate verifier
  NoDecisionMade,
  // connection certificate is verified and accepted
  CertificateAccepted,
  // connection certificate was processed but is rejected
  CertificateRejected
};

// System CA loading policy for SSL clients. Auto (the default) loads system
// CA certs only when no custom CA is configured; enable_system_ca() switches
// to an explicit policy.
enum class SystemCAMode { Auto, Enabled, Disabled };

enum StatusCode {
  // Information responses
  Continue_100 = 100,
  SwitchingProtocol_101 = 101,
  Processing_102 = 102,
  EarlyHints_103 = 103,

  // Successful responses
  OK_200 = 200,
  Created_201 = 201,
  Accepted_202 = 202,
  NonAuthoritativeInformation_203 = 203,
  NoContent_204 = 204,
  ResetContent_205 = 205,
  PartialContent_206 = 206,
  MultiStatus_207 = 207,
  AlreadyReported_208 = 208,
  IMUsed_226 = 226,

  // Redirection messages
  MultipleChoices_300 = 300,
  MovedPermanently_301 = 301,
  Found_302 = 302,
  SeeOther_303 = 303,
  NotModified_304 = 304,
  UseProxy_305 = 305,
  unused_306 = 306,
  TemporaryRedirect_307 = 307,
  PermanentRedirect_308 = 308,

  // Client error responses
  BadRequest_400 = 400,
  Unauthorized_401 = 401,
  PaymentRequired_402 = 402,
  Forbidden_403 = 403,
  NotFound_404 = 404,
  MethodNotAllowed_405 = 405,
  NotAcceptable_406 = 406,
  ProxyAuthenticationRequired_407 = 407,
  RequestTimeout_408 = 408,
  Conflict_409 = 409,
  Gone_410 = 410,
  LengthRequired_411 = 411,
  PreconditionFailed_412 = 412,
  PayloadTooLarge_413 = 413,
  UriTooLong_414 = 414,
  UnsupportedMediaType_415 = 415,
  RangeNotSatisfiable_416 = 416,
  ExpectationFailed_417 = 417,
  ImATeapot_418 = 418,
  MisdirectedRequest_421 = 421,
  UnprocessableContent_422 = 422,
  Locked_423 = 423,
  FailedDependency_424 = 424,
  TooEarly_425 = 425,
  UpgradeRequired_426 = 426,
  PreconditionRequired_428 = 428,
  TooManyRequests_429 = 429,
  RequestHeaderFieldsTooLarge_431 = 431,
  UnavailableForLegalReasons_451 = 451,

  // Server error responses
  InternalServerError_500 = 500,
  NotImplemented_501 = 501,
  BadGateway_502 = 502,
  ServiceUnavailable_503 = 503,
  GatewayTimeout_504 = 504,
  HttpVersionNotSupported_505 = 505,
  VariantAlsoNegotiates_506 = 506,
  InsufficientStorage_507 = 507,
  LoopDetected_508 = 508,
  NotExtended_510 = 510,
  NetworkAuthenticationRequired_511 = 511,
};

using Headers =
    std::unordered_multimap<std::string, std::string, detail::case_ignore::hash,
                            detail::case_ignore::equal_to>;

using Params = std::multimap<std::string, std::string>;
using Match = std::smatch;

using DownloadProgress = std::function<bool(size_t current, size_t total)>;
using UploadProgress = std::function<bool(size_t current, size_t total)>;

/*
 * detail: type-erased storage used by UserData.
 * ABI-stable regardless of C++ standard — always uses this custom
 * implementation instead of std::any.
 */
namespace detail {

using any_type_id = const void *;

template <typename T> any_type_id any_typeid() noexcept {
  static const char id = 0;
  return &id;
}

struct any_storage {
  virtual ~any_storage() = default;
  virtual std::unique_ptr<any_storage> clone() const = 0;
  virtual any_type_id type_id() const noexcept = 0;
};

template <typename T> struct any_value final : any_storage {
  T value;
  template <typename U> explicit any_value(U &&v) : value(std::forward<U>(v)) {}
  std::unique_ptr<any_storage> clone() const override {
    return std::unique_ptr<any_storage>(new any_value<T>(value));
  }
  any_type_id type_id() const noexcept override { return any_typeid<T>(); }
};

} // namespace detail

class UserData {
public:
  UserData() = default;
  UserData(UserData &&) noexcept = default;
  UserData &operator=(UserData &&) noexcept = default;

  UserData(const UserData &o) {
    for (const auto &e : o.entries_) {
      if (e.second) { entries_[e.first] = e.second->clone(); }
    }
  }

  UserData &operator=(const UserData &o) {
    if (this != &o) {
      entries_.clear();
      for (const auto &e : o.entries_) {
        if (e.second) { entries_[e.first] = e.second->clone(); }
      }
    }
    return *this;
  }

  template <typename T> void set(const std::string &key, T &&value) {
    using D = typename std::decay<T>::type;
    entries_[key].reset(new detail::any_value<D>(std::forward<T>(value)));
  }

  template <typename T> T *get(const std::string &key) noexcept {
    auto it = entries_.find(key);
    if (it == entries_.end() || !it->second) { return nullptr; }
    if (it->second->type_id() != detail::any_typeid<T>()) { return nullptr; }
    return &static_cast<detail::any_value<T> *>(it->second.get())->value;
  }

  template <typename T> const T *get(const std::string &key) const noexcept {
    auto it = entries_.find(key);
    if (it == entries_.end() || !it->second) { return nullptr; }
    if (it->second->type_id() != detail::any_typeid<T>()) { return nullptr; }
    return &static_cast<const detail::any_value<T> *>(it->second.get())->value;
  }

  bool has(const std::string &key) const noexcept {
    return entries_.find(key) != entries_.end();
  }

  void erase(const std::string &key) { entries_.erase(key); }

  void clear() noexcept { entries_.clear(); }

private:
  std::unordered_map<std::string, std::unique_ptr<detail::any_storage>>
      entries_;
};

struct Response;
using ResponseHandler = std::function<bool(const Response &response)>;

struct FormData {
  std::string name;
  std::string content;
  std::string filename;
  std::string content_type;
  Headers headers;
};

struct FormField {
  std::string name;
  std::string content;
  Headers headers;
};
using FormFields = std::multimap<std::string, FormField>;

using FormFiles = std::multimap<std::string, FormData>;

struct MultipartFormData {
  FormFields fields; // Text fields from multipart
  FormFiles files;   // Files from multipart

  // Text field access
  std::string get_field(const std::string &key, size_t id = 0) const;
  std::vector<std::string> get_fields(const std::string &key) const;
  bool has_field(const std::string &key) const;
  size_t get_field_count(const std::string &key) const;

  // File access
  FormData get_file(const std::string &key, size_t id = 0) const;
  std::vector<FormData> get_files(const std::string &key) const;
  bool has_file(const std::string &key) const;
  size_t get_file_count(const std::string &key) const;
};

struct UploadFormData {
  std::string name;
  std::string content;
  std::string filename;
  std::string content_type;
};
using UploadFormDataItems = std::vector<UploadFormData>;

class DataSink {
public:
  DataSink() : os(&sb_), sb_(*this) {}

  DataSink(const DataSink &) = delete;
  DataSink &operator=(const DataSink &) = delete;
  DataSink(DataSink &&) = delete;
  DataSink &operator=(DataSink &&) = delete;

  std::function<bool(const char *data, size_t data_len)> write;
  std::function<bool()> is_writable;
  std::function<void()> done;
  std::function<void(const Headers &trailer)> done_with_trailer;
  std::ostream os;

private:
  class data_sink_streambuf final : public std::streambuf {
  public:
    explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {}

  protected:
    std::streamsize xsputn(const char *s, std::streamsize n) override {
      if (sink_.write(s, static_cast<size_t>(n))) { return n; }
      return 0;
    }

  private:
    DataSink &sink_;
  };

  data_sink_streambuf sb_;
};

using ContentProvider =
    std::function<bool(size_t offset, size_t length, DataSink &sink)>;

using ContentProviderWithoutLength =
    std::function<bool(size_t offset, DataSink &sink)>;

using ContentProviderResourceReleaser = std::function<void(bool success)>;

struct FormDataProvider {
  std::string name;
  ContentProviderWithoutLength provider;
  std::string filename;
  std::string content_type;
};
using FormDataProviderItems = std::vector<FormDataProvider>;

inline FormDataProvider
make_file_provider(const std::string &name, const std::string &filepath,
                   const std::string &filename = std::string(),
                   const std::string &content_type = std::string()) {
  FormDataProvider fdp;
  fdp.name = name;
  fdp.filename = filename.empty() ? filepath : filename;
  fdp.content_type = content_type;
  fdp.provider = [filepath](size_t offset, DataSink &sink) -> bool {
    std::ifstream f(filepath, std::ios::binary);
    if (!f) { return false; }
    if (offset > 0) {
      f.seekg(static_cast<std::streamoff>(offset));
      if (!f.good()) {
        sink.done();
        return true;
      }
    }
    char buf[8192];
    f.read(buf, sizeof(buf));
    auto n = static_cast<size_t>(f.gcount());
    if (n > 0) { return sink.write(buf, n); }
    sink.done(); // EOF
    return true;
  };
  return fdp;
}

inline std::pair<size_t, ContentProvider>
make_file_body(const std::string &filepath) {
  size_t size = 0;
  {
    std::ifstream f(filepath, std::ios::binary | std::ios::ate);
    if (!f) { return {0, ContentProvider{}}; }
    size = static_cast<size_t>(f.tellg());
  }

  ContentProvider provider = [filepath](size_t offset, size_t length,
                                        DataSink &sink) -> bool {
    std::ifstream f(filepath, std::ios::binary);
    if (!f) { return false; }
    f.seekg(static_cast<std::streamoff>(offset));
    if (!f.good()) { return false; }
    char buf[8192];
    while (length > 0) {
      auto to_read = (std::min)(sizeof(buf), length);
      f.read(buf, static_cast<std::streamsize>(to_read));
      auto n = static_cast<size_t>(f.gcount());
      if (n == 0) { break; }
      if (!sink.write(buf, n)) { return false; }
      length -= n;
    }
    return true;
  };
  return {size, std::move(provider)};
}

using ContentReceiverWithProgress = std::function<bool(
    const char *data, size_t data_length, size_t offset, size_t total_length)>;

using ContentReceiver =
    std::function<bool(const char *data, size_t data_length)>;

using FormDataHeader = std::function<bool(const FormData &file)>;

class ContentReader {
public:
  using Reader = std::function<bool(ContentReceiver receiver)>;
  using FormDataReader =
      std::function<bool(FormDataHeader header, ContentReceiver receiver)>;

  ContentReader(Reader reader, FormDataReader multipart_reader)
      : reader_(std::move(reader)),
        formdata_reader_(std::move(multipart_reader)) {}

  bool operator()(FormDataHeader header, ContentReceiver receiver) const {
    return formdata_reader_(std::move(header), std::move(receiver));
  }

  bool operator()(ContentReceiver receiver) const {
    return reader_(std::move(receiver));
  }

  Reader reader_;
  FormDataReader formdata_reader_;
};

using Range = std::pair<ssize_t, ssize_t>;
using Ranges = std::vector<Range>;

#ifdef CPPHTTPLIB_SSL_ENABLED
// TLS abstraction layer - public type definitions and API
namespace tls {

// Opaque handles (defined as void* for abstraction)
using ctx_t = void *;
using session_t = void *;
using const_session_t = const void *; // For read-only session access
using cert_t = void *;
using ca_store_t = void *;

// TLS versions
enum class Version {
  TLS1_2 = 0x0303,
  TLS1_3 = 0x0304,
};

// Subject Alternative Names (SAN) entry types
enum class SanType { DNS, IP, EMAIL, URI, OTHER };

// SAN entry structure
struct SanEntry {
  SanType type;
  std::string value;
};

// Verification context for certificate verification callback
struct VerifyContext {
  session_t session;        // TLS session handle
  cert_t cert;              // Current certificate being verified
  int depth;                // Certificate chain depth (0 = leaf)
  bool preverify_ok;        // OpenSSL/Mbed TLS pre-verification result
  long error_code;          // Backend-specific error code (0 = no error)
  const char *error_string; // Human-readable error description

  // Certificate introspection methods
  std::string subject_cn() const;
  std::string issuer_name() const;
  bool check_hostname(const char *hostname) const;
  std::vector<SanEntry> sans() const;
  bool validity(time_t &not_before, time_t &not_after) const;
  std::string serial() const;
};

using VerifyCallback = std::function<bool(const VerifyContext &ctx)>;

// TlsError codes for TLS operations (backend-independent)
enum class ErrorCode : int {
  Success = 0,
  WantRead,         // Non-blocking: need to wait for read
  WantWrite,        // Non-blocking: need to wait for write
  PeerClosed,       // Peer closed the connection
  Fatal,            // Unrecoverable error
  SyscallError,     // System call error (check sys_errno)
  CertVerifyFailed, // Certificate verification failed
  HostnameMismatch, // Hostname verification failed
};

// TLS error information
struct TlsError {
  ErrorCode code = ErrorCode::Fatal;
  uint64_t backend_code = 0; // OpenSSL: ERR_get_error(), mbedTLS: return value
  int sys_errno = 0;         // errno when SyscallError

  // Convert verification error code to human-readable string
  static std::string verify_error_to_string(long error_code);
};

// RAII wrapper for peer certificate
class PeerCert {
public:
  PeerCert();
  PeerCert(PeerCert &&other) noexcept;
  PeerCert &operator=(PeerCert &&other) noexcept;
  ~PeerCert();

  PeerCert(const PeerCert &) = delete;
  PeerCert &operator=(const PeerCert &) = delete;

  explicit operator bool() const;
  std::string subject_cn() const;
  std::string issuer_name() const;
  bool check_hostname(const char *hostname) const;
  std::vector<SanEntry> sans() const;
  bool validity(time_t &not_before, time_t &not_after) const;
  std::string serial() const;

private:
  explicit PeerCert(cert_t cert);
  cert_t cert_ = nullptr;
  friend PeerCert get_peer_cert_from_session(const_session_t session);
};

// Callback for TLS context setup (used by SSLServer constructor)
using ContextSetupCallback = std::function<bool(ctx_t ctx)>;

} // namespace tls
#endif

struct Request {
  std::string method;
  std::string path;
  std::string matched_route;
  Params params;
  Headers headers;
  Headers trailers;
  std::string body;

  std::string remote_addr;
  int remote_port = -1;
  std::string local_addr;
  int local_port = -1;

  // for server
  std::string version;
  std::string target;
  MultipartFormData form;
  Ranges ranges;
  Match matches;
  std::unordered_map<std::string, std::string> path_params;
  std::function<bool()> is_connection_closed = []() { return true; };

  // for client
  std::vector<std::string> accept_content_types;
  ResponseHandler response_handler;
  ContentReceiverWithProgress content_receiver;
  DownloadProgress download_progress;
  UploadProgress upload_progress;

  bool has_header(const std::string &key) const;
  std::string get_header_value(const std::string &key, const char *def = "",
                               size_t id = 0) const;
  size_t get_header_value_u64(const std::string &key, size_t def = 0,
                              size_t id = 0) const;
  size_t get_header_value_count(const std::string &key) const;
  void set_header(const std::string &key, const std::string &val);

  bool has_trailer(const std::string &key) const;
  std::string get_trailer_value(const std::string &key, size_t id = 0) const;
  size_t get_trailer_value_count(const std::string &key) const;

  bool has_param(const std::string &key) const;
  std::string get_param_value(const std::string &key, size_t id = 0) const;
  std::vector<std::string> get_param_values(const std::string &key) const;
  size_t get_param_value_count(const std::string &key) const;

  bool is_multipart_form_data() const;

  // private members...
  bool body_consumed_ = false;
  size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT;
  size_t content_length_ = 0;
  ContentProvider content_provider_;
  bool is_chunked_content_provider_ = false;
  size_t authorization_count_ = 0;
  std::chrono::time_point<std::chrono::steady_clock> start_time_ =
      (std::chrono::steady_clock::time_point::min)();

#ifdef CPPHTTPLIB_SSL_ENABLED
  tls::const_session_t ssl = nullptr;
  tls::PeerCert peer_cert() const;
  std::string sni() const;
#endif
};

struct Response {
  std::string version;
  int status = -1;
  std::string reason;
  Headers headers;
  Headers trailers;
  std::string body;
  std::string location; // Redirect location

  // User-defined context — set by pre-routing/pre-request handlers and read
  // by route handlers to pass arbitrary data (e.g. decoded auth tokens).
  UserData user_data;

  bool has_header(const std::string &key) const;
  std::string get_header_value(const std::string &key, const char *def = "",
                               size_t id = 0) const;
  size_t get_header_value_u64(const std::string &key, size_t def = 0,
                              size_t id = 0) const;
  size_t get_header_value_count(const std::string &key) const;
  void set_header(const std::string &key, const std::string &val);

  bool has_trailer(const std::string &key) const;
  std::string get_trailer_value(const std::string &key, size_t id = 0) const;
  size_t get_trailer_value_count(const std::string &key) const;

  void set_redirect(const std::string &url, int status = StatusCode::Found_302);
  void set_content(const char *s, size_t n, const std::string &content_type);
  void set_content(const std::string &s, const std::string &content_type);
  void set_content(std::string &&s, const std::string &content_type);

  void set_content_provider(
      size_t length, const std::string &content_type, ContentProvider provider,
      ContentProviderResourceReleaser resource_releaser = nullptr);

  void set_content_provider(
      const std::string &content_type, ContentProviderWithoutLength provider,
      ContentProviderResourceReleaser resource_releaser = nullptr);

  void set_chunked_content_provider(
      const std::string &content_type, ContentProviderWithoutLength provider,
      ContentProviderResourceReleaser resource_releaser = nullptr);

  void set_file_content(const std::string &path,
                        const std::string &content_type);
  void set_file_content(const std::string &path);

  Response() = default;
  Response(const Response &) = default;
  Response &operator=(const Response &) = default;
  Response(Response &&) = default;
  Response &operator=(Response &&) = default;
  ~Response() {
    if (content_provider_resource_releaser_) {
      content_provider_resource_releaser_(content_provider_success_);
    }
  }

  // private members...
  size_t content_length_ = 0;
  ContentProvider content_provider_;
  ContentProviderResourceReleaser content_provider_resource_releaser_;
  bool is_chunked_content_provider_ = false;
  bool content_provider_success_ = false;
  std::string file_content_path_;
  std::string file_content_content_type_;
};

enum class Error {
  Success = 0,
  Unknown,
  Connection,
  BindIPAddress,
  Read,
  Write,
  ExceedRedirectCount,
  Canceled,
  SSLConnection,
  SSLLoadingCerts,
  SSLServerVerification,
  SSLServerHostnameVerification,
  UnsupportedMultipartBoundaryChars,
  Compression,
  ConnectionTimeout,
  ProxyConnection,
  ConnectionClosed,
  Timeout,
  ResourceExhaustion,
  TooManyFormDataFiles,
  ExceedMaxPayloadSize,
  ExceedUriMaxLength,
  ExceedMaxSocketDescriptorCount,
  InvalidRequestLine,
  InvalidHTTPMethod,
  InvalidHTTPVersion,
  InvalidHeaders,
  MultipartParsing,
  OpenFile,
  Listen,
  GetSockName,
  UnsupportedAddressFamily,
  HTTPParsing,
  InvalidRangeHeader,

  // For internal use only
  SSLPeerCouldBeClosed_,
};

std::string to_string(Error error);

std::ostream &operator<<(std::ostream &os, const Error &obj);

class Stream {
public:
  virtual ~Stream() = default;

  virtual bool is_readable() const = 0;
  virtual bool wait_readable() const = 0;
  virtual bool wait_writable() const = 0;
  virtual bool is_peer_alive() const { return wait_writable(); }

  virtual ssize_t read(char *ptr, size_t size) = 0;
  virtual ssize_t write(const char *ptr, size_t size) = 0;
  virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0;
  virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0;
  virtual socket_t socket() const = 0;

  virtual time_t duration() const = 0;

  virtual void set_read_timeout(time_t sec, time_t usec = 0) {
    (void)sec;
    (void)usec;
  }

  ssize_t write(const char *ptr);
  ssize_t write(const std::string &s);

  Error get_error() const { return error_; }

protected:
  Error error_ = Error::Success;
};

class TaskQueue {
public:
  TaskQueue() = default;
  virtual ~TaskQueue() = default;

  virtual bool enqueue(std::function<void()> fn) = 0;
  virtual void shutdown() = 0;

  virtual void on_idle() {}
};

class ThreadPool final : public TaskQueue {
public:
  explicit ThreadPool(
      size_t n, size_t max_n = 0, size_t mqr = 0,
      time_t idle_timeout_sec = CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT);
  ThreadPool(const ThreadPool &) = delete;
  ~ThreadPool() override = default;

  bool enqueue(std::function<void()> fn) override;
  void shutdown() override;

private:
  void worker(bool is_dynamic);
  void move_to_finished(std::thread::id id);
  void cleanup_finished_threads();

  size_t base_thread_count_;
  size_t max_thread_count_;
  size_t max_queued_requests_;
  time_t idle_timeout_sec_;
  size_t idle_thread_count_;

  bool shutdown_;

  std::list<std::function<void()>> jobs_;
  std::vector<std::thread> threads_;       // base threads
  std::list<std::thread> dynamic_threads_; // dynamic threads
  std::vector<std::thread>
      finished_threads_; // exited dynamic threads awaiting join

  std::condition_variable cond_;
  std::mutex mutex_;
};

using Logger = std::function<void(const Request &, const Response &)>;

// Forward declaration for Error type
enum class Error;
using ErrorLogger = std::function<void(const Error &, const Request *)>;

using SocketOptions = std::function<void(socket_t sock)>;

void default_socket_options(socket_t sock);

bool set_socket_opt(socket_t sock, int level, int optname, int optval);

const char *status_message(int status);

std::string to_string(Error error);

std::ostream &operator<<(std::ostream &os, const Error &obj);

std::string get_bearer_token_auth(const Request &req);

namespace detail {

class MatcherBase {
public:
  MatcherBase(std::string pattern) : pattern_(std::move(pattern)) {}
  virtual ~MatcherBase() = default;

  const std::string &pattern() const { return pattern_; }

  // Match request path and populate its matches and
  virtual bool match(Request &request) const = 0;

private:
  std::string pattern_;
};

/**
 * Captures parameters in request path and stores them in Request::path_params
 *
 * Capture name is a substring of a pattern from : to /.
 * The rest of the pattern is matched against the request path directly
 * Parameters are captured starting from the next character after
 * the end of the last matched static pattern fragment until the next /.
 *
 * Example pattern:
 * "/path/fragments/:capture/more/fragments/:second_capture"
 * Static fragments:
 * "/path/fragments/", "more/fragments/"
 *
 * Given the following request path:
 * "/path/fragments/:1/more/fragments/:2"
 * the resulting capture will be
 * {{"capture", "1"}, {"second_capture", "2"}}
 */
class PathParamsMatcher final : public MatcherBase {
public:
  PathParamsMatcher(const std::string &pattern);

  bool match(Request &request) const override;

private:
  // Treat segment separators as the end of path parameter capture
  // Does not need to handle query parameters as they are parsed before path
  // matching
  static constexpr char separator = '/';

  // Contains static path fragments to match against, excluding the '/' after
  // path params
  // Fragments are separated by path params
  std::vector<std::string> static_fragments_;
  // Stores the names of the path parameters to be used as keys in the
  // Request::path_params map
  std::vector<std::string> param_names_;
};

/**
 * Performs std::regex_match on request path
 * and stores the result in Request::matches
 *
 * Note that regex match is performed directly on the whole request.
 * This means that wildcard patterns may match multiple path segments with /:
 * "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end".
 */
class RegexMatcher final : public MatcherBase {
public:
  RegexMatcher(const std::string &pattern)
      : MatcherBase(pattern), regex_(pattern) {}

  bool match(Request &request) const override;

private:
  std::regex regex_;
};

int close_socket(socket_t sock) noexcept;

ssize_t write_headers(Stream &strm, const Headers &headers);

bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec,
                         time_t usec);

size_t get_multipart_content_length(const UploadFormDataItems &items,
                                    const std::string &boundary);

ContentProvider
make_multipart_content_provider(const UploadFormDataItems &items,
                                const std::string &boundary);

} // namespace detail

bool is_valid_multipart_boundary(const std::string &boundary);

// Serializer for multipart/form-data request bodies. The boundary is owned
// by the writer so that per-part framing and the final terminator always
// agree. Field names and filenames are escaped following the WHATWG HTML
// standard ('"' -> %22, CR -> %0D, LF -> %0A); CR and LF are also escaped
// in content types.
class MultipartFormDataWriter {
public:
  MultipartFormDataWriter();
  // precondition: is_valid_multipart_boundary(boundary)
  explicit MultipartFormDataWriter(std::string boundary);

  const std::string &boundary() const;
  std::string content_type() const;

  // In-memory items -> whole body (known length)
  std::string serialize(const UploadFormDataItems &items) const;
  size_t content_length(const UploadFormDataItems &items) const;

  // Per-part framing for streaming via a content provider
  std::string item_begin(const UploadFormData &item) const;
  static std::string item_end();
  std::string finish() const;

private:
  std::string boundary_;
};

class Server {
public:
  using Handler = std::function<void(const Request &, Response &)>;

  using ExceptionHandler =
      std::function<void(const Request &, Response &, std::exception_ptr ep)>;

  enum class HandlerResponse {
    Handled,
    Unhandled,
  };
  using HandlerWithResponse =
      std::function<HandlerResponse(const Request &, Response &)>;

  using HandlerWithContentReader = std::function<void(
      const Request &, Response &, const ContentReader &content_reader)>;

  using Expect100ContinueHandler =
      std::function<int(const Request &, Response &)>;

  using StartHandler = std::function<void()>;

  using WebSocketHandler =
      std::function<void(const Request &, ws::WebSocket &)>;
  using SubProtocolSelector =
      std::function<std::string(const std::vector<std::string> &protocols)>;

  Server();

  virtual ~Server();

  virtual bool is_valid() const;

  Server &Get(const std::string &pattern, Handler handler);
  Server &Post(const std::string &pattern, Handler handler);
  Server &Post(const std::string &pattern, HandlerWithContentReader handler);
  Server &Put(const std::string &pattern, Handler handler);
  Server &Put(const std::string &pattern, HandlerWithContentReader handler);
  Server &Patch(const std::string &pattern, Handler handler);
  Server &Patch(const std::string &pattern, HandlerWithContentReader handler);
  Server &Delete(const std::string &pattern, Handler handler);
  Server &Delete(const std::string &pattern, HandlerWithContentReader handler);
  Server &Options(const std::string &pattern, Handler handler);

  Server &WebSocket(const std::string &pattern, WebSocketHandler handler);
  Server &WebSocket(const std::string &pattern, WebSocketHandler handler,
                    SubProtocolSelector sub_protocol_selector);

  bool set_base_dir(const std::string &dir,
                    const std::string &mount_point = std::string());
  bool set_mount_point(const std::string &mount_point, const std::string &dir,
                       Headers headers = Headers());
  bool remove_mount_point(const std::string &mount_point);
  Server &set_file_extension_and_mimetype_mapping(const std::string &ext,
                                                  const std::string &mime);
  Server &set_default_file_mimetype(const std::string &mime);
  Server &set_file_request_handler(Handler handler);

  template <class ErrorHandlerFunc>
  Server &set_error_handler(ErrorHandlerFunc &&handler) {
    return set_error_handler_core(
        std::forward<ErrorHandlerFunc>(handler),
        std::is_convertible<ErrorHandlerFunc, HandlerWithResponse>{});
  }

  Server &set_exception_handler(ExceptionHandler handler);

  Server &set_pre_routing_handler(HandlerWithResponse handler);
  Server &set_post_routing_handler(Handler handler);

  Server &set_pre_request_handler(HandlerWithResponse handler);

  Server &set_expect_100_continue_handler(Expect100ContinueHandler handler);

  Server &set_start_handler(StartHandler handler);

  Server &set_logger(Logger logger);
  Server &set_pre_compression_logger(Logger logger);
  Server &set_error_logger(ErrorLogger error_logger);

  Server &set_address_family(int family);
  Server &set_tcp_nodelay(bool on);
  Server &set_ipv6_v6only(bool on);
  Server &set_socket_options(SocketOptions socket_options);

  Server &set_default_headers(Headers headers);
  Server &
  set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);

  Server &set_trusted_proxies(const std::vector<std::string> &proxies);

  Server &set_keep_alive_max_count(size_t count);
  Server &set_keep_alive_timeout(time_t sec);
  template <class Rep, class Period>
  Server &
  set_keep_alive_timeout(const std::chrono::duration<Rep, Period> &duration);

  Server &set_read_timeout(time_t sec, time_t usec = 0);
  template <class Rep, class Period>
  Server &set_read_timeout(const std::chrono::duration<Rep, Period> &duration);

  Server &set_write_timeout(time_t sec, time_t usec = 0);
  template <class Rep, class Period>
  Server &set_write_timeout(const std::chrono::duration<Rep, Period> &duration);

  Server &set_idle_interval(time_t sec, time_t usec = 0);
  template <class Rep, class Period>
  Server &set_idle_interval(const std::chrono::duration<Rep, Period> &duration);

  Server &set_payload_max_length(size_t length);

  Server &set_websocket_ping_interval(time_t sec);
  template <class Rep, class Period>
  Server &set_websocket_ping_interval(
      const std::chrono::duration<Rep, Period> &duration);

  Server &set_websocket_max_missed_pongs(int count);

  bool bind_to_port(const std::string &host, int port, int socket_flags = 0);
  int bind_to_any_port(const std::string &host, int socket_flags = 0);
  bool listen_after_bind();

  bool listen(const std::string &host, int port, int socket_flags = 0);

  bool is_running() const;
  void wait_until_ready() const;
  void stop() noexcept;
  void decommission();

  std::function<TaskQueue *(void)> new_task_queue;

protected:
  bool process_request(Stream &strm, const std::string &remote_addr,
                       int remote_port, const std::string &local_addr,
                       int local_port, bool close_connection,
                       bool &connection_closed,
                       const std::function<void(Request &)> &setup_request,
                       bool *websocket_upgraded = nullptr);

  std::atomic<socket_t> svr_sock_{INVALID_SOCKET};

  std::vector<std::string> trusted_proxies_;

  size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT;
  time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND;
  time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND;
  time_t read_timeout_usec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND;
  time_t write_timeout_sec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND;
  time_t write_timeout_usec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND;
  time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND;
  time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND;
  size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
  time_t websocket_ping_interval_sec_ =
      CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND;
  int websocket_max_missed_pongs_ = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS;

private:
  using Handlers =
      std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, Handler>>;
  using HandlersForContentReader =
      std::vector<std::pair<std::unique_ptr<detail::MatcherBase>,
                            HandlerWithContentReader>>;

  static std::unique_ptr<detail::MatcherBase>
  make_matcher(const std::string &pattern);

  template <typename H>
  Server &add_handler(
      std::vector<std::pair<std::unique_ptr<detail::MatcherBase>, H>> &handlers,
      const std::string &pattern, H handler) {
    handlers.emplace_back(make_matcher(pattern), std::move(handler));
    return *this;
  }

  Server &set_error_handler_core(HandlerWithResponse handler, std::true_type);
  Server &set_error_handler_core(Handler handler, std::false_type);

  socket_t create_server_socket(const std::string &host, int port,
                                int socket_flags,
                                SocketOptions socket_options) const;
  int bind_internal(const std::string &host, int port, int socket_flags);
  bool listen_internal();

  bool routing(Request &req, Response &res, Stream &strm);
  bool handle_file_request(Request &req, Response &res);
  bool check_if_not_modified(const Request &req, Response &res,
                             const std::string &etag, time_t mtime) const;
  bool check_if_range(Request &req, const std::string &etag,
                      time_t mtime) const;
  bool dispatch_request(Request &req, Response &res, const Handlers &handlers,
                        Stream &strm);
  bool dispatch_request_for_content_reader(
      Request &req, Response &res, ContentReader content_reader,
      const HandlersForContentReader &handlers) const;

  bool parse_request_line(const char *s, Request &req) const;
  void apply_ranges(const Request &req, Response &res,
                    std::string &content_type, std::string &boundary) const;
  bool write_response(Stream &strm, bool close_connection, Request &req,
                      Response &res);
  bool write_response_with_content(Stream &strm, bool close_connection,
                                   const Request &req, Response &res);
  bool write_response_core(Stream &strm, bool close_connection,
                           const Request &req, Response &res,
                           bool need_apply_ranges);
  bool write_content_with_provider(Stream &strm, const Request &req,
                                   Response &res, const std::string &boundary,
                                   const std::string &content_type);
  bool read_content(Stream &strm, Request &req, Response &res);
  bool read_content_with_content_receiver(Stream &strm, Request &req,
                                          Response &res,
                                          ContentReceiver receiver,
                                          FormDataHeader multipart_header,
                                          ContentReceiver multipart_receiver);
  bool read_content_core(Stream &strm, Request &req, Response &res,
                         ContentReceiver receiver,
                         FormDataHeader multipart_header,
                         ContentReceiver multipart_receiver) const;

  virtual bool process_and_close_socket(socket_t sock);

  void output_log(const Request &req, const Response &res) const;
  void output_pre_compression_log(const Request &req,
                                  const Response &res) const;
  void output_error_log(const Error &err, const Request *req) const;

  std::atomic<bool> is_running_{false};
  std::atomic<bool> is_decommissioned{false};

  struct MountPointEntry {
    std::string mount_point;
    std::string base_dir;
    std::string resolved_base_dir;
    Headers headers;
  };
  std::vector<MountPointEntry> base_dirs_;
  std::map<std::string, std::string> file_extension_and_mimetype_map_;
  std::string default_file_mimetype_ = "application/octet-stream";
  Handler file_request_handler_;

  Handlers get_handlers_;
  Handlers post_handlers_;
  HandlersForContentReader post_handlers_for_content_reader_;
  Handlers put_handlers_;
  HandlersForContentReader put_handlers_for_content_reader_;
  Handlers patch_handlers_;
  HandlersForContentReader patch_handlers_for_content_reader_;
  Handlers delete_handlers_;
  HandlersForContentReader delete_handlers_for_content_reader_;
  Handlers options_handlers_;

  struct WebSocketHandlerEntry {
    std::unique_ptr<detail::MatcherBase> matcher;
    WebSocketHandler handler;
    SubProtocolSelector sub_protocol_selector;
  };
  using WebSocketHandlers = std::vector<WebSocketHandlerEntry>;
  WebSocketHandlers websocket_handlers_;

  HandlerWithResponse error_handler_;
  ExceptionHandler exception_handler_;
  HandlerWithResponse pre_routing_handler_;
  Handler post_routing_handler_;
  HandlerWithResponse pre_request_handler_;
  Expect100ContinueHandler expect_100_continue_handler_;
  StartHandler start_handler_;

  mutable std::mutex logger_mutex_;
  Logger logger_;
  Logger pre_compression_logger_;
  ErrorLogger error_logger_;

  int address_family_ = AF_UNSPEC;
  bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
  bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
  SocketOptions socket_options_ = default_socket_options;

  Headers default_headers_;
  std::function<ssize_t(Stream &, Headers &)> header_writer_ =
      detail::write_headers;
};

class Result {
public:
  Result() = default;
  Result(std::unique_ptr<Response> &&res, Error err,
         Headers &&request_headers = Headers{})
      : res_(std::move(res)), err_(err),
        request_headers_(std::move(request_headers)) {}
  // Response
  operator bool() const { return res_ != nullptr; }
  bool operator==(std::nullptr_t) const { return res_ == nullptr; }
  bool operator!=(std::nullptr_t) const { return res_ != nullptr; }
  const Response &value() const { return *res_; }
  Response &value() { return *res_; }
  const Response &operator*() const { return *res_; }
  Response &operator*() { return *res_; }
  const Response *operator->() const { return res_.get(); }
  Response *operator->() { return res_.get(); }

  // Error
  Error error() const { return err_; }

  // Request Headers
  bool has_request_header(const std::string &key) const;
  std::string get_request_header_value(const std::string &key,
                                       const char *def = "",
                                       size_t id = 0) const;
  size_t get_request_header_value_u64(const std::string &key, size_t def = 0,
                                      size_t id = 0) const;
  size_t get_request_header_value_count(const std::string &key) const;

private:
  std::unique_ptr<Response> res_;
  Error err_ = Error::Unknown;
  Headers request_headers_;

#ifdef CPPHTTPLIB_SSL_ENABLED
public:
  Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
         int ssl_error)
      : res_(std::move(res)), err_(err),
        request_headers_(std::move(request_headers)), ssl_error_(ssl_error) {}
  Result(std::unique_ptr<Response> &&res, Error err, Headers &&request_headers,
         int ssl_error, uint64_t ssl_backend_error)
      : res_(std::move(res)), err_(err),
        request_headers_(std::move(request_headers)), ssl_error_(ssl_error),
        ssl_backend_error_(ssl_backend_error) {}

  int ssl_error() const { return ssl_error_; }
  uint64_t ssl_backend_error() const { return ssl_backend_error_; }

private:
  int ssl_error_ = 0;
  uint64_t ssl_backend_error_ = 0;
#endif
};

struct ClientConnection {
  socket_t sock = INVALID_SOCKET;

  bool is_open() const { return sock != INVALID_SOCKET; }

  ClientConnection() = default;

  ~ClientConnection();

  ClientConnection(const ClientConnection &) = delete;
  ClientConnection &operator=(const ClientConnection &) = delete;

  ClientConnection(ClientConnection &&other) noexcept
      : sock(other.sock)
#ifdef CPPHTTPLIB_SSL_ENABLED
        ,
        session(other.session)
#endif
  {
    other.sock = INVALID_SOCKET;
#ifdef CPPHTTPLIB_SSL_ENABLED
    other.session = nullptr;
#endif
  }

  ClientConnection &operator=(ClientConnection &&other) noexcept {
    if (this != &other) {
      sock = other.sock;
      other.sock = INVALID_SOCKET;
#ifdef CPPHTTPLIB_SSL_ENABLED
      session = other.session;
      other.session = nullptr;
#endif
    }
    return *this;
  }

#ifdef CPPHTTPLIB_SSL_ENABLED
  tls::session_t session = nullptr;
#endif
};

namespace detail {

struct ChunkedDecoder;

struct BodyReader {
  Stream *stream = nullptr;
  bool has_content_length = false;
  size_t content_length = 0;
  size_t payload_max_length = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
  size_t bytes_read = 0;
  bool chunked = false;
  bool eof = false;
  std::unique_ptr<ChunkedDecoder> chunked_decoder;
  Error last_error = Error::Success;

  ssize_t read(char *buf, size_t len);
  bool has_error() const { return last_error != Error::Success; }
};

inline ssize_t read_body_content(Stream *stream, BodyReader &br, char *buf,
                                 size_t len) {
  (void)stream;
  return br.read(buf, len);
}

class decompressor;

enum class NoProxyKind {
  Wildcard,       // "*"
  HostnameSuffix, // "example.com" or ".example.com"
  IPv4Cidr,       // "10.0.0.0/8" (or single IP, treated as /32)
  IPv6Cidr,       // "fe80::/10" (or single IP, treated as /128)
};

// Unified 16-byte buffer holding either a v4 (first 4 bytes) or v6 address.
// Lets one CIDR matcher cover both families.
using IPBytes = std::array<uint8_t, 16>;

struct NoProxyEntry {
  NoProxyKind kind = NoProxyKind::Wildcard;
  std::string hostname_pattern; // lowercased, leading/trailing dot stripped
  IPBytes net{};
  int prefix_bits = 0;
};

struct NormalizedTarget {
  std::string hostname; // lowercase; brackets and trailing dot removed
  bool is_ipv4 = false;
  bool is_ipv6 = false;
  IPBytes ip{};
};

} // namespace detail

class ClientImpl {
public:
  explicit ClientImpl(const std::string &host);

  explicit ClientImpl(const std::string &host, int port);

  explicit ClientImpl(const std::string &host, int port,
                      const std::string &client_cert_path,
                      const std::string &client_key_path);

  virtual ~ClientImpl();

  virtual bool is_valid() const;

  struct StreamHandle {
    std::unique_ptr<Response> response;
    Error error = Error::Success;

    StreamHandle() = default;
    StreamHandle(const StreamHandle &) = delete;
    StreamHandle &operator=(const StreamHandle &) = delete;
    StreamHandle(StreamHandle &&) = default;
    StreamHandle &operator=(StreamHandle &&) = default;
    ~StreamHandle() = default;

    bool is_valid() const {
      return response != nullptr && error == Error::Success;
    }

    ssize_t read(char *buf, size_t len);
    void parse_trailers_if_needed();
    Error get_read_error() const { return body_reader_.last_error; }
    bool has_read_error() const { return body_reader_.has_error(); }

    bool trailers_parsed_ = false;

  private:
    friend class ClientImpl;

    ssize_t read_with_decompression(char *buf, size_t len);

    std::unique_ptr<ClientConnection> connection_;
    std::unique_ptr<Stream> socket_stream_;
    Stream *stream_ = nullptr;
    detail::BodyReader body_reader_;

    std::unique_ptr<detail::decompressor> decompressor_;
    std::string decompress_buffer_;
    size_t decompress_offset_ = 0;
    size_t decompressed_bytes_read_ = 0;
  };

  // clang-format off
  Result Get(const std::string &path, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, const Params &params, const Headers &headers, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, const Params &params, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, const Params &params, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);

  Result Head(const std::string &path);
  Result Head(const std::string &path, const Headers &headers);

  Result Post(const std::string &path);
  Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Params &params);
  Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers);
  Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, const Params &params);
  Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);

  Result Put(const std::string &path);
  Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Params &params);
  Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers);
  Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, const Params &params);
  Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);

  Result Patch(const std::string &path);
  Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Params &params);
  Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, const Params &params);
  Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);

  Result Delete(const std::string &path, DownloadProgress progress = nullptr);
  Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
  Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
  Result Delete(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
  Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
  Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
  Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
  Result Delete(const std::string &path, const Headers &headers, const Params &params, DownloadProgress progress = nullptr);

  Result Options(const std::string &path);
  Result Options(const std::string &path, const Headers &headers);
  // clang-format on

  // Streaming API: Open a stream for reading response body incrementally
  // Socket ownership is transferred to StreamHandle for true streaming
  // Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.)
  StreamHandle open_stream(const std::string &method, const std::string &path,
                           const Params &params = {},
                           const Headers &headers = {},
                           const std::string &body = {},
                           const std::string &content_type = {});

  bool send(Request &req, Response &res, Error &error);
  Result send(const Request &req);

  void stop();

  std::string host() const;
  int port() const;

  size_t is_socket_open() const;
  socket_t socket() const;

  void set_hostname_addr_map(std::map<std::string, std::string> addr_map);

  void set_default_headers(Headers headers);

  void
  set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);

  void set_address_family(int family);
  void set_tcp_nodelay(bool on);
  void set_ipv6_v6only(bool on);
  void set_socket_options(SocketOptions socket_options);

  void set_connection_timeout(time_t sec, time_t usec = 0);
  template <class Rep, class Period>
  void
  set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);

  void set_read_timeout(time_t sec, time_t usec = 0);
  template <class Rep, class Period>
  void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);

  void set_write_timeout(time_t sec, time_t usec = 0);
  template <class Rep, class Period>
  void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);

  void set_max_timeout(time_t msec);
  template <class Rep, class Period>
  void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);

  void set_basic_auth(const std::string &username, const std::string &password);
  void set_bearer_token_auth(const std::string &token);

  void set_keep_alive(bool on);
  void set_follow_location(bool on);

  void set_path_encode(bool on);

  void set_compress(bool on);

  void set_decompress(bool on);

  void set_payload_max_length(size_t length);

  void set_interface(const std::string &intf);

  void set_proxy(const std::string &host, int port);
  void set_proxy_basic_auth(const std::string &username,
                            const std::string &password);
  void set_proxy_bearer_token_auth(const std::string &token);
  void set_no_proxy(const std::vector<std::string> &patterns);

  void set_logger(Logger logger);
  void set_error_logger(ErrorLogger error_logger);

protected:
  struct Socket {
    socket_t sock = INVALID_SOCKET;

    // For Mbed TLS compatibility: start_time for request timeout tracking
    std::chrono::time_point<std::chrono::steady_clock> start_time_;

    bool is_open() const { return sock != INVALID_SOCKET; }

#ifdef CPPHTTPLIB_SSL_ENABLED
    tls::session_t ssl = nullptr;
#endif
  };

  virtual bool create_and_connect_socket(Socket &socket, Error &error);
  virtual bool ensure_socket_connection(Socket &socket, Error &error);
  virtual bool setup_proxy_connection(
      Socket &socket,
      std::chrono::time_point<std::chrono::steady_clock> start_time,
      Response &res, bool &success, Error &error);

  bool is_proxy_enabled_for_host(const std::string &host) const;

  // All of:
  //   shutdown_ssl
  //   shutdown_socket
  //   close_socket
  //   disconnect
  // should ONLY be called when socket_mutex_ is locked, and only when
  // no other thread is using the socket.
  virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully);
  void shutdown_socket(Socket &socket) const;
  void close_socket(Socket &socket);
  void disconnect(bool gracefully);

  bool process_request(Stream &strm, Request &req, Response &res,
                       bool close_connection, Error &error);

  bool write_content_with_provider(Stream &strm, const Request &req,
                                   Error &error) const;

  void copy_settings(const ClientImpl &rhs);

  void output_log(const Request &req, const Response &res) const;
  void output_error_log(const Error &err, const Request *req) const;

  // Socket endpoint information
  const std::string host_;
  const int port_;

  // Current open socket
  Socket socket_;
  mutable std::mutex socket_mutex_;
  std::recursive_mutex request_mutex_;

  // These are all protected under socket_mutex
  size_t socket_requests_in_flight_ = 0;
  std::thread::id socket_requests_are_from_thread_ = std::thread::id();
  bool socket_should_be_closed_when_request_is_done_ = false;

  // Hostname-IP map
  std::map<std::string, std::string> addr_map_;

  // Default headers
  Headers default_headers_;

  // Header writer
  std::function<ssize_t(Stream &, Headers &)> header_writer_ =
      detail::write_headers;

  // Settings
  std::string client_cert_path_;
  std::string client_key_path_;

  time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
  time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
  time_t read_timeout_sec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND;
  time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND;
  time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND;
  time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND;
  time_t max_timeout_msec_ = CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND;

  std::string basic_auth_username_;
  std::string basic_auth_password_;
  std::string bearer_token_auth_token_;

  bool keep_alive_ = false;
  bool follow_location_ = false;

  bool path_encode_ = true;

  int address_family_ = AF_UNSPEC;
  bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
  bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
  SocketOptions socket_options_ = nullptr;

  bool compress_ = false;
  bool decompress_ = true;

  size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH;
  bool has_payload_max_length_ = false;

  std::string interface_;

  std::string proxy_host_;
  int proxy_port_ = -1;

  std::string proxy_basic_auth_username_;
  std::string proxy_basic_auth_password_;
  std::string proxy_bearer_token_auth_token_;

  std::vector<detail::NoProxyEntry> no_proxy_entries_;

  mutable detail::NormalizedTarget host_normalized_;
  mutable bool host_normalized_valid_ = false;

  mutable std::mutex logger_mutex_;
  Logger logger_;
  ErrorLogger error_logger_;

private:
  bool send_(Request &req, Response &res, Error &error);
  Result send_(Request &&req);

  socket_t create_client_socket(Error &error) const;
  bool read_response_line(Stream &strm, const Request &req, Response &res,
                          bool skip_100_continue = true) const;
  bool write_request(Stream &strm, Request &req, bool close_connection,
                     Error &error, bool skip_body = false);
  bool write_request_body(Stream &strm, Request &req, Error &error);
  void prepare_default_headers(Request &r, bool for_stream,
                               const std::string &ct);
  bool redirect(Request &req, Response &res, Error &error);
  bool create_redirect_client(const std::string &scheme,
                              const std::string &host, int port, Request &req,
                              Response &res, const std::string &path,
                              const std::string &location, Error &error);
  template <typename ClientType> void setup_redirect_client(ClientType &client);
  bool handle_request(Stream &strm, Request &req, Response &res,
                      bool close_connection, Error &error);
  std::unique_ptr<Response> send_with_content_provider_and_receiver(
      Request &req, const char *body, size_t content_length,
      ContentProvider content_provider,
      ContentProviderWithoutLength content_provider_without_length,
      const std::string &content_type, ContentReceiver content_receiver,
      Error &error);
  Result send_with_content_provider_and_receiver(
      const std::string &method, const std::string &path,
      const Headers &headers, const char *body, size_t content_length,
      ContentProvider content_provider,
      ContentProviderWithoutLength content_provider_without_length,
      const std::string &content_type, ContentReceiver content_receiver,
      UploadProgress progress);
  ContentProviderWithoutLength get_multipart_content_provider(
      const std::string &boundary, const UploadFormDataItems &items,
      const FormDataProviderItems &provider_items) const;

  virtual bool
  process_socket(const Socket &socket,
                 std::chrono::time_point<std::chrono::steady_clock> start_time,
                 std::function<bool(Stream &strm)> callback);
  virtual bool is_ssl() const;

  void transfer_socket_ownership_to_handle(StreamHandle &handle);

#ifdef CPPHTTPLIB_SSL_ENABLED
public:
  void set_digest_auth(const std::string &username,
                       const std::string &password);
  void set_proxy_digest_auth(const std::string &username,
                             const std::string &password);
  void set_ca_cert_path(const std::string &ca_cert_file_path,
                        const std::string &ca_cert_dir_path = std::string());
  void enable_server_certificate_verification(bool enabled);
  void enable_server_hostname_verification(bool enabled);
  void enable_system_ca(bool enabled);

protected:
  std::string digest_auth_username_;
  std::string digest_auth_password_;
  std::string proxy_digest_auth_username_;
  std::string proxy_digest_auth_password_;
  std::string ca_cert_file_path_;
  std::string ca_cert_dir_path_;
  bool server_certificate_verification_ = true;
  bool server_hostname_verification_ = true;
  SystemCAMode system_ca_mode_ = SystemCAMode::Auto;
  std::string ca_cert_pem_; // Store CA cert PEM for redirect transfer
  int last_ssl_error_ = 0;
  uint64_t last_backend_error_ = 0;
#endif
};

class Client {
public:
  // Universal interface
  explicit Client(const std::string &scheme_host_port);

  explicit Client(const std::string &scheme_host_port,
                  const std::string &client_cert_path,
                  const std::string &client_key_path);

  // HTTP only interface
  explicit Client(const std::string &host, int port);

  explicit Client(const std::string &host, int port,
                  const std::string &client_cert_path,
                  const std::string &client_key_path);

  Client(Client &&) = default;
  Client &operator=(Client &&) = default;

  ~Client();

  bool is_valid() const;

  // clang-format off
  Result Get(const std::string &path, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, const Params &params, const Headers &headers, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, const Params &params, const Headers &headers, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
  Result Get(const std::string &path, const Params &params, const Headers &headers, ResponseHandler response_handler, ContentReceiver content_receiver, DownloadProgress progress = nullptr);

  Result Head(const std::string &path);
  Result Head(const std::string &path, const Headers &headers);

  Result Post(const std::string &path);
  Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Params &params);
  Result Post(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers);
  Result Post(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, const Params &params);
  Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
  Result Post(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);

  Result Put(const std::string &path);
  Result Put(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Params &params);
  Result Put(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers);
  Result Put(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, const Params &params);
  Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
  Result Put(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);

  Result Patch(const std::string &path);
  Result Patch(const std::string &path, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Params &params);
  Result Patch(const std::string &path, const UploadFormDataItems &items, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers);
  Result Patch(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, size_t content_length, ContentProvider content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, ContentProviderWithoutLength content_provider, const std::string &content_type, ContentReceiver content_receiver, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, const Params &params);
  Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const std::string &boundary, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, const UploadFormDataItems &items, const FormDataProviderItems &provider_items, UploadProgress progress = nullptr);
  Result Patch(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, ContentReceiver content_receiver, DownloadProgress progress = nullptr);

  Result Delete(const std::string &path, DownloadProgress progress = nullptr);
  Result Delete(const std::string &path, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
  Result Delete(const std::string &path, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
  Result Delete(const std::string &path, const Params &params, DownloadProgress progress = nullptr);
  Result Delete(const std::string &path, const Headers &headers, DownloadProgress progress = nullptr);
  Result Delete(const std::string &path, const Headers &headers, const char *body, size_t content_length, const std::string &content_type, DownloadProgress progress = nullptr);
  Result Delete(const std::string &path, const Headers &headers, const std::string &body, const std::string &content_type, DownloadProgress progress = nullptr);
  Result Delete(const std::string &path, const Headers &headers, const Params &params, DownloadProgress progress = nullptr);

  Result Options(const std::string &path);
  Result Options(const std::string &path, const Headers &headers);
  // clang-format on

  // Streaming API: Open a stream for reading response body incrementally
  // Socket ownership is transferred to StreamHandle for true streaming
  // Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE, etc.)
  ClientImpl::StreamHandle open_stream(const std::string &method,
                                       const std::string &path,
                                       const Params &params = {},
                                       const Headers &headers = {},
                                       const std::string &body = {},
                                       const std::string &content_type = {});

  bool send(Request &req, Response &res, Error &error);
  Result send(const Request &req);

  void stop();

  std::string host() const;
  int port() const;

  size_t is_socket_open() const;
  socket_t socket() const;

  void set_hostname_addr_map(std::map<std::string, std::string> addr_map);

  void set_default_headers(Headers headers);

  void
  set_header_writer(std::function<ssize_t(Stream &, Headers &)> const &writer);

  void set_address_family(int family);
  void set_tcp_nodelay(bool on);
  void set_socket_options(SocketOptions socket_options);

  void set_connection_timeout(time_t sec, time_t usec = 0);
  template <class Rep, class Period>
  void
  set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);

  void set_read_timeout(time_t sec, time_t usec = 0);
  template <class Rep, class Period>
  void set_read_timeout(const std::chrono::duration<Rep, Period> &duration);

  void set_write_timeout(time_t sec, time_t usec = 0);
  template <class Rep, class Period>
  void set_write_timeout(const std::chrono::duration<Rep, Period> &duration);

  void set_max_timeout(time_t msec);
  template <class Rep, class Period>
  void set_max_timeout(const std::chrono::duration<Rep, Period> &duration);

  void set_basic_auth(const std::string &username, const std::string &password);
  void set_bearer_token_auth(const std::string &token);

  void set_keep_alive(bool on);
  void set_follow_location(bool on);

  void set_path_encode(bool on);

  void set_compress(bool on);

  void set_decompress(bool on);

  void set_payload_max_length(size_t length);

  void set_interface(const std::string &intf);

  void set_proxy(const std::string &host, int port);
  void set_proxy_basic_auth(const std::string &username,
                            const std::string &password);
  void set_proxy_bearer_token_auth(const std::string &token);
  void set_no_proxy(const std::vector<std::string> &patterns);
  void set_logger(Logger logger);
  void set_error_logger(ErrorLogger error_logger);

private:
  std::unique_ptr<ClientImpl> cli_;

#ifdef CPPHTTPLIB_SSL_ENABLED
public:
  void set_digest_auth(const std::string &username,
                       const std::string &password);
  void set_proxy_digest_auth(const std::string &username,
                             const std::string &password);
  void enable_server_certificate_verification(bool enabled);
  void enable_server_hostname_verification(bool enabled);
  void enable_system_ca(bool enabled);
  void set_ca_cert_path(const std::string &ca_cert_file_path,
                        const std::string &ca_cert_dir_path = std::string());

  void set_ca_cert_store(tls::ca_store_t ca_cert_store);
  void load_ca_cert_store(const char *ca_cert, std::size_t size);

  void set_server_certificate_verifier(tls::VerifyCallback verifier);

  void set_session_verifier(
      std::function<SSLVerifierResponse(tls::session_t)> verifier);

  tls::ctx_t tls_context() const;

#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
  void enable_windows_certificate_verification(bool enabled);
#endif

private:
  bool is_ssl_ = false;
#endif
};

#ifdef CPPHTTPLIB_SSL_ENABLED
class SSLServer : public Server {
public:
  SSLServer(const char *cert_path, const char *private_key_path,
            const char *client_ca_cert_file_path = nullptr,
            const char *client_ca_cert_dir_path = nullptr,
            const char *private_key_password = nullptr);

  struct PemMemory {
    const char *cert_pem;
    size_t cert_pem_len;
    const char *key_pem;
    size_t key_pem_len;
    const char *client_ca_pem;
    size_t client_ca_pem_len;
    const char *private_key_password;
  };
  explicit SSLServer(const PemMemory &pem);

  // The callback receives the ctx_t handle which can be cast to the
  // appropriate backend type (SSL_CTX* for OpenSSL,
  // tls::impl::MbedTlsContext* for Mbed TLS)
  explicit SSLServer(const tls::ContextSetupCallback &setup_callback);

  ~SSLServer() override;

  bool is_valid() const override;

  bool update_certs_pem(const char *cert_pem, const char *key_pem,
                        const char *client_ca_pem = nullptr,
                        const char *password = nullptr);

  tls::ctx_t tls_context() const { return ctx_; }

  int ssl_last_error() const { return last_ssl_error_; }

private:
  bool process_and_close_socket(socket_t sock) override;

  tls::ctx_t ctx_ = nullptr;
  std::mutex ctx_mutex_;

  int last_ssl_error_ = 0;
};

class SSLClient final : public ClientImpl {
public:
  explicit SSLClient(const std::string &host);

  explicit SSLClient(const std::string &host, int port);

  explicit SSLClient(const std::string &host, int port,
                     const std::string &client_cert_path,
                     const std::string &client_key_path,
                     const std::string &private_key_password = std::string());

  struct PemMemory {
    const char *cert_pem;
    size_t cert_pem_len;
    const char *key_pem;
    size_t key_pem_len;
    const char *private_key_password;
  };
  explicit SSLClient(const std::string &host, int port, const PemMemory &pem);

  ~SSLClient() override;

  bool is_valid() const override;

  void set_ca_cert_store(tls::ca_store_t ca_cert_store);
  void load_ca_cert_store(const char *ca_cert, std::size_t size);

  void set_server_certificate_verifier(tls::VerifyCallback verifier);

  // Post-handshake session verifier (backend-independent)
  void set_session_verifier(
      std::function<SSLVerifierResponse(tls::session_t)> verifier);

  tls::ctx_t tls_context() const { return ctx_; }

#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
  void enable_windows_certificate_verification(bool enabled);
#endif

private:
  bool create_and_connect_socket(Socket &socket, Error &error) override;
  bool ensure_socket_connection(Socket &socket, Error &error) override;
  void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override;
  void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully);

  bool
  process_socket(const Socket &socket,
                 std::chrono::time_point<std::chrono::steady_clock> start_time,
                 std::function<bool(Stream &strm)> callback) override;
  bool is_ssl() const override;

  bool setup_proxy_connection(
      Socket &socket,
      std::chrono::time_point<std::chrono::steady_clock> start_time,
      Response &res, bool &success, Error &error) override;
  bool connect_with_proxy(
      Socket &sock,
      std::chrono::time_point<std::chrono::steady_clock> start_time,
      Response &res, bool &success, Error &error);
  bool initialize_ssl(Socket &socket, Error &error);

  void init_ctx();
  void reset_ctx_on_error();

  bool load_certs();

  tls::ctx_t ctx_ = nullptr;
  std::mutex ctx_mutex_;
  std::once_flag initialize_cert_;

  // Tracks whether a custom CA store was applied via set_ca_cert_store(),
  // since the store handle itself is owned by ctx_ and leaves no other trace.
  // Used to keep custom CA configuration exclusive with system CA loading.
  bool ca_cert_store_set_ = false;

  long verify_result_ = 0;

  std::function<SSLVerifierResponse(tls::session_t)> session_verifier_;

#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
  bool enable_windows_cert_verification_ = true;
#endif

  friend class ClientImpl;
};
#endif // CPPHTTPLIB_SSL_ENABLED

namespace detail {

template <typename T, typename U>
inline void duration_to_sec_and_usec(const T &duration, U callback) {
  auto sec = std::chrono::duration_cast<std::chrono::seconds>(duration).count();
  auto usec = std::chrono::duration_cast<std::chrono::microseconds>(
                  duration - std::chrono::seconds(sec))
                  .count();
  callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
}

template <size_t N> inline constexpr size_t str_len(const char (&)[N]) {
  return N - 1;
}

inline bool is_numeric(const std::string &str) {
  return !str.empty() && std::all_of(str.cbegin(), str.cend(), is_ascii_digit);
}

inline size_t get_header_value_u64(const Headers &headers,
                                   const std::string &key, size_t def,
                                   size_t id, bool &is_invalid_value) {
  is_invalid_value = false;
  auto rng = headers.equal_range(key);
  auto it = rng.first;
  std::advance(it, static_cast<ssize_t>(id));
  if (it != rng.second) {
    if (is_numeric(it->second)) {
      return static_cast<size_t>(std::strtoull(it->second.data(), nullptr, 10));
    } else {
      is_invalid_value = true;
    }
  }
  return def;
}

inline size_t get_header_value_u64(const Headers &headers,
                                   const std::string &key, size_t def,
                                   size_t id) {
  auto dummy = false;
  return get_header_value_u64(headers, key, def, id, dummy);
}

} // namespace detail

template <class Rep, class Period>
inline Server &
Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
  detail::duration_to_sec_and_usec(
      duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
  return *this;
}

template <class Rep, class Period>
inline Server &
Server::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
  detail::duration_to_sec_and_usec(
      duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
  return *this;
}

template <class Rep, class Period>
inline Server &
Server::set_idle_interval(const std::chrono::duration<Rep, Period> &duration) {
  detail::duration_to_sec_and_usec(
      duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); });
  return *this;
}

template <class Rep, class Period>
inline void ClientImpl::set_connection_timeout(
    const std::chrono::duration<Rep, Period> &duration) {
  detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) {
    set_connection_timeout(sec, usec);
  });
}

template <class Rep, class Period>
inline void ClientImpl::set_read_timeout(
    const std::chrono::duration<Rep, Period> &duration) {
  detail::duration_to_sec_and_usec(
      duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); });
}

template <class Rep, class Period>
inline void ClientImpl::set_write_timeout(
    const std::chrono::duration<Rep, Period> &duration) {
  detail::duration_to_sec_and_usec(
      duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); });
}

template <class Rep, class Period>
inline void ClientImpl::set_max_timeout(
    const std::chrono::duration<Rep, Period> &duration) {
  auto msec =
      std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
  set_max_timeout(msec);
}

template <class Rep, class Period>
inline void Client::set_connection_timeout(
    const std::chrono::duration<Rep, Period> &duration) {
  cli_->set_connection_timeout(duration);
}

template <class Rep, class Period>
inline void
Client::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
  cli_->set_read_timeout(duration);
}

template <class Rep, class Period>
inline void
Client::set_write_timeout(const std::chrono::duration<Rep, Period> &duration) {
  cli_->set_write_timeout(duration);
}

inline void Client::set_max_timeout(time_t msec) {
  cli_->set_max_timeout(msec);
}

template <class Rep, class Period>
inline void
Client::set_max_timeout(const std::chrono::duration<Rep, Period> &duration) {
  cli_->set_max_timeout(duration);
}

/*
 * Forward declarations and types that will be part of the .h file if split into
 * .h + .cc.
 */

std::string hosted_at(const std::string &hostname);

void hosted_at(const std::string &hostname, std::vector<std::string> &addrs);

// JavaScript-style URL encoding/decoding functions
std::string encode_uri_component(const std::string &value);
std::string encode_uri(const std::string &value);
std::string decode_uri_component(const std::string &value);
std::string decode_uri(const std::string &value);

// RFC 3986 compliant URL component encoding/decoding functions
std::string encode_path_component(const std::string &component);
std::string decode_path_component(const std::string &component);
std::string encode_query_component(const std::string &component,
                                   bool space_as_plus = true);
std::string decode_query_component(const std::string &component,
                                   bool plus_as_space = true);

std::string sanitize_filename(const std::string &filename);

std::string append_query_params(const std::string &path, const Params &params);

std::pair<std::string, std::string> make_range_header(const Ranges &ranges);

std::pair<std::string, std::string>
make_basic_authentication_header(const std::string &username,
                                 const std::string &password,
                                 bool is_proxy = false);

namespace detail {

#if defined(_WIN32)
inline std::wstring u8string_to_wstring(const char *s) {
  if (!s) { return std::wstring(); }

  auto len = static_cast<int>(strlen(s));
  if (!len) { return std::wstring(); }

  auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0);
  if (!wlen) { return std::wstring(); }

  std::wstring ws;
  ws.resize(wlen);
  wlen = ::MultiByteToWideChar(
      CP_UTF8, 0, s, len,
      const_cast<LPWSTR>(reinterpret_cast<LPCWSTR>(ws.data())), wlen);
  if (wlen != static_cast<int>(ws.size())) { ws.clear(); }
  return ws;
}
#endif

struct FileStat {
  FileStat(const std::string &path);
  bool is_file() const;
  bool is_dir() const;
  time_t mtime() const;
  size_t size() const;

private:
#if defined(_WIN32)
  struct _stat st_;
#else
  struct stat st_;
#endif
  int ret_ = -1;
};

std::string make_host_and_port_string(const std::string &host, int port,
                                      bool is_ssl);

std::string trim_copy(const std::string &s);

void divide(
    const char *data, std::size_t size, char d,
    std::function<void(const char *, std::size_t, const char *, std::size_t)>
        fn);

void divide(
    const std::string &str, char d,
    std::function<void(const char *, std::size_t, const char *, std::size_t)>
        fn);

void split(const char *b, const char *e, char d,
           std::function<void(const char *, const char *)> fn);

void split(const char *b, const char *e, char d, size_t m,
           std::function<void(const char *, const char *)> fn);

bool process_client_socket(
    socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec,
    time_t write_timeout_sec, time_t write_timeout_usec,
    time_t max_timeout_msec,
    std::chrono::time_point<std::chrono::steady_clock> start_time,
    std::function<bool(Stream &)> callback);

socket_t create_client_socket(const std::string &host, const std::string &ip,
                              int port, int address_family, bool tcp_nodelay,
                              bool ipv6_v6only, SocketOptions socket_options,
                              time_t connection_timeout_sec,
                              time_t connection_timeout_usec,
                              time_t read_timeout_sec, time_t read_timeout_usec,
                              time_t write_timeout_sec,
                              time_t write_timeout_usec,
                              const std::string &intf, Error &error);

const char *get_header_value(const Headers &headers, const std::string &key,
                             const char *def, size_t id);

std::string params_to_query_str(const Params &params);

void parse_query_text(const char *data, std::size_t size, Params &params);

void parse_query_text(const std::string &s, Params &params);

bool parse_multipart_boundary(const std::string &content_type,
                              std::string &boundary);

bool parse_range_header(const std::string &s, Ranges &ranges);

bool parse_accept_header(const std::string &s,
                         std::vector<std::string> &content_types);

ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags);

ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags);

enum class EncodingType { None = 0, Gzip, Brotli, Zstd };

EncodingType encoding_type(const Request &req, const Response &res);

class BufferStream final : public Stream {
public:
  BufferStream() = default;
  ~BufferStream() override = default;

  bool is_readable() const override;
  bool wait_readable() const override;
  bool wait_writable() const override;
  ssize_t read(char *ptr, size_t size) override;
  ssize_t write(const char *ptr, size_t size) override;
  void get_remote_ip_and_port(std::string &ip, int &port) const override;
  void get_local_ip_and_port(std::string &ip, int &port) const override;
  socket_t socket() const override;
  time_t duration() const override;

  const std::string &get_buffer() const;

private:
  std::string buffer;
  size_t position = 0;
};

class compressor {
public:
  virtual ~compressor() = default;

  typedef std::function<bool(const char *data, size_t data_len)> Callback;
  virtual bool compress(const char *data, size_t data_length, bool last,
                        Callback callback) = 0;
};

class decompressor {
public:
  virtual ~decompressor() = default;

  virtual bool is_valid() const = 0;

  typedef std::function<bool(const char *data, size_t data_len)> Callback;
  virtual bool decompress(const char *data, size_t data_length,
                          Callback callback) = 0;
};

class nocompressor final : public compressor {
public:
  ~nocompressor() override = default;

  bool compress(const char *data, size_t data_length, bool /*last*/,
                Callback callback) override;
};

#ifdef CPPHTTPLIB_ZLIB_SUPPORT
class gzip_compressor final : public compressor {
public:
  gzip_compressor();
  ~gzip_compressor() override;

  bool compress(const char *data, size_t data_length, bool last,
                Callback callback) override;

private:
  bool is_valid_ = false;
  z_stream strm_;
};

class gzip_decompressor final : public decompressor {
public:
  gzip_decompressor();
  ~gzip_decompressor() override;

  bool is_valid() const override;

  bool decompress(const char *data, size_t data_length,
                  Callback callback) override;

private:
  bool is_valid_ = false;
  z_stream strm_;
};
#endif

#ifdef CPPHTTPLIB_BROTLI_SUPPORT
class brotli_compressor final : public compressor {
public:
  brotli_compressor();
  ~brotli_compressor();

  bool compress(const char *data, size_t data_length, bool last,
                Callback callback) override;

private:
  BrotliEncoderState *state_ = nullptr;
};

class brotli_decompressor final : public decompressor {
public:
  brotli_decompressor();
  ~brotli_decompressor();

  bool is_valid() const override;

  bool decompress(const char *data, size_t data_length,
                  Callback callback) override;

private:
  BrotliDecoderResult decoder_r;
  BrotliDecoderState *decoder_s = nullptr;
};
#endif

#ifdef CPPHTTPLIB_ZSTD_SUPPORT
class zstd_compressor : public compressor {
public:
  zstd_compressor();
  ~zstd_compressor();

  bool compress(const char *data, size_t data_length, bool last,
                Callback callback) override;

private:
  ZSTD_CCtx *ctx_ = nullptr;
};

class zstd_decompressor : public decompressor {
public:
  zstd_decompressor();
  ~zstd_decompressor();

  bool is_valid() const override;

  bool decompress(const char *data, size_t data_length,
                  Callback callback) override;

private:
  ZSTD_DCtx *ctx_ = nullptr;
};
#endif

// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer`
// to store data. The call can set memory on stack for performance.
class stream_line_reader {
public:
  stream_line_reader(Stream &strm, char *fixed_buffer,
                     size_t fixed_buffer_size);
  const char *ptr() const;
  size_t size() const;
  bool end_with_crlf() const;
  bool getline();

private:
  void append(char c);

  Stream &strm_;
  char *fixed_buffer_;
  const size_t fixed_buffer_size_;
  size_t fixed_buffer_used_size_ = 0;
  std::string growable_buffer_;
};

bool parse_trailers(stream_line_reader &line_reader, Headers &dest,
                    const Headers &src_headers);

struct ChunkedDecoder {
  Stream &strm;
  size_t chunk_remaining = 0;
  bool finished = false;
  char line_buf[64];
  size_t last_chunk_total = 0;
  size_t last_chunk_offset = 0;

  explicit ChunkedDecoder(Stream &s);

  ssize_t read_payload(char *buf, size_t len, size_t &out_chunk_offset,
                       size_t &out_chunk_total);

  bool parse_trailers_into(Headers &dest, const Headers &src_headers);
};

class mmap {
public:
  mmap(const char *path);
  ~mmap();

  bool open(const char *path);
  void close();

  bool is_open() const;
  size_t size() const;
  const char *data() const;

private:
#if defined(_WIN32)
  HANDLE hFile_ = NULL;
  HANDLE hMapping_ = NULL;
#else
  int fd_ = -1;
#endif
  size_t size_ = 0;
  void *addr_ = nullptr;
  bool is_open_empty_file = false;
};

// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5
namespace fields {

bool is_token_char(char c);
bool is_token(const std::string &s);
bool is_field_name(const std::string &s);
bool is_vchar(char c);
bool is_obs_text(char c);
bool is_field_vchar(char c);
bool is_field_content(const std::string &s);
bool is_field_value(const std::string &s);

} // namespace fields
} // namespace detail

/*
 * TLS Abstraction Layer Declarations
 */

#ifdef CPPHTTPLIB_SSL_ENABLED
// TLS abstraction layer - backend-specific type declarations
#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
namespace tls {
namespace impl {

// Mbed TLS context wrapper (holds config, entropy, DRBG, CA chain, own
// cert/key). This struct is accessible via tls::impl for use in SSL context
// setup callbacks (cast ctx_t to tls::impl::MbedTlsContext*).
struct MbedTlsContext {
  mbedtls_ssl_config conf;
  mbedtls_entropy_context entropy;
  mbedtls_ctr_drbg_context ctr_drbg;
  mbedtls_x509_crt ca_chain;
  mbedtls_x509_crt own_cert;
  mbedtls_pk_context own_key;
  bool is_server = false;
  bool verify_client = false;
  bool has_verify_callback = false;

  MbedTlsContext();
  ~MbedTlsContext();

  MbedTlsContext(const MbedTlsContext &) = delete;
  MbedTlsContext &operator=(const MbedTlsContext &) = delete;
};

} // namespace impl
} // namespace tls
#endif

#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT
namespace tls {
namespace impl {

// wolfSSL context wrapper (holds WOLFSSL_CTX and related state).
// This struct is accessible via tls::impl for use in SSL context
// setup callbacks (cast ctx_t to tls::impl::WolfSSLContext*).
struct WolfSSLContext {
  WOLFSSL_CTX *ctx = nullptr;
  bool is_server = false;
  bool verify_client = false;
  bool has_verify_callback = false;
  std::string ca_pem_data_; // accumulated PEM for get_ca_names/get_ca_certs

  WolfSSLContext();
  ~WolfSSLContext();

  WolfSSLContext(const WolfSSLContext &) = delete;
  WolfSSLContext &operator=(const WolfSSLContext &) = delete;
};

// CA store for wolfSSL: holds raw PEM bytes to allow reloading into any ctx
struct WolfSSLCAStore {
  std::string pem_data;
};

} // namespace impl
} // namespace tls
#endif

#endif // CPPHTTPLIB_SSL_ENABLED

namespace stream {

class Result {
public:
  Result();
  explicit Result(ClientImpl::StreamHandle &&handle, size_t chunk_size = 8192);
  Result(Result &&other) noexcept;
  Result &operator=(Result &&other) noexcept;
  Result(const Result &) = delete;
  Result &operator=(const Result &) = delete;

  // Response info
  bool is_valid() const;
  explicit operator bool() const;
  int status() const;
  const Headers &headers() const;
  std::string get_header_value(const std::string &key,
                               const char *def = "") const;
  bool has_header(const std::string &key) const;
  Error error() const;
  Error read_error() const;
  bool has_read_error() const;

  // Stream reading
  bool next();
  const char *data() const;
  size_t size() const;
  std::string read_all();

private:
  ClientImpl::StreamHandle handle_;
  std::string buffer_;
  size_t current_size_ = 0;
  size_t chunk_size_;
  bool finished_ = false;
};

// GET
template <typename ClientType>
inline Result Get(ClientType &cli, const std::string &path,
                  size_t chunk_size = 8192) {
  return Result{cli.open_stream("GET", path), chunk_size};
}

template <typename ClientType>
inline Result Get(ClientType &cli, const std::string &path,
                  const Headers &headers, size_t chunk_size = 8192) {
  return Result{cli.open_stream("GET", path, {}, headers), chunk_size};
}

template <typename ClientType>
inline Result Get(ClientType &cli, const std::string &path,
                  const Params &params, size_t chunk_size = 8192) {
  return Result{cli.open_stream("GET", path, params), chunk_size};
}

template <typename ClientType>
inline Result Get(ClientType &cli, const std::string &path,
                  const Params &params, const Headers &headers,
                  size_t chunk_size = 8192) {
  return Result{cli.open_stream("GET", path, params, headers), chunk_size};
}

// POST
template <typename ClientType>
inline Result Post(ClientType &cli, const std::string &path,
                   const std::string &body, const std::string &content_type,
                   size_t chunk_size = 8192) {
  return Result{cli.open_stream("POST", path, {}, {}, body, content_type),
                chunk_size};
}

template <typename ClientType>
inline Result Post(ClientType &cli, const std::string &path,
                   const Headers &headers, const std::string &body,
                   const std::string &content_type, size_t chunk_size = 8192) {
  return Result{cli.open_stream("POST", path, {}, headers, body, content_type),
                chunk_size};
}

template <typename ClientType>
inline Result Post(ClientType &cli, const std::string &path,
                   const Params &params, const std::string &body,
                   const std::string &content_type, size_t chunk_size = 8192) {
  return Result{cli.open_stream("POST", path, params, {}, body, content_type),
                chunk_size};
}

template <typename ClientType>
inline Result Post(ClientType &cli, const std::string &path,
                   const Params &params, const Headers &headers,
                   const std::string &body, const std::string &content_type,
                   size_t chunk_size = 8192) {
  return Result{
      cli.open_stream("POST", path, params, headers, body, content_type),
      chunk_size};
}

// PUT
template <typename ClientType>
inline Result Put(ClientType &cli, const std::string &path,
                  const std::string &body, const std::string &content_type,
                  size_t chunk_size = 8192) {
  return Result{cli.open_stream("PUT", path, {}, {}, body, content_type),
                chunk_size};
}

template <typename ClientType>
inline Result Put(ClientType &cli, const std::string &path,
                  const Headers &headers, const std::string &body,
                  const std::string &content_type, size_t chunk_size = 8192) {
  return Result{cli.open_stream("PUT", path, {}, headers, body, content_type),
                chunk_size};
}

template <typename ClientType>
inline Result Put(ClientType &cli, const std::string &path,
                  const Params &params, const std::string &body,
                  const std::string &content_type, size_t chunk_size = 8192) {
  return Result{cli.open_stream("PUT", path, params, {}, body, content_type),
                chunk_size};
}

template <typename ClientType>
inline Result Put(ClientType &cli, const std::string &path,
                  const Params &params, const Headers &headers,
                  const std::string &body, const std::string &content_type,
                  size_t chunk_size = 8192) {
  return Result{
      cli.open_stream("PUT", path, params, headers, body, content_type),
      chunk_size};
}

// PATCH
template <typename ClientType>
inline Result Patch(ClientType &cli, const std::string &path,
                    const std::string &body, const std::string &content_type,
                    size_t chunk_size = 8192) {
  return Result{cli.open_stream("PATCH", path, {}, {}, body, content_type),
                chunk_size};
}

template <typename ClientType>
inline Result Patch(ClientType &cli, const std::string &path,
                    const Headers &headers, const std::string &body,
                    const std::string &content_type, size_t chunk_size = 8192) {
  return Result{cli.open_stream("PATCH", path, {}, headers, body, content_type),
                chunk_size};
}

template <typename ClientType>
inline Result Patch(ClientType &cli, const std::string &path,
                    const Params &params, const std::string &body,
                    const std::string &content_type, size_t chunk_size = 8192) {
  return Result{cli.open_stream("PATCH", path, params, {}, body, content_type),
                chunk_size};
}

template <typename ClientType>
inline Result Patch(ClientType &cli, const std::string &path,
                    const Params &params, const Headers &headers,
                    const std::string &body, const std::string &content_type,
                    size_t chunk_size = 8192) {
  return Result{
      cli.open_stream("PATCH", path, params, headers, body, content_type),
      chunk_size};
}

// DELETE
template <typename ClientType>
inline Result Delete(ClientType &cli, const std::string &path,
                     size_t chunk_size = 8192) {
  return Result{cli.open_stream("DELETE", path), chunk_size};
}

template <typename ClientType>
inline Result Delete(ClientType &cli, const std::string &path,
                     const Headers &headers, size_t chunk_size = 8192) {
  return Result{cli.open_stream("DELETE", path, {}, headers), chunk_size};
}

template <typename ClientType>
inline Result Delete(ClientType &cli, const std::string &path,
                     const std::string &body, const std::string &content_type,
                     size_t chunk_size = 8192) {
  return Result{cli.open_stream("DELETE", path, {}, {}, body, content_type),
                chunk_size};
}

template <typename ClientType>
inline Result Delete(ClientType &cli, const std::string &path,
                     const Headers &headers, const std::string &body,
                     const std::string &content_type,
                     size_t chunk_size = 8192) {
  return Result{
      cli.open_stream("DELETE", path, {}, headers, body, content_type),
      chunk_size};
}

template <typename ClientType>
inline Result Delete(ClientType &cli, const std::string &path,
                     const Params &params, size_t chunk_size = 8192) {
  return Result{cli.open_stream("DELETE", path, params), chunk_size};
}

template <typename ClientType>
inline Result Delete(ClientType &cli, const std::string &path,
                     const Params &params, const Headers &headers,
                     size_t chunk_size = 8192) {
  return Result{cli.open_stream("DELETE", path, params, headers), chunk_size};
}

template <typename ClientType>
inline Result Delete(ClientType &cli, const std::string &path,
                     const Params &params, const std::string &body,
                     const std::string &content_type,
                     size_t chunk_size = 8192) {
  return Result{cli.open_stream("DELETE", path, params, {}, body, content_type),
                chunk_size};
}

template <typename ClientType>
inline Result Delete(ClientType &cli, const std::string &path,
                     const Params &params, const Headers &headers,
                     const std::string &body, const std::string &content_type,
                     size_t chunk_size = 8192) {
  return Result{
      cli.open_stream("DELETE", path, params, headers, body, content_type),
      chunk_size};
}

// HEAD
template <typename ClientType>
inline Result Head(ClientType &cli, const std::string &path,
                   size_t chunk_size = 8192) {
  return Result{cli.open_stream("HEAD", path), chunk_size};
}

template <typename ClientType>
inline Result Head(ClientType &cli, const std::string &path,
                   const Headers &headers, size_t chunk_size = 8192) {
  return Result{cli.open_stream("HEAD", path, {}, headers), chunk_size};
}

template <typename ClientType>
inline Result Head(ClientType &cli, const std::string &path,
                   const Params &params, size_t chunk_size = 8192) {
  return Result{cli.open_stream("HEAD", path, params), chunk_size};
}

template <typename ClientType>
inline Result Head(ClientType &cli, const std::string &path,
                   const Params &params, const Headers &headers,
                   size_t chunk_size = 8192) {
  return Result{cli.open_stream("HEAD", path, params, headers), chunk_size};
}

// OPTIONS
template <typename ClientType>
inline Result Options(ClientType &cli, const std::string &path,
                      size_t chunk_size = 8192) {
  return Result{cli.open_stream("OPTIONS", path), chunk_size};
}

template <typename ClientType>
inline Result Options(ClientType &cli, const std::string &path,
                      const Headers &headers, size_t chunk_size = 8192) {
  return Result{cli.open_stream("OPTIONS", path, {}, headers), chunk_size};
}

template <typename ClientType>
inline Result Options(ClientType &cli, const std::string &path,
                      const Params &params, size_t chunk_size = 8192) {
  return Result{cli.open_stream("OPTIONS", path, params), chunk_size};
}

template <typename ClientType>
inline Result Options(ClientType &cli, const std::string &path,
                      const Params &params, const Headers &headers,
                      size_t chunk_size = 8192) {
  return Result{cli.open_stream("OPTIONS", path, params, headers), chunk_size};
}

} // namespace stream

namespace sse {

struct SSEMessage {
  std::string event; // Event type (default: "message")
  std::string data;  // Event payload
  std::string id;    // Event ID for Last-Event-ID header

  SSEMessage();
  void clear();
};

class SSEClient {
public:
  using MessageHandler = std::function<void(const SSEMessage &)>;
  using ErrorHandler = std::function<void(Error)>;
  using OpenHandler = std::function<void()>;

  SSEClient(Client &client, const std::string &path);
  SSEClient(Client &client, const std::string &path, const Headers &headers);
  ~SSEClient();

  SSEClient(const SSEClient &) = delete;
  SSEClient &operator=(const SSEClient &) = delete;

  // Event handlers
  SSEClient &on_message(MessageHandler handler);
  SSEClient &on_event(const std::string &type, MessageHandler handler);
  SSEClient &on_open(OpenHandler handler);
  SSEClient &on_error(ErrorHandler handler);
  SSEClient &set_reconnect_interval(int ms);
  SSEClient &set_max_reconnect_attempts(int n);

  // Update headers (thread-safe)
  SSEClient &set_headers(const Headers &headers);

  // State accessors
  bool is_connected() const;
  const std::string &last_event_id() const;

  // Blocking start - runs event loop with auto-reconnect
  void start();

  // Non-blocking start - runs in background thread
  void start_async();

  // Stop the client (thread-safe)
  void stop();

private:
  bool parse_sse_line(const std::string &line, SSEMessage &msg, int &retry_ms);
  void run_event_loop();
  void dispatch_event(const SSEMessage &msg);
  bool should_reconnect(int count) const;
  void wait_for_reconnect();

  // Client and path
  Client &client_;
  std::string path_;
  Headers headers_;
  mutable std::mutex headers_mutex_;

  // Callbacks
  MessageHandler on_message_;
  std::map<std::string, MessageHandler> event_handlers_;
  OpenHandler on_open_;
  ErrorHandler on_error_;

  // Configuration
  int reconnect_interval_ms_ = 3000;
  int max_reconnect_attempts_ = 0; // 0 = unlimited

  // State
  std::atomic<bool> running_{false};
  std::atomic<bool> connected_{false};
  std::string last_event_id_;

  // Async support
  std::thread async_thread_;
};

} // namespace sse

namespace ws {

enum class Opcode : uint8_t {
  Continuation = 0x0,
  Text = 0x1,
  Binary = 0x2,
  Close = 0x8,
  Ping = 0x9,
  Pong = 0xA,
};

enum class CloseStatus : uint16_t {
  Normal = 1000,
  GoingAway = 1001,
  ProtocolError = 1002,
  UnsupportedData = 1003,
  NoStatus = 1005,
  Abnormal = 1006,
  InvalidPayload = 1007,
  PolicyViolation = 1008,
  MessageTooBig = 1009,
  MandatoryExtension = 1010,
  InternalError = 1011,
};

enum ReadResult : int { Fail = 0, Text = 1, Binary = 2 };

class WebSocket {
public:
  WebSocket(const WebSocket &) = delete;
  WebSocket &operator=(const WebSocket &) = delete;
  ~WebSocket();

  ReadResult read(std::string &msg);
  bool send(const std::string &data);
  bool send(const char *data, size_t len);
  void close(CloseStatus status = CloseStatus::Normal,
             const std::string &reason = "");
  const Request &request() const;
  bool is_open() const;

private:
  friend class httplib::Server;
  friend class WebSocketClient;

  WebSocket(
      Stream &strm, const Request &req, bool is_server,
      time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND,
      int max_missed_pongs = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS)
      : strm_(strm), req_(req), is_server_(is_server),
        ping_interval_sec_(ping_interval_sec),
        max_missed_pongs_(max_missed_pongs) {
    start_heartbeat();
  }

  WebSocket(
      std::unique_ptr<Stream> &&owned_strm, const Request &req, bool is_server,
      time_t ping_interval_sec = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND,
      int max_missed_pongs = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS)
      : strm_(*owned_strm), owned_strm_(std::move(owned_strm)), req_(req),
        is_server_(is_server), ping_interval_sec_(ping_interval_sec),
        max_missed_pongs_(max_missed_pongs) {
    start_heartbeat();
  }

  void start_heartbeat();
  bool send_frame(Opcode op, const char *data, size_t len, bool fin = true);

  Stream &strm_;
  std::unique_ptr<Stream> owned_strm_;
  Request req_;
  bool is_server_;
  time_t ping_interval_sec_;
  int max_missed_pongs_;
  int unacked_pings_ = 0;
  std::atomic<bool> closed_{false};
  std::mutex write_mutex_;
  std::thread ping_thread_;
  std::mutex ping_mutex_;
  std::condition_variable ping_cv_;
};

class WebSocketClient {
public:
  explicit WebSocketClient(const std::string &scheme_host_port_path,
                           const Headers &headers = {});

  ~WebSocketClient();
  WebSocketClient(const WebSocketClient &) = delete;
  WebSocketClient &operator=(const WebSocketClient &) = delete;

  bool is_valid() const;

  bool connect();
  ReadResult read(std::string &msg);
  bool send(const std::string &data);
  bool send(const char *data, size_t len);
  void close(CloseStatus status = CloseStatus::Normal,
             const std::string &reason = "");
  bool is_open() const;
  const std::string &subprotocol() const;
  void set_read_timeout(time_t sec, time_t usec = 0);
  void set_write_timeout(time_t sec, time_t usec = 0);
  void set_websocket_ping_interval(time_t sec);
  void set_websocket_max_missed_pongs(int count);
  void set_tcp_nodelay(bool on);
  void set_address_family(int family);
  void set_ipv6_v6only(bool on);
  void set_socket_options(SocketOptions socket_options);
  void set_connection_timeout(time_t sec, time_t usec = 0);
  void set_interface(const std::string &intf);
  void set_hostname_addr_map(std::map<std::string, std::string> addr_map);

#ifdef CPPHTTPLIB_SSL_ENABLED
  void set_ca_cert_path(const std::string &path);
  void set_ca_cert_store(tls::ca_store_t store);
  void load_ca_cert_store(const char *ca_cert, std::size_t size);
  void enable_server_certificate_verification(bool enabled);
  void enable_system_ca(bool enabled);
#endif

private:
  void shutdown_and_close();
  bool create_stream(std::unique_ptr<Stream> &strm);

  std::string host_;
  int port_;
  std::string path_;
  Headers headers_;
  std::string subprotocol_;
  bool is_valid_ = false;
  socket_t sock_ = INVALID_SOCKET;
  std::unique_ptr<WebSocket> ws_;
  time_t read_timeout_sec_ = CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND;
  time_t read_timeout_usec_ = 0;
  time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND;
  time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND;
  time_t websocket_ping_interval_sec_ =
      CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND;
  int websocket_max_missed_pongs_ = CPPHTTPLIB_WEBSOCKET_MAX_MISSED_PONGS;
  int address_family_ = AF_UNSPEC;
  bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY;
  bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY;
  SocketOptions socket_options_ = nullptr;
  time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND;
  time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
  std::string interface_;

  // Hostname-IP map
  std::map<std::string, std::string> addr_map_;

#ifdef CPPHTTPLIB_SSL_ENABLED
  bool is_ssl_ = false;
  tls::ctx_t tls_ctx_ = nullptr;
  tls::session_t tls_session_ = nullptr;
  std::string ca_cert_file_path_;
  bool custom_ca_loaded_ = false;
  bool certs_loaded_ = false;
  SystemCAMode system_ca_mode_ = SystemCAMode::Auto;
  bool server_certificate_verification_ = true;
#endif
};

namespace impl {

bool is_valid_utf8(const std::string &s);

bool read_websocket_frame(Stream &strm, Opcode &opcode, std::string &payload,
                          bool &fin, bool expect_masked, size_t max_len);

} // namespace impl

} // namespace ws


} // namespace httplib

#endif // CPPHTTPLIB_HTTPLIB_H
