char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
718f7b417
char/aliceai-t5-moe
1#include "httplib.h" 2namespace httplib { 3 4/* 5* Implementation that will be part of the .cc file if split into .h + .cc. 6*/ 7 8namespace stream { 9 10// stream::Result implementations 11Result ::Result () :chunk_size_ (8192 ) {} 12 13Result ::Result (ClientImpl ::StreamHandle && handle ,size_t chunk_size ) 14 :handle_ (std::move (handle )),chunk_size_ (chunk_size ) {} 15 16Result ::Result (Result && other )noexcept 17 :handle_ (std::move (other .handle_ )),buffer_ (std::move (other .buffer_ )), 18current_size_ (other .current_size_ ),chunk_size_ (other .chunk_size_ ), 19finished_ (other .finished_ ) { 20other .current_size_ = 0 ; 21other .finished_ = true; 22} 23 24Result & Result ::operator= (Result && other )noexcept { 25if (this != & other ) { 26handle_ = std::move (other .handle_ ); 27buffer_ = std::move (other .buffer_ ); 28current_size_ = other .current_size_ ; 29chunk_size_ = other .chunk_size_ ; 30finished_ = other .finished_ ; 31other .current_size_ = 0 ; 32other .finished_ = true; 33 } 34return * this ; 35} 36 37bool Result ::is_valid ()const {return handle_ .is_valid (); } 38Result ::operatorbool ()const {return is_valid (); } 39 40int Result ::status ()const { 41return handle_ .response ?handle_ .response -> status :-1 ; 42} 43 44const Headers & Result ::headers ()const { 45static const Headers empty_headers ; 46return handle_ .response ?handle_ .response -> headers :empty_headers ; 47} 48 49std::string Result ::get_header_value (const std::string & key , 50const char * def )const { 51return handle_ .response ?handle_ .response -> get_header_value (key ,def ) :def ; 52} 53 54bool Result ::has_header (const std::string & key )const { 55return handle_ .response ?handle_ .response -> has_header (key ) : false; 56} 57 58Error Result ::error ()const {return handle_ .error ; } 59Error Result ::read_error ()const {return handle_ .get_read_error (); } 60bool Result ::has_read_error ()const {return handle_ .has_read_error (); } 61 62bool Result ::next () { 63if (!handle_ .is_valid ()|| finished_ ) {return false; } 64 65if (buffer_ .size ()< chunk_size_ ) {buffer_ .resize (chunk_size_ ); } 66 67ssize_t n = handle_ .read (& buffer_ [0 ],chunk_size_ ); 68if (n > 0 ) { 69current_size_ = static_cast < size_t > (n ); 70return true; 71 } 72 73current_size_ = 0 ; 74finished_ = true; 75return false; 76} 77 78const char * Result ::data ()const {return buffer_ .data (); } 79size_t Result ::size ()const {return current_size_ ; } 80 81std::string Result ::read_all () { 82 std::string result ; 83while (next ()) { 84result .append (data (),size ()); 85 } 86return result ; 87} 88 89}// namespace stream 90 91namespace sse { 92 93// SSEMessage implementations 94SSEMessage ::SSEMessage () :event ("message" ) {} 95 96void SSEMessage ::clear () { 97event = "message" ; 98data .clear (); 99id .clear (); 100} 101 102// SSEClient implementations 103SSEClient ::SSEClient (Client & client ,const std::string & path ) 104 :client_ (client ),path_ (path ) {} 105 106SSEClient ::SSEClient (Client & client ,const std::string & path , 107const Headers & headers ) 108 :client_ (client ),path_ (path ),headers_ (headers ) {} 109 110SSEClient ::~SSEClient () {stop (); } 111 112SSEClient & SSEClient ::on_message (MessageHandler handler ) { 113on_message_ = std::move (handler ); 114return * this ; 115} 116 117SSEClient & SSEClient ::on_event (const std::string & type , 118MessageHandler handler ) { 119event_handlers_ [type ]= std::move (handler ); 120return * this ; 121} 122 123SSEClient & SSEClient ::on_open (OpenHandler handler ) { 124on_open_ = std::move (handler ); 125return * this ; 126} 127 128SSEClient & SSEClient ::on_error (ErrorHandler handler ) { 129on_error_ = std::move (handler ); 130return * this ; 131} 132 133SSEClient & SSEClient ::set_reconnect_interval (int ms ) { 134reconnect_interval_ms_ = ms ; 135return * this ; 136} 137 138SSEClient & SSEClient ::set_max_reconnect_attempts (int n ) { 139max_reconnect_attempts_ = n ; 140return * this ; 141} 142 143SSEClient & SSEClient ::set_headers (const Headers & headers ) { 144 std::lock_guard < std::mutex > lock (headers_mutex_ ); 145headers_ = headers ; 146return * this ; 147} 148 149bool SSEClient ::is_connected ()const {return connected_ .load (); } 150 151const std::string & SSEClient ::last_event_id ()const { 152return last_event_id_ ; 153} 154 155void SSEClient ::start () { 156running_ .store (true); 157run_event_loop (); 158} 159 160void SSEClient ::start_async () { 161running_ .store (true); 162async_thread_ = std::thread ([this ]() {run_event_loop (); }); 163} 164 165void SSEClient ::stop () { 166running_ .store (false); 167client_ .stop ();// Cancel any pending operations 168if (async_thread_ .joinable ()) {async_thread_ .join (); } 169} 170 171bool SSEClient ::parse_sse_line (const std::string & line ,SSEMessage & msg , 172int & retry_ms ) { 173// Blank line signals end of event 174if (line .empty ()|| line == "\r" ) {return true; } 175 176// Lines starting with ':' are comments (ignored) 177if (!line .empty ()&& line [0 ]== ':' ) {return false; } 178 179// Find the colon separator 180auto colon_pos = line .find (':' ); 181if (colon_pos == std::string::npos ) { 182// Line with no colon is treated as field name with empty value 183return false; 184 } 185 186auto field = line .substr (0 ,colon_pos ); 187 std::string value ; 188 189// Value starts after colon, skip optional single space 190if (colon_pos + 1 < line .size ()) { 191auto value_start = colon_pos + 1 ; 192if (line [value_start ]== ' ' ) {value_start ++ ; } 193value = line .substr (value_start ); 194// Remove trailing \r if present 195if (!value .empty ()&& value .back ()== '\r' ) {value .pop_back (); } 196 } 197 198// Handle known fields 199if (field == "event" ) { 200msg .event = value ; 201 }else if (field == "data" ) { 202// Multiple data lines are concatenated with newlines 203if (!msg .data .empty ()) {msg .data += "\n" ; } 204msg .data += value ; 205 }else if (field == "id" ) { 206// Empty id is valid (clears the last event ID) 207msg .id = value ; 208 }else if (field == "retry" ) { 209// Parse retry interval in milliseconds 210 { 211int v = 0 ; 212auto res = 213 detail::from_chars (value .data (),value .data ()+ value .size (),v ); 214if (res .ec == std::errc {}) {retry_ms = v ; } 215 } 216 } 217// Unknown fields are ignored per SSE spec 218 219return false; 220} 221 222void SSEClient ::run_event_loop () { 223auto reconnect_count = 0 ; 224 225while (running_ .load ()) { 226// Build headers, including Last-Event-ID if we have one 227Headers request_headers ; 228 { 229 std::lock_guard < std::mutex > lock (headers_mutex_ ); 230request_headers = headers_ ; 231 } 232if (!last_event_id_ .empty ()) { 233request_headers .emplace ("Last-Event-ID" ,last_event_id_ ); 234 } 235 236// Open streaming connection 237auto result = stream::Get (client_ ,path_ ,request_headers ); 238 239// Connection error handling 240if (!result ) { 241connected_ .store (false); 242if (on_error_ ) {on_error_ (result .error ()); } 243 244if (!should_reconnect (reconnect_count )) {break ; } 245wait_for_reconnect (); 246reconnect_count ++ ; 247continue ; 248 } 249 250if (result .status ()!= StatusCode ::OK_200 ) { 251connected_ .store (false); 252if (on_error_ ) {on_error_ (Error ::Connection ); } 253 254// For certain errors, don't reconnect. 255// Note: 401 is intentionally absent so that handlers can refresh 256// credentials via set_headers() and let the client reconnect. 257if (result .status ()== StatusCode ::NoContent_204 || 258result .status ()== StatusCode ::NotFound_404 || 259result .status ()== StatusCode ::Forbidden_403 ) { 260break ; 261 } 262 263if (!should_reconnect (reconnect_count )) {break ; } 264wait_for_reconnect (); 265reconnect_count ++ ; 266continue ; 267 } 268 269// Connection successful 270connected_ .store (true); 271reconnect_count = 0 ; 272if (on_open_ ) {on_open_ (); } 273 274// Event receiving loop 275 std::string buffer ; 276SSEMessage current_msg ; 277 278while (running_ .load ()&& result .next ()) { 279buffer .append (result .data (),result .size ()); 280 281// Process complete lines in the buffer 282size_t line_start = 0 ; 283size_t newline_pos ; 284 285while ((newline_pos = buffer .find ('\n' ,line_start ))!= 286 std::string::npos ) { 287auto line = buffer .substr (line_start ,newline_pos - line_start ); 288line_start = newline_pos + 1 ; 289 290// Parse the line and check if event is complete 291auto event_complete = 292parse_sse_line (line ,current_msg ,reconnect_interval_ms_ ); 293 294if (event_complete && !current_msg .data .empty ()) { 295// Update last_event_id for reconnection 296if (!current_msg .id .empty ()) {last_event_id_ = current_msg .id ; } 297 298// Dispatch event to appropriate handler 299dispatch_event (current_msg ); 300 301current_msg .clear (); 302 } 303 } 304 305// Keep unprocessed data in buffer 306buffer .erase (0 ,line_start ); 307 } 308 309// Connection ended 310connected_ .store (false); 311 312if (!running_ .load ()) {break ; } 313 314// Check for read errors 315if (result .has_read_error ()) { 316if (on_error_ ) {on_error_ (result .read_error ()); } 317 } 318 319if (!should_reconnect (reconnect_count )) {break ; } 320wait_for_reconnect (); 321reconnect_count ++ ; 322 } 323 324connected_ .store (false); 325} 326 327void SSEClient ::dispatch_event (const SSEMessage & msg ) { 328// Check for specific event type handler first 329auto it = event_handlers_ .find (msg .event ); 330if (it != event_handlers_ .end ()) { 331it -> second (msg ); 332return ; 333 } 334 335// Fall back to generic message handler 336if (on_message_ ) {on_message_ (msg ); } 337} 338 339bool SSEClient ::should_reconnect (int count )const { 340if (!running_ .load ()) {return false; } 341if (max_reconnect_attempts_ == 0 ) {return true; }// unlimited 342return count < max_reconnect_attempts_ ; 343} 344 345void SSEClient ::wait_for_reconnect () { 346// Use small increments to check running_ flag frequently 347auto waited = 0 ; 348while (running_ .load ()&& waited < reconnect_interval_ms_ ) { 349 std::this_thread::sleep_for (std::chrono::milliseconds (100 )); 350waited += 100 ; 351 } 352} 353 354}// namespace sse 355 356#ifdef CPPHTTPLIB_SSL_ENABLED 357/* 358* TLS abstraction layer - internal function declarations 359* These are implementation details and not part of the public API. 360*/ 361namespace tls { 362 363// Client context 364ctx_t create_client_context (); 365void free_context (ctx_t ctx ); 366bool set_min_version (ctx_t ctx ,Version version ); 367bool load_ca_pem (ctx_t ctx ,const char * pem ,size_t len ); 368bool load_ca_file (ctx_t ctx ,const char * file_path ); 369bool load_ca_dir (ctx_t ctx ,const char * dir_path ); 370bool load_system_certs (ctx_t ctx ); 371bool set_client_cert_pem (ctx_t ctx ,const char * cert ,const char * key , 372const char * password ); 373bool set_client_cert_file (ctx_t ctx ,const char * cert_path , 374const char * key_path ,const char * password ); 375 376// Server context 377ctx_t create_server_context (); 378bool set_server_cert_pem (ctx_t ctx ,const char * cert ,const char * key , 379const char * password ); 380bool set_server_cert_file (ctx_t ctx ,const char * cert_path , 381const char * key_path ,const char * password ); 382bool set_client_ca_file (ctx_t ctx ,const char * ca_file ,const char * ca_dir ); 383void set_verify_client (ctx_t ctx ,bool require ); 384 385// Session management 386session_t create_session (ctx_t ctx ,socket_t sock ); 387void free_session (session_t session ); 388bool set_sni (session_t session ,const char * hostname ,bool verify_hostname ); 389 390// Handshake (non-blocking capable) 391TlsError connect (session_t session ); 392TlsError accept (session_t session ); 393 394// Handshake with timeout (blocking until timeout) 395bool connect_nonblocking (session_t session ,socket_t sock ,time_t timeout_sec , 396time_t timeout_usec ,TlsError * err ); 397bool accept_nonblocking (session_t session ,socket_t sock ,time_t timeout_sec , 398time_t timeout_usec ,TlsError * err ); 399 400// I/O (non-blocking capable) 401ssize_t read (session_t session ,void * buf ,size_t len ,TlsError & err ); 402ssize_t write (session_t session ,const void * buf ,size_t len ,TlsError & err ); 403int pending (const_session_t session ); 404void shutdown (session_t session ,bool graceful ); 405 406// Connection state 407bool is_peer_closed (session_t session ,socket_t sock ); 408 409// Certificate verification 410cert_t get_peer_cert (const_session_t session ); 411void free_cert (cert_t cert ); 412bool verify_hostname (cert_t cert ,const char * hostname ); 413uint64_t hostname_mismatch_code (); 414long get_verify_result (const_session_t session ); 415 416// Certificate introspection 417std::string get_cert_subject_cn (cert_t cert ); 418std::string get_cert_issuer_name (cert_t cert ); 419bool get_cert_sans (cert_t cert , std::vector < SanEntry > & sans ); 420bool get_cert_validity (cert_t cert ,time_t & not_before ,time_t & not_after ); 421std::string get_cert_serial (cert_t cert ); 422bool get_cert_der (cert_t cert , std::vector < unsigned char > & der ); 423const char * get_sni (const_session_t session ); 424 425// CA store management 426ca_store_t create_ca_store (const char * pem ,size_t len ); 427void free_ca_store (ca_store_t store ); 428bool set_ca_store (ctx_t ctx ,ca_store_t store ); 429size_t get_ca_certs (ctx_t ctx , std::vector < cert_t > & certs ); 430std::vector < std::string > get_ca_names (ctx_t ctx ); 431 432// Dynamic certificate update (for servers) 433bool update_server_cert (ctx_t ctx ,const char * cert_pem ,const char * key_pem , 434const char * password ); 435bool update_server_client_ca (ctx_t ctx ,const char * ca_pem ); 436 437// Certificate verification callback 438bool set_verify_callback (ctx_t ctx ,VerifyCallback callback ); 439long get_verify_error (const_session_t session ); 440std::string verify_error_string (long error_code ); 441 442// TlsError information 443uint64_t peek_error (); 444uint64_t get_error (); 445std::string error_string (uint64_t code ); 446 447}// namespace tls 448#endif // CPPHTTPLIB_SSL_ENABLED 449 450/* 451* Group 1: detail namespace - Non-SSL utilities 452*/ 453 454namespace detail { 455 456bool set_socket_opt_impl (socket_t sock ,int level ,int optname , 457const void * optval ,socklen_t optlen ) { 458return setsockopt (sock ,level ,optname , 459#ifdef _WIN32 460reinterpret_cast < const char *> (optval ), 461#else 462optval , 463#endif 464optlen )== 0 ; 465} 466 467bool set_socket_opt_time (socket_t sock ,int level ,int optname , 468time_t sec ,time_t usec ) { 469#ifdef _WIN32 470auto timeout = static_cast < uint32_t > (sec * 1000 + usec /1000 ); 471#else 472timeval timeout ; 473timeout .tv_sec = static_cast < long > (sec ); 474timeout .tv_usec = static_cast < decltype(timeout .tv_usec )> (usec ); 475#endif 476return set_socket_opt_impl (sock ,level ,optname ,& timeout ,sizeof (timeout )); 477} 478 479bool is_hex (char c ,int & v ) { 480if (is_ascii_digit (c )) { 481v = c - '0' ; 482return true; 483 }else if ('A' <=c && c <='F' ) { 484v = c - 'A' + 10 ; 485return true; 486 }else if ('a' <=c && c <='f' ) { 487v = c - 'a' + 10 ; 488return true; 489 } 490return false; 491} 492 493bool from_hex_to_i (const std::string & s ,size_t i ,size_t cnt , 494int & val ) { 495if (i >=s .size ()) {return false; } 496 497val = 0 ; 498for (;cnt ;i ++ ,cnt -- ) { 499if (!s [i ]) {return false; } 500auto v = 0 ; 501if (is_hex (s [i ],v )) { 502val = val * 16 + v ; 503 }else { 504return false; 505 } 506 } 507return true; 508} 509 510std::string from_i_to_hex (size_t n ) { 511static const auto charset = "0123456789abcdef" ; 512 std::string ret ; 513do { 514ret = charset [n & 15 ]+ ret ; 515n >>=4 ; 516 }while (n > 0 ); 517return ret ; 518} 519 520std::string compute_etag (const FileStat & fs , 521const std::string & suffix = std::string ()) { 522if (!fs .is_file ()) {return std::string (); } 523 524// If mtime cannot be determined (negative value indicates an error 525// or sentinel), do not generate an ETag. Returning a neutral / fixed 526// value like 0 could collide with a real file that legitimately has 527// mtime == 0 (epoch) and lead to misleading validators. 528auto mtime_raw = fs .mtime (); 529if (mtime_raw < 0 ) {return std::string (); } 530 531auto mtime = static_cast < size_t > (mtime_raw ); 532auto size = fs .size (); 533 534return std::string ("W/\"" )+ from_i_to_hex (mtime )+ "-" + 535from_i_to_hex (size )+ suffix + "\"" ; 536} 537 538// Format time_t as HTTP-date (RFC 9110 Section 5.6.7): "Sun, 06 Nov 1994 539// 08:49:37 GMT" This implementation is defensive: it validates `mtime`, checks 540// return values from `gmtime_r`/`gmtime_s`, and ensures `strftime` succeeds. 541std::string file_mtime_to_http_date (time_t mtime ) { 542if (mtime < 0 ) {return std::string (); } 543 544struct tm tm_buf ; 545#ifdef _WIN32 546if (gmtime_s (& tm_buf ,& mtime )!= 0 ) {return std::string (); } 547#else 548if (gmtime_r (& mtime ,& tm_buf )== nullptr ) {return std::string (); } 549#endif 550char buf [64 ]; 551if (strftime (buf ,sizeof (buf ),"%a, %d %b %Y %H:%M:%S GMT" ,& tm_buf )== 0 ) { 552return std::string (); 553 } 554 555return std::string (buf ); 556} 557 558// Parse HTTP-date (RFC 9110 Section 5.6.7) to time_t. Returns -1 on failure. 559time_t parse_http_date (const std::string & date_str ) { 560struct tm tm_buf ; 561 562// Create a classic locale object once for all parsing attempts 563const std::locale classic_locale = std::locale::classic (); 564 565// Try to parse using std::get_time (C++11, cross-platform) 566auto try_parse = [& ](const char * fmt )-> bool { 567 std::istringstream ss (date_str ); 568ss .imbue (classic_locale ); 569 570memset (& tm_buf ,0 ,sizeof (tm_buf )); 571ss >> std::get_time (& tm_buf ,fmt ); 572 573return !ss .fail (); 574 }; 575 576// RFC 9110 preferred format (HTTP-date): "Sun, 06 Nov 1994 08:49:37 GMT" 577if (!try_parse ("%a, %d %b %Y %H:%M:%S" )) { 578// RFC 850 format: "Sunday, 06-Nov-94 08:49:37 GMT" 579if (!try_parse ("%A, %d-%b-%y %H:%M:%S" )) { 580// asctime format: "Sun Nov 6 08:49:37 1994" 581if (!try_parse ("%a %b %d %H:%M:%S %Y" )) { 582return static_cast < time_t > (-1 ); 583 } 584 } 585 } 586 587#ifdef _WIN32 588return _mkgmtime (& tm_buf ); 589#elif defined_AIX 590return mktime (& tm_buf ); 591#else 592return timegm (& tm_buf ); 593#endif 594} 595 596bool is_weak_etag (const std::string & s ) { 597// Check if the string is a weak ETag (starts with 'W/"') 598return s .size ()> 3 && s [0 ]== 'W' && s [1 ]== '/' && s [2 ]== '"' ; 599} 600 601bool is_strong_etag (const std::string & s ) { 602// Check if the string is a strong ETag (starts and ends with '"', at least 2 603// chars) 604return s .size () >=2 && s [0 ]== '"' && s .back ()== '"' ; 605} 606 607size_t to_utf8 (int code ,char * buff ) { 608if (code < 0x0080 ) { 609buff [0 ]= static_cast < char > (code & 0x7F ); 610return 1 ; 611 }else if (code < 0x0800 ) { 612buff [0 ]= static_cast < char > (0xC0 | ((code >>6 )& 0x1F )); 613buff [1 ]= static_cast < char > (0x80 | (code & 0x3F )); 614return 2 ; 615 }else if (code < 0xD800 ) { 616buff [0 ]= static_cast < char > (0xE0 | ((code >>12 )& 0xF )); 617buff [1 ]= static_cast < char > (0x80 | ((code >>6 )& 0x3F )); 618buff [2 ]= static_cast < char > (0x80 | (code & 0x3F )); 619return 3 ; 620 }else if (code < 0xE000 ) {// D800 - DFFF is invalid... 621return 0 ; 622 }else if (code < 0x10000 ) { 623buff [0 ]= static_cast < char > (0xE0 | ((code >>12 )& 0xF )); 624buff [1 ]= static_cast < char > (0x80 | ((code >>6 )& 0x3F )); 625buff [2 ]= static_cast < char > (0x80 | (code & 0x3F )); 626return 3 ; 627 }else if (code < 0x110000 ) { 628buff [0 ]= static_cast < char > (0xF0 | ((code >>18 )& 0x7 )); 629buff [1 ]= static_cast < char > (0x80 | ((code >>12 )& 0x3F )); 630buff [2 ]= static_cast < char > (0x80 | ((code >>6 )& 0x3F )); 631buff [3 ]= static_cast < char > (0x80 | (code & 0x3F )); 632return 4 ; 633 } 634 635// NOTREACHED 636return 0 ; 637} 638 639}// namespace detail 640 641namespace ws { 642namespace impl { 643 644bool is_valid_utf8 (const std::string & s ) { 645size_t i = 0 ; 646auto n = s .size (); 647while (i < n ) { 648auto c = static_cast < unsigned char > (s [i ]); 649size_t len ; 650uint32_t cp ; 651if (c < 0x80 ) { 652i ++ ; 653continue ; 654 }else if ((c & 0xE0 )== 0xC0 ) { 655len = 2 ; 656cp = c & 0x1F ; 657 }else if ((c & 0xF0 )== 0xE0 ) { 658len = 3 ; 659cp = c & 0x0F ; 660 }else if ((c & 0xF8 )== 0xF0 ) { 661len = 4 ; 662cp = c & 0x07 ; 663 }else { 664return false; 665 } 666if (i + len > n ) {return false; } 667for (size_t j = 1 ;j < len ;j ++ ) { 668auto b = static_cast < unsigned char > (s [i + j ]); 669if ((b & 0xC0 )!= 0x80 ) {return false; } 670cp = (cp <<6 ) | (b & 0x3F ); 671 } 672// Overlong encoding check 673if (len == 2 && cp < 0x80 ) {return false; } 674if (len == 3 && cp < 0x800 ) {return false; } 675if (len == 4 && cp < 0x10000 ) {return false; } 676// Surrogate halves (U+D800..U+DFFF) and beyond U+10FFFF are invalid 677if (cp >=0xD800 && cp <=0xDFFF ) {return false; } 678if (cp > 0x10FFFF ) {return false; } 679i += len ; 680 } 681return true; 682} 683 684}// namespace impl 685}// namespace ws 686 687namespace detail { 688 689// NOTE: This code came up with the following stackoverflow post: 690// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c 691std::string base64_encode (const std::string & in ) { 692static const auto lookup = 693"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ; 694 695 std::string out ; 696out .reserve (in .size ()); 697 698// Unsigned: the accumulator is never masked, so with a signed int the 699// `val << 8` below overflows once enough bytes are folded in (undefined 700// behaviour before C++20). Only the low bits are ever emitted, so the 701// wrap-around of an unsigned accumulator does not affect the output. 702uint32_t val = 0 ; 703auto valb = -6 ; 704 705for (auto c :in ) { 706val = (val <<8 )+ static_cast < uint8_t > (c ); 707valb += 8 ; 708while (valb >=0 ) { 709out .push_back (lookup [(val >>valb )& 0x3F ]); 710valb -= 6 ; 711 } 712 } 713 714if (valb > -6 ) {out .push_back (lookup [((val <<8 ) >> (valb + 8 ))& 0x3F ]); } 715 716while (out .size () %4 ) { 717out .push_back ('=' ); 718 } 719 720return out ; 721} 722 723std::string sha1 (const std::string & input ) { 724// RFC 3174 SHA-1 implementation 725auto left_rotate = [](uint32_t x ,uint32_t n )-> uint32_t { 726return (x <<n ) | (x >> (32 - n )); 727 }; 728 729uint32_t h0 = 0x67452301 ; 730uint32_t h1 = 0xEFCDAB89 ; 731uint32_t h2 = 0x98BADCFE ; 732uint32_t h3 = 0x10325476 ; 733uint32_t h4 = 0xC3D2E1F0 ; 734 735// Pre-processing: adding padding bits 736 std::string msg = input ; 737uint64_t original_bit_len = static_cast < uint64_t > (msg .size ())* 8 ; 738msg .push_back (static_cast < char > (0x80u )); 739while (msg .size () %64 != 56 ) { 740msg .push_back (0 ); 741 } 742 743// Append original length in bits as 64-bit big-endian 744for (int i = 56 ;i >=0 ;i -= 8 ) { 745msg .push_back (static_cast < char > ((original_bit_len >>i )& 0xFF )); 746 } 747 748// Process each 512-bit chunk 749for (size_t offset = 0 ;offset < msg .size ();offset += 64 ) { 750uint32_t w [80 ]; 751 752for (size_t i = 0 ;i < 16 ;i ++ ) { 753w [i ]= 754 (static_cast < uint32_t > (static_cast < uint8_t > (msg [offset + i * 4 ])) 755 <<24 ) | 756 (static_cast < uint32_t > (static_cast < uint8_t > (msg [offset + i * 4 + 1 ])) 757 <<16 ) | 758 (static_cast < uint32_t > (static_cast < uint8_t > (msg [offset + i * 4 + 2 ])) 759 <<8 ) | 760 (static_cast < uint32_t > ( 761static_cast < uint8_t > (msg [offset + i * 4 + 3 ]))); 762 } 763 764for (int i = 16 ;i < 80 ;i ++ ) { 765w [i ]= left_rotate (w [i - 3 ] ^w [i - 8 ] ^w [i - 14 ] ^w [i - 16 ],1 ); 766 } 767 768uint32_t a = h0 ,b = h1 ,c = h2 ,d = h3 ,e = h4 ; 769 770for (int i = 0 ;i < 80 ;i ++ ) { 771uint32_t f ,k ; 772if (i < 20 ) { 773f = (b & c ) | ((~b )& d ); 774k = 0x5A827999 ; 775 }else if (i < 40 ) { 776f = b ^c ^d ; 777k = 0x6ED9EBA1 ; 778 }else if (i < 60 ) { 779f = (b & c ) | (b & d ) | (c & d ); 780k = 0x8F1BBCDC ; 781 }else { 782f = b ^c ^d ; 783k = 0xCA62C1D6 ; 784 } 785 786uint32_t temp = left_rotate (a ,5 )+ f + e + k + w [i ]; 787e = d ; 788d = c ; 789c = left_rotate (b ,30 ); 790b = a ; 791a = temp ; 792 } 793 794h0 += a ; 795h1 += b ; 796h2 += c ; 797h3 += d ; 798h4 += e ; 799 } 800 801// Produce the final hash as a 20-byte binary string 802 std::string hash (20 ,'\0' ); 803for (size_t i = 0 ;i < 4 ;i ++ ) { 804hash [i ]= static_cast < char > ((h0 >> (24 - i * 8 ))& 0xFF ); 805hash [4 + i ]= static_cast < char > ((h1 >> (24 - i * 8 ))& 0xFF ); 806hash [8 + i ]= static_cast < char > ((h2 >> (24 - i * 8 ))& 0xFF ); 807hash [12 + i ]= static_cast < char > ((h3 >> (24 - i * 8 ))& 0xFF ); 808hash [16 + i ]= static_cast < char > ((h4 >> (24 - i * 8 ))& 0xFF ); 809 } 810return hash ; 811} 812 813std::string websocket_accept_key (const std::string & client_key ) { 814const std::string magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ; 815return base64_encode (sha1 (client_key + magic )); 816} 817 818bool is_websocket_upgrade (const Request & req ) { 819if (req .method != "GET" ) {return false; } 820 821// Check Upgrade: websocket. RFC 9110 7.8 defines Upgrade as a comma-separated 822// list of protocols and asks recipients to match each name 823// case-insensitively, so look for the token rather than compare the whole 824// field value. 825if (!has_header_token (req .headers ,"Upgrade" ,"websocket" )) {return false; } 826 827// Check Connection: Upgrade 828if (!has_header_token (req .headers ,"Connection" ,"upgrade" )) {return false; } 829 830// Check Sec-WebSocket-Key is a valid base64-encoded 16-byte value (24 chars) 831// RFC 6455 Section 4.2.1 832auto ws_key = req .get_header_value ("Sec-WebSocket-Key" ); 833if (ws_key .size ()!= 24 || ws_key [22 ]!= '=' || ws_key [23 ]!= '=' ) { 834return false; 835 } 836static const std::string b64chars = 837"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ; 838for (size_t i = 0 ;i < 22 ;i ++ ) { 839if (b64chars .find (ws_key [i ])== std::string::npos ) {return false; } 840 } 841 842// Check Sec-WebSocket-Version: 13 843auto version = req .get_header_value ("Sec-WebSocket-Version" ); 844if (version != "13" ) {return false; } 845 846return true; 847} 848 849bool write_websocket_frame (Stream & strm , ws::Opcode opcode , 850const char * data ,size_t len ,bool fin , 851bool mask ) { 852// First byte: FIN + opcode 853uint8_t header [2 ]; 854header [0 ]= static_cast < uint8_t > ((fin ?0x80 :0x00 ) | 855 (static_cast < uint8_t > (opcode )& 0x0F )); 856 857// Second byte: MASK + payload length 858if (len < 126 ) { 859header [1 ]= static_cast < uint8_t > (len ); 860if (mask ) {header [1 ] |=0x80 ; } 861if (strm .write (reinterpret_cast < char *> (header ),2 )< 0 ) {return false; } 862 }else if (len <=0xFFFF ) { 863header [1 ]= 126 ; 864if (mask ) {header [1 ] |=0x80 ; } 865if (strm .write (reinterpret_cast < char *> (header ),2 )< 0 ) {return false; } 866uint8_t ext [2 ]; 867ext [0 ]= static_cast < uint8_t > ((len >>8 )& 0xFF ); 868ext [1 ]= static_cast < uint8_t > (len & 0xFF ); 869if (strm .write (reinterpret_cast < char *> (ext ),2 )< 0 ) {return false; } 870 }else { 871header [1 ]= 127 ; 872if (mask ) {header [1 ] |=0x80 ; } 873if (strm .write (reinterpret_cast < char *> (header ),2 )< 0 ) {return false; } 874uint8_t ext [8 ]; 875for (int i = 7 ;i >=0 ;i -- ) { 876ext [7 - i ]= 877static_cast < uint8_t > ((static_cast < uint64_t > (len ) >> (i * 8 ))& 0xFF ); 878 } 879if (strm .write (reinterpret_cast < char *> (ext ),8 )< 0 ) {return false; } 880 } 881 882if (mask ) { 883// Generate random mask key 884 thread_local std::mt19937 rng (std::random_device {}()); 885uint8_t mask_key [4 ]; 886auto r = rng (); 887 std::memcpy (mask_key ,& r ,4 ); 888if (strm .write (reinterpret_cast < char *> (mask_key ),4 )< 0 ) {return false; } 889 890// Write masked payload in chunks 891const size_t chunk_size = 4096 ; 892 std::vector < char > buf ((std::min )(len ,chunk_size )); 893for (size_t offset = 0 ;offset < len ;offset += chunk_size ) { 894size_t n = (std::min )(chunk_size ,len - offset ); 895for (size_t i = 0 ;i < n ;i ++ ) { 896buf [i ]= 897data [offset + i ] ^static_cast < char > (mask_key [(offset + i ) %4 ]); 898 } 899if (strm .write (buf .data (),n )< 0 ) {return false; } 900 } 901 }else { 902if (len > 0 ) { 903if (strm .write (data ,len )< 0 ) {return false; } 904 } 905 } 906 907return true; 908} 909 910}// namespace detail 911 912namespace ws { 913namespace impl { 914 915// Read exactly `size` bytes. Stream::read may return less than asked for -- it 916// hands back whatever its buffer already holds -- so every multi-byte field has 917// to loop. Reading a 2-byte header with a single read() fails whenever the 918// header straddles the read buffer's boundary. 919// 920// Timeout is reported only when nothing at all was consumed. Once a byte has 921// been taken the stream sits mid-field and cannot be resumed, so a timeout 922// there is a failure like any other. (When read() fails it always records why, 923// so the error belongs to this call and not to an earlier one.) 924FrameRead read_exact (Stream & strm ,void * buf ,size_t size ) { 925auto p = static_cast < char *> (buf ); 926size_t total = 0 ; 927while (total < size ) { 928auto n = strm .read (p + total ,size - total ); 929if (n <=0 ) { 930auto timed_out = total == 0 && strm .get_error ()== Error ::Timeout ; 931return timed_out ?FrameRead ::Timeout :FrameRead ::Fail ; 932 } 933total += static_cast < size_t > (n ); 934 } 935return FrameRead ::Ok ; 936} 937 938FrameRead read_websocket_frame (Stream & strm ,Opcode & opcode , 939 std::string & payload ,bool & fin , 940bool expect_masked ,size_t max_len ) { 941// Read first 2 bytes. This is the only read that may report a timeout: it 942// sits on a frame boundary, where nothing has been consumed yet. 943uint8_t header [2 ]; 944FrameRead first = read_exact (strm ,header ,2 ); 945if (first != FrameRead ::Ok ) {return first ; } 946 947fin = (header [0 ]& 0x80 )!= 0 ; 948 949// RSV1, RSV2, RSV3 must be 0 when no extension is negotiated 950if (header [0 ]& 0x70 ) {return FrameRead ::Fail ; } 951 952opcode = static_cast < Opcode > (header [0 ]& 0x0F ); 953bool masked = (header [1 ]& 0x80 )!= 0 ; 954uint64_t payload_len = header [1 ]& 0x7F ; 955 956// RFC 6455 Section 5.5: control frames MUST NOT be fragmented and 957// MUST have a payload length of 125 bytes or less 958bool is_control = (static_cast < uint8_t > (opcode )& 0x08 )!= 0 ; 959if (is_control ) { 960if (!fin ) {return FrameRead ::Fail ; } 961if (payload_len > 125 ) {return FrameRead ::Fail ; } 962 } 963 964if (masked != expect_masked ) {return FrameRead ::Fail ; } 965 966// Extended payload length 967if (payload_len == 126 ) { 968uint8_t ext [2 ]; 969if (read_exact (strm ,ext ,2 )!= FrameRead ::Ok ) {return FrameRead ::Fail ; } 970payload_len = (static_cast < uint64_t > (ext [0 ]) <<8 ) |ext [1 ]; 971 }else if (payload_len == 127 ) { 972uint8_t ext [8 ]; 973if (read_exact (strm ,ext ,8 )!= FrameRead ::Ok ) {return FrameRead ::Fail ; } 974// RFC 6455 Section 5.2: the most significant bit MUST be 0 975if (ext [0 ]& 0x80 ) {return FrameRead ::Fail ; } 976payload_len = 0 ; 977for (int i = 0 ;i < 8 ;i ++ ) { 978payload_len = (payload_len <<8 ) |ext [i ]; 979 } 980 } 981 982if (payload_len > max_len ) {return FrameRead ::Fail ; } 983 984// Read mask key if present 985uint8_t mask_key [4 ]= {0 }; 986if (masked ) { 987if (read_exact (strm ,mask_key ,4 )!= FrameRead ::Ok ) { 988return FrameRead ::Fail ; 989 } 990 } 991 992// Read payload 993payload .resize (static_cast < size_t > (payload_len )); 994if (payload_len > 0 && 995read_exact (strm ,& payload [0 ],static_cast < size_t > (payload_len ))!= 996FrameRead ::Ok ) { 997return FrameRead ::Fail ; 998 } 999 1000// Unmask if needed 1001if (masked ) { 1002for (size_t i = 0 ;i < payload .size ();i ++ ) { 1003payload [i ] ^=static_cast < char > (mask_key [i %4 ]); 1004 } 1005 } 1006 1007return FrameRead ::Ok ; 1008} 1009 1010}// namespace impl 1011}// namespace ws 1012 1013namespace detail { 1014 1015bool is_valid_path (const std::string & path ) { 1016size_t level = 0 ; 1017size_t i = 0 ; 1018 1019// Skip slash 1020while (i < path .size ()&& path [i ]== '/' ) { 1021i ++ ; 1022 } 1023 1024while (i < path .size ()) { 1025// Read component 1026auto beg = i ; 1027while (i < path .size ()&& path [i ]!= '/' ) { 1028if (path [i ]== '\0' ) { 1029return false; 1030 }else if (path [i ]== '\\' ) { 1031return false; 1032 } 1033i ++ ; 1034 } 1035 1036auto len = i - beg ; 1037assert (len > 0 ); 1038 1039if (!path .compare (beg ,len ,"." )) { 1040 ; 1041 }else if (!path .compare (beg ,len ,".." )) { 1042if (level == 0 ) {return false; } 1043level -- ; 1044 }else { 1045level ++ ; 1046 } 1047 1048// Skip slash 1049while (i < path .size ()&& path [i ]== '/' ) { 1050i ++ ; 1051 } 1052 } 1053 1054return true; 1055} 1056 1057bool canonicalize_path (const char * path , std::string & resolved ) { 1058#if defined(_WIN32 ) 1059char buf [_MAX_PATH ]; 1060if (_fullpath (buf ,path ,_MAX_PATH )== nullptr ) {return false; } 1061resolved = buf ; 1062#elif defined(PATH_MAX ) 1063char buf [PATH_MAX ]; 1064if (realpath (path ,buf )== nullptr ) {return false; } 1065resolved = buf ; 1066#else 1067auto buf = realpath (path ,nullptr ); 1068auto guard = scope_exit ([& ]() { std::free (buf ); }); 1069if (buf == nullptr ) {return false; } 1070resolved = buf ; 1071#endif 1072return true; 1073} 1074 1075bool is_path_within_base (const std::string & resolved_path , 1076const std::string & resolved_base ) { 1077#if defined(_WIN32 ) 1078return _strnicmp (resolved_path .c_str (),resolved_base .c_str (), 1079resolved_base .size ())== 0 ; 1080#else 1081return strncmp (resolved_path .c_str (),resolved_base .c_str (), 1082resolved_base .size ())== 0 ; 1083#endif 1084} 1085 1086FileStat ::FileStat (const std::string & path ) { 1087#if defined(_WIN32 ) 1088auto wpath = u8string_to_wstring (path .c_str ()); 1089ret_ = _wstat (wpath .c_str (),& st_ ); 1090#else 1091ret_ = stat (path .c_str (),& st_ ); 1092#endif 1093} 1094bool FileStat ::is_file ()const { 1095return ret_ >=0 && S_ISREG (st_ .st_mode ); 1096} 1097bool FileStat ::is_dir ()const { 1098return ret_ >=0 && S_ISDIR (st_ .st_mode ); 1099} 1100 1101time_t FileStat ::mtime ()const { 1102return ret_ >=0 ?static_cast < time_t > (st_ .st_mtime ) 1103 :static_cast < time_t > (-1 ); 1104} 1105 1106size_t FileStat ::size ()const { 1107return ret_ >=0 ?static_cast < size_t > (st_ .st_size ) :0 ; 1108} 1109 1110std::string encode_path (const std::string & s ) { 1111 std::string result ; 1112result .reserve (s .size ()); 1113 1114for (size_t i = 0 ;s [i ];i ++ ) { 1115switch (s [i ]) { 1116case ' ' :result += "%20" ;break ; 1117case '+' :result += "%2B" ;break ; 1118case '\r' :result += "%0D" ;break ; 1119case '\n' :result += "%0A" ;break ; 1120case '\'' :result += "%27" ;break ; 1121case ',' :result += "%2C" ;break ; 1122// case ':': result += "%3A"; break; // ok? probably... 1123case ';' :result += "%3B" ;break ; 1124default : 1125auto c = static_cast < uint8_t > (s [i ]); 1126if (c >=0x80 ) { 1127result += '%' ; 1128char hex [4 ]; 1129auto len = snprintf (hex ,sizeof (hex )- 1 ,"%02X" ,c ); 1130assert (len == 2 ); 1131result .append (hex ,static_cast < size_t > (len )); 1132 }else { 1133result += s [i ]; 1134 } 1135break ; 1136 } 1137 } 1138 1139return result ; 1140} 1141 1142std::string file_extension (const std::string & path ) { 1143 std::smatch m ; 1144 thread_localauto re = std::regex ("\\.([a-zA-Z0-9]+)$" ); 1145if (std::regex_search (path ,m ,re )) {return m [1 ].str (); } 1146return std::string (); 1147} 1148 1149bool is_space_or_tab (char c ) {return c == ' ' || c == '\t' ; } 1150 1151template < typename T > 1152bool parse_header (const char * beg ,const char * end ,T fn ); 1153 1154template < typename T > 1155bool parse_header (const char * beg ,const char * end ,T fn ) { 1156// Skip trailing spaces and tabs. 1157while (beg < end && is_space_or_tab (end [-1 ])) { 1158end -- ; 1159 } 1160 1161auto p = beg ; 1162while (p < end && * p != ':' ) { 1163p ++ ; 1164 } 1165 1166auto name = std::string (beg ,p ); 1167if (!detail::fields::is_field_name (name )) {return false; } 1168 1169if (p == end ) {return false; } 1170 1171auto key_end = p ; 1172 1173if (* p ++ != ':' ) {return false; } 1174 1175while (p < end && is_space_or_tab (* p )) { 1176p ++ ; 1177 } 1178 1179if (p <=end ) { 1180auto key_len = key_end - beg ; 1181if (!key_len ) {return false; } 1182 1183auto key = std::string (beg ,key_end ); 1184auto val = std::string (p ,end ); 1185 1186if (!detail::fields::is_field_value (val )) {return false; } 1187 1188// RFC 9110 §5.5: header field values are opaque octets and MUST NOT be 1189// percent-decoded by the recipient. Applications that need to interpret a 1190// value as a URI component should call httplib::decode_uri_component() 1191// (or decode_path_component()) explicitly. 1192fn (key ,val ); 1193 1194return true; 1195 } 1196 1197return false; 1198} 1199 1200bool parse_trailers (stream_line_reader & line_reader ,Headers & dest , 1201const Headers & src_headers ) { 1202// NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentions "The chunked 1203// transfer coding is complete when a chunk with a chunk-size of zero is 1204// received, possibly followed by a trailer section, and finally terminated by 1205// an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1 1206// 1207// In '7.1.3. Decoding Chunked', however, the pseudo-code in the section 1208// doesn't care for the existence of the final CRLF. In other words, it seems 1209// to be ok whether the final CRLF exists or not in the chunked data. 1210// https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3 1211// 1212// According to the reference code in RFC 9112, cpp-httplib now allows 1213// chunked transfer coding data without the final CRLF. 1214 1215// RFC 7230 Section 4.1.2 - Headers prohibited in trailers 1216 thread_local case_ignore::unordered_set < std::string > prohibited_trailers = { 1217"transfer-encoding" , 1218"content-length" , 1219"host" , 1220"authorization" , 1221"www-authenticate" , 1222"proxy-authenticate" , 1223"proxy-authorization" , 1224"cookie" , 1225"set-cookie" , 1226"cache-control" , 1227"expect" , 1228"max-forwards" , 1229"pragma" , 1230"range" , 1231"te" , 1232"age" , 1233"expires" , 1234"date" , 1235"location" , 1236"retry-after" , 1237"vary" , 1238"warning" , 1239"content-encoding" , 1240"content-type" , 1241"content-range" , 1242"trailer" }; 1243 1244 case_ignore::unordered_set < std::string > declared_trailers ; 1245auto trailer_header = get_combined_header_value (src_headers ,"Trailer" ); 1246if (!trailer_header .empty ()) { 1247// split() trims each token and skips empty ones, so the name arrives ready 1248// to look up. 1249split (trailer_header .data (),trailer_header .data ()+ trailer_header .size (), 1250',' , [& ](const char * b ,const char * e ) { 1251 std::string key (b ,e ); 1252if (prohibited_trailers .find (key )== prohibited_trailers .end ()) { 1253declared_trailers .insert (key ); 1254 } 1255 }); 1256 } 1257 1258size_t trailer_header_count = 0 ; 1259while (strcmp (line_reader .ptr (),"\r\n" )!= 0 ) { 1260if (line_reader .size ()> CPPHTTPLIB_HEADER_MAX_LENGTH ) {return false; } 1261if (trailer_header_count >=CPPHTTPLIB_HEADER_MAX_COUNT ) {return false; } 1262 1263constexpr auto line_terminator_len = 2 ; 1264auto line_beg = line_reader .ptr (); 1265auto line_end = 1266line_reader .ptr ()+ line_reader .size ()- line_terminator_len ; 1267 1268if (!parse_header (line_beg ,line_end , 1269 [& ](const std::string & key ,const std::string & val ) { 1270if (declared_trailers .find (key )!= 1271declared_trailers .end ()) { 1272dest .emplace (key ,val ); 1273trailer_header_count ++ ; 1274 } 1275 })) { 1276return false; 1277 } 1278 1279if (!line_reader .getline ()) {return false; } 1280 } 1281 1282return true; 1283} 1284 1285std::pair < size_t ,size_t > trim (const char * b ,const char * e ,size_t left , 1286size_t right ) { 1287while (b + left < e && is_space_or_tab (b [left ])) { 1288left ++ ; 1289 } 1290while (right > 0 && is_space_or_tab (b [right - 1 ])) { 1291right -- ; 1292 } 1293return std::make_pair (left ,right ); 1294} 1295 1296std::string trim_copy (const std::string & s ) { 1297auto r = trim (s .data (),s .data ()+ s .size (),0 ,s .size ()); 1298return s .substr (r .first ,r .second - r .first ); 1299} 1300 1301std::string trim_double_quotes_copy (const std::string & s ) { 1302if (s .length () >=2 && s .front ()== '"' && s .back ()== '"' ) { 1303return s .substr (1 ,s .size ()- 2 ); 1304 } 1305return s ; 1306} 1307 1308void 1309divide (const char * data , std::size_t size ,char d , 1310 std::function < void (const char * , std::size_t ,const char * , std::size_t )> 1311fn ) { 1312const auto it = std::find (data ,data + size ,d ); 1313const auto found = static_cast < std::size_t > (it != data + size ); 1314const auto lhs_data = data ; 1315const auto lhs_size = static_cast < std::size_t > (it - data ); 1316const auto rhs_data = it + found ; 1317const auto rhs_size = size - lhs_size - found ; 1318 1319fn (lhs_data ,lhs_size ,rhs_data ,rhs_size ); 1320} 1321 1322void 1323divide (const std::string & str ,char d , 1324 std::function < void (const char * , std::size_t ,const char * , std::size_t )> 1325fn ) { 1326divide (str .data (),str .size (),d , std::move (fn )); 1327} 1328 1329void split (const char * b ,const char * e ,char d , 1330 std::function < void (const char * ,const char * )> fn ) { 1331return split (b ,e ,d , (std::numeric_limits < size_t > ::max )(), std::move (fn )); 1332} 1333 1334void split (const char * b ,const char * e ,char d ,size_t m , 1335 std::function < void (const char * ,const char * )> fn ) { 1336size_t i = 0 ; 1337size_t beg = 0 ; 1338size_t count = 1 ; 1339 1340while (e ? (b + i < e ) : (b [i ]!= '\0' )) { 1341if (b [i ]== d && count < m ) { 1342auto r = trim (b ,e ,beg ,i ); 1343if (r .first < r .second ) {fn (& b [r .first ],& b [r .second ]); } 1344beg = i + 1 ; 1345count ++ ; 1346 } 1347i ++ ; 1348 } 1349 1350if (i ) { 1351auto r = trim (b ,e ,beg ,i ); 1352if (r .first < r .second ) {fn (& b [r .first ],& b [r .second ]); } 1353 } 1354} 1355 1356// Same contract as split(), except that a delimiter inside a quoted-string is 1357// not a delimiter. RFC 9110 Section 5.6.6 lets a parameter value be a 1358// quoted-string, and ';' and '=' are legal characters inside one. 1359void split_unquoted (const char * b ,const char * e ,char d ,size_t m , 1360 std::function < void (const char * ,const char * )> fn ) { 1361size_t i = 0 ; 1362size_t beg = 0 ; 1363size_t count = 1 ; 1364auto in_quotes = false; 1365 1366while (e ? (b + i < e ) : (b [i ]!= '\0' )) { 1367if (b [i ]== '"' ) { 1368in_quotes = !in_quotes ; 1369 }else if (b [i ]== d && !in_quotes && count < m ) { 1370auto r = trim (b ,e ,beg ,i ); 1371if (r .first < r .second ) {fn (& b [r .first ],& b [r .second ]); } 1372beg = i + 1 ; 1373count ++ ; 1374 } 1375i ++ ; 1376 } 1377 1378if (i ) { 1379auto r = trim (b ,e ,beg ,i ); 1380if (r .first < r .second ) {fn (& b [r .first ],& b [r .second ]); } 1381 } 1382} 1383 1384void split_unquoted (const char * b ,const char * e ,char d , 1385 std::function < void (const char * ,const char * )> fn ) { 1386return split_unquoted (b ,e ,d , (std::numeric_limits < size_t > ::max )(), 1387 std::move (fn )); 1388} 1389 1390// Divide a header parameter at its first '='. RFC 9110 Section 5.6.6 makes the 1391// key a token, so the first '=' is the separator even when the value is a 1392// quoted-string carrying more of them. 1393void divide_param_pair (const char * b ,const char * e , std::string & key , 1394 std::string & val ) { 1395divide ( 1396b ,static_cast < std::size_t > (e - b ),'=' , 1397 [& ](const char * kb , std::size_t klen ,const char * vb , std::size_t vlen ) { 1398const auto kr = trim (kb ,kb + klen ,0 ,klen ); 1399key .assign (kb + kr .first ,kb + kr .second ); 1400const auto vr = trim (vb ,vb + vlen ,0 ,vlen ); 1401val .assign (vb + vr .first ,vb + vr .second ); 1402 }); 1403} 1404 1405bool split_find (const char * b ,const char * e ,char d ,size_t m , 1406 std::function < bool (const char * ,const char * )> fn ) { 1407size_t i = 0 ; 1408size_t beg = 0 ; 1409size_t count = 1 ; 1410 1411while (e ? (b + i < e ) : (b [i ]!= '\0' )) { 1412if (b [i ]== d && count < m ) { 1413auto r = trim (b ,e ,beg ,i ); 1414if (r .first < r .second ) { 1415auto found = fn (& b [r .first ],& b [r .second ]); 1416if (found ) {return true; } 1417 } 1418beg = i + 1 ; 1419count ++ ; 1420 } 1421i ++ ; 1422 } 1423 1424if (i ) { 1425auto r = trim (b ,e ,beg ,i ); 1426if (r .first < r .second ) { 1427auto found = fn (& b [r .first ],& b [r .second ]); 1428if (found ) {return true; } 1429 } 1430 } 1431 1432return false; 1433} 1434 1435bool split_find (const char * b ,const char * e ,char d , 1436 std::function < bool (const char * ,const char * )> fn ) { 1437return split_find (b ,e ,d , (std::numeric_limits < size_t > ::max )(), 1438 std::move (fn )); 1439} 1440 1441stream_line_reader::stream_line_reader (Stream & strm ,char * fixed_buffer , 1442size_t fixed_buffer_size ) 1443 :strm_ (strm ),fixed_buffer_ (fixed_buffer ), 1444fixed_buffer_size_ (fixed_buffer_size ) {} 1445 1446const char * stream_line_reader::ptr ()const { 1447if (growable_buffer_ .empty ()) { 1448return fixed_buffer_ ; 1449 }else { 1450return growable_buffer_ .data (); 1451 } 1452} 1453 1454size_t stream_line_reader::size ()const { 1455if (growable_buffer_ .empty ()) { 1456return fixed_buffer_used_size_ ; 1457 }else { 1458return growable_buffer_ .size (); 1459 } 1460} 1461 1462bool stream_line_reader::end_with_crlf ()const { 1463auto end = ptr ()+ size (); 1464return size () >=2 && end [-2 ]== '\r' && end [-1 ]== '\n' ; 1465} 1466 1467bool stream_line_reader::getline () { 1468fixed_buffer_used_size_ = 0 ; 1469growable_buffer_ .clear (); 1470 1471#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR 1472char prev_byte = 0 ; 1473#endif 1474 1475for (size_t i = 0 ;;i ++ ) { 1476// Fast path: whatever the stream has already buffered can be scanned for 1477// the terminator in one pass. Asking for a byte at a time costs a virtual 1478// call, a bounds check and a one-byte copy per character of the request. 1479size_t buffered_size = 0 ; 1480if (auto buffered = strm_ .buffered_data (buffered_size )) { 1481auto take = buffered_size ; 1482auto terminated = false; 1483 1484for (size_t at = 0 ;at < buffered_size ;) { 1485auto nl = static_cast < const char *> ( 1486memchr (buffered + at ,'\n' ,buffered_size - at )); 1487if (!nl ) {break ; } 1488auto pos = static_cast < size_t > (nl - buffered ); 1489#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR 1490take = pos + 1 ; 1491terminated = true; 1492break ; 1493#else 1494// A bare LF does not end the line; keep looking for CRLF. The CR may 1495// be the last byte of an earlier chunk, hence prev_byte. 1496if ((pos > 0 ?buffered [pos - 1 ] :prev_byte )== '\r' ) { 1497take = pos + 1 ; 1498terminated = true; 1499break ; 1500 } 1501at = pos + 1 ; 1502#endif 1503 } 1504 1505if (size ()+ take > CPPHTTPLIB_MAX_LINE_LENGTH ) {return false; } 1506#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR 1507prev_byte = buffered [take - 1 ]; 1508#endif 1509append (buffered ,take ); 1510strm_ .consume_buffered (take ); 1511i += take ; 1512if (terminated ) {return true; } 1513continue ; 1514 } 1515 1516if (size () >=CPPHTTPLIB_MAX_LINE_LENGTH ) { 1517// Treat exceptionally long lines as an error to 1518// prevent infinite loops/memory exhaustion 1519return false; 1520 } 1521char byte ; 1522auto n = strm_ .read (& byte ,1 ); 1523 1524if (n < 0 ) { 1525return false; 1526 }else if (n == 0 ) { 1527if (i == 0 ) { 1528return false; 1529 }else { 1530break ; 1531 } 1532 } 1533 1534append (byte ); 1535 1536#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR 1537if (byte == '\n' ) {break ; } 1538#else 1539if (prev_byte == '\r' && byte == '\n' ) {break ; } 1540prev_byte = byte ; 1541#endif 1542 } 1543 1544return true; 1545} 1546 1547void stream_line_reader::append (char c ) {append (& c ,1 ); } 1548 1549void stream_line_reader::append (const char * data ,size_t size ) { 1550// Once the line has outgrown the fixed buffer everything must keep going to 1551// the growable one, even if a later chunk would have fit. Without the 1552// emptiness check a short append after a long one would land in the fixed 1553// buffer, which ptr() and size() no longer look at, and be lost. 1554if (growable_buffer_ .empty ()&& 1555fixed_buffer_used_size_ + size < fixed_buffer_size_ ) { 1556memcpy (fixed_buffer_ + fixed_buffer_used_size_ ,data ,size ); 1557fixed_buffer_used_size_ += size ; 1558fixed_buffer_ [fixed_buffer_used_size_ ]= '\0' ; 1559 }else { 1560// Unlike the per-character overload, this can be the very first append of 1561// the line, so the fixed buffer may hold nothing and carry no terminator 1562// yet. assign() takes an explicit length and does not need one. 1563if (growable_buffer_ .empty ()) { 1564growable_buffer_ .assign (fixed_buffer_ ,fixed_buffer_used_size_ ); 1565 } 1566growable_buffer_ .append (data ,size ); 1567 } 1568} 1569 1570mmap::mmap (const char * path ) {open (path ); } 1571 1572mmap::~mmap () {close (); } 1573 1574bool mmap::open (const char * path ) { 1575close (); 1576 1577#if defined(_WIN32 ) 1578auto wpath = u8string_to_wstring (path ); 1579if (wpath .empty ()) {return false; } 1580 1581hFile_ = 1582 ::CreateFile2 (wpath .c_str (),GENERIC_READ , 1583FILE_SHARE_READ |FILE_SHARE_WRITE ,OPEN_EXISTING ,NULL ); 1584 1585if (hFile_ == INVALID_HANDLE_VALUE ) {return false; } 1586 1587LARGE_INTEGER size {}; 1588if (!::GetFileSizeEx (hFile_ ,& size )) {return false; } 1589// If the following line doesn't compile due to QuadPart, update Windows SDK. 1590// See: 1591// https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721 1592if (static_cast < ULONGLONG > (size .QuadPart )> 1593 (std::numeric_limits < decltype(size_ )> ::max )()) { 1594// `size_t` might be 32-bits, on 32-bits Windows. 1595return false; 1596 } 1597size_ = static_cast < size_t > (size .QuadPart ); 1598 1599hMapping_ = 1600 ::CreateFileMappingFromApp (hFile_ ,NULL ,PAGE_READONLY ,size_ ,NULL ); 1601 1602// Special treatment for an empty file... 1603if (hMapping_ == NULL && size_ == 0 ) { 1604close (); 1605is_open_empty_file = true; 1606return true; 1607 } 1608 1609if (hMapping_ == NULL ) { 1610close (); 1611return false; 1612 } 1613 1614addr_ = ::MapViewOfFileFromApp (hMapping_ ,FILE_MAP_READ ,0 ,0 ); 1615 1616if (addr_ == nullptr ) { 1617close (); 1618return false; 1619 } 1620#else 1621fd_ = ::open (path ,O_RDONLY ); 1622if (fd_ == -1 ) {return false; } 1623 1624struct stat sb ; 1625if (fstat (fd_ ,& sb )== -1 ) { 1626close (); 1627return false; 1628 } 1629size_ = static_cast < size_t > (sb .st_size ); 1630 1631addr_ = ::mmap (NULL ,size_ ,PROT_READ ,MAP_PRIVATE ,fd_ ,0 ); 1632 1633// Special treatment for an empty file... 1634if (addr_ == MAP_FAILED && size_ == 0 ) { 1635close (); 1636is_open_empty_file = true; 1637return false; 1638 } 1639 1640if (addr_ == MAP_FAILED ) { 1641// Clear the sentinel before `close()`, since `is_open()` only checks 1642// `addr_` against nullptr and `munmap()` must not be called with it. 1643addr_ = nullptr ; 1644close (); 1645return false; 1646 } 1647#endif 1648 1649return true; 1650} 1651 1652bool mmap::is_open ()const { 1653return is_open_empty_file ? true :addr_ != nullptr ; 1654} 1655 1656size_t mmap::size ()const {return size_ ; } 1657 1658const char * mmap::data ()const { 1659return is_open_empty_file ?"" :static_cast < const char *> (addr_ ); 1660} 1661 1662void mmap::close () { 1663#if defined(_WIN32 ) 1664if (addr_ ) { 1665 ::UnmapViewOfFile (addr_ ); 1666addr_ = nullptr ; 1667 } 1668 1669if (hMapping_ ) { 1670 ::CloseHandle (hMapping_ ); 1671hMapping_ = NULL ; 1672 } 1673 1674if (hFile_ != INVALID_HANDLE_VALUE ) { 1675 ::CloseHandle (hFile_ ); 1676hFile_ = INVALID_HANDLE_VALUE ; 1677 } 1678 1679is_open_empty_file = false; 1680#else 1681if (addr_ != nullptr ) { 1682munmap (addr_ ,size_ ); 1683addr_ = nullptr ; 1684 } 1685 1686if (fd_ != -1 ) { 1687 ::close (fd_ ); 1688fd_ = -1 ; 1689 } 1690#endif 1691size_ = 0 ; 1692} 1693int close_socket (socket_t sock )noexcept { 1694#ifdef _WIN32 1695return closesocket (sock ); 1696#else 1697return close (sock ); 1698#endif 1699} 1700 1701template < typename T > ssize_t handle_EINTR (T fn ) { 1702ssize_t res = 0 ; 1703while (true) { 1704res = fn (); 1705if (res < 0 && errno == EINTR ) { 1706 std::this_thread::sleep_for (std::chrono::microseconds {1 }); 1707continue ; 1708 } 1709break ; 1710 } 1711return res ; 1712} 1713 1714ssize_t read_socket (socket_t sock ,void * ptr ,size_t size ,int flags ) { 1715return handle_EINTR ([& ]() { 1716return recv (sock , 1717#ifdef _WIN32 1718static_cast < char *> (ptr ),static_cast < int > (size ), 1719#else 1720ptr ,size , 1721#endif 1722flags ); 1723}); 1724} 1725 1726ssize_t send_socket ( socket_t sock , const void * ptr , size_t size , 1727int flags ) { 1728return handle_EINTR ([ & ]() { 1729return send ( sock , 1730#ifdef _WIN32 1731static_cast < const char *> ( ptr ), static_cast < int > ( size ), 1732#else 1733ptr , size , 1734#endif 1735flags ); 1736}); 1737} 1738 1739int poll_wrapper (struct pollfd * fds , nfds_t nfds , int timeout ) { 1740#ifdef _WIN32 1741return :: WSAPoll ( fds , nfds , timeout ); 1742#else 1743return :: poll ( fds , nfds , timeout ); 1744#endif 1745} 1746 1747ssize_t select_impl ( socket_t sock , short events , time_t sec , 1748time_t usec ) { 1749struct pollfd pfd ; 1750pfd . fd = sock ; 1751pfd . events = events ; 1752pfd . revents = 0 ; 1753 1754// A negative timeout waits forever, poll's own convention. 0 keeps meaning 1755// "return immediately", which callers here rely on to probe a socket. 1756auto timeout = sec < 0 ? -1 : static_cast < int > ( sec * 1000 + usec / 1000 ); 1757 1758return handle_EINTR ([ & ]() { return poll_wrapper ( & pfd , 1 , timeout ); }); 1759} 1760 1761ssize_t select_read ( socket_t sock , time_t sec , time_t usec ) { 1762return select_impl ( sock , POLLIN , sec , usec ); 1763} 1764 1765ssize_t select_write ( socket_t sock , time_t sec , time_t usec ) { 1766return select_impl ( sock , POLLOUT , sec , usec ); 1767} 1768 1769Error wait_until_socket_is_ready ( socket_t sock , time_t sec , 1770time_t usec ) { 1771struct pollfd pfd_read ; 1772pfd_read . fd = sock ; 1773pfd_read . events = POLLIN | POLLOUT ; 1774pfd_read . revents = 0 ; 1775 1776auto timeout = static_cast < int > ( sec * 1000 + usec / 1000 ); 1777 1778auto poll_res = 1779handle_EINTR ([ & ]() { return poll_wrapper ( & pfd_read , 1 , timeout ); }); 1780 1781if ( poll_res == 0 ) { return Error :: ConnectionTimeout ; } 1782 1783if ( poll_res > 0 && pfd_read . revents & ( POLLIN | POLLOUT )) { 1784auto error = 0 ; 1785socklen_t len = sizeof( error ); 1786auto res = getsockopt ( sock , SOL_SOCKET , SO_ERROR , 1787reinterpret_cast < char *> ( & error ), & len ); 1788auto successful = res >= 0 && ! error ; 1789return successful ? Error :: Success : Error :: Connection ; 1790} 1791 1792return Error :: Connection ; 1793} 1794 1795bool is_socket_alive ( socket_t sock ) { 1796const auto val = detail:: select_read ( sock , 0 , 0 ); 1797if ( val == 0 ) { 1798return true; 1799} else if ( val < 0 && errno == EBADF ) { 1800return false; 1801} 1802char buf [ 1 ]; 1803return detail:: read_socket ( sock , & buf [ 0 ], sizeof( buf ), MSG_PEEK ) > 0 ; 1804} 1805 1806class SocketStream final : public Stream { 1807public: 1808SocketStream ( socket_t sock , time_t read_timeout_sec , time_t read_timeout_usec , 1809time_t write_timeout_sec , time_t write_timeout_usec , 1810time_t max_timeout_msec = 0 , 1811std::chrono:: time_point < std::chrono:: steady_clock > start_time = 1812(std::chrono::steady_clock::time_point:: min )()); 1813~ SocketStream () override; 1814 1815bool is_readable () const override; 1816bool wait_readable () const override; 1817bool wait_writable () const override; 1818bool is_peer_alive () const override; 1819ssize_t read ( char * ptr , size_t size ) override; 1820ssize_t write (const char * ptr , size_t size ) override; 1821void get_remote_ip_and_port (std:: string & ip , int & port ) const override; 1822void get_local_ip_and_port (std:: string & ip , int & port ) const override; 1823socket_t socket () const override; 1824time_t duration () const override; 1825void set_read_timeout ( time_t sec , time_t usec = 0 ) override; 1826const char * buffered_data ( size_t & size ) const override; 1827void consume_buffered ( size_t size ) override; 1828 1829// The caller has just seen this socket become readable. Lets the next read 1830// skip its own readiness wait, which would otherwise ask the kernel a 1831// question that was answered a moment ago. Consumed by that read. 1832void set_readable_hint () { readable_hint_ = true; } 1833 1834private: 1835bool ensure_readable (); 1836 1837socket_t sock_ ; 1838// Atomic because ws::WebSocket::set_read_timeout() reaches this from another 1839// thread while a read is in flight -- that is the point of it, for a caller 1840// holding one connection and wanting control back to send on it. 1841std:: atomic < time_t > read_timeout_sec_ ; 1842std:: atomic < time_t > read_timeout_usec_ ; 1843time_t write_timeout_sec_ ; 1844time_t write_timeout_usec_ ; 1845time_t max_timeout_msec_ ; 1846const std::chrono:: time_point < std::chrono:: steady_clock > start_time_ ; 1847 1848std:: vector < char > read_buff_ ; 1849size_t read_buff_off_ = 0 ; 1850size_t read_buff_content_size_ = 0 ; 1851bool readable_hint_ = false; 1852 1853static const size_t read_buff_size_ = 1024l * 4 ; 1854}; 1855 1856bool keep_alive (const std:: atomic < socket_t > & svr_sock , socket_t sock , 1857time_t keep_alive_timeout_sec ) { 1858using namespace std:: chrono ; 1859 1860const auto interval_usec = 1861CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND ; 1862 1863// Avoid expensive `steady_clock::now()` call for the first time 1864if ( select_read ( sock , 0 , interval_usec ) > 0 ) { return true; } 1865 1866const auto start = steady_clock:: now () - microseconds { interval_usec }; 1867const auto timeout = seconds { keep_alive_timeout_sec }; 1868 1869while (true) { 1870if ( svr_sock == INVALID_SOCKET ) { 1871break; // Server socket is closed 1872} 1873 1874auto val = select_read ( sock , 0 , interval_usec ); 1875if ( val < 0 ) { 1876break; // Ssocket error 1877} else if ( val == 0 ) { 1878if (steady_clock:: now () - start > timeout ) { 1879break; // Timeout 1880} 1881} else { 1882return true; // Ready for read 1883} 1884} 1885 1886return false; 1887} 1888 1889template < typename T > 1890bool 1891process_server_socket_core (const std:: atomic < socket_t > & svr_sock , socket_t sock , 1892size_t keep_alive_max_count , 1893time_t keep_alive_timeout_sec , T callback ) { 1894assert ( keep_alive_max_count > 0 ); 1895auto ret = false; 1896auto count = keep_alive_max_count ; 1897while ( count > 0 && keep_alive ( svr_sock , sock , keep_alive_timeout_sec )) { 1898auto close_connection = count == 1 ; 1899auto connection_closed = false; 1900ret = callback ( close_connection , connection_closed ); 1901if (! ret || connection_closed ) { break; } 1902count -- ; 1903} 1904return ret ; 1905} 1906 1907template < typename T > 1908bool 1909process_server_socket (const std:: atomic < socket_t > & svr_sock , socket_t sock , 1910size_t keep_alive_max_count , 1911time_t keep_alive_timeout_sec , time_t read_timeout_sec , 1912time_t read_timeout_usec , time_t write_timeout_sec , 1913time_t write_timeout_usec , T callback ) { 1914return process_server_socket_core ( 1915svr_sock , sock , keep_alive_max_count , keep_alive_timeout_sec , 1916[ & ]( bool close_connection , bool & connection_closed ) { 1917SocketStream strm ( sock , read_timeout_sec , read_timeout_usec , 1918write_timeout_sec , write_timeout_usec ); 1919// process_server_socket_core() only gets here once keep_alive() has 1920// seen the socket go readable. 1921strm . set_readable_hint (); 1922return callback ( strm , close_connection , connection_closed ); 1923}); 1924} 1925 1926bool process_client_socket ( 1927socket_t sock , time_t read_timeout_sec , time_t read_timeout_usec , 1928time_t write_timeout_sec , time_t write_timeout_usec , 1929time_t max_timeout_msec , 1930std::chrono:: time_point < std::chrono:: steady_clock > start_time , 1931std:: function < bool ( Stream & ) > callback ) { 1932SocketStream strm ( sock , read_timeout_sec , read_timeout_usec , 1933write_timeout_sec , write_timeout_usec , max_timeout_msec , 1934start_time ); 1935return callback ( strm ); 1936} 1937 1938int shutdown_socket ( socket_t sock ) noexcept { 1939#ifdef _WIN32 1940return shutdown ( sock , SD_BOTH ); 1941#else 1942return shutdown ( sock , SHUT_RDWR ); 1943#endif 1944} 1945 1946// Half-closes the write side and drains any in-flight/queued bytes before 1947// the final shutdown+close. Closing with unread data in the receive queue 1948// (or bytes arriving after the receive side is closed) makes the stack send 1949// an abortive RST instead of a graceful FIN, which can make the peer see the 1950// response as a failed read even though it was fully written. 1951void drain_and_close_socket ( socket_t sock ) noexcept { 1952#ifdef _WIN32 1953shutdown ( sock , SD_SEND ); 1954#else 1955shutdown ( sock , SHUT_WR ); 1956#endif 1957 1958char buf [ CPPHTTPLIB_RECV_BUFSIZ ]; 1959size_t total = 0 ; 1960const auto deadline = std::chrono::steady_clock:: now () + 1961std::chrono:: milliseconds ( 100 ); // bound #1 1962 1963while ( total < size_t ( 1024u * 1024u )) { // bound #2 1964const auto remaining = 1965std::chrono:: duration_cast < std::chrono:: microseconds > ( 1966deadline - std::chrono::steady_clock:: now ()) 1967. count (); 1968if ( remaining <= 0 ) { break; } 1969if ( select_read ( sock , 0 , static_cast < time_t > ( remaining )) <= 0 ) { break; } 1970const auto n = read_socket ( sock , buf , sizeof( buf ), CPPHTTPLIB_RECV_FLAGS ); 1971if ( n <= 0 ) { break; } 1972total += static_cast < size_t > ( n ); 1973} 1974 1975shutdown_socket ( sock ); 1976close_socket ( sock ); 1977} 1978 1979std:: string escape_abstract_namespace_unix_domain (const std:: string & s ) { 1980if ( s . size () > 1 && s [ 0 ] == '\0' ) { 1981auto ret = s ; 1982ret [ 0 ] = '@' ; 1983return ret ; 1984} 1985return s ; 1986} 1987 1988std:: string 1989unescape_abstract_namespace_unix_domain (const std:: string & s ) { 1990if ( s . size () > 1 && s [ 0 ] == '@' ) { 1991auto ret = s ; 1992ret [ 0 ] = '\0' ; 1993return ret ; 1994} 1995return s ; 1996} 1997 1998int getaddrinfo_with_timeout (const char * node , const char * service , 1999const struct addrinfo * hints , 2000struct addrinfo ** res , time_t timeout_sec ) { 2001#ifdef CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO 2002if ( timeout_sec <= 0 ) { 2003// No timeout specified, use standard getaddrinfo 2004return getaddrinfo ( node , service , hints , res ); 2005} 2006 2007#ifdef _WIN32 2008// Windows-specific implementation using GetAddrInfoEx with overlapped I/O 2009OVERLAPPED overlapped = {}; 2010HANDLE event = CreateEventW ( nullptr , TRUE, FALSE, nullptr ); 2011if (! event ) { return EAI_FAIL ; } 2012 2013overlapped . hEvent = event ; 2014 2015PADDRINFOEXW result_addrinfo = nullptr ; 2016HANDLE cancel_handle = nullptr ; 2017 2018ADDRINFOEXW hints_ex = {}; 2019if ( hints ) { 2020hints_ex . ai_flags = hints -> ai_flags ; 2021hints_ex . ai_family = hints -> ai_family ; 2022hints_ex . ai_socktype = hints -> ai_socktype ; 2023hints_ex . ai_protocol = hints -> ai_protocol ; 2024} 2025 2026auto wnode = u8string_to_wstring ( node ); 2027auto wservice = u8string_to_wstring ( service ); 2028 2029auto ret = :: GetAddrInfoExW ( wnode . data (), wservice . data (), NS_DNS , nullptr , 2030hints ? & hints_ex : nullptr , & result_addrinfo , 2031nullptr , & overlapped , nullptr , & cancel_handle ); 2032 2033if ( ret == WSA_IO_PENDING ) { 2034auto wait_result = 2035:: WaitForSingleObject ( event , static_cast < DWORD > ( timeout_sec * 1000 )); 2036if ( wait_result == WAIT_TIMEOUT ) { 2037if ( cancel_handle ) { :: GetAddrInfoExCancel ( & cancel_handle ); } 2038:: CloseHandle ( event ); 2039return EAI_AGAIN ; 2040} 2041 2042DWORD bytes_returned ; 2043if (!:: GetOverlappedResult (( HANDLE ) INVALID_SOCKET , & overlapped , 2044& bytes_returned , FALSE)) { 2045:: CloseHandle ( event ); 2046return :: WSAGetLastError (); 2047} 2048} 2049 2050:: CloseHandle ( event ); 2051 2052if ( ret == NO_ERROR || ret == WSA_IO_PENDING ) { 2053* res = reinterpret_cast < struct addrinfo *> ( result_addrinfo ); 2054return 0 ; 2055} 2056 2057return ret ; 2058#elif TARGET_OS_MAC && defined( __clang__ ) 2059if (! node ) { return EAI_NONAME ; } 2060// macOS implementation using CFHost API for asynchronous DNS resolution 2061CFStringRef hostname_ref = CFStringCreateWithCString ( 2062kCFAllocatorDefault , node , kCFStringEncodingUTF8 ); 2063if (! hostname_ref ) { return EAI_MEMORY ; } 2064 2065CFHostRef host_ref = CFHostCreateWithName ( kCFAllocatorDefault , hostname_ref ); 2066CFRelease ( hostname_ref ); 2067if (! host_ref ) { return EAI_MEMORY ; } 2068 2069// Set up context for callback 2070struct CFHostContext { 2071bool completed = false; 2072bool success = false; 2073CFArrayRef addresses = nullptr ; 2074std:: mutex mutex ; 2075std:: condition_variable cv ; 2076} context ; 2077 2078CFHostClientContext client_context ; 2079memset ( & client_context , 0 , sizeof( client_context )); 2080client_context . info = & context ; 2081 2082// Set callback 2083auto callback = []( CFHostRef theHost , CFHostInfoType /*typeInfo*/ , 2084const CFStreamError * error , void * info ) { 2085auto ctx = static_cast < CFHostContext *> ( info ); 2086std:: lock_guard < std:: mutex > lock ( ctx -> mutex ); 2087 2088if ( error && error -> error != 0 ) { 2089ctx -> success = false; 2090} else { 2091Boolean hasBeenResolved ; 2092ctx -> addresses = CFHostGetAddressing ( theHost , & hasBeenResolved ); 2093if ( ctx -> addresses && hasBeenResolved ) { 2094CFRetain ( ctx -> addresses ); 2095ctx -> success = true; 2096} else { 2097ctx -> success = false; 2098} 2099} 2100ctx -> completed = true; 2101ctx -> cv . notify_one (); 2102}; 2103 2104if (! CFHostSetClient ( host_ref , callback , & client_context )) { 2105CFRelease ( host_ref ); 2106return EAI_SYSTEM ; 2107} 2108 2109// Schedule on run loop 2110CFRunLoopRef run_loop = CFRunLoopGetCurrent (); 2111CFHostScheduleWithRunLoop ( host_ref , run_loop , kCFRunLoopDefaultMode ); 2112 2113// Start resolution 2114CFStreamError stream_error ; 2115if (! CFHostStartInfoResolution ( host_ref , kCFHostAddresses , & stream_error )) { 2116CFHostUnscheduleFromRunLoop ( host_ref , run_loop , kCFRunLoopDefaultMode ); 2117CFRelease ( host_ref ); 2118return EAI_FAIL ; 2119} 2120 2121// Wait for completion with timeout 2122auto timeout_time = 2123std::chrono::steady_clock:: now () + std::chrono:: seconds ( timeout_sec ); 2124bool timed_out = false; 2125 2126{ 2127std:: unique_lock < std:: mutex > lock ( context . mutex ); 2128 2129while (! context . completed ) { 2130auto now = std::chrono::steady_clock:: now (); 2131if ( now >= timeout_time ) { 2132timed_out = true; 2133break; 2134} 2135 2136// Run the runloop for a short time 2137lock . unlock (); 2138CFRunLoopRunInMode ( kCFRunLoopDefaultMode , 0.1 , true); 2139lock . lock (); 2140} 2141} 2142 2143// Clean up 2144CFHostUnscheduleFromRunLoop ( host_ref , run_loop , kCFRunLoopDefaultMode ); 2145CFHostSetClient ( host_ref , nullptr , nullptr ); 2146 2147if ( timed_out || ! context . completed ) { 2148CFHostCancelInfoResolution ( host_ref , kCFHostAddresses ); 2149CFRelease ( host_ref ); 2150return EAI_AGAIN ; 2151} 2152 2153if (! context . success || ! context . addresses ) { 2154CFRelease ( host_ref ); 2155return EAI_NODATA ; 2156} 2157 2158// Convert CFArray to addrinfo 2159CFIndex count = CFArrayGetCount ( context . addresses ); 2160if ( count == 0 ) { 2161CFRelease ( context . addresses ); 2162CFRelease ( host_ref ); 2163return EAI_NODATA ; 2164} 2165 2166struct addrinfo * result_addrinfo = nullptr ; 2167struct addrinfo ** current = & result_addrinfo ; 2168 2169for ( CFIndex i = 0 ; i < count ; i ++ ) { 2170CFDataRef addr_data = 2171static_cast < CFDataRef > ( CFArrayGetValueAtIndex ( context . addresses , i )); 2172if (! addr_data ) continue; 2173 2174const struct sockaddr * sockaddr_ptr = 2175reinterpret_cast < const struct sockaddr *> ( CFDataGetBytePtr ( addr_data )); 2176socklen_t sockaddr_len = static_cast < socklen_t > ( CFDataGetLength ( addr_data )); 2177 2178// Allocate addrinfo structure 2179* current = static_cast < struct addrinfo *> ( malloc (sizeof(struct addrinfo ))); 2180if (! * current ) { 2181freeaddrinfo ( result_addrinfo ); 2182CFRelease ( context . addresses ); 2183CFRelease ( host_ref ); 2184return EAI_MEMORY ; 2185} 2186 2187memset ( * current , 0 , sizeof(struct addrinfo )); 2188 2189// Set up addrinfo fields 2190( * current ) -> ai_family = sockaddr_ptr -> sa_family ; 2191( * current ) -> ai_socktype = hints ? hints -> ai_socktype : SOCK_STREAM ; 2192( * current ) -> ai_protocol = hints ? hints -> ai_protocol : IPPROTO_TCP ; 2193( * current ) -> ai_addrlen = sockaddr_len ; 2194 2195// Copy sockaddr 2196( * current ) -> ai_addr = static_cast < struct sockaddr *> ( malloc ( sockaddr_len )); 2197if (!( * current ) -> ai_addr ) { 2198freeaddrinfo ( result_addrinfo ); 2199CFRelease ( context . addresses ); 2200CFRelease ( host_ref ); 2201return EAI_MEMORY ; 2202} 2203memcpy (( * current ) -> ai_addr , sockaddr_ptr , sockaddr_len ); 2204 2205// Set port if service is specified 2206if ( service && * service ) { 2207int port = 0 ; 2208if ( parse_port ( service , strlen ( service ), port )) { 2209if ( sockaddr_ptr -> sa_family == AF_INET ) { 2210reinterpret_cast < struct sockaddr_in *> (( * current ) -> ai_addr ) 2211-> sin_port = htons ( static_cast < uint16_t > ( port )); 2212} else if ( sockaddr_ptr -> sa_family == AF_INET6 ) { 2213reinterpret_cast < struct sockaddr_in6 *> (( * current ) -> ai_addr ) 2214-> sin6_port = htons ( static_cast < uint16_t > ( port )); 2215} 2216} 2217} 2218 2219current = & (( * current ) -> ai_next ); 2220} 2221 2222CFRelease ( context . addresses ); 2223CFRelease ( host_ref ); 2224 2225* res = result_addrinfo ; 2226return 0 ; 2227#elif defined( _GNU_SOURCE ) && defined( __GLIBC__ ) && \ 2228( __GLIBC__ > 2 || ( __GLIBC__ == 2 && __GLIBC_MINOR__ >= 2 )) 2229// #2431: gai_cancel() is non-blocking and may return EAI_NOTCANCELED while 2230// the resolver worker still references the stack-local gaicb. The cancel 2231// path therefore waits (gai_suspend with no timeout) for the worker to 2232// actually finish before letting the stack frame go. The trade-off is that 2233// a wedged DNS server can hold this thread for the system resolver timeout 2234// (~30s by default) past the caller's connection timeout. 2235struct gaicb request {}; 2236struct gaicb * requests [ 1 ] = { & request }; 2237struct sigevent sevp {}; 2238struct timespec timeout { timeout_sec , 0 }; 2239 2240request . ar_name = node ; 2241request . ar_service = service ; 2242request . ar_request = hints ; 2243sevp . sigev_notify = SIGEV_NONE ; 2244 2245int rc = getaddrinfo_a ( GAI_NOWAIT , requests , 1 , & sevp ); 2246if ( rc != 0 ) { return rc ; } 2247 2248auto cleanup = scope_exit ([ & ] { 2249if ( request . ar_result ) { freeaddrinfo ( request . ar_result ); } 2250}); 2251 2252int wait_result = gai_suspend ( requests , 1 , & timeout ); 2253 2254if ( wait_result == 0 || wait_result == EAI_ALLDONE ) { 2255int gai_result = gai_error ( & request ); 2256if ( gai_result == 0 ) { 2257* res = request . ar_result ; 2258request . ar_result = nullptr ; 2259return 0 ; 2260} 2261return gai_result ; 2262} 2263 2264gai_cancel ( & request ); 2265while ( gai_error ( & request ) == EAI_INPROGRESS ) { 2266gai_suspend ( requests , 1 , nullptr ); 2267} 2268return wait_result ; 2269#else 2270// Fallback implementation using thread-based timeout for other Unix systems. 2271 2272struct GetAddrInfoState { 2273~ GetAddrInfoState () { 2274if ( info ) { freeaddrinfo ( info ); } 2275} 2276 2277std:: mutex mutex ; 2278std:: condition_variable result_cv ; 2279bool completed = false; 2280int result = EAI_SYSTEM ; 2281std:: string node ; 2282std:: string service ; 2283struct addrinfo hints ; 2284struct addrinfo * info = nullptr ; 2285}; 2286 2287// Allocate on the heap, so the resolver thread can keep using the data. 2288auto state = std:: make_shared < GetAddrInfoState > (); 2289if ( node ) { state -> node = node ; } 2290state -> service = service ; 2291state -> hints = * hints ; 2292 2293std:: thread resolve_thread ([ state ]() { 2294auto thread_result = 2295getaddrinfo ( state -> node . c_str (), state -> service . c_str (), & state -> hints , 2296& state -> info ); 2297 2298std:: lock_guard < std:: mutex > lock ( state -> mutex ); 2299state -> result = thread_result ; 2300state -> completed = true; 2301state -> result_cv . notify_one (); 2302}); 2303 2304// Wait for completion or timeout 2305std:: unique_lock < std:: mutex > lock ( state -> mutex ); 2306auto finished = 2307state -> result_cv . wait_for ( lock , std::chrono:: seconds ( timeout_sec ), 2308[ & ] { return state -> completed ; }); 2309 2310if ( finished ) { 2311// Operation completed within timeout 2312resolve_thread . join (); 2313* res = state -> info ; 2314state -> info = nullptr ; // Pass ownership to caller 2315return state -> result ; 2316} else { 2317// Timeout occurred 2318resolve_thread . detach (); // Let the thread finish in background 2319return EAI_AGAIN ; // Return timeout error 2320} 2321#endif 2322#else 2323( void )( timeout_sec ); // Unused parameter for non-blocking getaddrinfo 2324return getaddrinfo ( node , service , hints , res ); 2325#endif 2326} 2327 2328template < typename BindOrConnect > 2329socket_t create_socket (const std:: string & host , const std:: string & ip , int port , 2330int address_family , int socket_flags , bool tcp_nodelay , 2331bool ipv6_v6only , SocketOptions socket_options , 2332BindOrConnect bind_or_connect , time_t timeout_sec = 0 ) { 2333// Get address info 2334const char * node = nullptr ; 2335struct addrinfo hints ; 2336struct addrinfo * result ; 2337 2338memset ( & hints , 0 , sizeof(struct addrinfo )); 2339hints . ai_socktype = SOCK_STREAM ; 2340hints . ai_protocol = IPPROTO_IP ; 2341 2342if (! ip . empty ()) { 2343node = ip . c_str (); 2344// Ask getaddrinfo to convert IP in c-string to address 2345hints . ai_family = AF_UNSPEC ; 2346hints . ai_flags = AI_NUMERICHOST ; 2347} else { 2348if (! host . empty ()) { node = host . c_str (); } 2349hints . ai_family = address_family ; 2350hints . ai_flags = socket_flags ; 2351} 2352 2353#if !defined( _WIN32 ) || defined( CPPHTTPLIB_HAVE_AFUNIX_H ) 2354if ( hints . ai_family == AF_UNIX ) { 2355const auto addrlen = host . length (); 2356if ( addrlen > sizeof(sockaddr_un:: sun_path )) { return INVALID_SOCKET ; } 2357 2358#ifdef SOCK_CLOEXEC 2359auto sock = socket ( hints . ai_family , hints . ai_socktype | SOCK_CLOEXEC , 2360hints . ai_protocol ); 2361#else 2362auto sock = socket ( hints . ai_family , hints . ai_socktype , hints . ai_protocol ); 2363#endif 2364 2365if ( sock != INVALID_SOCKET ) { 2366sockaddr_un addr {}; 2367addr . sun_family = AF_UNIX ; 2368 2369auto unescaped_host = unescape_abstract_namespace_unix_domain ( host ); 2370std:: copy ( unescaped_host . begin (), unescaped_host . end (), addr . sun_path ); 2371 2372hints . ai_addr = reinterpret_cast < sockaddr *> ( & addr ); 2373hints . ai_addrlen = static_cast < socklen_t > ( 2374sizeof( addr ) - sizeof( addr . sun_path ) + addrlen ); 2375 2376#ifndef SOCK_CLOEXEC 2377#ifndef _WIN32 2378fcntl ( sock , F_SETFD , FD_CLOEXEC ); 2379#endif 2380#endif 2381 2382if ( socket_options ) { socket_options ( sock ); } 2383 2384#ifdef _WIN32 2385// Setting SO_REUSEADDR seems not to work well with AF_UNIX on windows, so 2386// remove the option. 2387set_socket_opt ( sock , SOL_SOCKET , SO_REUSEADDR , 0 ); 2388#endif 2389 2390bool dummy ; 2391if (! bind_or_connect ( sock , hints , dummy )) { 2392close_socket ( sock ); 2393sock = INVALID_SOCKET ; 2394} 2395} 2396return sock ; 2397} 2398#endif 2399 2400auto service = std:: to_string ( port ); 2401 2402if ( getaddrinfo_with_timeout ( node , service . c_str (), & hints , & result , 2403timeout_sec )) { 2404#if defined __linux__ && !defined __ANDROID__ 2405res_init (); 2406#endif 2407return INVALID_SOCKET ; 2408} 2409auto se = detail:: scope_exit ([ & ] { freeaddrinfo ( result ); }); 2410 2411for ( auto rp = result ; rp ; rp = rp -> ai_next ) { 2412// Create a socket 2413#ifdef _WIN32 2414auto sock = 2415WSASocketW ( rp -> ai_family , rp -> ai_socktype , rp -> ai_protocol , nullptr , 0 , 2416WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED ); 2417/** 2418* Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 2419* and above the socket creation fails on older Windows Systems. 2420* 2421* Let's try to create a socket the old way in this case. 2422* 2423* Reference: 2424* https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa 2425* 2426* WSA_FLAG_NO_HANDLE_INHERIT: 2427* This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with 2428* SP1, and later 2429* 2430*/ 2431if ( sock == INVALID_SOCKET ) { 2432sock = socket ( rp -> ai_family , rp -> ai_socktype , rp -> ai_protocol ); 2433} 2434#else 2435 2436#ifdef SOCK_CLOEXEC 2437auto sock = 2438socket ( rp -> ai_family , rp -> ai_socktype | SOCK_CLOEXEC , rp -> ai_protocol ); 2439#else 2440auto sock = socket ( rp -> ai_family , rp -> ai_socktype , rp -> ai_protocol ); 2441#endif 2442 2443#endif 2444if ( sock == INVALID_SOCKET ) { continue; } 2445 2446#if !defined _WIN32 && !defined SOCK_CLOEXEC 2447if ( fcntl ( sock , F_SETFD , FD_CLOEXEC ) == -1 ) { 2448close_socket ( sock ); 2449continue; 2450} 2451#endif 2452 2453if ( tcp_nodelay ) { set_socket_opt ( sock , IPPROTO_TCP , TCP_NODELAY , 1 ); } 2454 2455if ( rp -> ai_family == AF_INET6 ) { 2456set_socket_opt ( sock , IPPROTO_IPV6 , IPV6_V6ONLY , ipv6_v6only ? 1 : 0 ); 2457} 2458 2459if ( socket_options ) { socket_options ( sock ); } 2460 2461// bind or connect 2462auto quit = false; 2463if ( bind_or_connect ( sock , * rp , quit )) { return sock ; } 2464 2465close_socket ( sock ); 2466 2467if ( quit ) { break; } 2468} 2469 2470return INVALID_SOCKET ; 2471} 2472 2473void set_nonblocking ( socket_t sock , bool nonblocking ) { 2474#ifdef _WIN32 2475auto flags = nonblocking ? 1UL : 0UL ; 2476ioctlsocket ( sock , FIONBIO , & flags ); 2477#else 2478auto flags = fcntl ( sock , F_GETFL , 0 ); 2479fcntl ( sock , F_SETFL , 2480nonblocking ? ( flags | O_NONBLOCK ) : ( flags & (~ O_NONBLOCK ))); 2481#endif 2482} 2483 2484bool is_connection_error () { 2485#ifdef _WIN32 2486return WSAGetLastError () != WSAEWOULDBLOCK ; 2487#else 2488return errno != EINPROGRESS ; 2489#endif 2490} 2491 2492// accept() failed because the process or the network stack is temporarily out 2493// of resources. The listening socket is still usable, so back off briefly and 2494// try again. 2495bool is_accept_resource_error () { 2496#ifdef _WIN32 2497auto err = WSAGetLastError (); 2498return err == WSAEMFILE || err == WSAENOBUFS ; 2499#else 2500auto err = errno ; 2501return err == EMFILE || err == ENFILE || err == ENOBUFS || err == ENOMEM ; 2502#endif 2503} 2504 2505// accept() failed for a reason that says nothing about the listening socket: 2506// the pending connection went away before it could be accepted, or the call 2507// was interrupted. Retry immediately. WSAAccept()'s own documentation omits 2508// WSAECONNRESET, but the accept() it wraps reports an aborted pending 2509// connection that way. 2510bool is_accept_transient_error () { 2511#ifdef _WIN32 2512auto err = WSAGetLastError (); 2513return err == WSAEINTR || err == WSAEWOULDBLOCK || err == WSAECONNRESET || 2514err == WSAECONNABORTED ; 2515#else 2516auto err = errno ; 2517return err == EINTR || err == EAGAIN || err == EWOULDBLOCK || 2518err == ECONNABORTED ; 2519#endif 2520} 2521 2522bool bind_ip_address ( socket_t sock , const std:: string & host ) { 2523struct addrinfo hints ; 2524struct addrinfo * result ; 2525 2526memset ( & hints , 0 , sizeof(struct addrinfo )); 2527hints . ai_family = AF_UNSPEC ; 2528hints . ai_socktype = SOCK_STREAM ; 2529hints . ai_protocol = 0 ; 2530 2531if ( getaddrinfo_with_timeout ( host . c_str (), "0" , & hints , & result , 0 )) { 2532return false; 2533} 2534 2535auto se = detail:: scope_exit ([ & ] { freeaddrinfo ( result ); }); 2536 2537auto ret = false; 2538for ( auto rp = result ; rp ; rp = rp -> ai_next ) { 2539const auto & ai = * rp ; 2540if (!:: bind ( sock , ai . ai_addr , static_cast < socklen_t > ( ai . ai_addrlen ))) { 2541ret = true; 2542break; 2543} 2544} 2545 2546return ret ; 2547} 2548 2549#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__ 2550#define USE_IF2IP 2551#endif 2552 2553#ifdef USE_IF2IP 2554std:: string if2ip ( int address_family , const std:: string & ifn ) { 2555struct ifaddrs * ifap ; 2556getifaddrs ( & ifap ); 2557auto se = detail:: scope_exit ([ & ] { freeifaddrs ( ifap ); }); 2558 2559std:: string addr_candidate ; 2560for ( auto ifa = ifap ; ifa ; ifa = ifa -> ifa_next ) { 2561if ( ifa -> ifa_addr && ifn == ifa -> ifa_name && 2562( AF_UNSPEC == address_family || 2563ifa -> ifa_addr -> sa_family == address_family )) { 2564if ( ifa -> ifa_addr -> sa_family == AF_INET ) { 2565auto sa = reinterpret_cast < struct sockaddr_in *> ( ifa -> ifa_addr ); 2566char buf [ INET_ADDRSTRLEN ]; 2567if ( inet_ntop ( AF_INET , & sa -> sin_addr , buf , INET_ADDRSTRLEN )) { 2568return std:: string ( buf , INET_ADDRSTRLEN ); 2569} 2570} else if ( ifa -> ifa_addr -> sa_family == AF_INET6 ) { 2571auto sa = reinterpret_cast < struct sockaddr_in6 *> ( ifa -> ifa_addr ); 2572if (! IN6_IS_ADDR_LINKLOCAL ( & sa -> sin6_addr )) { 2573char buf [ INET6_ADDRSTRLEN ] = {}; 2574if ( inet_ntop ( AF_INET6 , & sa -> sin6_addr , buf , INET6_ADDRSTRLEN )) { 2575// equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL 2576auto s6_addr_head = sa -> sin6_addr . s6_addr [ 0 ]; 2577if ( s6_addr_head == 0xfc || s6_addr_head == 0xfd ) { 2578addr_candidate = std:: string ( buf , INET6_ADDRSTRLEN ); 2579} else { 2580return std:: string ( buf , INET6_ADDRSTRLEN ); 2581} 2582} 2583} 2584} 2585} 2586} 2587return addr_candidate ; 2588} 2589#endif 2590 2591socket_t create_client_socket ( 2592const std:: string & host , const std:: string & ip , int port , 2593int address_family , bool tcp_nodelay , bool ipv6_v6only , 2594SocketOptions socket_options , time_t connection_timeout_sec , 2595time_t connection_timeout_usec , time_t read_timeout_sec , 2596time_t read_timeout_usec , time_t write_timeout_sec , 2597time_t write_timeout_usec , const std:: string & intf , Error & error ) { 2598auto sock = create_socket ( 2599host , ip , port , address_family , 0 , tcp_nodelay , ipv6_v6only , 2600std:: move ( socket_options ), 2601[ & ]( socket_t sock2 , struct addrinfo & ai , bool & quit ) -> bool { 2602if (! intf . empty ()) { 2603#ifdef USE_IF2IP 2604auto ip_from_if = if2ip ( address_family , intf ); 2605if ( ip_from_if . empty ()) { ip_from_if = intf ; } 2606if (! bind_ip_address ( sock2 , ip_from_if )) { 2607error = Error :: BindIPAddress ; 2608return false; 2609} 2610#endif 2611} 2612 2613set_nonblocking ( sock2 , true); 2614 2615auto ret = 2616:: connect ( sock2 , ai . ai_addr , static_cast < socklen_t > ( ai . ai_addrlen )); 2617 2618if ( ret < 0 ) { 2619if ( is_connection_error ()) { 2620error = Error :: Connection ; 2621return false; 2622} 2623error = wait_until_socket_is_ready ( sock2 , connection_timeout_sec , 2624connection_timeout_usec ); 2625if ( error != Error :: Success ) { 2626if ( error == Error :: ConnectionTimeout ) { quit = true; } 2627return false; 2628} 2629} 2630 2631set_nonblocking ( sock2 , false); 2632set_socket_opt_time ( sock2 , SOL_SOCKET , SO_RCVTIMEO , read_timeout_sec , 2633read_timeout_usec ); 2634set_socket_opt_time ( sock2 , SOL_SOCKET , SO_SNDTIMEO , write_timeout_sec , 2635write_timeout_usec ); 2636 2637error = Error :: Success ; 2638return true; 2639}, 2640connection_timeout_sec ); // Pass DNS timeout 2641 2642if ( sock != INVALID_SOCKET ) { 2643error = Error :: Success ; 2644} else { 2645if ( error == Error :: Success ) { error = Error :: Connection ; } 2646} 2647 2648return sock ; 2649} 2650 2651bool get_ip_and_port (const struct sockaddr_storage & addr , 2652socklen_t addr_len , std:: string & ip , int & port ) { 2653if ( addr . ss_family == AF_INET ) { 2654port = ntohs ( reinterpret_cast < const struct sockaddr_in *> ( & addr ) -> sin_port ); 2655} else if ( addr . ss_family == AF_INET6 ) { 2656port = 2657ntohs ( reinterpret_cast < const struct sockaddr_in6 *> ( & addr ) -> sin6_port ); 2658} else { 2659return false; 2660} 2661 2662std:: array < char , NI_MAXHOST > ipstr {}; 2663if ( getnameinfo ( reinterpret_cast < const struct sockaddr *> ( & addr ), addr_len , 2664ipstr . data (), static_cast < socklen_t > ( ipstr . size ()), nullptr , 26650 , NI_NUMERICHOST )) { 2666return false; 2667} 2668 2669ip = ipstr . data (); 2670return true; 2671} 2672 2673void get_local_ip_and_port ( socket_t sock , std:: string & ip , int & port ) { 2674struct sockaddr_storage addr ; 2675socklen_t addr_len = sizeof( addr ); 2676if (! getsockname ( sock , reinterpret_cast < struct sockaddr *> ( & addr ), 2677& addr_len )) { 2678get_ip_and_port ( addr , addr_len , ip , port ); 2679} 2680} 2681 2682void get_remote_ip_and_port ( socket_t sock , std:: string & ip , int & port ) { 2683struct sockaddr_storage addr ; 2684socklen_t addr_len = sizeof( addr ); 2685 2686if (! getpeername ( sock , reinterpret_cast < struct sockaddr *> ( & addr ), 2687& addr_len )) { 2688#ifndef _WIN32 2689if ( addr . ss_family == AF_UNIX ) { 2690#if defined( __linux__ ) 2691struct ucred ucred ; 2692socklen_t len = sizeof( ucred ); 2693if ( getsockopt ( sock , SOL_SOCKET , SO_PEERCRED , & ucred , & len ) == 0 ) { 2694port = ucred . pid ; 2695} 2696#elif defined( SOL_LOCAL ) && defined( SO_PEERPID ) 2697pid_t pid ; 2698socklen_t len = sizeof( pid ); 2699if ( getsockopt ( sock , SOL_LOCAL , SO_PEERPID , & pid , & len ) == 0 ) { 2700port = pid ; 2701} 2702#endif 2703return; 2704} 2705#endif 2706get_ip_and_port ( addr , addr_len , ip , port ); 2707} 2708} 2709 2710// Recursive form retained so operator""_t below can compute hashes for 2711// switch-case labels at compile time (C++11 constexpr forbids loops). Do not 2712// call from runtime paths with arbitrary-length inputs — use str2tag() 2713// instead, which is iterative and stack-safe. 2714constexpr unsigned int str2tag_core (const char * s , size_t l , 2715unsigned int h ) { 2716return ( l == 0 ) 2717? h 2718: str2tag_core ( 2719s + 1 , l - 1 , 2720// Unsets the 6 high bits of h, therefore no overflow happens 2721(((std:: numeric_limits < unsigned int > :: max )() >> 6 ) & 2722h * 33 ) ^ 2723static_cast < unsigned char > ( * s )); 2724} 2725 2726unsigned int str2tag (const std:: string & s ) { 2727// Iterative form of str2tag_core: the recursive constexpr version is kept 2728// for compile-time UDL evaluation of short string literals, but at runtime 2729// we may receive arbitrarily long inputs (e.g. fuzzed Content-Type) that 2730// would blow the stack with one frame per character. 2731unsigned int h = 0 ; 2732for ( auto c : s ) { 2733h = (((std:: numeric_limits < unsigned int > :: max )() >> 6 ) & h * 33 ) ^ 2734static_cast < unsigned char > ( c ); 2735} 2736return h ; 2737} 2738 2739namespace udl { 2740 2741constexpr unsigned int operator"" _t (const char * s , size_t l ) { 2742return str2tag_core ( s , l , 0 ); 2743} 2744 2745} // namespace udl 2746 2747std:: string 2748find_content_type (const std:: string & path , 2749const std:: map < std:: string , std:: string > & user_data , 2750const std:: string & default_content_type ) { 2751auto ext = file_extension ( path ); 2752 2753auto it = user_data . find ( ext ); 2754if ( it != user_data . end ()) { return it -> second ; } 2755 2756using udl::operator"" _t ; 2757 2758switch ( str2tag ( ext )) { 2759default: return default_content_type ; 2760 2761case "css" _t: return "text/css" ; 2762case "csv" _t: return "text/csv" ; 2763case "htm" _t: 2764case "html" _t: return "text/html" ; 2765case "js" _t: 2766case "mjs" _t: return "text/javascript" ; 2767case "txt" _t: return "text/plain" ; 2768case "vtt" _t: return "text/vtt" ; 2769 2770case "apng" _t: return "image/apng" ; 2771case "avif" _t: return "image/avif" ; 2772case "bmp" _t: return "image/bmp" ; 2773case "gif" _t: return "image/gif" ; 2774case "png" _t: return "image/png" ; 2775case "svg" _t: return "image/svg+xml" ; 2776case "webp" _t: return "image/webp" ; 2777case "ico" _t: return "image/x-icon" ; 2778case "tif" _t: return "image/tiff" ; 2779case "tiff" _t: return "image/tiff" ; 2780case "jpg" _t: 2781case "jpeg" _t: return "image/jpeg" ; 2782 2783case "mp4" _t: return "video/mp4" ; 2784case "mpeg" _t: return "video/mpeg" ; 2785case "webm" _t: return "video/webm" ; 2786 2787case "mp3" _t: return "audio/mp3" ; 2788case "mpga" _t: return "audio/mpeg" ; 2789case "weba" _t: return "audio/webm" ; 2790case "wav" _t: return "audio/wave" ; 2791 2792case "otf" _t: return "font/otf" ; 2793case "ttf" _t: return "font/ttf" ; 2794case "woff" _t: return "font/woff" ; 2795case "woff2" _t: return "font/woff2" ; 2796 2797case "7z" _t: return "application/x-7z-compressed" ; 2798case "atom" _t: return "application/atom+xml" ; 2799case "pdf" _t: return "application/pdf" ; 2800case "json" _t: return "application/json" ; 2801case "rss" _t: return "application/rss+xml" ; 2802case "tar" _t: return "application/x-tar" ; 2803case "xht" _t: 2804case "xhtml" _t: return "application/xhtml+xml" ; 2805case "xslt" _t: return "application/xslt+xml" ; 2806case "xml" _t: return "application/xml" ; 2807case "gz" _t: return "application/gzip" ; 2808case "zip" _t: return "application/zip" ; 2809case "wasm" _t: return "application/wasm" ; 2810} 2811} 2812 2813std:: string 2814extract_media_type (const std:: string & content_type , 2815std:: map < std:: string , std:: string > * params = nullptr ) { 2816// Extract type/subtype from Content-Type value (RFC 2045) 2817// e.g. "application/json; charset=utf-8" -> "application/json" 2818auto media_type = content_type ; 2819auto semicolon_pos = media_type . find ( ';' ); 2820if ( semicolon_pos != std::string:: npos ) { 2821auto param_str = media_type . substr ( semicolon_pos + 1 ); 2822media_type = media_type . substr ( 0 , semicolon_pos ); 2823 2824if ( params ) { 2825// Parse parameters: key=value pairs separated by ';' 2826split_unquoted ( param_str . data (), param_str . data () + param_str . size (), ';' , 2827[ & ](const char * b , const char * e ) { 2828std:: string key ; 2829std:: string val ; 2830divide_param_pair ( b , e , key , val ); 2831if (! key . empty ()) { 2832params -> emplace ( trim_copy ( key ), 2833trim_double_quotes_copy ( val )); 2834} 2835}); 2836} 2837} 2838 2839// Trim whitespace from media type 2840return trim_copy ( media_type ); 2841} 2842 2843bool can_compress_content_type (const std:: string & content_type ) { 2844using udl::operator"" _t ; 2845 2846auto mime_type = extract_media_type ( content_type ); 2847auto tag = str2tag ( mime_type ); 2848 2849switch ( tag ) { 2850case "image/svg+xml" _t: 2851case "application/javascript" _t: 2852case "application/x-javascript" _t: 2853case "application/json" _t: 2854case "application/ld+json" _t: 2855case "application/xml" _t: 2856case "application/xhtml+xml" _t: 2857case "application/rss+xml" _t: 2858case "application/atom+xml" _t: 2859case "application/xslt+xml" _t: 2860case "application/protobuf" _t: return true; 2861 2862case "text/event-stream" _t: return false; 2863 2864default: return ! mime_type . rfind ( "text/" , 0 ); 2865} 2866} 2867 2868bool parse_quality (const char * b , const char * e , std:: string & token , 2869double & quality ) { 2870quality = 1.0 ; 2871token . clear (); 2872 2873// Split on first ';': left = token name, right = parameters 2874const char * params_b = nullptr ; 2875std:: size_t params_len = 0 ; 2876 2877divide ( 2878b , static_cast < std:: size_t > ( e - b ), ';' , 2879[ & ](const char * lb , std:: size_t llen , const char * rb , std:: size_t rlen ) { 2880auto r = trim ( lb , lb + llen , 0 , llen ); 2881if ( r . first < r . second ) { token . assign ( lb + r . first , lb + r . second ); } 2882params_b = rb ; 2883params_len = rlen ; 2884}); 2885 2886if ( token . empty ()) { return false; } 2887if ( params_len == 0 ) { return true; } 2888 2889// Scan parameters for q= (stops on first match) 2890bool invalid = false; 2891split_find ( params_b , params_b + params_len , ';' , 2892(std:: numeric_limits < size_t > :: max )(), 2893[ & ](const char * pb , const char * pe ) -> bool { 2894// Match exactly "q=" or "Q=" (not "query=" etc.) 2895auto len = static_cast < size_t > ( pe - pb ); 2896if ( len < 2 ) { return false; } 2897if (( pb [ 0 ] != 'q' && pb [ 0 ] != 'Q' ) || pb [ 1 ] != '=' ) { 2898return false; 2899} 2900 2901// Trim the value portion 2902auto r = trim ( pb , pe , 2 , len ); 2903if ( r . first >= r . second ) { 2904invalid = true; 2905return true; 2906} 2907 2908double v = 0.0 ; 2909auto res = from_chars ( pb + r . first , pb + r . second , v ); 2910if ( res . ec != std:: errc {} || v < 0.0 || v > 1.0 ) { 2911invalid = true; 2912return true; 2913} 2914quality = v ; 2915return true; 2916}); 2917 2918return ! invalid ; 2919} 2920 2921EncodingType encoding_type (const Request & req , 2922const std:: string & content_type ) { 2923if (! can_compress_content_type ( content_type )) { return EncodingType :: None ; } 2924 2925auto s = get_combined_header_value ( req . headers , "Accept-Encoding" ); 2926if ( s . empty ()) { return EncodingType :: None ; } 2927 2928// Single-pass: iterate tokens and track the best supported encoding. 2929// Server preference breaks ties (br > gzip > zstd). 2930EncodingType best = EncodingType :: None ; 2931double best_q = 0.0 ; // q=0 means "not acceptable" 2932 2933// Server preference: Brotli > Gzip > Zstd (lower = more preferred) 2934auto priority = []( EncodingType t ) -> int { 2935switch ( t ) { 2936case EncodingType :: Brotli : return 0 ; 2937case EncodingType :: Gzip : return 1 ; 2938case EncodingType :: Zstd : return 2 ; 2939default: return 3 ; 2940} 2941}; 2942 2943std:: string name ; 2944split ( s . data (), s . data () + s . size (), ',' , [ & ](const char * b , const char * e ) { 2945double quality = 1.0 ; 2946if (! parse_quality ( b , e , name , quality )) { return; } 2947if ( quality <= 0.0 ) { return; } 2948 2949EncodingType type = EncodingType :: None ; 2950#ifdef CPPHTTPLIB_BROTLI_SUPPORT 2951if (case_ignore:: equal ( name , "br" )) { type = EncodingType :: Brotli ; } 2952#endif 2953#ifdef CPPHTTPLIB_ZLIB_SUPPORT 2954if ( type == EncodingType :: None && case_ignore:: equal ( name , "gzip" )) { 2955type = EncodingType :: Gzip ; 2956} 2957#endif 2958#ifdef CPPHTTPLIB_ZSTD_SUPPORT 2959if ( type == EncodingType :: None && case_ignore:: equal ( name , "zstd" )) { 2960type = EncodingType :: Zstd ; 2961} 2962#endif 2963 2964if ( type == EncodingType :: None ) { return; } 2965 2966// Higher q-value wins; for equal q, server preference breaks ties 2967if ( quality > best_q || 2968( quality == best_q && priority ( type ) < priority ( best ))) { 2969best_q = quality ; 2970best = type ; 2971} 2972}); 2973 2974return best ; 2975} 2976 2977// `content_type` is taken separately because a file-backed response has not 2978// been given one yet when its coding has to be decided. 2979EncodingType encoding_type (const Request & req , const Response & res , 2980const std:: string & content_type ) { 2981// The response already names a content coding of its own: a handler serving 2982// a body it encoded itself (pre-compressed static assets, say), or a mount 2983// point whose headers name the coding its files are stored in. Applying one 2984// on top of that would double-encode the body and append a second 2985// `Content-Encoding` field line. 2986if ( res . has_header ( "Content-Encoding" )) { return EncodingType :: None ; } 2987return encoding_type ( req , content_type ); 2988} 2989 2990EncodingType encoding_type (const Request & req , const Response & res ) { 2991return encoding_type ( req , res , res . get_header_value ( "Content-Type" )); 2992} 2993 2994std:: unique_ptr < compressor > make_compressor ( EncodingType type ) { 2995#ifdef CPPHTTPLIB_ZLIB_SUPPORT 2996if ( type == EncodingType :: Gzip ) { 2997return detail:: make_unique < gzip_compressor > (); 2998} 2999#endif 3000#ifdef CPPHTTPLIB_BROTLI_SUPPORT 3001if ( type == EncodingType :: Brotli ) { 3002return detail:: make_unique < brotli_compressor > (); 3003} 3004#endif 3005#ifdef CPPHTTPLIB_ZSTD_SUPPORT 3006if ( type == EncodingType :: Zstd ) { 3007return detail:: make_unique < zstd_compressor > (); 3008} 3009#endif 3010( void ) type ; 3011return nullptr ; 3012} 3013 3014const char * encoding_name ( EncodingType type ) { 3015switch ( type ) { 3016case EncodingType :: Gzip : return "gzip" ; 3017case EncodingType :: Brotli : return "br" ; 3018case EncodingType :: Zstd : return "zstd" ; 3019default: return "" ; 3020} 3021} 3022 3023bool nocompressor:: compress (const char * data , size_t data_length , 3024bool /*last*/ , Callback callback ) { 3025if (! data_length ) { return true; } 3026return callback ( data , data_length ); 3027} 3028 3029#ifdef CPPHTTPLIB_ZLIB_SUPPORT 3030gzip_compressor:: gzip_compressor () { 3031std:: memset ( & strm_ , 0 , sizeof( strm_ )); 3032strm_ . zalloc = Z_NULL ; 3033strm_ . zfree = Z_NULL ; 3034strm_ . opaque = Z_NULL ; 3035 3036is_valid_ = deflateInit2 ( & strm_ , Z_DEFAULT_COMPRESSION , Z_DEFLATED , 31 , 8 , 3037Z_DEFAULT_STRATEGY ) == Z_OK ; 3038} 3039 3040gzip_compressor::~ gzip_compressor () { deflateEnd ( & strm_ ); } 3041 3042bool gzip_compressor:: compress (const char * data , size_t data_length , 3043bool last , Callback callback ) { 3044assert ( is_valid_ ); 3045 3046do { 3047constexpr size_t max_avail_in = 3048(std:: numeric_limits < decltype( strm_ . avail_in ) > :: max )(); 3049 3050strm_ . avail_in = static_cast < decltype( strm_ . avail_in ) > ( 3051(std:: min )( data_length , max_avail_in )); 3052strm_ . next_in = const_cast < Bytef *> ( reinterpret_cast < const Bytef *> ( data )); 3053 3054data_length -= strm_ . avail_in ; 3055data += strm_ . avail_in ; 3056 3057auto flush = ( last && data_length == 0 ) ? Z_FINISH : Z_NO_FLUSH ; 3058auto ret = Z_OK ; 3059 3060std:: array < char , CPPHTTPLIB_COMPRESSION_BUFSIZ > buff {}; 3061do { 3062strm_ . avail_out = static_cast < uInt > ( buff . size ()); 3063strm_ . next_out = reinterpret_cast < Bytef *> ( buff . data ()); 3064 3065ret = deflate ( & strm_ , flush ); 3066if ( ret == Z_STREAM_ERROR ) { return false; } 3067 3068if (! callback ( buff . data (), buff . size () - strm_ . avail_out )) { 3069return false; 3070} 3071} while ( strm_ . avail_out == 0 ); 3072 3073assert (( flush == Z_FINISH && ret == Z_STREAM_END ) || 3074( flush == Z_NO_FLUSH && ret == Z_OK )); 3075assert ( strm_ . avail_in == 0 ); 3076} while ( data_length > 0 ); 3077 3078return true; 3079} 3080 3081gzip_decompressor:: gzip_decompressor () { 3082std:: memset ( & strm_ , 0 , sizeof( strm_ )); 3083strm_ . zalloc = Z_NULL ; 3084strm_ . zfree = Z_NULL ; 3085strm_ . opaque = Z_NULL ; 3086 3087// 15 is the value of wbits, which should be at the maximum possible value 3088// to ensure that any gzip stream can be decoded. The offset of 32 specifies 3089// that the stream type should be automatically detected either gzip or 3090// deflate. 3091is_valid_ = inflateInit2 ( & strm_ , 32 + 15 ) == Z_OK ; 3092} 3093 3094gzip_decompressor::~ gzip_decompressor () { inflateEnd ( & strm_ ); } 3095 3096bool gzip_decompressor:: is_valid () const { return is_valid_ ; } 3097 3098bool gzip_decompressor:: decompress (const char * data , size_t data_length , 3099Callback callback ) { 3100assert ( is_valid_ ); 3101 3102auto ret = Z_OK ; 3103 3104do { 3105constexpr size_t max_avail_in = 3106(std:: numeric_limits < decltype( strm_ . avail_in ) > :: max )(); 3107 3108strm_ . avail_in = static_cast < decltype( strm_ . avail_in ) > ( 3109(std:: min )( data_length , max_avail_in )); 3110strm_ . next_in = const_cast < Bytef *> ( reinterpret_cast < const Bytef *> ( data )); 3111 3112data_length -= strm_ . avail_in ; 3113data += strm_ . avail_in ; 3114 3115std:: array < char , CPPHTTPLIB_COMPRESSION_BUFSIZ > buff {}; 3116while ( strm_ . avail_in > 0 && ret == Z_OK ) { 3117strm_ . avail_out = static_cast < uInt > ( buff . size ()); 3118strm_ . next_out = reinterpret_cast < Bytef *> ( buff . data ()); 3119 3120ret = inflate ( & strm_ , Z_NO_FLUSH ); 3121 3122assert ( ret != Z_STREAM_ERROR ); 3123switch ( ret ) { 3124case Z_NEED_DICT : 3125case Z_DATA_ERROR : 3126case Z_MEM_ERROR : inflateEnd ( & strm_ ); return false; 3127} 3128 3129if (! callback ( buff . data (), buff . size () - strm_ . avail_out )) { 3130return false; 3131} 3132} 3133 3134if ( ret != Z_OK && ret != Z_STREAM_END ) { return false; } 3135 3136} while ( data_length > 0 ); 3137 3138return true; 3139} 3140#endif 3141 3142#ifdef CPPHTTPLIB_BROTLI_SUPPORT 3143brotli_compressor:: brotli_compressor () { 3144state_ = BrotliEncoderCreateInstance ( nullptr , nullptr , nullptr ); 3145} 3146 3147brotli_compressor::~ brotli_compressor () { 3148BrotliEncoderDestroyInstance ( state_ ); 3149} 3150 3151bool brotli_compressor:: compress (const char * data , size_t data_length , 3152bool last , Callback callback ) { 3153std:: array < uint8_t , CPPHTTPLIB_COMPRESSION_BUFSIZ > buff {}; 3154 3155auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS ; 3156auto available_in = data_length ; 3157auto next_in = reinterpret_cast < const uint8_t *> ( data ); 3158 3159for (;;) { 3160if ( last ) { 3161if ( BrotliEncoderIsFinished ( state_ )) { break; } 3162} else { 3163if (! available_in ) { break; } 3164} 3165 3166auto available_out = buff . size (); 3167auto next_out = buff . data (); 3168 3169if (! BrotliEncoderCompressStream ( state_ , operation , & available_in , & next_in , 3170& available_out , & next_out , nullptr )) { 3171return false; 3172} 3173 3174auto output_bytes = buff . size () - available_out ; 3175if ( output_bytes ) { 3176callback ( reinterpret_cast < const char *> ( buff . data ()), output_bytes ); 3177} 3178} 3179 3180return true; 3181} 3182 3183brotli_decompressor:: brotli_decompressor () { 3184decoder_s = BrotliDecoderCreateInstance ( 0 , 0 , 0 ); 3185decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT 3186: BROTLI_DECODER_RESULT_ERROR ; 3187} 3188 3189brotli_decompressor::~ brotli_decompressor () { 3190if ( decoder_s ) { BrotliDecoderDestroyInstance ( decoder_s ); } 3191} 3192 3193bool brotli_decompressor:: is_valid () const { return decoder_s ; } 3194 3195bool brotli_decompressor:: decompress (const char * data , 3196size_t data_length , 3197Callback callback ) { 3198if ( decoder_r == BROTLI_DECODER_RESULT_SUCCESS || 3199decoder_r == BROTLI_DECODER_RESULT_ERROR ) { 3200return 0 ; 3201} 3202 3203auto next_in = reinterpret_cast < const uint8_t *> ( data ); 3204size_t avail_in = data_length ; 3205size_t total_out ; 3206 3207decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT ; 3208 3209std:: array < char , CPPHTTPLIB_COMPRESSION_BUFSIZ > buff {}; 3210while ( decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT ) { 3211char * next_out = buff . data (); 3212size_t avail_out = buff . size (); 3213 3214decoder_r = BrotliDecoderDecompressStream ( 3215decoder_s , & avail_in , & next_in , & avail_out , 3216reinterpret_cast < uint8_t **> ( & next_out ), & total_out ); 3217 3218if ( decoder_r == BROTLI_DECODER_RESULT_ERROR ) { return false; } 3219 3220if (! callback ( buff . data (), buff . size () - avail_out )) { return false; } 3221} 3222 3223return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || 3224decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT ; 3225} 3226#endif 3227 3228#ifdef CPPHTTPLIB_ZSTD_SUPPORT 3229zstd_compressor:: zstd_compressor () { 3230ctx_ = ZSTD_createCCtx (); 3231ZSTD_CCtx_setParameter ( ctx_ , ZSTD_c_compressionLevel , ZSTD_fast ); 3232} 3233 3234zstd_compressor::~ zstd_compressor () { ZSTD_freeCCtx ( ctx_ ); } 3235 3236bool zstd_compressor:: compress (const char * data , size_t data_length , 3237bool last , Callback callback ) { 3238std:: array < char , CPPHTTPLIB_COMPRESSION_BUFSIZ > buff {}; 3239 3240ZSTD_EndDirective mode = last ? ZSTD_e_end : ZSTD_e_continue ; 3241ZSTD_inBuffer input = { data , data_length , 0 }; 3242 3243bool finished ; 3244do { 3245ZSTD_outBuffer output = { buff . data (), CPPHTTPLIB_COMPRESSION_BUFSIZ , 0 }; 3246size_t const remaining = ZSTD_compressStream2 ( ctx_ , & output , & input , mode ); 3247 3248if ( ZSTD_isError ( remaining )) { return false; } 3249 3250if (! callback ( buff . data (), output . pos )) { return false; } 3251 3252finished = last ? ( remaining == 0 ) : ( input . pos == input . size ); 3253 3254} while (! finished ); 3255 3256return true; 3257} 3258 3259zstd_decompressor:: zstd_decompressor () { ctx_ = ZSTD_createDCtx (); } 3260 3261zstd_decompressor::~ zstd_decompressor () { ZSTD_freeDCtx ( ctx_ ); } 3262 3263bool zstd_decompressor:: is_valid () const { return ctx_ != nullptr ; } 3264 3265bool zstd_decompressor:: decompress (const char * data , size_t data_length , 3266Callback callback ) { 3267std:: array < char , CPPHTTPLIB_COMPRESSION_BUFSIZ > buff {}; 3268ZSTD_inBuffer input = { data , data_length , 0 }; 3269 3270while ( input . pos < input . size ) { 3271ZSTD_outBuffer output = { buff . data (), CPPHTTPLIB_COMPRESSION_BUFSIZ , 0 }; 3272size_t const remaining = ZSTD_decompressStream ( ctx_ , & output , & input ); 3273 3274if ( ZSTD_isError ( remaining )) { return false; } 3275 3276if (! callback ( buff . data (), output . pos )) { return false; } 3277} 3278 3279return true; 3280} 3281#endif 3282 3283// Content codings are case-insensitive (RFC 9110 8.4.1). Matching them 3284// case-sensitively would make a response labeled e.g. "GZIP" look like an 3285// unknown coding, and its payload would be handed back still compressed. 3286bool is_zlib_encoding (const std:: string & encoding ) { 3287return case_ignore:: equal ( encoding , "gzip" ) || 3288case_ignore:: equal ( encoding , "deflate" ); 3289} 3290 3291bool is_brotli_encoding (const std:: string & encoding ) { 3292return case_ignore:: equal ( encoding , "br" ); 3293} 3294 3295bool is_zstd_encoding (const std:: string & encoding ) { 3296return case_ignore:: equal ( encoding , "zstd" ); 3297} 3298 3299// Returns true if the content coding is one cpp-httplib is able to decompress 3300// when the corresponding support is compiled in. 3301bool is_known_content_encoding (const std:: string & encoding ) { 3302return is_zlib_encoding ( encoding ) || is_brotli_encoding ( encoding ) || 3303is_zstd_encoding ( encoding ); 3304} 3305 3306std:: unique_ptr < decompressor > 3307create_decompressor (const std:: string & encoding ) { 3308std:: unique_ptr < decompressor > decompressor ; 3309 3310if ( is_zlib_encoding ( encoding )) { 3311#ifdef CPPHTTPLIB_ZLIB_SUPPORT 3312decompressor = detail:: make_unique < gzip_decompressor > (); 3313#endif 3314} else if ( is_brotli_encoding ( encoding )) { 3315#ifdef CPPHTTPLIB_BROTLI_SUPPORT 3316decompressor = detail:: make_unique < brotli_decompressor > (); 3317#endif 3318} else if ( is_zstd_encoding ( encoding )) { 3319#ifdef CPPHTTPLIB_ZSTD_SUPPORT 3320decompressor = detail:: make_unique < zstd_decompressor > (); 3321#endif 3322} 3323 3324return decompressor ; 3325} 3326 3327// Returns the best available compressor and its Content-Encoding name. 3328// Priority: Brotli > Gzip > Zstd (matches server-side preference). 3329std:: pair < std:: unique_ptr < compressor > , const char *> 3330create_compressor () { 3331#ifdef CPPHTTPLIB_BROTLI_SUPPORT 3332return {detail:: make_unique < brotli_compressor > (), "br" }; 3333#elif defined( CPPHTTPLIB_ZLIB_SUPPORT ) 3334return {detail:: make_unique < gzip_compressor > (), "gzip" }; 3335#elif defined( CPPHTTPLIB_ZSTD_SUPPORT ) 3336return {detail:: make_unique < zstd_compressor > (), "zstd" }; 3337#else 3338return { nullptr , nullptr }; 3339#endif 3340} 3341 3342bool is_prohibited_header_name (const std:: string & name ) { 3343using udl::operator"" _t ; 3344 3345switch ( str2tag ( name )) { 3346case "REMOTE_ADDR" _t: 3347case "REMOTE_PORT" _t: 3348case "LOCAL_ADDR" _t: 3349case "LOCAL_PORT" _t: return true; 3350default: return false; 3351} 3352} 3353 3354bool has_header (const Headers & headers , const std:: string & key ) { 3355if ( is_prohibited_header_name ( key )) { return false; } 3356return headers . find ( key ) != headers . end (); 3357} 3358 3359const char * get_header_value (const Headers & headers , 3360const std:: string & key , const char * def , 3361size_t id ) { 3362if ( is_prohibited_header_name ( key )) { 3363#ifndef CPPHTTPLIB_NO_EXCEPTIONS 3364std:: string msg = "Prohibited header name '" + key + "' is specified." ; 3365throw std:: invalid_argument ( msg ); 3366#else 3367return "" ; 3368#endif 3369} 3370 3371auto rng = headers . equal_range ( key ); 3372auto it = rng . first ; 3373std:: advance ( it , static_cast < ssize_t > ( id )); 3374if ( it != rng . second ) { return it -> second . c_str (); } 3375return def ; 3376} 3377 3378size_t get_header_value_count (const Headers & headers , 3379const std:: string & key ) { 3380return headers . count ( key ); 3381} 3382 3383// RFC 9110 Section 5.2 and 5.3: a field that is defined as a comma-separated 3384// list may be sent as several field lines, and the combined field value is 3385// those values joined by commas in the order they were received. Callers that 3386// parse such a list must work on the combined value; reading only the first 3387// occurrence silently drops whatever the later field lines carry. 3388std:: string get_combined_header_value (const Headers & headers , 3389const std:: string & key ) { 3390std:: string combined ; 3391auto rng = headers . equal_range ( key ); 3392for ( auto it = rng . first ; it != rng . second ; ++ it ) { 3393// RFC 9110 Section 5.6.1.2: a recipient has to parse and ignore empty list 3394// elements, so an empty field line must not contribute a bare comma to the 3395// combined value. 3396if ( it -> second . empty ()) { continue; } 3397if (! combined . empty ()) { combined += ", " ; } 3398combined += it -> second ; 3399} 3400return combined ; 3401} 3402 3403bool has_header_token (const Headers & headers , const std:: string & key , 3404const std:: string & token ) { 3405// RFC 9110 7.6.1: a comma-separated token list field such as Connection may 3406// carry several tokens, and RFC 9110 5.3 lets that list be split across 3407// several lines. Match complete tokens rather than searching the raw value, 3408// so that a value such as "notupgrade" is not read as the token "upgrade". 3409auto rng = headers . equal_range ( key ); 3410for ( auto it = rng . first ; it != rng . second ; ++ it ) { 3411const auto & value = it -> second ; 3412if ( split_find ( value . data (), value . data () + value . size (), ',' , 3413[ & ](const char * b , const char * e ) { 3414return case_ignore:: equal (std:: string ( b , e ), token ); 3415})) { 3416return true; 3417} 3418} 3419return false; 3420} 3421 3422template < typename Map > 3423typename Map :: mapped_type 3424get_multimap_value (const Map & m , const std:: string & key , size_t id ) { 3425auto rng = m . equal_range ( key ); 3426auto it = rng . first ; 3427std:: advance ( it , static_cast < ssize_t > ( id )); 3428if ( it != rng . second ) { return it -> second ; } 3429return typename Map :: mapped_type (); 3430} 3431 3432void set_header ( Headers & headers , const std:: string & key , 3433const std:: string & val ) { 3434if (fields:: is_field_valid ( key , val )) { headers . emplace ( key , val ); } 3435} 3436 3437bool read_headers ( Stream & strm , Headers & headers ) { 3438const auto bufsiz = 2048 ; 3439char buf [ bufsiz ]; 3440stream_line_reader line_reader ( strm , buf , bufsiz ); 3441 3442size_t header_count = 0 ; 3443 3444for (;;) { 3445if (! line_reader . getline ()) { return false; } 3446 3447// Check if the line ends with CRLF. 3448auto line_terminator_len = 2 ; 3449if ( line_reader . end_with_crlf ()) { 3450// Blank line indicates end of headers. 3451if ( line_reader . size () == 2 ) { break; } 3452} else { 3453#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR 3454// Blank line indicates end of headers. 3455if ( line_reader . size () == 1 ) { break; } 3456line_terminator_len = 1 ; 3457#else 3458continue; // Skip invalid line. 3459#endif 3460} 3461 3462if ( line_reader . size () > CPPHTTPLIB_HEADER_MAX_LENGTH ) { return false; } 3463 3464// Check header count limit 3465if ( header_count >= CPPHTTPLIB_HEADER_MAX_COUNT ) { return false; } 3466 3467// Exclude line terminator 3468auto end = line_reader . ptr () + line_reader . size () - line_terminator_len ; 3469 3470if (! parse_header ( line_reader . ptr (), end , 3471[ & ](const std:: string & key , const std:: string & val ) { 3472headers . emplace ( key , val ); 3473})) { 3474return false; 3475} 3476 3477header_count ++ ; 3478} 3479 3480// RFC 9110 Section 8.6: Reject requests with multiple Content-Length 3481// headers that have different values to prevent request smuggling. 3482auto cl_range = headers . equal_range ( "Content-Length" ); 3483if ( cl_range . first != cl_range . second ) { 3484const auto & first_val = cl_range . first -> second ; 3485for ( auto it = std:: next ( cl_range . first ); it != cl_range . second ; ++ it ) { 3486if ( it -> second != first_val ) { return false; } 3487} 3488} 3489 3490return true; 3491} 3492 3493bool parse_status_line (const char * line , std:: string & version , 3494int & status , std:: string & reason ) { 3495#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR 3496thread_local const std:: regex re ( "(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n" ); 3497#else 3498thread_local const std:: regex re ( "(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n" ); 3499#endif 3500 3501std:: cmatch m ; 3502if (!std:: regex_match ( line , m , re )) { return false; } 3503version = std:: string ( m [ 1 ]); 3504status = std:: stoi (std:: string ( m [ 2 ])); 3505reason = std:: string ( m [ 3 ]); 3506return true; 3507} 3508 3509// Everything WebSocketClient::connect() reports about the upgrade exchange. 3510// status stays -1 until a status line is parsed, mirroring stream::Result. 3511struct WebSocketUpgradeResponse { 3512Error error = Error :: Success ; 3513int status = -1 ; 3514Headers headers ; 3515std:: string selected_subprotocol ; 3516}; 3517 3518bool read_websocket_upgrade_response ( Stream & strm , 3519const std:: string & expected_accept , 3520WebSocketUpgradeResponse & upgrade ) { 3521// Read status line 3522const auto bufsiz = 2048 ; 3523char buf [ bufsiz ]; 3524stream_line_reader line_reader ( strm , buf , bufsiz ); 3525if (! line_reader . getline ()) { 3526upgrade . error = Error :: Read ; 3527return false; 3528} 3529 3530std:: string version ; 3531std:: string reason ; 3532if (! parse_status_line ( line_reader . ptr (), version , upgrade . status , reason )) { 3533upgrade . error = Error :: WebSocketHandshake ; 3534return false; 3535} 3536 3537// Read the headers even for a rejection so the caller can see why the 3538// server refused the upgrade. A non-101 response may carry a body; it is 3539// deliberately left unread since the caller closes the socket right away. 3540if (! read_headers ( strm , upgrade . headers )) { 3541upgrade . error = Error :: Read ; 3542return false; 3543} 3544 3545const auto & headers = upgrade . headers ; 3546 3547if ( upgrade . status != StatusCode :: SwitchingProtocol_101 ) { 3548upgrade . error = Error :: WebSocketHandshake ; 3549return false; 3550} 3551 3552// Verify Upgrade: websocket (a comma-separated list, matched per token) 3553if (! has_header_token ( headers , "Upgrade" , "websocket" )) { 3554upgrade . error = Error :: WebSocketHandshake ; 3555return false; 3556} 3557 3558// Verify Connection: Upgrade 3559if (! has_header_token ( headers , "Connection" , "upgrade" )) { 3560upgrade . error = Error :: WebSocketHandshake ; 3561return false; 3562} 3563 3564// Verify Sec-WebSocket-Accept header value 3565auto it = headers . find ( "Sec-WebSocket-Accept" ); 3566if ( it == headers . end () || it -> second != expected_accept ) { 3567upgrade . error = Error :: WebSocketHandshake ; 3568return false; 3569} 3570 3571// Extract negotiated subprotocol 3572auto proto_it = headers . find ( "Sec-WebSocket-Protocol" ); 3573if ( proto_it != headers . end ()) { 3574upgrade . selected_subprotocol = proto_it -> second ; 3575} 3576 3577return true; 3578} 3579 3580enum class ReadContentResult { 3581Success , // Successfully read the content 3582PayloadTooLarge , // The content exceeds the specified payload limit 3583Error // An error occurred while reading the content 3584}; 3585 3586ReadContentResult read_content_with_length ( 3587Stream & strm , size_t len , DownloadProgress progress , 3588ContentReceiverWithProgress out , 3589size_t payload_max_length = (std:: numeric_limits < size_t > :: max )()) { 3590char buf [ CPPHTTPLIB_RECV_BUFSIZ ]; 3591 3592detail:: BodyReader br ; 3593br . stream = & strm ; 3594br . has_content_length = true; 3595br . content_length = len ; 3596br . payload_max_length = payload_max_length ; 3597br . chunked = false; 3598br . bytes_read = 0 ; 3599br . last_error = Error :: Success ; 3600 3601size_t r = 0 ; 3602while ( r < len ) { 3603auto read_len = static_cast < size_t > ( len - r ); 3604auto to_read = (std:: min )( read_len , CPPHTTPLIB_RECV_BUFSIZ ); 3605auto n = detail:: read_body_content ( & strm , br , buf , to_read ); 3606if ( n <= 0 ) { 3607// Check if it was a payload size error 3608if ( br . last_error == Error :: ExceedMaxPayloadSize ) { 3609return ReadContentResult :: PayloadTooLarge ; 3610} 3611return ReadContentResult :: Error ; 3612} 3613 3614if (! out ( buf , static_cast < size_t > ( n ), r , len )) { 3615return ReadContentResult :: Error ; 3616} 3617r += static_cast < size_t > ( n ); 3618 3619if ( progress ) { 3620if (! progress ( r , len )) { return ReadContentResult :: Error ; } 3621} 3622} 3623 3624return ReadContentResult :: Success ; 3625} 3626 3627ReadContentResult 3628read_content_without_length ( Stream & strm , size_t payload_max_length , 3629ContentReceiverWithProgress out ) { 3630char buf [ CPPHTTPLIB_RECV_BUFSIZ ]; 3631size_t r = 0 ; 3632for (;;) { 3633auto n = strm . read ( buf , CPPHTTPLIB_RECV_BUFSIZ ); 3634if ( n == 0 ) { return ReadContentResult :: Success ; } 3635if ( n < 0 ) { return ReadContentResult :: Error ; } 3636 3637// Check if adding this data would exceed the payload limit 3638if ( r > payload_max_length || 3639payload_max_length - r < static_cast < size_t > ( n )) { 3640return ReadContentResult :: PayloadTooLarge ; 3641} 3642 3643if (! out ( buf , static_cast < size_t > ( n ), r , 0 )) { 3644return ReadContentResult :: Error ; 3645} 3646r += static_cast < size_t > ( n ); 3647} 3648 3649return ReadContentResult :: Success ; 3650} 3651 3652template < typename T > 3653ReadContentResult read_content_chunked ( Stream & strm , T & x , 3654size_t payload_max_length , 3655ContentReceiverWithProgress out ) { 3656detail:: ChunkedDecoder dec ( strm ); 3657 3658char buf [ CPPHTTPLIB_RECV_BUFSIZ ]; 3659size_t total_len = 0 ; 3660 3661for (;;) { 3662size_t chunk_offset = 0 ; 3663size_t chunk_total = 0 ; 3664auto n = dec . read_payload ( buf , sizeof( buf ), chunk_offset , chunk_total ); 3665if ( n < 0 ) { return ReadContentResult :: Error ; } 3666 3667if ( n == 0 ) { 3668if (! dec . parse_trailers_into ( x . trailers , x . headers )) { 3669return ReadContentResult :: Error ; 3670} 3671return ReadContentResult :: Success ; 3672} 3673 3674if ( total_len > payload_max_length || 3675payload_max_length - total_len < static_cast < size_t > ( n )) { 3676return ReadContentResult :: PayloadTooLarge ; 3677} 3678 3679if (! out ( buf , static_cast < size_t > ( n ), chunk_offset , chunk_total )) { 3680return ReadContentResult :: Error ; 3681} 3682 3683total_len += static_cast < size_t > ( n ); 3684} 3685} 3686 3687bool is_chunked_transfer_encoding (const Headers & headers ) { 3688// RFC 9112 6.1: a message is framed with the chunked coding when "chunked" 3689// is the final transfer coding. A single field value may list several 3690// codings ("gzip, chunked"), and RFC 9110 5.3 lets that list be split across 3691// several Transfer-Encoding lines, which combine into one comma-separated 3692// list in the order the lines were received. Headers preserves that order, 3693// so the final coding is the last token of the last line. Match it 3694// case-insensitively rather than comparing the whole value against 3695// "chunked". 3696// 3697// Security: reading a chunked message as unframed leaves its body in the 3698// socket, where a keep-alive connection parses it as a smuggled request. 3699// Server::process_request() answers 400 and closes when the final coding is 3700// not chunked, so a request whose framing cannot be determined never 3701// reaches the "no body" path. 3702auto rng = headers . equal_range ( "Transfer-Encoding" ); 3703if ( rng . first == rng . second ) { return false; } 3704 3705// Cleared per line, so a trailing line carrying no coding at all leaves the 3706// combined list ending in nothing rather than inheriting the line before it. 3707std:: string last_coding ; 3708 3709for ( auto it = rng . first ; it != rng . second ; ++ it ) { 3710const auto & value = it -> second ; 3711last_coding . clear (); 3712split ( value . data (), value . data () + value . size (), ',' , 3713[ & ](const char * b , const char * e ) { last_coding . assign ( b , e ); }); 3714} 3715 3716return case_ignore:: equal ( last_coding , "chunked" ); 3717} 3718 3719bool has_conflicting_content_length (const Headers & headers ) { 3720// RFC 9112 §6.3: a message carrying both Transfer-Encoding and a non-zero 3721// Content-Length is framed ambiguously. The body readers here delimit it by 3722// the transfer coding and drop Content-Length, while an intermediary may do 3723// the reverse, so the two disagree on where the body ends and a reused 3724// connection is desynchronised (request/response smuggling). Content-Length: 3725// 0 is tolerated for compatibility with existing peers. 3726return has_header ( headers , "Transfer-Encoding" ) && 3727get_header_value_u64 ( headers , "Content-Length" , 0 , 0 ) > 0 ; 3728} 3729 3730template < typename T , typename U > 3731bool prepare_content_receiver ( T & x , int & status , 3732ContentReceiverWithProgress receiver , 3733bool decompress , size_t payload_max_length , 3734bool & exceed_payload_max_length , U callback ) { 3735if ( decompress ) { 3736auto encoding = get_combined_header_value ( x . headers , "Content-Encoding" ); 3737std:: unique_ptr < decompressor > decompressor ; 3738 3739if (! encoding . empty ()) { 3740// A coding we know about but were not built with is an error. An 3741// unrecognized coding (including "identity") is left alone and the 3742// payload is passed through as-is, since some servers misuse the header, 3743// e.g. by sending a character set such as "Content-Encoding: UTF-8". 3744decompressor = detail:: create_decompressor ( encoding ); 3745if (! decompressor && detail:: is_known_content_encoding ( encoding )) { 3746status = StatusCode :: UnsupportedMediaType_415 ; 3747return false; 3748} 3749} 3750 3751if ( decompressor ) { 3752if ( decompressor -> is_valid ()) { 3753size_t decompressed_size = 0 ; 3754ContentReceiverWithProgress out = [ & ](const char * buf , size_t n , 3755size_t off , size_t len ) { 3756return decompressor -> decompress ( 3757buf , n , [ & ](const char * buf2 , size_t n2 ) { 3758// Guard against zip-bomb: check 3759// decompressed size against limit. 3760if ( payload_max_length > 0 && 3761( decompressed_size >= payload_max_length || 3762n2 > payload_max_length - decompressed_size )) { 3763exceed_payload_max_length = true; 3764return false; 3765} 3766decompressed_size += n2 ; 3767return receiver ( buf2 , n2 , off , len ); 3768}); 3769}; 3770return callback (std:: move ( out )); 3771} else { 3772status = StatusCode :: InternalServerError_500 ; 3773return false; 3774} 3775} 3776} 3777 3778ContentReceiverWithProgress out = [ & ](const char * buf , size_t n , size_t off , 3779size_t len ) { 3780return receiver ( buf , n , off , len ); 3781}; 3782return callback (std:: move ( out )); 3783} 3784 3785template < typename T > 3786bool read_content ( Stream & strm , T & x , size_t payload_max_length , int & status , 3787DownloadProgress progress , 3788ContentReceiverWithProgress receiver , bool decompress ) { 3789bool exceed_payload_max_length = false; 3790return prepare_content_receiver ( 3791x , status , std:: move ( receiver ), decompress , payload_max_length , 3792exceed_payload_max_length , [ & ](const ContentReceiverWithProgress & out ) { 3793auto ret = true; 3794// Note: exceed_payload_max_length may also be set by the decompressor 3795// wrapper in prepare_content_receiver when the decompressed payload 3796// size exceeds the limit. 3797 3798if ( is_chunked_transfer_encoding ( x . headers )) { 3799auto result = read_content_chunked ( strm , x , payload_max_length , out ); 3800if ( result == ReadContentResult :: Success ) { 3801ret = true; 3802} else if ( result == ReadContentResult :: PayloadTooLarge ) { 3803exceed_payload_max_length = true; 3804ret = false; 3805} else { 3806ret = false; 3807} 3808} else if (! has_header ( x . headers , "Content-Length" )) { 3809auto result = 3810read_content_without_length ( strm , payload_max_length , out ); 3811if ( result == ReadContentResult :: Success ) { 3812ret = true; 3813} else if ( result == ReadContentResult :: PayloadTooLarge ) { 3814exceed_payload_max_length = true; 3815ret = false; 3816} else { 3817ret = false; 3818} 3819} else { 3820auto is_invalid_value = false; 3821auto len = get_header_value_u64 ( x . headers , "Content-Length" , 3822(std:: numeric_limits < size_t > :: max )(), 38230 , is_invalid_value ); 3824 3825if ( is_invalid_value ) { 3826ret = false; 3827} else if ( len > 0 ) { 3828auto result = read_content_with_length ( 3829strm , len , std:: move ( progress ), out , payload_max_length ); 3830ret = ( result == ReadContentResult :: Success ); 3831if ( result == ReadContentResult :: PayloadTooLarge ) { 3832exceed_payload_max_length = true; 3833} 3834} 3835} 3836 3837if (! ret ) { 3838status = exceed_payload_max_length ? StatusCode :: PayloadTooLarge_413 3839: StatusCode :: BadRequest_400 ; 3840} 3841return ret ; 3842}); 3843} 3844 3845ssize_t write_request_line ( Stream & strm , const std:: string & method , 3846const std:: string & path ) { 3847// A request target must not carry CR/LF (or other control octets); otherwise 3848// a value smuggled into it splits the request line and injects headers or a 3849// whole request. The same field-value check already guards header values in 3850// check_and_write_headers and the request target in 3851// perform_websocket_handshake; apply it here too. 3852if (!fields:: is_field_value ( path )) { return -1 ; } 3853 3854std:: string s = method ; 3855s += ' ' ; 3856s += path ; 3857s += " HTTP/1.1\r\n" ; 3858return strm . write ( s . data (), s . size ()); 3859} 3860 3861ssize_t write_response_line ( Stream & strm , int status ) { 3862std:: string s = "HTTP/1.1 " ; 3863s += std:: to_string ( status ); 3864s += ' ' ; 3865s += httplib:: status_message ( status ); 3866s += "\r\n" ; 3867return strm . write ( s . data (), s . size ()); 3868} 3869 3870ssize_t write_headers ( Stream & strm , const Headers & headers ) { 3871ssize_t write_len = 0 ; 3872for (const auto & x : headers ) { 3873// Skip fields with invalid names or values to prevent response splitting 3874// via CR/LF injection, matching set_header(). The client validates request 3875// headers up front in check_and_write_headers, but the server passes 3876// res.headers straight to this writer, and res.headers is a public field 3877// an application can populate directly with request-derived values. 3878if (!fields:: is_field_valid ( x . first , x . second )) { continue; } 3879 3880std:: string s ; 3881s = x . first ; 3882s += ": " ; 3883s += x . second ; 3884s += "\r\n" ; 3885 3886auto len = strm . write ( s . data (), s . size ()); 3887if ( len < 0 ) { return len ; } 3888write_len += len ; 3889} 3890auto len = strm . write ( "\r\n" ); 3891if ( len < 0 ) { return len ; } 3892write_len += len ; 3893return write_len ; 3894} 3895 3896bool write_data ( Stream & strm , const char * d , size_t l ) { 3897size_t offset = 0 ; 3898while ( offset < l ) { 3899auto length = strm . write ( d + offset , l - offset ); 3900if ( length < 0 ) { return false; } 3901offset += static_cast < size_t > ( length ); 3902} 3903return true; 3904} 3905 3906template < typename T > 3907bool write_content_with_progress ( Stream & strm , 3908const ContentProvider & content_provider , 3909size_t offset , size_t length , 3910T is_shutting_down , 3911const UploadProgress & upload_progress , 3912Error & error ) { 3913size_t end_offset = offset + length ; 3914size_t start_offset = offset ; 3915auto ok = true; 3916auto finished = false; 3917DataSink data_sink ; 3918 3919data_sink . write = [ & ](const char * d , size_t l ) -> bool { 3920if ( ok ) { 3921if ( write_data ( strm , d , l )) { 3922offset += l ; 3923 3924if ( upload_progress && length > 0 ) { 3925size_t current_written = offset - start_offset ; 3926if (! upload_progress ( current_written , length )) { 3927ok = false; 3928return false; 3929} 3930} 3931} else { 3932ok = false; 3933} 3934} 3935return ok ; 3936}; 3937 3938data_sink . is_writable = [ & ]() -> bool { return strm . is_peer_alive (); }; 3939 3940// The body is framed by `length`, so a provider that reports itself done 3941// early has truncated it. Record that and let the short-body check below 3942// fail the write, rather than calling the provider again forever. 3943data_sink . done = [ & ]() { finished = true; }; 3944 3945while ( offset < end_offset && ! finished && ! is_shutting_down ()) { 3946auto last_offset = offset ; 3947 3948if (! strm . wait_writable () || ! strm . is_peer_alive ()) { 3949error = Error :: Write ; 3950return false; 3951} else if (! content_provider ( offset , end_offset - offset , data_sink )) { 3952error = Error :: Canceled ; 3953return false; 3954} else if (! ok ) { 3955error = Error :: Write ; 3956return false; 3957} 3958 3959// A provider that reports success without writing anything and without 3960// reporting itself done gets handed the same offset and length again on 3961// the next pass, so it would spin here for as long as the peer stays 3962// connected. Treat making no progress as a short body, like done() early. 3963if (! finished && offset == last_offset ) { 3964error = Error :: Write ; 3965return false; 3966} 3967} 3968 3969if ( offset < end_offset ) { // done() called early, or is_shutting_down() 3970error = Error :: Write ; 3971return false; 3972} 3973 3974error = Error :: Success ; 3975return true; 3976} 3977 3978template < typename T > 3979bool write_content ( Stream & strm , const ContentProvider & content_provider , 3980size_t offset , size_t length , T is_shutting_down , 3981Error & error ) { 3982return write_content_with_progress < T > ( strm , content_provider , offset , length , 3983is_shutting_down , nullptr , error ); 3984} 3985 3986template < typename T > 3987bool write_content ( Stream & strm , const ContentProvider & content_provider , 3988size_t offset , size_t length , 3989const T & is_shutting_down ) { 3990auto error = Error :: Success ; 3991return write_content ( strm , content_provider , offset , length , is_shutting_down , 3992error ); 3993} 3994 3995template < typename T > 3996bool 3997write_content_without_length ( Stream & strm , 3998const ContentProvider & content_provider , 3999const T & is_shutting_down ) { 4000size_t offset = 0 ; 4001auto data_available = true; 4002auto ok = true; 4003DataSink data_sink ; 4004 4005data_sink . write = [ & ](const char * d , size_t l ) -> bool { 4006if ( ok ) { 4007offset += l ; 4008if (! write_data ( strm , d , l )) { ok = false; } 4009} 4010return ok ; 4011}; 4012 4013data_sink . is_writable = [ & ]() -> bool { return strm . is_peer_alive (); }; 4014 4015data_sink . done = [ & ]( void ) { data_available = false; }; 4016 4017while ( data_available && ! is_shutting_down ()) { 4018if (! strm . wait_writable () || ! strm . is_peer_alive ()) { 4019return false; 4020} else if (! content_provider ( offset , 0 , data_sink )) { 4021return false; 4022} else if (! ok ) { 4023return false; 4024} 4025} 4026return ! data_available ; // true only if done() was called, false if shutting 4027// down 4028} 4029 4030// Runs a known-length content provider to completion and compresses what it 4031// writes into `out`. Nothing is buffered in identity form: a provider backed 4032// by an mmap hands the compressor a pointer straight into the mapping. 4033bool compress_content_provider (const ContentProvider & content_provider , 4034size_t length , compressor & cmp , 4035std:: string & out ) { 4036size_t offset = 0 ; 4037auto ok = true; 4038auto finished = false; 4039DataSink data_sink ; 4040 4041auto append = [ & ](const char * data , size_t data_len ) { 4042out . append ( data , data_len ); 4043return true; 4044}; 4045 4046data_sink . write = [ & ](const char * d , size_t l ) -> bool { 4047if (! ok ) { return false; } 4048offset += l ; 4049if ( l > 0 && ! cmp . compress ( d , l , false, append )) { ok = false; } 4050return ok ; 4051}; 4052 4053// The body is framed by `length`, so a provider that reports itself done 4054// early has truncated it; the short-body check below turns that into a 4055// failure rather than calling the provider again forever. 4056data_sink . done = [ & ]() { finished = true; }; 4057 4058while ( offset < length && ! finished ) { 4059auto prev_offset = offset ; 4060if (! content_provider ( offset , length - offset , data_sink ) || ! ok ) { 4061return false; 4062} 4063// No Stream to block on here, so a provider that keeps returning true 4064// without writing would spin. Treat a pass that made no progress as a 4065// failure. 4066if ( offset == prev_offset ) { return false; } 4067} 4068 4069if ( offset != length ) { return false; } 4070 4071return cmp . compress ( nullptr , 0 , true, append ); 4072} 4073 4074// Serves `m` as the response body. `set_content_provider()` clears the coding, 4075// so recording it has to come after; keeping both here means a third 4076// file-serving path cannot get that order wrong. 4077void set_file_content_provider ( Response & res , 4078const std:: shared_ptr < mmap > & m , 4079const std:: string & content_type , 4080EncodingType encoding ) { 4081res . set_content_provider ( 4082m -> size (), content_type , 4083[ m ]( size_t offset , size_t length , DataSink & sink ) -> bool { 4084sink . write ( m -> data () + offset , length ); 4085return true; 4086}); 4087 4088res . content_coding_ = encoding ; 4089} 4090 4091template < typename T , typename U > 4092bool 4093write_content_chunked ( Stream & strm , const ContentProvider & content_provider , 4094const T & is_shutting_down , U & compressor , Error & error ) { 4095size_t offset = 0 ; 4096auto data_available = true; 4097auto ok = true; 4098DataSink data_sink ; 4099 4100data_sink . write = [ & ](const char * d , size_t l ) -> bool { 4101// Only done()/done_with_trailer() end a chunked body. A pass with nothing 4102// to hand over is ordinary (an empty buffer popped off a queue), and a 4103// zero-length chunk is the terminator, so it must not be emitted here. 4104if ( ok && l > 0 ) { 4105offset += l ; 4106 4107std:: string payload ; 4108if ( compressor . compress ( d , l , false, 4109[ & ](const char * data , size_t data_len ) { 4110payload . append ( data , data_len ); 4111return true; 4112})) { 4113if (! payload . empty ()) { 4114// Emit chunked response header and footer for each chunk 4115auto chunk = 4116from_i_to_hex ( payload . size ()) + "\r\n" + payload + "\r\n" ; 4117if (! write_data ( strm , chunk . data (), chunk . size ())) { ok = false; } 4118} 4119} else { 4120ok = false; 4121} 4122} 4123return ok ; 4124}; 4125 4126data_sink . is_writable = [ & ]() -> bool { return strm . is_peer_alive (); }; 4127 4128auto done_with_trailer = [ & ](const Headers * trailer ) { 4129if (! ok ) { return; } 4130 4131data_available = false; 4132 4133std:: string payload ; 4134if (! compressor . compress ( nullptr , 0 , true, 4135[ & ](const char * data , size_t data_len ) { 4136payload . append ( data , data_len ); 4137return true; 4138})) { 4139ok = false; 4140return; 4141} 4142 4143if (! payload . empty ()) { 4144// Emit chunked response header and footer for each chunk 4145auto chunk = from_i_to_hex ( payload . size ()) + "\r\n" + payload + "\r\n" ; 4146if (! write_data ( strm , chunk . data (), chunk . size ())) { 4147ok = false; 4148return; 4149} 4150} 4151 4152constexpr const char done_marker [] = "0\r\n" ; 4153if (! write_data ( strm , done_marker , str_len ( done_marker ))) { ok = false; } 4154 4155// Trailer 4156if ( trailer ) { 4157for (const auto & kv : * trailer ) { 4158// Skip fields with invalid names or values to prevent response 4159// splitting via CR/LF injection, matching set_header(). 4160if (!fields:: is_field_valid ( kv . first , kv . second )) { continue; } 4161std:: string field_line = kv . first + ": " + kv . second + "\r\n" ; 4162if (! write_data ( strm , field_line . data (), field_line . size ())) { 4163ok = false; 4164} 4165} 4166} 4167 4168constexpr const char crlf [] = "\r\n" ; 4169if (! write_data ( strm , crlf , str_len ( crlf ))) { ok = false; } 4170}; 4171 4172data_sink . done = [ & ]( void ) { done_with_trailer ( nullptr ); }; 4173 4174data_sink . done_with_trailer = [ & ](const Headers & trailer ) { 4175done_with_trailer ( & trailer ); 4176}; 4177 4178while ( data_available && ! is_shutting_down ()) { 4179if (! strm . wait_writable () || ! strm . is_peer_alive ()) { 4180error = Error :: Write ; 4181return false; 4182} else if (! content_provider ( offset , 0 , data_sink )) { 4183error = Error :: Canceled ; 4184return false; 4185} else if (! ok ) { 4186error = Error :: Write ; 4187return false; 4188} 4189} 4190 4191if ( data_available ) { // exited due to is_shutting_down(), not done() 4192error = Error :: Write ; 4193return false; 4194} 4195 4196error = Error :: Success ; 4197return true; 4198} 4199 4200template < typename T , typename U > 4201bool write_content_chunked ( Stream & strm , 4202const ContentProvider & content_provider , 4203const T & is_shutting_down , U & compressor ) { 4204auto error = Error :: Success ; 4205return write_content_chunked ( strm , content_provider , is_shutting_down , 4206compressor , error ); 4207} 4208 4209template < typename T > 4210bool redirect ( T & cli , Request & req , Response & res , 4211const std:: string & path , const std:: string & location , 4212Error & error ) { 4213Request new_req = req ; 4214new_req . path = path ; 4215new_req . redirect_count_ -= 1 ; 4216 4217if ( res . status == StatusCode :: SeeOther_303 && 4218( req . method != "GET" && req . method != "HEAD" )) { 4219new_req . method = "GET" ; 4220new_req . body . clear (); 4221new_req . headers . clear (); 4222} 4223 4224Response new_res ; 4225 4226auto ret = cli . send ( new_req , new_res , error ); 4227if ( ret ) { 4228req = std:: move ( new_req ); 4229res = std:: move ( new_res ); 4230 4231if ( res . location . empty ()) { res . location = location ; } 4232} 4233return ret ; 4234} 4235 4236std:: string params_to_query_str (const Params & params ) { 4237std:: string query ; 4238 4239for ( auto it = params . begin (); it != params . end (); ++ it ) { 4240if ( it != params . begin ()) { query += '&' ; } 4241query += encode_query_component ( it -> first ); 4242query += '=' ; 4243query += encode_query_component ( it -> second ); 4244} 4245return query ; 4246} 4247 4248// Splits one "key=value" span of a query string at its first '='. A span with 4249// no '=' at all lands entirely in key, leaving val empty, which is how a bare 4250// "?flag" keeps its name. 4251void divide_query_pair (const char * b , const char * e , std:: string & key , 4252std:: string & val ) { 4253divide ( b , static_cast < std:: size_t > ( e - b ), '=' , 4254[ & ](const char * lhs_data , std:: size_t lhs_size , const char * rhs_data , 4255std:: size_t rhs_size ) { 4256key . assign ( lhs_data , lhs_size ); 4257val . assign ( rhs_data , rhs_size ); 4258}); 4259} 4260 4261void parse_query_text (const char * data , std:: size_t size , 4262Params & params ) { 4263std:: set < std:: string > cache ; 4264split ( data , data + size , '&' , [ & ](const char * b , const char * e ) { 4265std:: string kv ( b , e ); 4266if ( cache . find ( kv ) != cache . end ()) { return; } 4267cache . insert (std:: move ( kv )); 4268 4269std:: string key ; 4270std:: string val ; 4271divide_query_pair ( b , e , key , val ); 4272 4273if (! key . empty ()) { 4274params . emplace ( decode_query_component ( key ), decode_query_component ( val )); 4275} 4276}); 4277} 4278 4279void parse_query_text (const std:: string & s , Params & params ) { 4280parse_query_text ( s . data (), s . size (), params ); 4281} 4282 4283// Normalize a query string by decoding and re-encoding each key/value pair 4284// while preserving the original parameter order. This avoids double-encoding 4285// and ensures consistent encoding. It works on the raw string rather than 4286// parsing into Params and re-serializing, because that round trip cannot 4287// reproduce the input: params_to_query_str() always emits '=', so a bare 4288// "flag" would come back as "flag=", and parse_query_text() drops exactly 4289// duplicated pairs. 4290std:: string normalize_query_string (const std:: string & query ) { 4291std:: string result ; 4292split ( query . data (), query . data () + query . size (), '&' , 4293[ & ](const char * b , const char * e ) { 4294std:: string key ; 4295std:: string val ; 4296divide_query_pair ( b , e , key , val ); 4297 4298if (! key . empty ()) { 4299auto dec_key = decode_query_component ( key ); 4300auto dec_val = decode_query_component ( val ); 4301 4302if (! result . empty ()) { result += '&' ; } 4303result += encode_query_component ( dec_key ); 4304if (! val . empty () || std:: find ( b , e , '=' ) != e ) { 4305result += '=' ; 4306result += encode_query_component ( dec_val ); 4307} 4308} 4309}); 4310return result ; 4311} 4312 4313// Build the request target that goes on the wire from a caller-supplied path. 4314// Shared by the buffered send path and the streaming API so that both put the 4315// same bytes in the request line for the same input. 4316std:: string encode_request_target (const std:: string & target , 4317bool path_encode ) { 4318// `substr(0, npos)` yields the whole string, which is what the no-query 4319// case needs. 4320auto query_pos = target . find ( '?' ); 4321auto path_part = target . substr ( 0 , query_pos ); 4322std:: string query_part ; 4323if ( query_pos != std::string:: npos ) { 4324query_part = target . substr ( query_pos + 1 ); 4325} 4326 4327auto result = path_encode ? encode_path ( path_part ) : std:: move ( path_part ); 4328 4329if (! query_part . empty ()) { 4330// When path encoding is disabled the caller has supplied an already-encoded 4331// target and expects the exact bytes to be sent on the wire, so skip 4332// normalization for the query too. Normalizing would decode-then-re-encode 4333// it and corrupt pre-encoded binary payloads (e.g. turning `%20` into `+`, 4334// which a strict RFC 3986 server decodes back as `+`, not a space). 4335if ( path_encode ) { 4336auto normalized = normalize_query_string ( query_part ); 4337if (! normalized . empty ()) { 4338result += '?' ; 4339result += normalized ; 4340} 4341} else { 4342result += '?' ; 4343result += query_part ; 4344} 4345} 4346 4347return result ; 4348} 4349 4350bool parse_multipart_boundary (const std:: string & content_type , 4351std:: string & boundary ) { 4352std:: map < std:: string , std:: string > params ; 4353extract_media_type ( content_type , & params ); 4354auto it = params . find ( "boundary" ); 4355if ( it == params . end ()) { return false; } 4356boundary = it -> second ; 4357// RFC 2046 5.1.1 caps a boundary at 70 characters. The parser scans the body 4358// for "--" + boundary, so a body crafted to repeat that delimiter's leading 4359// bytes costs a nearly full comparison at nearly every position: the 4360// boundary's length multiplies the worst-case cost of scanning a body. 4361return ! boundary . empty () && boundary . size () <= 70 ; 4362} 4363 4364void parse_disposition_params (const std:: string & s , Params & params ) { 4365std:: set < std:: string > cache ; 4366split_unquoted ( s . data (), s . data () + s . size (), ';' , 4367[ & ](const char * b , const char * e ) { 4368std:: string kv ( b , e ); 4369if ( cache . find ( kv ) != cache . end ()) { return; } 4370cache . insert ( kv ); 4371 4372std:: string key ; 4373std:: string val ; 4374divide_param_pair ( b , e , key , val ); 4375 4376if (! key . empty ()) { 4377params . emplace ( trim_double_quotes_copy ( key ), 4378trim_double_quotes_copy ( val )); 4379} 4380}); 4381} 4382 4383#ifdef CPPHTTPLIB_NO_EXCEPTIONS 4384bool parse_range_header (const std:: string & s , Ranges & ranges ) { 4385#else 4386bool parse_range_header (const std:: string & s , Ranges & ranges ) try { 4387#endif 4388auto is_valid = [](const std:: string & str ) { 4389return std:: all_of ( str . cbegin (), str . cend (), is_ascii_digit ); 4390}; 4391 4392if ( s . size () > 7 && s . compare ( 0 , 6 , "bytes=" ) == 0 ) { 4393const auto pos = static_cast < size_t > ( 6 ); 4394const auto len = static_cast < size_t > ( s . size () - 6 ); 4395auto all_valid_ranges = true; 4396split ( & s [ pos ], & s [ pos + len ], ',' , [ & ](const char * b , const char * e ) { 4397if (! all_valid_ranges ) { return; } 4398 4399const auto it = std:: find ( b , e , '-' ); 4400if ( it == e ) { 4401all_valid_ranges = false; 4402return; 4403} 4404 4405const auto lhs = std:: string ( b , it ); 4406const auto rhs = std:: string ( it + 1 , e ); 4407if (! is_valid ( lhs ) || ! is_valid ( rhs )) { 4408all_valid_ranges = false; 4409return; 4410} 4411 4412ssize_t first = -1 ; 4413if (! lhs . empty ()) { 4414// Reject an overflowing first-byte-pos; treating it as absent (-1) 4415// would turn the range into a suffix range. 4416auto res = 4417detail:: from_chars ( lhs . data (), lhs . data () + lhs . size (), first ); 4418if ( res . ec != std:: errc {}) { 4419all_valid_ranges = false; 4420return; 4421} 4422} 4423 4424ssize_t last = -1 ; 4425if (! rhs . empty ()) { 4426// An overflowing last-byte-pos is past any content length, so keeping 4427// -1 ("remainder", RFC 9110 14.1.2) is correct here. 4428ssize_t v ; 4429auto res = detail:: from_chars ( rhs . data (), rhs . data () + rhs . size (), v ); 4430if ( res . ec == std:: errc {}) { last = v ; } 4431} 4432 4433if (( first == -1 && last == -1 ) || 4434( first != -1 && last != -1 && first > last )) { 4435all_valid_ranges = false; 4436return; 4437} 4438 4439ranges . emplace_back ( first , last ); 4440}); 4441return all_valid_ranges && ! ranges . empty (); 4442} 4443return false; 4444#ifdef CPPHTTPLIB_NO_EXCEPTIONS 4445} 4446#else 4447} catch (...) { return false; } 4448#endif 4449 4450bool parse_accept_header (const std:: string & s , 4451std:: vector < std:: string > & content_types ) { 4452content_types . clear (); 4453 4454// Empty string is considered valid (no preference) 4455if ( s . empty ()) { return true; } 4456 4457struct AcceptEntry { 4458std:: string media_type ; 4459double quality ; 4460int order ; 4461}; 4462 4463std:: vector < AcceptEntry > entries ; 4464int order = 0 ; 4465bool has_invalid_entry = false; 4466 4467// Split by comma and parse each entry. RFC 9110 Section 5.6.1.2: a recipient 4468// has to parse and ignore empty list elements, so a leading, trailing or 4469// doubled comma must not turn a legal Accept value into 400 Bad Request. 4470// split() skips them, and the header length limit bounds how many a sender 4471// can send, so ignoring all of them cannot be used as a denial-of-service 4472// vector. 4473split ( s . data (), s . data () + s . size (), ',' , [ & ](const char * b , const char * e ) { 4474std:: string entry ( b , e ); 4475entry = trim_copy ( entry ); 4476 4477AcceptEntry accept_entry ; 4478accept_entry . order = order ++ ; 4479 4480if (! parse_quality ( entry . data (), entry . data () + entry . size (), 4481accept_entry . media_type , accept_entry . quality )) { 4482has_invalid_entry = true; 4483return; 4484} 4485 4486// Remove additional parameters from media type 4487accept_entry . media_type = extract_media_type ( accept_entry . media_type ); 4488 4489// Basic validation of media type format 4490if ( accept_entry . media_type . empty ()) { 4491has_invalid_entry = true; 4492return; 4493} 4494 4495// Check for basic media type format (should contain '/' or be '*') 4496if ( accept_entry . media_type != "*" && 4497accept_entry . media_type . find ( '/' ) == std::string:: npos ) { 4498has_invalid_entry = true; 4499return; 4500} 4501 4502entries . push_back (std:: move ( accept_entry )); 4503}); 4504 4505// Return false if any invalid entry was found 4506if ( has_invalid_entry ) { return false; } 4507 4508// Sort by quality (descending), then by original order (ascending) 4509std:: sort ( entries . begin (), entries . end (), 4510[](const AcceptEntry & a , const AcceptEntry & b ) { 4511if ( a . quality != b . quality ) { 4512return a . quality > b . quality ; // Higher quality first 4513} 4514return a . order < b . order ; // Earlier order first for same quality 4515}); 4516 4517// Extract sorted media types 4518content_types . reserve ( entries . size ()); 4519for ( auto & entry : entries ) { 4520content_types . push_back (std:: move ( entry . media_type )); 4521} 4522 4523return true; 4524} 4525 4526class FormDataParser { 4527public: 4528FormDataParser () = default; 4529 4530void set_boundary (std:: string && boundary ) { 4531boundary_ = std:: move ( boundary ); 4532dash_boundary_crlf_ = dash_ + boundary_ + crlf_ ; 4533crlf_dash_boundary_ = crlf_ + dash_ + boundary_ ; 4534} 4535 4536bool is_valid () const { return is_valid_ ; } 4537 4538bool parse (const char * buf , size_t n , const FormDataHeader & header_callback , 4539const ContentReceiver & content_callback ) { 4540 4541// Once the close delimiter has been seen the rest of the body is epilogue 4542// to be discarded (RFC 2046). Drop it without buffering so a large epilogue 4543// spread across reads is not copied in only to be erased right away. 4544if ( state_ == 5 ) { return true; } 4545 4546buf_append ( buf , n ); 4547 4548while ( buf_size () > 0 ) { 4549switch ( state_ ) { 4550case 0 : { // Initial boundary 4551auto pos = buf_find ( dash_boundary_crlf_ ); 4552if ( pos == buf_size ()) { 4553// Not found yet: keep only a possible partial boundary at the tail so 4554// that a body which never contains the boundary cannot grow the 4555// buffer (and get rescanned from the start) without bound. 4556auto keep = dash_boundary_crlf_ . size () - 1 ; 4557if ( buf_size () > keep ) { buf_erase ( buf_size () - keep ); } 4558return true; 4559} 4560buf_erase ( pos + dash_boundary_crlf_ . size ()); 4561state_ = 1 ; 4562break; 4563} 4564case 1 : { // New entry 4565clear_file_info (); 4566state_ = 2 ; 4567break; 4568} 4569case 2 : { // Headers 4570auto pos = buf_find ( crlf_ ); 4571if ( pos > CPPHTTPLIB_HEADER_MAX_LENGTH ) { return false; } 4572while ( pos < buf_size ()) { 4573// Empty line 4574if ( pos == 0 ) { 4575if (! header_callback ( file_ )) { 4576is_valid_ = false; 4577return false; 4578} 4579buf_erase ( crlf_ . size ()); 4580state_ = 3 ; 4581break; 4582} 4583 4584// Check header count limit 4585if ( header_count_ >= CPPHTTPLIB_HEADER_MAX_COUNT ) { 4586is_valid_ = false; 4587return false; 4588} 4589header_count_ ++ ; 4590 4591const auto header = buf_head ( pos ); 4592 4593if (! parse_header ( header . data (), header . data () + header . size (), 4594[ & ](const std:: string & , const std:: string & ) {})) { 4595is_valid_ = false; 4596return false; 4597} 4598 4599// Parse and emplace space trimmed headers into a map 4600if (! parse_header ( 4601header . data (), header . data () + header . size (), 4602[ & ](const std:: string & key , const std:: string & val ) { 4603file_ . headers . emplace ( key , val ); 4604})) { 4605is_valid_ = false; 4606return false; 4607} 4608 4609constexpr const char header_content_type [] = "Content-Type:" ; 4610 4611if ( start_with_case_ignore ( header , header_content_type )) { 4612file_ . content_type = 4613trim_copy ( header . substr ( str_len ( header_content_type ))); 4614} else { 4615std:: string disposition_params ; 4616if ( parse_content_disposition ( header , disposition_params )) { 4617Params params ; 4618parse_disposition_params ( disposition_params , params ); 4619 4620auto it = params . find ( "name" ); 4621if ( it != params . end ()) { 4622file_ . name = it -> second ; 4623} else { 4624is_valid_ = false; 4625return false; 4626} 4627 4628it = params . find ( "filename" ); 4629if ( it != params . end ()) { file_ . filename = it -> second ; } 4630 4631it = params . find ( "filename*" ); 4632if ( it != params . end ()) { 4633// RFC 5987: only UTF-8 encoding is allowed 4634const auto & val = it -> second ; 4635constexpr const char utf8_prefix [] = "UTF-8''" ; 4636constexpr size_t prefix_len = str_len ( utf8_prefix ); 4637if ( val . size () > prefix_len && 4638start_with_case_ignore ( val , utf8_prefix )) { 4639file_ . filename = decode_path_component ( 4640val . substr ( prefix_len )); // override... 4641} else { 4642is_valid_ = false; 4643return false; 4644} 4645} 4646} 4647} 4648buf_erase ( pos + crlf_ . size ()); 4649pos = buf_find ( crlf_ ); 4650} 4651if ( state_ != 3 ) { return true; } 4652break; 4653} 4654case 3 : { // Body 4655if ( crlf_dash_boundary_ . size () > buf_size ()) { return true; } 4656auto pos = buf_find ( crlf_dash_boundary_ ); 4657if ( pos < buf_size ()) { 4658if (! content_callback ( buf_data (), pos )) { 4659is_valid_ = false; 4660return false; 4661} 4662buf_erase ( pos + crlf_dash_boundary_ . size ()); 4663state_ = 4 ; 4664} else { 4665auto len = buf_size () - crlf_dash_boundary_ . size (); 4666if ( len > 0 ) { 4667if (! content_callback ( buf_data (), len )) { 4668is_valid_ = false; 4669return false; 4670} 4671buf_erase ( len ); 4672} 4673return true; 4674} 4675break; 4676} 4677case 4 : { // Boundary 4678if ( crlf_ . size () > buf_size ()) { return true; } 4679if ( buf_start_with ( crlf_ )) { 4680buf_erase ( crlf_ . size ()); 4681state_ = 1 ; 4682} else if ( buf_start_with ( dash_ )) { 4683buf_erase ( dash_ . size ()); 4684is_valid_ = true; 4685state_ = 5 ; 4686} else { 4687// Only CRLF (another part follows) and "--" (close-delimiter) are 4688// accepted after a boundary; RFC 2046 allows transport-padding in 4689// between, but this parser has never supported it. Either way the 4690// body is already destined to be rejected, so fail now instead of 4691// buffering the rest of it. Both are two bytes, so the check above 4692// already guarantees enough buffered data to decide. 4693is_valid_ = false; 4694return false; 4695} 4696break; 4697} 4698case 5 : { // Epilogue 4699buf_erase ( buf_size ()); 4700break; 4701} 4702} 4703} 4704 4705return true; 4706} 4707 4708private: 4709void clear_file_info () { 4710file_ . name . clear (); 4711file_ . filename . clear (); 4712file_ . content_type . clear (); 4713file_ . headers . clear (); 4714header_count_ = 0 ; 4715} 4716 4717bool start_with_case_ignore (const std:: string & a , const char * b , 4718size_t offset = 0 ) const { 4719const auto b_len = strlen ( b ); 4720if ( a . size () < offset + b_len ) { return false; } 4721for ( size_t i = 0 ; i < b_len ; i ++ ) { 4722if (case_ignore:: to_lower ( a [ offset + i ]) != case_ignore:: to_lower ( b [ i ])) { 4723return false; 4724} 4725} 4726return true; 4727} 4728 4729// Parses "Content-Disposition: form-data; <params>" without std::regex. 4730// Returns true if header matches, with the params portion in `params_out`. 4731bool parse_content_disposition (const std:: string & header , 4732std:: string & params_out ) const { 4733constexpr const char prefix [] = "Content-Disposition:" ; 4734constexpr size_t prefix_len = str_len ( prefix ); 4735 4736if (! start_with_case_ignore ( header , prefix )) { return false; } 4737 4738// Skip whitespace after "Content-Disposition:" 4739auto pos = prefix_len ; 4740while ( pos < header . size () && ( header [ pos ] == ' ' || header [ pos ] == '\t' )) { 4741pos ++ ; 4742} 4743 4744// Match "form-data;" (case-insensitive) 4745constexpr const char form_data [] = "form-data;" ; 4746constexpr size_t form_data_len = str_len ( form_data ); 4747if (! start_with_case_ignore ( header , form_data , pos )) { return false; } 4748pos += form_data_len ; 4749 4750// Skip whitespace after "form-data;" 4751while ( pos < header . size () && ( header [ pos ] == ' ' || header [ pos ] == '\t' )) { 4752pos ++ ; 4753} 4754 4755params_out = header . substr ( pos ); 4756return true; 4757} 4758 4759const std:: string dash_ = "--" ; 4760const std:: string crlf_ = "\r\n" ; 4761std:: string boundary_ ; 4762std:: string dash_boundary_crlf_ ; 4763std:: string crlf_dash_boundary_ ; 4764 4765size_t state_ = 0 ; 4766bool is_valid_ = false; 4767FormData file_ ; 4768size_t header_count_ = 0 ; 4769 4770// Buffer 4771bool start_with (const std:: string & a , size_t spos , size_t epos , 4772const std:: string & b ) const { 4773if ( epos - spos < b . size ()) { return false; } 4774for ( size_t i = 0 ; i < b . size (); i ++ ) { 4775if ( a [ i + spos ] != b [ i ]) { return false; } 4776} 4777return true; 4778} 4779 4780size_t buf_size () const { return buf_epos_ - buf_spos_ ; } 4781 4782const char * buf_data () const { return & buf_ [ buf_spos_ ]; } 4783 4784std:: string buf_head ( size_t l ) const { return buf_ . substr ( buf_spos_ , l ); } 4785 4786bool buf_start_with (const std:: string & s ) const { 4787return start_with ( buf_ , buf_spos_ , buf_epos_ , s ); 4788} 4789 4790size_t buf_find (const std:: string & s ) const { 4791auto c = s . front (); 4792 4793size_t off = buf_spos_ ; 4794while ( off < buf_epos_ ) { 4795auto pos = off ; 4796while (true) { 4797if ( pos == buf_epos_ ) { return buf_size (); } 4798if ( buf_ [ pos ] == c ) { break; } 4799pos ++ ; 4800} 4801 4802auto remaining_size = buf_epos_ - pos ; 4803if ( s . size () > remaining_size ) { return buf_size (); } 4804 4805if ( start_with ( buf_ , pos , buf_epos_ , s )) { return pos - buf_spos_ ; } 4806 4807off = pos + 1 ; 4808} 4809 4810return buf_size (); 4811} 4812 4813void buf_append (const char * data , size_t n ) { 4814auto remaining_size = buf_size (); 4815if ( remaining_size > 0 && buf_spos_ > 0 ) { 4816for ( size_t i = 0 ; i < remaining_size ; i ++ ) { 4817buf_ [ i ] = buf_ [ buf_spos_ + i ]; 4818} 4819} 4820buf_spos_ = 0 ; 4821buf_epos_ = remaining_size ; 4822 4823if ( remaining_size + n > buf_ . size ()) { buf_ . resize ( remaining_size + n ); } 4824 4825for ( size_t i = 0 ; i < n ; i ++ ) { 4826buf_ [ buf_epos_ + i ] = data [ i ]; 4827} 4828buf_epos_ += n ; 4829} 4830 4831void buf_erase ( size_t size ) { buf_spos_ += size ; } 4832 4833std:: string buf_ ; 4834size_t buf_spos_ = 0 ; 4835size_t buf_epos_ = 0 ; 4836}; 4837 4838std:: string random_string ( size_t length ) { 4839constexpr const char data [] = 4840"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ; 4841 4842thread_local auto engine ([]() { 4843// std::random_device might actually be deterministic on some 4844// platforms, but due to lack of support in the c++ standard library, 4845// doing better requires either some ugly hacks or breaking portability. 4846std:: random_device seed_gen ; 4847// Request 128 bits of entropy for initialization 4848std:: seed_seq seed_sequence { seed_gen (), seed_gen (), seed_gen (), seed_gen ()}; 4849return std:: mt19937 ( seed_sequence ); 4850}()); 4851 4852std:: string result ; 4853for ( size_t i = 0 ; i < length ; i ++ ) { 4854result += data [ engine () % (sizeof( data ) - 1 )]; 4855} 4856return result ; 4857} 4858 4859std:: string make_multipart_data_boundary () { 4860return "--cpp-httplib-multipart-data-" + detail:: random_string ( 16 ); 4861} 4862 4863bool is_multipart_boundary_chars_valid (const std:: string & boundary ) { 4864auto valid = true; 4865for ( size_t i = 0 ; i < boundary . size (); i ++ ) { 4866auto c = boundary [ i ]; 4867if (! is_ascii_alnum ( c ) && c != '-' && c != '_' ) { 4868valid = false; 4869break; 4870} 4871} 4872return valid ; 4873} 4874 4875// Escape a multipart field name/filename following the WHATWG HTML standard 4876// ("escape a multipart form-data name"), which is what browsers send: 4877// '"' -> %22, CR -> %0D, LF -> %0A 4878// With escape_quote = false, only CR and LF are escaped; this is for header 4879// values outside a quoted-string (e.g. Content-Type), where '"' is legal. 4880std:: string escape_multipart_field (const std:: string & s , 4881bool escape_quote = true) { 4882std:: string result ; 4883result . reserve ( s . size ()); 4884for ( auto c : s ) { 4885switch ( c ) { 4886case '"' : 4887if ( escape_quote ) { 4888result += "%22" ; 4889} else { 4890result += c ; 4891} 4892break; 4893case '\r' : result += "%0D" ; break; 4894case '\n' : result += "%0A" ; break; 4895default: result += c ; break; 4896} 4897} 4898return result ; 4899} 4900 4901template < typename T > 4902std:: string 4903serialize_multipart_formdata_item_begin (const T & item , 4904const std:: string & boundary ) { 4905std:: string body = "--" + boundary + "\r\n" ; 4906body += "Content-Disposition: form-data; name=\"" + 4907escape_multipart_field ( item . name ) + "\"" ; 4908if (! item . filename . empty ()) { 4909body += "; filename=\"" + escape_multipart_field ( item . filename ) + "\"" ; 4910} 4911body += "\r\n" ; 4912if (! item . content_type . empty ()) { 4913body += 4914"Content-Type: " + escape_multipart_field ( item . content_type , false) + 4915"\r\n" ; 4916} 4917body += "\r\n" ; 4918 4919return body ; 4920} 4921 4922std:: string serialize_multipart_formdata_item_end () { return "\r\n" ; } 4923 4924std:: string 4925serialize_multipart_formdata_finish (const std:: string & boundary ) { 4926return "--" + boundary + "--\r\n" ; 4927} 4928 4929std:: string 4930serialize_multipart_formdata_get_content_type (const std:: string & boundary ) { 4931return "multipart/form-data; boundary=" + boundary ; 4932} 4933 4934std:: string 4935serialize_multipart_formdata (const UploadFormDataItems & items , 4936const std:: string & boundary , bool finish = true) { 4937std:: string body ; 4938 4939for (const auto & item : items ) { 4940body += serialize_multipart_formdata_item_begin ( item , boundary ); 4941body += item . content + serialize_multipart_formdata_item_end (); 4942} 4943 4944if ( finish ) { body += serialize_multipart_formdata_finish ( boundary ); } 4945 4946return body ; 4947} 4948 4949size_t get_multipart_content_length (const UploadFormDataItems & items , 4950const std:: string & boundary ) { 4951size_t total = 0 ; 4952for (const auto & item : items ) { 4953total += serialize_multipart_formdata_item_begin ( item , boundary ). size (); 4954total += item . content . size (); 4955total += serialize_multipart_formdata_item_end (). size (); 4956} 4957total += serialize_multipart_formdata_finish ( boundary ). size (); 4958return total ; 4959} 4960 4961struct MultipartSegment { 4962const char * data ; 4963size_t size ; 4964}; 4965 4966// NOTE: items must outlive the returned ContentProvider 4967// (safe for synchronous use inside Post/Put/Patch) 4968ContentProvider 4969make_multipart_content_provider (const UploadFormDataItems & items , 4970const std:: string & boundary ) { 4971// Own the per-item header strings and the finish string 4972std:: vector < std:: string > owned ; 4973owned . reserve ( items . size () + 1 ); 4974for (const auto & item : items ) 4975owned . push_back ( serialize_multipart_formdata_item_begin ( item , boundary )); 4976owned . push_back ( serialize_multipart_formdata_finish ( boundary )); 4977 4978// Flat segment list: [header, content, "\r\n"] * N + [finish] 4979std:: vector < MultipartSegment > segs ; 4980segs . reserve ( items . size () * 3 + 1 ); 4981static const char crlf [] = "\r\n" ; 4982for ( size_t i = 0 ; i < items . size (); i ++ ) { 4983segs . push_back ({ owned [ i ]. data (), owned [ i ]. size ()}); 4984segs . push_back ({ items [ i ]. content . data (), items [ i ]. content . size ()}); 4985segs . push_back ({ crlf , 2 }); 4986} 4987segs . push_back ({ owned . back (). data (), owned . back (). size ()}); 4988 4989struct MultipartState { 4990std:: vector < std:: string > owned ; 4991std:: vector < MultipartSegment > segs ; 4992std:: vector < char > buf = std:: vector < char > ( CPPHTTPLIB_SEND_BUFSIZ ); 4993}; 4994auto state = std:: make_shared < MultipartState > (); 4995state -> owned = std:: move ( owned ); 4996// `segs` holds raw pointers into owned strings; std::string move preserves 4997// the data pointer, so these pointers remain valid after the move above. 4998state -> segs = std:: move ( segs ); 4999 5000return [ state ]( size_t offset , size_t length , DataSink & sink ) -> bool { 5001// Buffer multiple small segments into fewer, larger writes to avoid 5002// excessive TCP packets when there are many form data items (#2410) 5003auto & buf = state -> buf ; 5004auto buf_size = buf . size (); 5005size_t buf_len = 0 ; 5006size_t remaining = length ; 5007 5008// Find the first segment containing 'offset' 5009size_t pos = 0 ; 5010size_t seg_idx = 0 ; 5011for (; seg_idx < state -> segs . size (); seg_idx ++ ) { 5012const auto & seg = state -> segs [ seg_idx ]; 5013if ( seg . size > 0 && offset - pos < seg . size ) { break; } 5014pos += seg . size ; 5015} 5016 5017size_t seg_offset = ( seg_idx < state -> segs . size ()) ? offset - pos : 0 ; 5018 5019for (; seg_idx < state -> segs . size () && remaining > 0 ; seg_idx ++ ) { 5020const auto & seg = state -> segs [ seg_idx ]; 5021size_t available = seg . size - seg_offset ; 5022size_t to_copy = (std:: min )( available , remaining ); 5023const char * src = seg . data + seg_offset ; 5024seg_offset = 0 ; // only the first segment has a non-zero offset 5025 5026while ( to_copy > 0 ) { 5027size_t space = buf_size - buf_len ; 5028size_t chunk = (std:: min )( to_copy , space ); 5029std:: memcpy ( buf . data () + buf_len , src , chunk ); 5030buf_len += chunk ; 5031src += chunk ; 5032to_copy -= chunk ; 5033remaining -= chunk ; 5034 5035if ( buf_len == buf_size ) { 5036if (! sink . write ( buf . data (), buf_len )) { return false; } 5037buf_len = 0 ; 5038} 5039} 5040} 5041 5042if ( buf_len > 0 ) { return sink . write ( buf . data (), buf_len ); } 5043return true; 5044}; 5045} 5046 5047void coalesce_ranges ( Ranges & ranges , size_t content_length ) { 5048if ( ranges . size () <= 1 ) return; 5049 5050// Sort ranges by start position 5051std:: sort ( ranges . begin (), ranges . end (), 5052[](const Range & a , const Range & b ) { return a . first < b . first ; }); 5053 5054Ranges coalesced ; 5055coalesced . reserve ( ranges . size ()); 5056 5057for ( auto & r : ranges ) { 5058auto first_pos = r . first ; 5059auto last_pos = r . second ; 5060 5061// Handle special cases like in range_error 5062if ( first_pos == -1 && last_pos == -1 ) { 5063first_pos = 0 ; 5064last_pos = static_cast < ssize_t > ( content_length ); 5065} 5066 5067if ( first_pos == -1 ) { 5068first_pos = static_cast < ssize_t > ( content_length ) - last_pos ; 5069last_pos = static_cast < ssize_t > ( content_length ) - 1 ; 5070} 5071 5072if ( last_pos == -1 || last_pos >= static_cast < ssize_t > ( content_length )) { 5073last_pos = static_cast < ssize_t > ( content_length ) - 1 ; 5074} 5075 5076// Skip invalid ranges 5077if (!( 0 <= first_pos && first_pos <= last_pos && 5078last_pos < static_cast < ssize_t > ( content_length ))) { 5079continue; 5080} 5081 5082// Coalesce with previous range if overlapping or adjacent (but not 5083// identical) 5084if (! coalesced . empty ()) { 5085auto & prev = coalesced . back (); 5086// Check if current range overlaps or is adjacent to previous range 5087// but don't coalesce identical ranges (allow duplicates) 5088if ( first_pos <= prev . second + 1 && 5089!( first_pos == prev . first && last_pos == prev . second )) { 5090// Extend the previous range 5091prev . second = (std:: max )( prev . second , last_pos ); 5092continue; 5093} 5094} 5095 5096// Add new range 5097coalesced . emplace_back ( first_pos , last_pos ); 5098} 5099 5100ranges = std:: move ( coalesced ); 5101} 5102 5103bool range_error ( Request & req , Response & res ) { 5104if (! req . ranges . empty () && 200 <= res . status && res . status < 300 ) { 5105if ( res . body . empty () && res . content_provider_ && res . content_length_ == 0 ) { 5106req . ranges . clear (); 5107if ( res . status == StatusCode :: PartialContent_206 ) { 5108res . status = StatusCode :: OK_200 ; 5109} 5110return false; 5111} 5112 5113ssize_t content_len = static_cast < ssize_t > ( 5114res . content_length_ ? res . content_length_ : res . body . size ()); 5115 5116std:: vector < std:: pair < ssize_t , ssize_t >> processed_ranges ; 5117size_t overwrapping_count = 0 ; 5118 5119// NOTE: The following Range check is based on '14.2. Range' in RFC 9110 5120// 'HTTP Semantics' to avoid potential denial-of-service attacks. 5121// https://www.rfc-editor.org/rfc/rfc9110#section-14.2 5122 5123// Too many ranges 5124if ( req . ranges . size () > CPPHTTPLIB_RANGE_MAX_COUNT ) { return true; } 5125 5126for ( auto & r : req . ranges ) { 5127auto & first_pos = r . first ; 5128auto & last_pos = r . second ; 5129 5130if ( first_pos == -1 && last_pos == -1 ) { 5131first_pos = 0 ; 5132last_pos = content_len ; 5133} 5134 5135if ( first_pos == -1 ) { 5136first_pos = content_len - last_pos ; 5137last_pos = content_len - 1 ; 5138} 5139 5140// NOTE: RFC-9110 '14.1.2. Byte Ranges': 5141// A client can limit the number of bytes requested without knowing the 5142// size of the selected representation. If the last-pos value is absent, 5143// or if the value is greater than or equal to the current length of the 5144// representation data, the byte range is interpreted as the remainder of 5145// the representation (i.e., the server replaces the value of last-pos 5146// with a value that is one less than the current length of the selected 5147// representation). 5148// https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6 5149if ( last_pos == -1 || last_pos >= content_len ) { 5150last_pos = content_len - 1 ; 5151} 5152 5153// Range must be within content length 5154if (!( 0 <= first_pos && first_pos <= last_pos && 5155last_pos <= content_len - 1 )) { 5156return true; 5157} 5158 5159// Request must not have more than two overlapping ranges 5160for (const auto & processed_range : processed_ranges ) { 5161if (!( last_pos < processed_range . first || 5162first_pos > processed_range . second )) { 5163overwrapping_count ++ ; 5164if ( overwrapping_count > 2 ) { return true; } 5165break; // Only count once per range 5166} 5167} 5168 5169processed_ranges . emplace_back ( first_pos , last_pos ); 5170} 5171 5172// After validation, coalesce overlapping ranges as per RFC 9110 5173coalesce_ranges ( req . ranges , static_cast < size_t > ( content_len )); 5174} 5175 5176return false; 5177} 5178 5179std:: pair < size_t , size_t > 5180get_range_offset_and_length ( Range r , size_t content_length ) { 5181assert ( r . first != -1 && r . second != -1 ); 5182assert ( 0 <= r . first && r . first < static_cast < ssize_t > ( content_length )); 5183assert ( r . first <= r . second && 5184r . second < static_cast < ssize_t > ( content_length )); 5185( void )( content_length ); 5186return std:: make_pair ( static_cast < size_t > ( r . first ), 5187static_cast < size_t > ( r . second - r . first ) + 1 ); 5188} 5189 5190std:: string make_content_range_header_field ( 5191const std:: pair < size_t , size_t > & offset_and_length , size_t content_length ) { 5192auto st = offset_and_length . first ; 5193auto ed = st + offset_and_length . second - 1 ; 5194 5195std:: string field = "bytes " ; 5196field += std:: to_string ( st ); 5197field += '-' ; 5198field += std:: to_string ( ed ); 5199field += '/' ; 5200field += std:: to_string ( content_length ); 5201return field ; 5202} 5203 5204template < typename SToken , typename CToken , typename Content > 5205bool process_multipart_ranges_data (const Request & req , 5206const std:: string & boundary , 5207const std:: string & content_type , 5208size_t content_length , SToken stoken , 5209CToken ctoken , Content content ) { 5210for ( size_t i = 0 ; i < req . ranges . size (); i ++ ) { 5211ctoken ( "--" ); 5212stoken ( boundary ); 5213ctoken ( "\r\n" ); 5214if (! content_type . empty ()) { 5215ctoken ( "Content-Type: " ); 5216stoken ( content_type ); 5217ctoken ( "\r\n" ); 5218} 5219 5220auto offset_and_length = 5221get_range_offset_and_length ( req . ranges [ i ], content_length ); 5222 5223ctoken ( "Content-Range: " ); 5224stoken ( make_content_range_header_field ( offset_and_length , content_length )); 5225ctoken ( "\r\n" ); 5226ctoken ( "\r\n" ); 5227 5228if (! content ( offset_and_length . first , offset_and_length . second )) { 5229return false; 5230} 5231ctoken ( "\r\n" ); 5232} 5233 5234ctoken ( "--" ); 5235stoken ( boundary ); 5236ctoken ( "--" ); 5237 5238return true; 5239} 5240 5241void make_multipart_ranges_data (const Request & req , Response & res , 5242const std:: string & boundary , 5243const std:: string & content_type , 5244size_t content_length , 5245std:: string & data ) { 5246process_multipart_ranges_data ( 5247req , boundary , content_type , content_length , 5248[ & ](const std:: string & token ) { data += token ; }, 5249[ & ](const std:: string & token ) { data += token ; }, 5250[ & ]( size_t offset , size_t length ) { 5251assert ( offset + length <= content_length ); 5252data += res . body . substr ( offset , length ); 5253return true; 5254}); 5255} 5256 5257size_t get_multipart_ranges_data_length (const Request & req , 5258const std:: string & boundary , 5259const std:: string & content_type , 5260size_t content_length ) { 5261size_t data_length = 0 ; 5262 5263process_multipart_ranges_data ( 5264req , boundary , content_type , content_length , 5265[ & ](const std:: string & token ) { data_length += token . size (); }, 5266[ & ](const std:: string & token ) { data_length += token . size (); }, 5267[ & ]( size_t /*offset*/ , size_t length ) { 5268data_length += length ; 5269return true; 5270}); 5271 5272return data_length ; 5273} 5274 5275template < typename T > 5276bool 5277write_multipart_ranges_data ( Stream & strm , const Request & req , Response & res , 5278const std:: string & boundary , 5279const std:: string & content_type , 5280size_t content_length , const T & is_shutting_down ) { 5281return process_multipart_ranges_data ( 5282req , boundary , content_type , content_length , 5283[ & ](const std:: string & token ) { strm . write ( token ); }, 5284[ & ](const std:: string & token ) { strm . write ( token ); }, 5285[ & ]( size_t offset , size_t length ) { 5286return write_content ( strm , res . content_provider_ , offset , length , 5287is_shutting_down ); 5288}); 5289} 5290 5291bool has_framed_body (const Request & req ) { 5292return is_chunked_transfer_encoding ( req . headers ) || 5293req . get_header_value_u64 ( "Content-Length" ) > 0 ; 5294} 5295 5296bool is_connection_persistent (const Request & req ) { 5297if ( has_header_token ( req . headers , "Connection" , "close" )) { return false; } 5298if ( req . version == "HTTP/1.0" && 5299! has_header_token ( req . headers , "Connection" , "keep-alive" )) { 5300return false; 5301} 5302return true; 5303} 5304 5305bool expect_content (const Request & req ) { 5306if ( req . method == "POST" || req . method == "PUT" || req . method == "PATCH" || 5307req . method == "DELETE" ) { 5308return true; 5309} 5310return has_framed_body ( req ); 5311} 5312 5313#ifdef _WIN32 5314class WSInit { 5315public: 5316WSInit () { 5317WSADATA wsaData ; 5318if ( WSAStartup ( 0x0002 , & wsaData ) == 0 ) is_valid_ = true; 5319} 5320 5321~ WSInit () { 5322if ( is_valid_ ) WSACleanup (); 5323} 5324 5325bool is_valid_ = false; 5326}; 5327 5328static WSInit wsinit_ ; 5329#endif 5330 5331// RFC 9110 Section 11.6.1 defines a challenge list as 5332// WWW-Authenticate = #challenge 5333// challenge = auth-scheme [ 1*SP ( token68 / [ #auth-param ] ) ] 5334// auth-param = token BWS "=" BWS ( token / quoted-string ) 5335// so a server may offer several schemes, each with its own comma-separated 5336// auth-param list, in either order and either as separate field lines or 5337// packed into one. Splitting on every comma would break apart a challenge's 5338// own param list; splitting only on the first space would miss a Digest 5339// challenge that isn't first. Split on commas that aren't inside a 5340// quoted-string instead, then track which scheme each resulting segment 5341// belongs to: a segment whose text before "=" contains whitespace (or that 5342// has no "=" at all) starts a new challenge named by its leading token. 5343std:: vector < std:: string > split_challenge_segments (const std:: string & s ) { 5344std:: vector < std:: string > segments ; 5345size_t start = 0 ; 5346auto in_quotes = false; 5347for ( size_t i = 0 ; i < s . size (); i ++ ) { 5348auto c = s [ i ]; 5349if ( in_quotes ) { 5350if ( c == '\\' && i + 1 < s . size ()) { 5351i ++ ; 5352} else if ( c == '"' ) { 5353in_quotes = false; 5354} 5355} else if ( c == '"' ) { 5356in_quotes = true; 5357} else if ( c == ',' ) { 5358segments . push_back ( s . substr ( start , i - start )); 5359start = i + 1 ; 5360} 5361} 5362segments . push_back ( s . substr ( start )); 5363return segments ; 5364} 5365 5366std:: string unescape_quoted_pairs (const std:: string & s ) { 5367std:: string out ; 5368out . reserve ( s . size ()); 5369for ( size_t i = 0 ; i < s . size (); i ++ ) { 5370if ( s [ i ] == '\\' && i + 1 < s . size ()) { 5371out += s [ ++ i ]; 5372} else { 5373out += s [ i ]; 5374} 5375} 5376return out ; 5377} 5378 5379bool parse_www_authenticate (const Response & res , 5380std:: map < std:: string , std:: string > & auth , 5381bool is_proxy ) { 5382auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate" ; 5383auto combined = get_combined_header_value ( res . headers , auth_key ); 5384if ( combined . empty ()) { return false; } 5385 5386auto found_digest = false; 5387auto in_digest_challenge = false; 5388for (const auto & raw_segment : split_challenge_segments ( combined )) { 5389auto segment = trim_copy ( raw_segment ); 5390if ( segment . empty ()) { continue; } 5391 5392auto eq_pos = segment . find ( '=' ); 5393// BWS is allowed on both sides of "=", so the text naming the key (or, 5394// for the first segment of a challenge, "<scheme> <key>") must be 5395// trimmed before its boundaries are inspected. 5396auto key_part = trim_copy ( 5397eq_pos == std::string:: npos ? segment : segment . substr ( 0 , eq_pos )); 5398auto space_pos = key_part . find_last_of ( " \t" ); 5399if ( space_pos != std::string:: npos || eq_pos == std::string:: npos ) { 5400// "<scheme>[ <key>]" starts a new challenge. 5401auto scheme_end = 5402space_pos == std::string:: npos ? key_part . size () : space_pos ; 5403// RFC 7616 Section 3.7: a server may offer more than one Digest 5404// challenge (e.g. SHA-256 and MD5); keep only the first so a nonce 5405// from one challenge is never paired with another's algorithm. 5406in_digest_challenge = 5407! found_digest && 5408case_ignore:: equal ( key_part . substr ( 0 , scheme_end ), "Digest" ); 5409if ( in_digest_challenge ) { found_digest = true; } 5410if ( space_pos == std::string:: npos ) { 5411// Bare scheme (or a token68), no auth-param on this segment. 5412continue; 5413} 5414key_part = key_part . substr ( space_pos + 1 ); 5415} 5416 5417if (! in_digest_challenge ) { continue; } 5418 5419auto val = trim_copy ( segment . substr ( eq_pos + 1 )); 5420auto unquoted = trim_double_quotes_copy ( val ); 5421if ( unquoted . size () != val . size ()) { 5422unquoted = unescape_quoted_pairs ( unquoted ); 5423} 5424auth [std:: move ( key_part )] = std:: move ( unquoted ); 5425} 5426 5427// RFC 7616 Section 3.3 requires realm and nonce on every Digest challenge; 5428// make_digest_authentication_header() dereferences both unconditionally, so 5429// a challenge missing either can't produce a usable Authorization header. 5430// Treat it the same as no Digest challenge at all. 5431return found_digest && auth . find ( "realm" ) != auth . end () && 5432auth . find ( "nonce" ) != auth . end (); 5433} 5434 5435class ContentProviderAdapter { 5436public: 5437explicit ContentProviderAdapter ( 5438ContentProviderWithoutLength && content_provider ) 5439: content_provider_ (std:: move ( content_provider )) {} 5440 5441bool operator()( size_t offset , size_t , DataSink & sink ) { 5442return content_provider_ ( offset , sink ); 5443} 5444 5445private: 5446ContentProviderWithoutLength content_provider_ ; 5447}; 5448 5449// NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5 5450namespace fields { 5451 5452bool is_token_char ( char c ) { 5453return is_ascii_alnum ( c ) || c == '!' || c == '#' || c == '$' || c == '%' || 5454c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' || 5455c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~' ; 5456} 5457 5458bool is_token (const std:: string & s ) { 5459if ( s . empty ()) { return false; } 5460for ( auto c : s ) { 5461if (! is_token_char ( c )) { return false; } 5462} 5463return true; 5464} 5465 5466bool is_field_name (const std:: string & s ) { return is_token ( s ); } 5467 5468bool is_vchar ( char c ) { return c >= 33 && c <= 126 ; } 5469 5470bool is_obs_text ( char c ) { return 128 <= static_cast < unsigned char > ( c ); } 5471 5472bool is_field_vchar ( char c ) { return is_vchar ( c ) || is_obs_text ( c ); } 5473 5474bool is_field_content (const std:: string & s ) { 5475if ( s . empty ()) { return true; } 5476 5477if ( s . size () == 1 ) { 5478return is_field_vchar ( s [ 0 ]); 5479} else if ( s . size () == 2 ) { 5480return is_field_vchar ( s [ 0 ]) && is_field_vchar ( s [ 1 ]); 5481} else { 5482size_t i = 0 ; 5483 5484if (! is_field_vchar ( s [ i ])) { return false; } 5485i ++ ; 5486 5487while ( i < s . size () - 1 ) { 5488auto c = s [ i ++ ]; 5489if ( c == ' ' || c == '\t' || is_field_vchar ( c )) { 5490} else { 5491return false; 5492} 5493} 5494 5495return is_field_vchar ( s [ i ]); 5496} 5497} 5498 5499bool is_field_value (const std:: string & s ) { return is_field_content ( s ); } 5500 5501bool is_field_valid (const std:: string & name , const std:: string & value ) { 5502return is_field_name ( name ) && is_field_value ( value ); 5503} 5504 5505} // namespace fields 5506 5507bool perform_websocket_handshake ( Stream & strm , Request & req , 5508WebSocketUpgradeResponse & upgrade ) { 5509// Generate random Sec-WebSocket-Key 5510thread_local std:: mt19937 rng (std:: random_device {}()); 5511std:: string key_bytes ( 16 , '\0' ); 5512for ( size_t i = 0 ; i < 16 ; i += 4 ) { 5513auto r = rng (); 5514std:: memcpy ( & key_bytes [ i ], & r , (std:: min )( size_t ( 4 ), size_t ( 16 - i ))); 5515} 5516auto client_key = base64_encode ( key_bytes ); 5517 5518req . headers . erase ( "Upgrade" ); 5519req . headers . erase ( "Connection" ); 5520req . headers . erase ( "Sec-WebSocket-Key" ); 5521req . headers . erase ( "Sec-WebSocket-Version" ); 5522req . headers . emplace ( "Upgrade" , "websocket" ); 5523req . headers . emplace ( "Connection" , "Upgrade" ); 5524req . headers . emplace ( "Sec-WebSocket-Key" , client_key ); 5525req . headers . emplace ( "Sec-WebSocket-Version" , "13" ); 5526 5527// Build the request in memory first, like ClientImpl::write_request does. 5528// Writing straight to the socket would leak a request line onto the wire 5529// before check_and_write_headers gets a chance to reject an invalid header, 5530// and would emit one small write per header. 5531BufferStream bstrm ; 5532 5533if ( write_request_line ( bstrm , req . method , req . path ) < 0 ) { 5534upgrade . error = Error :: Write ; 5535return false; 5536} 5537 5538auto error = Error :: Success ; 5539if (! check_and_write_headers ( bstrm , req . headers , write_headers , error )) { 5540upgrade . error = error ; 5541return false; 5542} 5543 5544const auto & data = bstrm . get_buffer (); 5545if (! write_data ( strm , data . data (), data . size ())) { 5546upgrade . error = Error :: Write ; 5547return false; 5548} 5549 5550// Verify 101 response and Sec-WebSocket-Accept header 5551auto expected_accept = websocket_accept_key ( client_key ); 5552return read_websocket_upgrade_response ( strm , expected_accept , upgrade ); 5553} 5554 5555bool is_ip_address (const std:: string & host ) { 5556struct in_addr addr4 ; 5557struct in6_addr addr6 ; 5558return inet_pton ( AF_INET , host . c_str (), & addr4 ) == 1 || 5559inet_pton ( AF_INET6 , host . c_str (), & addr6 ) == 1 ; 5560} 5561 5562// Resolve where a client should connect for `host`, honoring a user-supplied 5563// hostname-to-address map. `host` itself is never rewritten, so it keeps 5564// supplying the Host header and SNI; only the connection target changes. 5565// 5566// A mapped IP literal goes to `ip`, which keeps create_socket's AI_NUMERICHOST 5567// path. Anything else goes to `connect_host`, which create_socket resolves as 5568// a name, or uses as the socket path when the address family is AF_UNIX. An 5569// absent or empty mapping leaves `host` as the connection target; without the 5570// empty check the value would reach getaddrinfo as a null node and silently 5571// resolve to loopback. 5572void apply_addr_map (const std:: map < std:: string , std:: string > & addr_map , 5573const std:: string & host , std:: string & connect_host , 5574std:: string & ip ) { 5575connect_host = host ; 5576ip . clear (); 5577 5578auto it = addr_map . find ( host ); 5579if ( it == addr_map . end () || it -> second . empty ()) { return; } 5580 5581if ( is_ip_address ( it -> second )) { 5582ip = it -> second ; 5583} else { 5584connect_host = it -> second ; 5585} 5586} 5587 5588} // namespace detail 5589 5590/* 5591* Group 2: detail namespace - SSL common utilities 5592*/ 5593 5594#ifdef CPPHTTPLIB_SSL_ENABLED 5595namespace detail { 5596 5597class SSLSocketStream final : public Stream { 5598public: 5599SSLSocketStream ( 5600socket_t sock , tls:: session_t session , time_t read_timeout_sec , 5601time_t read_timeout_usec , time_t write_timeout_sec , 5602time_t write_timeout_usec , time_t max_timeout_msec = 0 , 5603std::chrono:: time_point < std::chrono:: steady_clock > start_time = 5604(std::chrono::steady_clock::time_point:: min )()); 5605~ SSLSocketStream () override; 5606 5607bool is_readable () const override; 5608bool wait_readable () const override; 5609bool wait_writable () const override; 5610bool is_peer_alive () const override; 5611ssize_t read ( char * ptr , size_t size ) override; 5612ssize_t write (const char * ptr , size_t size ) override; 5613void get_remote_ip_and_port (std:: string & ip , int & port ) const override; 5614void get_local_ip_and_port (std:: string & ip , int & port ) const override; 5615socket_t socket () const override; 5616time_t duration () const override; 5617void set_read_timeout ( time_t sec , time_t usec = 0 ) override; 5618 5619// See SocketStream::set_readable_hint(). 5620void set_readable_hint () { readable_hint_ = true; } 5621 5622private: 5623bool ensure_readable (); 5624 5625socket_t sock_ ; 5626tls:: session_t session_ ; 5627time_t read_timeout_sec_ ; 5628time_t read_timeout_usec_ ; 5629time_t write_timeout_sec_ ; 5630time_t write_timeout_usec_ ; 5631time_t max_timeout_msec_ ; 5632const std::chrono:: time_point < std::chrono:: steady_clock > start_time_ ; 5633bool readable_hint_ = false; 5634}; 5635 5636// A TLS stream for WebSocket connections, where the receive path and the 5637// send path (application send() plus the heartbeat ping thread) run on 5638// different threads. A single TLS session must never be entered 5639// concurrently, so every call into the session is serialized by one mutex. 5640// 5641// Unlike SSLSocketStream, the socket is kept non-blocking for the stream's 5642// whole lifetime and each read()/write() performs a single non-blocking TLS 5643// call under the lock, then waits for readiness with select() outside the 5644// lock. The lock is therefore held only for CPU-bound work, so a reader 5645// blocked waiting for data never stalls a concurrent sender. 5646// 5647// This stream is used only for wss:// connections. Plain ws:// and ordinary 5648// HTTP/HTTPS keep using SocketStream/SSLSocketStream unchanged. 5649class WebSocketSSLStream final : public Stream { 5650public: 5651WebSocketSSLStream ( socket_t sock , tls:: session_t session , 5652time_t read_timeout_sec , time_t read_timeout_usec , 5653time_t write_timeout_sec , time_t write_timeout_usec ); 5654~ WebSocketSSLStream () override; 5655 5656bool is_readable () const override; 5657bool wait_readable () const override; 5658bool wait_writable () const override; 5659ssize_t read ( char * ptr , size_t size ) override; 5660ssize_t write (const char * ptr , size_t size ) override; 5661void get_remote_ip_and_port (std:: string & ip , int & port ) const override; 5662void get_local_ip_and_port (std:: string & ip , int & port ) const override; 5663socket_t socket () const override; 5664time_t duration () const override; 5665void set_read_timeout ( time_t sec , time_t usec = 0 ) override; 5666 5667private: 5668mutable std:: mutex session_mutex_ ; 5669 5670socket_t sock_ ; 5671tls:: session_t session_ ; 5672// WebSocket::close() shortens the read timeout from the closing thread 5673// while the receive thread is inside wait_readable(), so these two are read 5674// and written concurrently. The write timeouts are never mutated. 5675std:: atomic < time_t > read_timeout_sec_ ; 5676std:: atomic < time_t > read_timeout_usec_ ; 5677time_t write_timeout_sec_ ; 5678time_t write_timeout_usec_ ; 5679const std::chrono:: time_point < std::chrono:: steady_clock > start_time_ ; 5680}; 5681 5682#ifdef CPPHTTPLIB_OPENSSL_SUPPORT 5683std:: string message_digest (const std:: string & s , const EVP_MD * algo ) { 5684auto context = std:: unique_ptr < EVP_MD_CTX , decltype( & EVP_MD_CTX_free ) > ( 5685EVP_MD_CTX_new (), EVP_MD_CTX_free ); 5686 5687unsigned int hash_length = 0 ; 5688unsigned char hash [ EVP_MAX_MD_SIZE ]; 5689 5690EVP_DigestInit_ex ( context . get (), algo , nullptr ); 5691EVP_DigestUpdate ( context . get (), s . c_str (), s . size ()); 5692EVP_DigestFinal_ex ( context . get (), hash , & hash_length ); 5693 5694std:: stringstream ss ; 5695for ( auto i = 0u ; i < hash_length ; ++ i ) { 5696ss << std:: hex << std:: setw ( 2 ) << std:: setfill ( '0' ) 5697<< static_cast < unsigned int > ( hash [ i ]); 5698} 5699 5700return ss . str (); 5701} 5702 5703std:: string MD5 (const std:: string & s ) { 5704return message_digest ( s , EVP_md5 ()); 5705} 5706 5707std:: string SHA_256 (const std:: string & s ) { 5708return message_digest ( s , EVP_sha256 ()); 5709} 5710 5711std:: string SHA_512 (const std:: string & s ) { 5712return message_digest ( s , EVP_sha512 ()); 5713} 5714#elif defined( CPPHTTPLIB_MBEDTLS_SUPPORT ) 5715namespace { 5716template < size_t N > 5717std:: string hash_to_hex (const unsigned char ( & hash )[ N ]) { 5718std:: stringstream ss ; 5719for ( size_t i = 0 ; i < N ; ++ i ) { 5720ss << std:: hex << std:: setw ( 2 ) << std:: setfill ( '0' ) 5721<< static_cast < unsigned int > ( hash [ i ]); 5722} 5723return ss . str (); 5724} 5725} // namespace 5726 5727#ifdef CPPHTTPLIB_MBEDTLS_V4 5728// Mbed TLS 4.x provides hashing (and TLS RNG) via PSA Crypto, which must be 5729// initialized once. PSA state is process-global; do not free it. 5730bool ensure_mbedtls_psa_crypto () { 5731static std:: once_flag once ; 5732static bool ok = false; 5733std:: call_once ( once , []() { ok = ( psa_crypto_init () == PSA_SUCCESS ); }); 5734return ok ; 5735} 5736 5737bool psa_hash ( psa_algorithm_t alg , const std:: string & s , 5738unsigned char * out , size_t out_size ) { 5739if (! ensure_mbedtls_psa_crypto ()) { return false; } 5740size_t olen = 0 ; 5741return psa_hash_compute ( alg , reinterpret_cast < const uint8_t *> ( s . data ()), 5742s . size (), out , out_size , & olen ) == PSA_SUCCESS && 5743olen == out_size ; 5744} 5745#endif 5746 5747std:: string MD5 (const std:: string & s ) { 5748unsigned char hash [ 16 ]; 5749#ifdef CPPHTTPLIB_MBEDTLS_V4 5750if (! psa_hash ( PSA_ALG_MD5 , s , hash , sizeof( hash ))) { return {}; } 5751#elif defined( CPPHTTPLIB_MBEDTLS_V3 ) 5752mbedtls_md5 ( reinterpret_cast < const unsigned char *> ( s . c_str ()), s . size (), 5753hash ); 5754#else 5755mbedtls_md5_ret ( reinterpret_cast < const unsigned char *> ( s . c_str ()), s . size (), 5756hash ); 5757#endif 5758return hash_to_hex ( hash ); 5759} 5760 5761std:: string SHA_256 (const std:: string & s ) { 5762unsigned char hash [ 32 ]; 5763#ifdef CPPHTTPLIB_MBEDTLS_V4 5764if (! psa_hash ( PSA_ALG_SHA_256 , s , hash , sizeof( hash ))) { return {}; } 5765#elif defined( CPPHTTPLIB_MBEDTLS_V3 ) 5766mbedtls_sha256 ( reinterpret_cast < const unsigned char *> ( s . c_str ()), s . size (), 5767hash , 0 ); 5768#else 5769mbedtls_sha256_ret ( reinterpret_cast < const unsigned char *> ( s . c_str ()), 5770s . size (), hash , 0 ); 5771#endif 5772return hash_to_hex ( hash ); 5773} 5774 5775std:: string SHA_512 (const std:: string & s ) { 5776unsigned char hash [ 64 ]; 5777#ifdef CPPHTTPLIB_MBEDTLS_V4 5778if (! psa_hash ( PSA_ALG_SHA_512 , s , hash , sizeof( hash ))) { return {}; } 5779#elif defined( CPPHTTPLIB_MBEDTLS_V3 ) 5780mbedtls_sha512 ( reinterpret_cast < const unsigned char *> ( s . c_str ()), s . size (), 5781hash , 0 ); 5782#else 5783mbedtls_sha512_ret ( reinterpret_cast < const unsigned char *> ( s . c_str ()), 5784s . size (), hash , 0 ); 5785#endif 5786return hash_to_hex ( hash ); 5787} 5788#elif defined( CPPHTTPLIB_WOLFSSL_SUPPORT ) 5789namespace { 5790template < size_t N > 5791std:: string hash_to_hex (const unsigned char ( & hash )[ N ]) { 5792std:: stringstream ss ; 5793for ( size_t i = 0 ; i < N ; ++ i ) { 5794ss << std:: hex << std:: setw ( 2 ) << std:: setfill ( '0' ) 5795<< static_cast < unsigned int > ( hash [ i ]); 5796} 5797return ss . str (); 5798} 5799} // namespace 5800 5801std:: string MD5 (const std:: string & s ) { 5802unsigned char hash [ WC_MD5_DIGEST_SIZE ]; 5803wc_Md5Hash ( reinterpret_cast < const unsigned char *> ( s . c_str ()), 5804static_cast < word32 > ( s . size ()), hash ); 5805return hash_to_hex ( hash ); 5806} 5807 5808std:: string SHA_256 (const std:: string & s ) { 5809unsigned char hash [ WC_SHA256_DIGEST_SIZE ]; 5810wc_Sha256Hash ( reinterpret_cast < const unsigned char *> ( s . c_str ()), 5811static_cast < word32 > ( s . size ()), hash ); 5812return hash_to_hex ( hash ); 5813} 5814 5815std:: string SHA_512 (const std:: string & s ) { 5816unsigned char hash [ WC_SHA512_DIGEST_SIZE ]; 5817wc_Sha512Hash ( reinterpret_cast < const unsigned char *> ( s . c_str ()), 5818static_cast < word32 > ( s . size ()), hash ); 5819return hash_to_hex ( hash ); 5820} 5821#endif 5822 5823template < typename T > 5824bool process_server_socket_ssl ( 5825const std:: atomic < socket_t > & svr_sock , tls:: session_t session , 5826socket_t sock , size_t keep_alive_max_count , time_t keep_alive_timeout_sec , 5827time_t read_timeout_sec , time_t read_timeout_usec , time_t write_timeout_sec , 5828time_t write_timeout_usec , T callback ) { 5829return process_server_socket_core ( 5830svr_sock , sock , keep_alive_max_count , keep_alive_timeout_sec , 5831[ & ]( bool close_connection , bool & connection_closed ) { 5832SSLSocketStream strm ( sock , session , read_timeout_sec , read_timeout_usec , 5833write_timeout_sec , write_timeout_usec ); 5834// See the non-TLS path in process_server_socket(). 5835strm . set_readable_hint (); 5836return callback ( strm , close_connection , connection_closed ); 5837}); 5838} 5839 5840template < typename T > 5841bool process_client_socket_ssl ( 5842tls:: session_t session , socket_t sock , time_t read_timeout_sec , 5843time_t read_timeout_usec , time_t write_timeout_sec , 5844time_t write_timeout_usec , time_t max_timeout_msec , 5845std::chrono:: time_point < std::chrono:: steady_clock > start_time , T callback ) { 5846SSLSocketStream strm ( sock , session , read_timeout_sec , read_timeout_usec , 5847write_timeout_sec , write_timeout_usec , max_timeout_msec , 5848start_time ); 5849return callback ( strm ); 5850} 5851 5852std:: pair < std:: string , std:: string > make_digest_authentication_header ( 5853const Request & req , const std:: map < std:: string , std:: string > & auth , 5854size_t cnonce_count , const std:: string & cnonce , const std:: string & username , 5855const std:: string & password , bool is_proxy = false) { 5856std:: string nc ; 5857{ 5858std:: stringstream ss ; 5859ss << std:: setfill ( '0' ) << std:: setw ( 8 ) << std:: hex << cnonce_count ; 5860nc = ss . str (); 5861} 5862 5863std:: string qop ; 5864if ( auth . find ( "qop" ) != auth . end ()) { 5865qop = auth . at ( "qop" ); 5866if ( qop . find ( "auth-int" ) != std::string:: npos ) { 5867qop = "auth-int" ; 5868} else if ( qop . find ( "auth" ) != std::string:: npos ) { 5869qop = "auth" ; 5870} else { 5871qop . clear (); 5872} 5873} 5874 5875std:: string algo = "MD5" ; 5876if ( auth . find ( "algorithm" ) != auth . end ()) { algo = auth . at ( "algorithm" ); } 5877 5878std:: string response ; 5879{ 5880auto H = algo == "SHA-256" ? detail:: SHA_256 5881: algo == "SHA-512" ? detail:: SHA_512 5882: detail:: MD5 ; 5883 5884auto A1 = username + ":" + auth . at ( "realm" ) + ":" + password ; 5885 5886auto A2 = req . method + ":" + req . path ; 5887if ( qop == "auth-int" ) { A2 += ":" + H ( req . body ); } 5888 5889if ( qop . empty ()) { 5890response = H ( H ( A1 ) + ":" + auth . at ( "nonce" ) + ":" + H ( A2 )); 5891} else { 5892response = H ( H ( A1 ) + ":" + auth . at ( "nonce" ) + ":" + nc + ":" + cnonce + 5893":" + qop + ":" + H ( A2 )); 5894} 5895} 5896 5897auto opaque = ( auth . find ( "opaque" ) != auth . end ()) ? auth . at ( "opaque" ) : "" ; 5898 5899auto field = "Digest username=\"" + username + "\", realm=\"" + 5900auth . at ( "realm" ) + "\", nonce=\"" + auth . at ( "nonce" ) + 5901"\", uri=\"" + req . path + "\", algorithm=" + algo + 5902( qop . empty () ? ", response=\"" 5903: ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + 5904cnonce + "\", response=\"" ) + 5905response + "\"" + 5906( opaque . empty () ? "" : ", opaque=\"" + opaque + "\"" ); 5907 5908auto key = is_proxy ? "Proxy-Authorization" : "Authorization" ; 5909return std:: make_pair ( key , field ); 5910} 5911 5912bool match_hostname (const std:: string & pattern , 5913const std:: string & hostname ) { 5914// Exact match (case-insensitive) 5915if (detail::case_ignore:: equal ( hostname , pattern )) { return true; } 5916 5917// Split both pattern and hostname into components by '.' 5918std:: vector < std:: string > pattern_components ; 5919if (! pattern . empty ()) { 5920split ( pattern . data (), pattern . data () + pattern . size (), '.' , 5921[ & ](const char * b , const char * e ) { 5922pattern_components . emplace_back ( b , e ); 5923}); 5924} 5925 5926std:: vector < std:: string > host_components ; 5927if (! hostname . empty ()) { 5928split ( hostname . data (), hostname . data () + hostname . size (), '.' , 5929[ & ](const char * b , const char * e ) { 5930host_components . emplace_back ( b , e ); 5931}); 5932} 5933 5934// Component count must match 5935if ( host_components . size () != pattern_components . size ()) { return false; } 5936 5937// Compare each component with wildcard support 5938// Supports: "*" (full wildcard), "prefix*" (partial wildcard) 5939// https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 5940auto itr = pattern_components . begin (); 5941for (const auto & h : host_components ) { 5942auto & p = * itr ; 5943if (!detail::case_ignore:: equal ( p , h ) && p != "*" ) { 5944bool partial_match = false; 5945if (! p . empty () && p [ p . size () - 1 ] == '*' ) { 5946const auto prefix_length = p . size () - 1 ; 5947if ( prefix_length == 0 ) { 5948partial_match = true; 5949} else if ( h . size () >= prefix_length ) { 5950partial_match = 5951std:: equal ( p . begin (), 5952p . begin () + static_cast < std::string:: difference_type > ( 5953prefix_length ), 5954h . begin (), [](const char ca , const char cb ) { 5955return detail::case_ignore:: to_lower ( ca ) == 5956detail::case_ignore:: to_lower ( cb ); 5957}); 5958} 5959} 5960if (! partial_match ) { return false; } 5961} 5962++ itr ; 5963} 5964 5965return true; 5966} 5967 5968#ifdef _WIN32 5969// Verify certificate using Windows CertGetCertificateChain API. 5970// This provides real-time certificate validation with Windows Update 5971// integration, independent of the TLS backend (OpenSSL or MbedTLS). 5972bool 5973verify_cert_with_windows_schannel (const std:: vector < unsigned char > & der_cert , 5974const std:: string & hostname , 5975bool verify_hostname , uint64_t & out_error ) { 5976if ( der_cert . empty ()) { return false; } 5977 5978out_error = 0 ; 5979 5980// Create Windows certificate context from DER data 5981auto cert_context = CertCreateCertificateContext ( 5982X509_ASN_ENCODING | PKCS_7_ASN_ENCODING , der_cert . data (), 5983static_cast < DWORD > ( der_cert . size ())); 5984 5985if (! cert_context ) { 5986out_error = GetLastError (); 5987return false; 5988} 5989 5990auto cert_guard = 5991scope_exit ([ & ] { CertFreeCertificateContext ( cert_context ); }); 5992 5993// Setup chain parameters 5994CERT_CHAIN_PARA chain_para = {}; 5995chain_para . cbSize = sizeof( chain_para ); 5996 5997// Build certificate chain with revocation checking 5998PCCERT_CHAIN_CONTEXT chain_context = nullptr ; 5999auto chain_result = CertGetCertificateChain ( 6000nullptr , cert_context , nullptr , cert_context -> hCertStore , & chain_para , 6001CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_END_CERT | 6002CERT_CHAIN_REVOCATION_ACCUMULATIVE_TIMEOUT , 6003nullptr , & chain_context ); 6004 6005if (! chain_result || ! chain_context ) { 6006out_error = GetLastError (); 6007return false; 6008} 6009 6010auto chain_guard = 6011scope_exit ([ & ] { CertFreeCertificateChain ( chain_context ); }); 6012 6013// Check if chain has errors 6014if ( chain_context -> TrustStatus . dwErrorStatus != CERT_TRUST_NO_ERROR ) { 6015out_error = chain_context -> TrustStatus . dwErrorStatus ; 6016return false; 6017} 6018 6019// Verify SSL policy 6020SSL_EXTRA_CERT_CHAIN_POLICY_PARA extra_policy_para = {}; 6021extra_policy_para . cbSize = sizeof( extra_policy_para ); 6022#ifdef AUTHTYPE_SERVER 6023extra_policy_para . dwAuthType = AUTHTYPE_SERVER ; 6024#endif 6025 6026std:: wstring whost ; 6027if ( verify_hostname ) { 6028whost = u8string_to_wstring ( hostname . c_str ()); 6029extra_policy_para . pwszServerName = const_cast < wchar_t *> ( whost . c_str ()); 6030} 6031 6032CERT_CHAIN_POLICY_PARA policy_para = {}; 6033policy_para . cbSize = sizeof( policy_para ); 6034#ifdef CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS 6035policy_para . dwFlags = CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS ; 6036#else 6037policy_para . dwFlags = 0 ; 6038#endif 6039policy_para . pvExtraPolicyPara = & extra_policy_para ; 6040 6041CERT_CHAIN_POLICY_STATUS policy_status = {}; 6042policy_status . cbSize = sizeof( policy_status ); 6043 6044if (! CertVerifyCertificateChainPolicy ( CERT_CHAIN_POLICY_SSL , chain_context , 6045& policy_para , & policy_status )) { 6046out_error = GetLastError (); 6047return false; 6048} 6049 6050if ( policy_status . dwError != 0 ) { 6051out_error = policy_status . dwError ; 6052return false; 6053} 6054 6055return true; 6056} 6057#endif // _WIN32 6058 6059// Loads CA file/dir configuration and applies the system CA policy to a 6060// client TLS context. PEM data and native stores are applied to the context 6061// directly at set time; has_custom_store reflects them for the Auto policy 6062// decision. 6063bool load_client_ca_config (tls:: ctx_t ctx , 6064const std:: string & ca_cert_file_path , 6065const std:: string & ca_cert_dir_path , 6066bool has_custom_store , SystemCAMode mode , 6067uint64_t & backend_error ) { 6068auto ret = true; 6069 6070if (! ca_cert_file_path . empty ()) { 6071if (!tls:: load_ca_file ( ctx , ca_cert_file_path . c_str ())) { 6072backend_error = tls:: get_error (); 6073ret = false; 6074} 6075} else if (! ca_cert_dir_path . empty ()) { 6076if (!tls:: load_ca_dir ( ctx , ca_cert_dir_path . c_str ())) { 6077backend_error = tls:: get_error (); 6078ret = false; 6079} 6080} 6081 6082auto has_custom_ca = ! ca_cert_file_path . empty () || 6083! ca_cert_dir_path . empty () || has_custom_store ; 6084if ( mode == SystemCAMode :: Enabled || 6085( mode == SystemCAMode :: Auto && ! has_custom_ca )) { 6086if (!tls:: load_system_certs ( ctx )) { backend_error = tls:: get_error (); } 6087} 6088 6089return ret ; 6090} 6091 6092// The parts of session setup that only SSLClient needs, plus the handful 6093// WebSocketClient also exposes; everything else takes the defaults, which is 6094// what keeps the two clients on one implementation. 6095struct ClientTlsSessionOptions { 6096// Both SSLClient and WebSocketClient expose this independently of 6097// certificate verification. 6098bool server_hostname_verification = true; 6099std:: function < SSLVerifierResponse (tls:: session_t ) > session_verifier ; 6100// When non-null, guards session creation against concurrent use of the 6101// context. A WebSocketClient is not safe to use from several threads to 6102// begin with, so it passes nothing. 6103std:: mutex * ctx_mutex = nullptr ; 6104#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE 6105// The caller decides whether Schannel has anything to say about this 6106// connection; see SSLClient::initialize_ssl(). 6107bool windows_cert_verification = false; 6108#endif 6109}; 6110 6111// Filled in on failure for callers that report error details. 6112struct ClientTlsSessionError { 6113Error error = Error :: Success ; 6114int ssl_error = 0 ; 6115uint64_t backend_error = 0 ; 6116}; 6117 6118// Establishes a client TLS session on an already connected socket. On failure 6119// the session is left for the caller to free: SSLClient frees it right away, 6120// WebSocketClient keeps it in a member that shutdown_and_close() cleans up. 6121bool setup_client_tls_session ( 6122const std:: string & host , tls:: ctx_t ctx , tls:: session_t & session , 6123socket_t sock , bool server_certificate_verification , time_t timeout_sec , 6124time_t timeout_usec , ClientTlsSessionError * out_error = nullptr , 6125const ClientTlsSessionOptions & options = ClientTlsSessionOptions ()) { 6126using namespace tls ; 6127 6128auto fail = [ & ]( Error error , int ssl_error , uint64_t backend_error ) { 6129if ( out_error ) { 6130out_error -> error = error ; 6131out_error -> ssl_error = ssl_error ; 6132out_error -> backend_error = backend_error ; 6133} 6134return false; 6135}; 6136 6137if (! ctx ) { 6138session = nullptr ; 6139return fail ( Error :: SSLConnection , 0 , 0 ); 6140} 6141 6142#if defined( CPPHTTPLIB_MBEDTLS_SUPPORT ) || defined( CPPHTTPLIB_WOLFSSL_SUPPORT ) 6143// Mbed TLS and wolfSSL need the verification mode set explicitly; OpenSSL 6144// uses SSL_VERIFY_NONE and does all verification post-handshake. Chain 6145// verification happens during the handshake even for IP hosts; the 6146// certificate identity is verified post-handshake via verify_hostname(). 6147set_verify_client ( ctx , server_certificate_verification ); 6148#endif 6149 6150{ 6151std:: unique_lock < std:: mutex > guard ; 6152if ( options . ctx_mutex ) { 6153guard = std:: unique_lock < std:: mutex > ( * options . ctx_mutex ); 6154} 6155session = create_session ( ctx , sock ); 6156} 6157if (! session ) { return fail ( Error :: SSLConnection , 0 , get_error ()); } 6158 6159// RFC 6066: SNI must not be set for IP addresses; skip it for IP hosts, so 6160// their identity is checked post-handshake below instead. On Mbed TLS and 6161// wolfSSL, set_sni also drives handshake-time hostname verification, so 6162// options.server_hostname_verification is threaded through here. 6163if (! is_ip_address ( host )) { 6164if (! set_sni ( session , host . c_str (), options . server_hostname_verification )) { 6165return fail ( Error :: SSLConnection , 0 , get_error ()); 6166} 6167} 6168 6169TlsError tls_err ; 6170if (! connect_nonblocking ( session , sock , timeout_sec , timeout_usec , 6171& tls_err )) { 6172auto error = Error :: SSLConnection ; 6173if ( tls_err . code == ErrorCode :: CertVerifyFailed ) { 6174error = Error :: SSLServerVerification ; 6175} else if ( tls_err . code == ErrorCode :: HostnameMismatch ) { 6176error = Error :: SSLServerHostnameVerification ; 6177} 6178return fail ( error , static_cast < int > ( tls_err . code ), tls_err . backend_code ); 6179} 6180 6181auto verification_status = SSLVerifierResponse :: NoDecisionMade ; 6182if ( options . session_verifier ) { 6183verification_status = options . session_verifier ( session ); 6184} 6185 6186if ( verification_status == SSLVerifierResponse :: CertificateRejected ) { 6187return fail ( Error :: SSLServerVerification , 0 , get_error ()); 6188} 6189 6190if ( verification_status == SSLVerifierResponse :: NoDecisionMade && 6191server_certificate_verification ) { 6192auto verify_result = get_verify_result ( session ); 6193if ( verify_result != 0 ) { 6194return fail ( Error :: SSLServerVerification , 0 , 6195static_cast < uint64_t > ( verify_result )); 6196} 6197 6198auto server_cert = get_peer_cert ( session ); 6199if (! server_cert ) { 6200return fail ( Error :: SSLServerVerification , 0 , get_error ()); 6201} 6202auto cert_guard = detail:: scope_exit ([ & ] { free_cert ( server_cert ); }); 6203 6204// Identity check against the peer certificate, post-handshake for all 6205// backends. For IP hosts this is the only identity verification, since no 6206// hostname is bound during the handshake. 6207if ( options . server_hostname_verification ) { 6208if (! verify_hostname ( server_cert , host . c_str ())) { 6209return fail ( Error :: SSLServerHostnameVerification , 0 , 6210hostname_mismatch_code ()); 6211} 6212} 6213 6214#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE 6215// Additional Windows Schannel verification. 6216// This provides real-time certificate validation with Windows Update 6217// integration, working with both OpenSSL and MbedTLS backends. 6218if ( options . windows_cert_verification ) { 6219std:: vector < unsigned char > der ; 6220if ( get_cert_der ( server_cert , der )) { 6221uint64_t wincrypt_error = 0 ; 6222if (! verify_cert_with_windows_schannel ( 6223der , host , options . server_hostname_verification , 6224wincrypt_error )) { 6225return fail ( Error :: SSLServerVerification , 0 , wincrypt_error ); 6226} 6227} 6228} 6229#endif 6230} 6231 6232return true; 6233} 6234 6235} // namespace detail 6236#endif // CPPHTTPLIB_SSL_ENABLED 6237 6238/* 6239* Group 3: httplib namespace - Non-SSL public API implementations 6240*/ 6241 6242void default_socket_options ( socket_t sock ) { 6243set_socket_opt ( sock , SOL_SOCKET , 6244#ifdef SO_REUSEPORT 6245SO_REUSEPORT , 6246#else 6247SO_REUSEADDR , 6248#endif 62491 ); 6250} 6251 6252bool set_socket_opt ( socket_t sock , int level , int optname , int optval ) { 6253return detail:: set_socket_opt_impl ( sock , level , optname , & optval , 6254sizeof( optval )); 6255} 6256 6257std:: string get_bearer_token_auth (const Request & req ) { 6258// The auth scheme is case-insensitive (RFC 9110 11.1), and a value shorter 6259// than the prefix carries no token. 6260constexpr const char bearer_prefix [] = "Bearer " ; 6261constexpr auto bearer_prefix_len = detail:: str_len ( bearer_prefix ); 6262auto value = req . get_header_value ( "Authorization" ); 6263if ( value . size () >= bearer_prefix_len && 6264detail::case_ignore:: equal ( value . substr ( 0 , bearer_prefix_len ), 6265bearer_prefix )) { 6266return value . substr ( bearer_prefix_len ); 6267} 6268return "" ; 6269} 6270 6271const char * status_message ( int status ) { 6272switch ( status ) { 6273case StatusCode :: Continue_100 : return "Continue" ; 6274case StatusCode :: SwitchingProtocol_101 : return "Switching Protocol" ; 6275case StatusCode :: Processing_102 : return "Processing" ; 6276case StatusCode :: EarlyHints_103 : return "Early Hints" ; 6277case StatusCode :: OK_200 : return "OK" ; 6278case StatusCode :: Created_201 : return "Created" ; 6279case StatusCode :: Accepted_202 : return "Accepted" ; 6280case StatusCode :: NonAuthoritativeInformation_203 : 6281return "Non-Authoritative Information" ; 6282case StatusCode :: NoContent_204 : return "No Content" ; 6283case StatusCode :: ResetContent_205 : return "Reset Content" ; 6284case StatusCode :: PartialContent_206 : return "Partial Content" ; 6285case StatusCode :: MultiStatus_207 : return "Multi-Status" ; 6286case StatusCode :: AlreadyReported_208 : return "Already Reported" ; 6287case StatusCode :: IMUsed_226 : return "IM Used" ; 6288case StatusCode :: MultipleChoices_300 : return "Multiple Choices" ; 6289case StatusCode :: MovedPermanently_301 : return "Moved Permanently" ; 6290case StatusCode :: Found_302 : return "Found" ; 6291case StatusCode :: SeeOther_303 : return "See Other" ; 6292case StatusCode :: NotModified_304 : return "Not Modified" ; 6293case StatusCode :: UseProxy_305 : return "Use Proxy" ; 6294case StatusCode :: unused_306 : return "unused" ; 6295case StatusCode :: TemporaryRedirect_307 : return "Temporary Redirect" ; 6296case StatusCode :: PermanentRedirect_308 : return "Permanent Redirect" ; 6297case StatusCode :: BadRequest_400 : return "Bad Request" ; 6298case StatusCode :: Unauthorized_401 : return "Unauthorized" ; 6299case StatusCode :: PaymentRequired_402 : return "Payment Required" ; 6300case StatusCode :: Forbidden_403 : return "Forbidden" ; 6301case StatusCode :: NotFound_404 : return "Not Found" ; 6302case StatusCode :: MethodNotAllowed_405 : return "Method Not Allowed" ; 6303case StatusCode :: NotAcceptable_406 : return "Not Acceptable" ; 6304case StatusCode :: ProxyAuthenticationRequired_407 : 6305return "Proxy Authentication Required" ; 6306case StatusCode :: RequestTimeout_408 : return "Request Timeout" ; 6307case StatusCode :: Conflict_409 : return "Conflict" ; 6308case StatusCode :: Gone_410 : return "Gone" ; 6309case StatusCode :: LengthRequired_411 : return "Length Required" ; 6310case StatusCode :: PreconditionFailed_412 : return "Precondition Failed" ; 6311case StatusCode :: PayloadTooLarge_413 : return "Payload Too Large" ; 6312case StatusCode :: UriTooLong_414 : return "URI Too Long" ; 6313case StatusCode :: UnsupportedMediaType_415 : return "Unsupported Media Type" ; 6314case StatusCode :: RangeNotSatisfiable_416 : return "Range Not Satisfiable" ; 6315case StatusCode :: ExpectationFailed_417 : return "Expectation Failed" ; 6316case StatusCode :: ImATeapot_418 : return "I'm a teapot" ; 6317case StatusCode :: MisdirectedRequest_421 : return "Misdirected Request" ; 6318case StatusCode :: UnprocessableContent_422 : return "Unprocessable Content" ; 6319case StatusCode :: Locked_423 : return "Locked" ; 6320case StatusCode :: FailedDependency_424 : return "Failed Dependency" ; 6321case StatusCode :: TooEarly_425 : return "Too Early" ; 6322case StatusCode :: UpgradeRequired_426 : return "Upgrade Required" ; 6323case StatusCode :: PreconditionRequired_428 : return "Precondition Required" ; 6324case StatusCode :: TooManyRequests_429 : return "Too Many Requests" ; 6325case StatusCode :: RequestHeaderFieldsTooLarge_431 : 6326return "Request Header Fields Too Large" ; 6327case StatusCode :: UnavailableForLegalReasons_451 : 6328return "Unavailable For Legal Reasons" ; 6329case StatusCode :: NotImplemented_501 : return "Not Implemented" ; 6330case StatusCode :: BadGateway_502 : return "Bad Gateway" ; 6331case StatusCode :: ServiceUnavailable_503 : return "Service Unavailable" ; 6332case StatusCode :: GatewayTimeout_504 : return "Gateway Timeout" ; 6333case StatusCode :: HttpVersionNotSupported_505 : 6334return "HTTP Version Not Supported" ; 6335case StatusCode :: VariantAlsoNegotiates_506 : return "Variant Also Negotiates" ; 6336case StatusCode :: InsufficientStorage_507 : return "Insufficient Storage" ; 6337case StatusCode :: LoopDetected_508 : return "Loop Detected" ; 6338case StatusCode :: NotExtended_510 : return "Not Extended" ; 6339case StatusCode :: NetworkAuthenticationRequired_511 : 6340return "Network Authentication Required" ; 6341 6342default: 6343case StatusCode :: InternalServerError_500 : return "Internal Server Error" ; 6344} 6345} 6346 6347std:: string to_string (const Error error ) { 6348switch ( error ) { 6349case Error :: Success : return "Success (no error)" ; 6350case Error :: Unknown : return "Unknown" ; 6351case Error :: Connection : return "Could not establish connection" ; 6352case Error :: BindIPAddress : return "Failed to bind IP address" ; 6353case Error :: Read : return "Failed to read connection" ; 6354case Error :: Write : return "Failed to write connection" ; 6355case Error :: ExceedRedirectCount : return "Maximum redirect count exceeded" ; 6356case Error :: Canceled : return "Connection handling canceled" ; 6357case Error :: SSLConnection : return "SSL connection failed" ; 6358case Error :: SSLLoadingCerts : return "SSL certificate loading failed" ; 6359case Error :: SSLServerVerification : return "SSL server verification failed" ; 6360case Error :: SSLServerHostnameVerification : 6361return "SSL server hostname verification failed" ; 6362case Error :: UnsupportedMultipartBoundaryChars : 6363return "Unsupported HTTP multipart boundary characters" ; 6364case Error :: Compression : return "Compression failed" ; 6365case Error :: ConnectionTimeout : return "Connection timed out" ; 6366case Error :: ProxyConnection : return "Proxy connection failed" ; 6367case Error :: ConnectionClosed : return "Connection closed by server" ; 6368case Error :: Timeout : return "Read timeout" ; 6369case Error :: ResourceExhaustion : return "Resource exhaustion" ; 6370case Error :: TooManyFormDataFiles : return "Too many form data files" ; 6371case Error :: ExceedMaxPayloadSize : return "Exceeded maximum payload size" ; 6372case Error :: ExceedUriMaxLength : return "Exceeded maximum URI length" ; 6373case Error :: ExceedMaxSocketDescriptorCount : 6374return "Exceeded maximum socket descriptor count" ; 6375case Error :: InvalidRequestLine : return "Invalid request line" ; 6376case Error :: InvalidHTTPMethod : return "Invalid HTTP method" ; 6377case Error :: InvalidHTTPVersion : return "Invalid HTTP version" ; 6378case Error :: InvalidHeaders : return "Invalid headers" ; 6379case Error :: MultipartParsing : return "Multipart parsing failed" ; 6380case Error :: OpenFile : return "Failed to open file" ; 6381case Error :: Listen : return "Failed to listen on socket" ; 6382case Error :: GetSockName : return "Failed to get socket name" ; 6383case Error :: UnsupportedAddressFamily : return "Unsupported address family" ; 6384case Error :: HTTPParsing : return "HTTP parsing failed" ; 6385case Error :: InvalidRangeHeader : return "Invalid Range header" ; 6386case Error :: UnsupportedContentEncoding : return "Unsupported Content-Encoding" ; 6387case Error :: WebSocketHandshake : return "WebSocket handshake failed" ; 6388case Error :: UserCallbackException : return "User callback threw an exception" ; 6389default: break; 6390} 6391 6392return "Invalid" ; 6393} 6394 6395std:: ostream & operator<<(std:: ostream & os , const Error & obj ) { 6396os << to_string ( obj ); 6397os << " (" << static_cast < std:: underlying_type < Error > :: type > ( obj ) << ')' ; 6398return os ; 6399} 6400 6401std:: string hosted_at (const std:: string & hostname ) { 6402std:: vector < std:: string > addrs ; 6403hosted_at ( hostname , addrs ); 6404if ( addrs . empty ()) { return std:: string (); } 6405return addrs [ 0 ]; 6406} 6407 6408void hosted_at (const std:: string & hostname , 6409std:: vector < std:: string > & addrs ) { 6410struct addrinfo hints ; 6411struct addrinfo * result ; 6412 6413memset ( & hints , 0 , sizeof(struct addrinfo )); 6414hints . ai_family = AF_UNSPEC ; 6415hints . ai_socktype = SOCK_STREAM ; 6416hints . ai_protocol = 0 ; 6417 6418if (detail:: getaddrinfo_with_timeout ( hostname . c_str (), nullptr , & hints , 6419& result , 0 )) { 6420#if defined __linux__ && !defined __ANDROID__ 6421res_init (); 6422#endif 6423return; 6424} 6425auto se = detail:: scope_exit ([ & ] { freeaddrinfo ( result ); }); 6426 6427for ( auto rp = result ; rp ; rp = rp -> ai_next ) { 6428const auto & addr = 6429* reinterpret_cast < struct sockaddr_storage *> ( rp -> ai_addr ); 6430std:: string ip ; 6431auto dummy = -1 ; 6432if (detail:: get_ip_and_port ( addr , sizeof(struct sockaddr_storage ), ip , 6433dummy )) { 6434addrs . emplace_back (std:: move ( ip )); 6435} 6436} 6437} 6438 6439std:: string encode_uri_component (const std:: string & value ) { 6440std:: ostringstream escaped ; 6441escaped . fill ( '0' ); 6442escaped << std:: hex ; 6443 6444for ( auto c : value ) { 6445if (detail:: is_ascii_alnum ( c ) || c == '-' || c == '_' || c == '.' || 6446c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')' ) { 6447escaped << c ; 6448} else { 6449escaped << std:: uppercase ; 6450escaped << '%' << std:: setw ( 2 ) 6451<< static_cast < int > ( static_cast < unsigned char > ( c )); 6452escaped << std:: nouppercase ; 6453} 6454} 6455 6456return escaped . str (); 6457} 6458 6459std:: string encode_uri (const std:: string & value ) { 6460std:: ostringstream escaped ; 6461escaped . fill ( '0' ); 6462escaped << std:: hex ; 6463 6464for ( auto c : value ) { 6465if (detail:: is_ascii_alnum ( c ) || c == '-' || c == '_' || c == '.' || 6466c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')' || 6467c == ';' || c == '/' || c == '?' || c == ':' || c == '@' || c == '&' || 6468c == '=' || c == '+' || c == '$' || c == ',' || c == '#' ) { 6469escaped << c ; 6470} else { 6471escaped << std:: uppercase ; 6472escaped << '%' << std:: setw ( 2 ) 6473<< static_cast < int > ( static_cast < unsigned char > ( c )); 6474escaped << std:: nouppercase ; 6475} 6476} 6477 6478return escaped . str (); 6479} 6480 6481std:: string decode_uri_component (const std:: string & value ) { 6482std:: string result ; 6483 6484for ( size_t i = 0 ; i < value . size (); i ++ ) { 6485if ( value [ i ] == '%' && i + 2 < value . size ()) { 6486auto val = 0 ; 6487if (detail:: from_hex_to_i ( value , i + 1 , 2 , val )) { 6488result += static_cast < char > ( val ); 6489i += 2 ; 6490} else { 6491result += value [ i ]; 6492} 6493} else { 6494result += value [ i ]; 6495} 6496} 6497 6498return result ; 6499} 6500 6501std:: string decode_uri (const std:: string & value ) { 6502std:: string result ; 6503 6504for ( size_t i = 0 ; i < value . size (); i ++ ) { 6505if ( value [ i ] == '%' && i + 2 < value . size ()) { 6506auto val = 0 ; 6507if (detail:: from_hex_to_i ( value , i + 1 , 2 , val )) { 6508auto c = static_cast < char > ( val ); 6509// Keep escapes of the reserved characters that encode_uri leaves 6510// literal, so decode_uri is the inverse of encode_uri and an escaped 6511// delimiter is not promoted into a real one (as with JS decodeURI). 6512if ( c == ';' || c == '/' || c == '?' || c == ':' || c == '@' || 6513c == '&' || c == '=' || c == '+' || c == '$' || c == ',' || 6514c == '#' ) { 6515result += value [ i ]; 6516result += value [ i + 1 ]; 6517result += value [ i + 2 ]; 6518} else { 6519result += c ; 6520} 6521i += 2 ; 6522} else { 6523result += value [ i ]; 6524} 6525} else { 6526result += value [ i ]; 6527} 6528} 6529 6530return result ; 6531} 6532 6533std:: string encode_path_component (const std:: string & component ) { 6534std:: string result ; 6535result . reserve ( component . size () * 3 ); 6536 6537for ( size_t i = 0 ; i < component . size (); i ++ ) { 6538auto c = static_cast < unsigned char > ( component [ i ]); 6539 6540// Unreserved characters per RFC 3986: ALPHA / DIGIT / "-" / "." / "_" / "~" 6541if (detail:: is_ascii_alnum ( static_cast < char > ( c )) || c == '-' || c == '.' || 6542c == '_' || c == '~' ) { 6543result += static_cast < char > ( c ); 6544} 6545// Path-safe sub-delimiters: "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / 6546// "," / ";" / "=" 6547else if ( c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' || 6548c == ')' || c == '*' || c == '+' || c == ',' || c == ';' || 6549c == '=' ) { 6550result += static_cast < char > ( c ); 6551} 6552// Colon is allowed in path segments except first segment 6553else if ( c == ':' ) { 6554result += static_cast < char > ( c ); 6555} 6556// @ is allowed in path 6557else if ( c == '@' ) { 6558result += static_cast < char > ( c ); 6559} else { 6560result += '%' ; 6561char hex [ 3 ]; 6562snprintf ( hex , sizeof( hex ), "%02X" , c ); 6563result . append ( hex , 2 ); 6564} 6565} 6566return result ; 6567} 6568 6569std:: string decode_path_component (const std:: string & component ) { 6570std:: string result ; 6571result . reserve ( component . size ()); 6572 6573for ( size_t i = 0 ; i < component . size (); i ++ ) { 6574if ( component [ i ] == '%' && i + 1 < component . size ()) { 6575if ( component [ i + 1 ] == 'u' ) { 6576// Unicode %uXXXX encoding 6577auto val = 0 ; 6578if (detail:: from_hex_to_i ( component , i + 2 , 4 , val )) { 6579// 4 digits Unicode codes: val is 0x0000-0xFFFF (from 4 hex digits), 6580// so to_utf8 writes at most 3 bytes. buff[4] is safe. 6581char buff [ 4 ]; 6582size_t len = detail:: to_utf8 ( val , buff ); 6583if ( len > 0 ) { result . append ( buff , len ); } 6584i += 5 ; // 'u0000' 6585} else { 6586result += component [ i ]; 6587} 6588} else { 6589// Standard %XX encoding 6590auto val = 0 ; 6591if (detail:: from_hex_to_i ( component , i + 1 , 2 , val )) { 6592// 2 digits hex codes 6593result += static_cast < char > ( val ); 6594i += 2 ; // 'XX' 6595} else { 6596result += component [ i ]; 6597} 6598} 6599} else { 6600result += component [ i ]; 6601} 6602} 6603return result ; 6604} 6605 6606std:: string encode_query_component (const std:: string & component , 6607bool space_as_plus ) { 6608std:: string result ; 6609result . reserve ( component . size () * 3 ); 6610 6611for ( size_t i = 0 ; i < component . size (); i ++ ) { 6612auto c = static_cast < unsigned char > ( component [ i ]); 6613 6614// Unreserved characters per RFC 3986 6615if (detail:: is_ascii_alnum ( static_cast < char > ( c )) || c == '-' || c == '.' || 6616c == '_' || c == '~' ) { 6617result += static_cast < char > ( c ); 6618} 6619// Space handling 6620else if ( c == ' ' ) { 6621if ( space_as_plus ) { 6622result += '+' ; 6623} else { 6624result += "%20" ; 6625} 6626} 6627// Plus sign handling 6628else if ( c == '+' ) { 6629if ( space_as_plus ) { 6630result += "%2B" ; 6631} else { 6632result += static_cast < char > ( c ); 6633} 6634} 6635// Query-safe sub-delimiters (excluding & and = which are query delimiters) 6636else if ( c == '!' || c == '$' || c == '\'' || c == '(' || c == ')' || 6637c == '*' || c == ',' || c == ';' ) { 6638result += static_cast < char > ( c ); 6639} 6640// Colon and @ are allowed in query 6641else if ( c == ':' || c == '@' ) { 6642result += static_cast < char > ( c ); 6643} 6644// Forward slash is allowed in query values 6645else if ( c == '/' ) { 6646result += static_cast < char > ( c ); 6647} 6648// Question mark is allowed in query values (after first ?) 6649else if ( c == '?' ) { 6650result += static_cast < char > ( c ); 6651} else { 6652result += '%' ; 6653char hex [ 3 ]; 6654snprintf ( hex , sizeof( hex ), "%02X" , c ); 6655result . append ( hex , 2 ); 6656} 6657} 6658return result ; 6659} 6660 6661std:: string decode_query_component (const std:: string & component , 6662bool plus_as_space ) { 6663std:: string result ; 6664result . reserve ( component . size ()); 6665 6666for ( size_t i = 0 ; i < component . size (); i ++ ) { 6667if ( component [ i ] == '%' && i + 2 < component . size ()) { 6668auto val = 0 ; 6669if (detail:: from_hex_to_i ( component , i + 1 , 2 , val )) { 6670result += static_cast < char > ( val ); 6671i += 2 ; 6672} else { 6673result += component [ i ]; 6674} 6675} else if ( component [ i ] == '+' && plus_as_space ) { 6676result += ' ' ; // + becomes space in form-urlencoded 6677} else { 6678result += component [ i ]; 6679} 6680} 6681return result ; 6682} 6683 6684std:: string sanitize_filename (const std:: string & filename ) { 6685// Extract basename: find the last path separator (/ or \) 6686auto pos = filename . find_last_of ( "/\\" ); 6687auto result = 6688( pos != std::string:: npos ) ? filename . substr ( pos + 1 ) : filename ; 6689 6690// Strip null bytes 6691result . erase (std:: remove ( result . begin (), result . end (), '\0' ), result . end ()); 6692 6693// Trim whitespace 6694{ 6695auto start = result . find_first_not_of ( " \t" ); 6696auto end = result . find_last_not_of ( " \t" ); 6697result = ( start == std::string:: npos ) 6698? "" 6699: result . substr ( start , end - start + 1 ); 6700} 6701 6702// Reject . and .. 6703if ( result == "." || result == ".." ) { return "" ; } 6704 6705return result ; 6706} 6707 6708std:: string append_query_params (const std:: string & path , 6709const Params & params ) { 6710std:: string path_with_query = path ; 6711thread_local const std:: regex re ( "[^?]+\\?.*" ); 6712auto delm = std:: regex_match ( path , re ) ? '&' : '?' ; 6713path_with_query += delm + detail:: params_to_query_str ( params ); 6714return path_with_query ; 6715} 6716 6717// Header utilities 6718std:: pair < std:: string , std:: string > 6719make_range_header (const Ranges & ranges ) { 6720std:: string field = "bytes=" ; 6721auto i = 0 ; 6722for (const auto & r : ranges ) { 6723if ( i != 0 ) { field += ", " ; } 6724if ( r . first != -1 ) { field += std:: to_string ( r . first ); } 6725field += '-' ; 6726if ( r . second != -1 ) { field += std:: to_string ( r . second ); } 6727i ++ ; 6728} 6729return std:: make_pair ( "Range" , std:: move ( field )); 6730} 6731 6732std:: pair < std:: string , std:: string > 6733make_basic_authentication_header (const std:: string & username , 6734const std:: string & password , bool is_proxy ) { 6735auto field = "Basic " + detail:: base64_encode ( username + ":" + password ); 6736auto key = is_proxy ? "Proxy-Authorization" : "Authorization" ; 6737return std:: make_pair ( key , std:: move ( field )); 6738} 6739 6740std:: pair < std:: string , std:: string > 6741make_bearer_token_authentication_header (const std:: string & token , 6742bool is_proxy = false) { 6743auto field = "Bearer " + token ; 6744auto key = is_proxy ? "Proxy-Authorization" : "Authorization" ; 6745return std:: make_pair ( key , std:: move ( field )); 6746} 6747 6748// Request implementation 6749size_t Request :: get_header_value_u64 (const std:: string & key , size_t def , 6750size_t id ) const { 6751return detail:: get_header_value_u64 ( headers , key , def , id ); 6752} 6753 6754bool Request :: has_header (const std:: string & key ) const { 6755return detail:: has_header ( headers , key ); 6756} 6757 6758std:: string Request :: get_header_value (const std:: string & key , 6759const char * def , size_t id ) const { 6760return detail:: get_header_value ( headers , key , def , id ); 6761} 6762 6763size_t Request :: get_header_value_count (const std:: string & key ) const { 6764return detail:: get_header_value_count ( headers , key ); 6765} 6766 6767void Request :: set_header (const std:: string & key , 6768const std:: string & val ) { 6769detail:: set_header ( headers , key , val ); 6770} 6771 6772bool Request :: has_trailer (const std:: string & key ) const { 6773return trailers . find ( key ) != trailers . end (); 6774} 6775 6776std:: string Request :: get_trailer_value (const std:: string & key , 6777size_t id ) const { 6778return detail:: get_multimap_value ( trailers , key , id ); 6779} 6780 6781size_t Request :: get_trailer_value_count (const std:: string & key ) const { 6782return trailers . count ( key ); 6783} 6784 6785bool Request :: has_param (const std:: string & key ) const { 6786return params . find ( key ) != params . end (); 6787} 6788 6789std:: string Request :: get_param_value (const std:: string & key , 6790size_t id ) const { 6791return detail:: get_multimap_value ( params , key , id ); 6792} 6793 6794std:: vector < std:: string > 6795Request :: get_param_values (const std:: string & key ) const { 6796auto rng = params . equal_range ( key ); 6797std:: vector < std:: string > values ; 6798values . reserve ( static_cast < size_t > (std:: distance ( rng . first , rng . second ))); 6799for ( auto it = rng . first ; it != rng . second ; ++ it ) { 6800values . push_back ( it -> second ); 6801} 6802return values ; 6803} 6804 6805size_t Request :: get_param_value_count (const std:: string & key ) const { 6806return params . count ( key ); 6807} 6808 6809bool Request :: is_multipart_form_data () const { 6810const auto & content_type = get_header_value ( "Content-Type" ); 6811return detail:: extract_media_type ( content_type ) == "multipart/form-data" ; 6812} 6813 6814// Multipart FormData implementation 6815std:: string MultipartFormData :: get_field (const std:: string & key , 6816size_t id ) const { 6817auto rng = fields . equal_range ( key ); 6818auto it = rng . first ; 6819std:: advance ( it , static_cast < ssize_t > ( id )); 6820if ( it != rng . second ) { return it -> second . content ; } 6821return std:: string (); 6822} 6823 6824std:: vector < std:: string > 6825MultipartFormData :: get_fields (const std:: string & key ) const { 6826std:: vector < std:: string > values ; 6827auto rng = fields . equal_range ( key ); 6828for ( auto it = rng . first ; it != rng . second ; it ++ ) { 6829values . push_back ( it -> second . content ); 6830} 6831return values ; 6832} 6833 6834bool MultipartFormData :: has_field (const std:: string & key ) const { 6835return fields . find ( key ) != fields . end (); 6836} 6837 6838size_t MultipartFormData :: get_field_count (const std:: string & key ) const { 6839return fields . count ( key ); 6840} 6841 6842FormData MultipartFormData :: get_file (const std:: string & key , 6843size_t id ) const { 6844return detail:: get_multimap_value ( files , key , id ); 6845} 6846 6847std:: vector < FormData > 6848MultipartFormData :: get_files (const std:: string & key ) const { 6849std:: vector < FormData > values ; 6850auto rng = files . equal_range ( key ); 6851for ( auto it = rng . first ; it != rng . second ; it ++ ) { 6852values . push_back ( it -> second ); 6853} 6854return values ; 6855} 6856 6857bool MultipartFormData :: has_file (const std:: string & key ) const { 6858return files . find ( key ) != files . end (); 6859} 6860 6861size_t MultipartFormData :: get_file_count (const std:: string & key ) const { 6862return files . count ( key ); 6863} 6864 6865// Multipart FormData writer implementation 6866bool is_valid_multipart_boundary (const std:: string & boundary ) { 6867return detail:: is_multipart_boundary_chars_valid ( boundary ); 6868} 6869 6870MultipartFormDataWriter :: MultipartFormDataWriter () 6871: boundary_ (detail:: make_multipart_data_boundary ()) {} 6872 6873MultipartFormDataWriter :: MultipartFormDataWriter (std:: string boundary ) 6874: boundary_ (std:: move ( boundary )) {} 6875 6876const std:: string & MultipartFormDataWriter :: boundary () const { 6877return boundary_ ; 6878} 6879 6880std:: string MultipartFormDataWriter :: content_type () const { 6881return detail:: serialize_multipart_formdata_get_content_type ( boundary_ ); 6882} 6883 6884std:: string 6885MultipartFormDataWriter :: serialize (const UploadFormDataItems & items ) const { 6886return detail:: serialize_multipart_formdata ( items , boundary_ ); 6887} 6888 6889size_t MultipartFormDataWriter :: content_length ( 6890const UploadFormDataItems & items ) const { 6891return detail:: get_multipart_content_length ( items , boundary_ ); 6892} 6893 6894std:: string 6895MultipartFormDataWriter :: item_begin (const UploadFormData & item ) const { 6896return detail:: serialize_multipart_formdata_item_begin ( item , boundary_ ); 6897} 6898 6899std:: string MultipartFormDataWriter :: item_end () { 6900return detail:: serialize_multipart_formdata_item_end (); 6901} 6902 6903std:: string MultipartFormDataWriter :: finish () const { 6904return detail:: serialize_multipart_formdata_finish ( boundary_ ); 6905} 6906 6907// Response implementation 6908size_t Response :: get_header_value_u64 (const std:: string & key , size_t def , 6909size_t id ) const { 6910return detail:: get_header_value_u64 ( headers , key , def , id ); 6911} 6912 6913bool Response :: has_header (const std:: string & key ) const { 6914return headers . find ( key ) != headers . end (); 6915} 6916 6917std:: string Response :: get_header_value (const std:: string & key , 6918const char * def , 6919size_t id ) const { 6920return detail:: get_header_value ( headers , key , def , id ); 6921} 6922 6923size_t Response :: get_header_value_count (const std:: string & key ) const { 6924return detail:: get_header_value_count ( headers , key ); 6925} 6926 6927void Response :: set_header (const std:: string & key , 6928const std:: string & val ) { 6929detail:: set_header ( headers , key , val ); 6930} 6931bool Response :: has_trailer (const std:: string & key ) const { 6932return trailers . find ( key ) != trailers . end (); 6933} 6934 6935std:: string Response :: get_trailer_value (const std:: string & key , 6936size_t id ) const { 6937return detail:: get_multimap_value ( trailers , key , id ); 6938} 6939 6940size_t Response :: get_trailer_value_count (const std:: string & key ) const { 6941return trailers . count ( key ); 6942} 6943 6944void Response :: set_redirect (const std:: string & url , int stat ) { 6945if (detail::fields:: is_field_value ( url )) { 6946set_header ( "Location" , url ); 6947if ( 300 <= stat && stat < 400 ) { 6948this -> status = stat ; 6949} else { 6950this -> status = StatusCode :: Found_302 ; 6951} 6952} 6953} 6954 6955void Response :: set_content (const char * s , size_t n , 6956const std:: string & content_type ) { 6957body . assign ( s , n ); 6958 6959auto rng = headers . equal_range ( "Content-Type" ); 6960headers . erase ( rng . first , rng . second ); 6961set_header ( "Content-Type" , content_type ); 6962content_coding_ = detail:: EncodingType :: None ; 6963} 6964 6965void Response :: set_content (const std:: string & s , 6966const std:: string & content_type ) { 6967set_content ( s . data (), s . size (), content_type ); 6968} 6969 6970void Response :: set_content (std:: string && s , 6971const std:: string & content_type ) { 6972body = std:: move ( s ); 6973 6974auto rng = headers . equal_range ( "Content-Type" ); 6975headers . erase ( rng . first , rng . second ); 6976set_header ( "Content-Type" , content_type ); 6977content_coding_ = detail:: EncodingType :: None ; 6978} 6979 6980void Response :: set_content_provider ( 6981size_t in_length , const std:: string & content_type , ContentProvider provider , 6982ContentProviderResourceReleaser resource_releaser ) { 6983set_header ( "Content-Type" , content_type ); 6984content_length_ = in_length ; 6985if ( in_length > 0 ) { content_provider_ = std:: move ( provider ); } 6986content_provider_resource_releaser_ = std:: move ( resource_releaser ); 6987is_chunked_content_provider_ = false; 6988content_coding_ = detail:: EncodingType :: None ; 6989} 6990 6991void Response :: set_content_provider ( 6992const std:: string & content_type , ContentProviderWithoutLength provider , 6993ContentProviderResourceReleaser resource_releaser ) { 6994set_header ( "Content-Type" , content_type ); 6995content_length_ = 0 ; 6996content_provider_ = detail:: ContentProviderAdapter (std:: move ( provider )); 6997content_provider_resource_releaser_ = std:: move ( resource_releaser ); 6998is_chunked_content_provider_ = false; 6999content_coding_ = detail:: EncodingType :: None ; 7000} 7001 7002void Response :: set_chunked_content_provider ( 7003const std:: string & content_type , ContentProviderWithoutLength provider , 7004ContentProviderResourceReleaser resource_releaser ) { 7005set_header ( "Content-Type" , content_type ); 7006content_length_ = 0 ; 7007content_provider_ = detail:: ContentProviderAdapter (std:: move ( provider )); 7008content_provider_resource_releaser_ = std:: move ( resource_releaser ); 7009is_chunked_content_provider_ = true; 7010content_coding_ = detail:: EncodingType :: None ; 7011} 7012 7013void Response :: set_file_content (const std:: string & path , 7014const std:: string & content_type ) { 7015file_content_path_ = path ; 7016file_content_content_type_ = content_type ; 7017} 7018 7019void Response :: set_file_content (const std:: string & path ) { 7020file_content_path_ = path ; 7021} 7022 7023// Result implementation 7024size_t Result :: get_request_header_value_u64 (const std:: string & key , 7025size_t def , 7026size_t id ) const { 7027return detail:: get_header_value_u64 ( request_headers_ , key , def , id ); 7028} 7029 7030bool Result :: has_request_header (const std:: string & key ) const { 7031return request_headers_ . find ( key ) != request_headers_ . end (); 7032} 7033 7034std:: string Result :: get_request_header_value (const std:: string & key , 7035const char * def , 7036size_t id ) const { 7037return detail:: get_header_value ( request_headers_ , key , def , id ); 7038} 7039 7040size_t 7041Result :: get_request_header_value_count (const std:: string & key ) const { 7042return request_headers_ . count ( key ); 7043} 7044 7045// Stream implementation 7046ssize_t Stream :: write (const char * ptr ) { 7047return write ( ptr , strlen ( ptr )); 7048} 7049 7050ssize_t Stream :: write (const std:: string & s ) { 7051return write ( s . data (), s . size ()); 7052} 7053 7054// BodyReader implementation 7055ssize_t detail:: BodyReader :: read ( char * buf , size_t len ) { 7056if (! stream ) { 7057last_error = Error :: Connection ; 7058return -1 ; 7059} 7060if ( eof ) { return 0 ; } 7061 7062if (! chunked ) { 7063// Content-Length based reading 7064if ( has_content_length && bytes_read >= content_length ) { 7065eof = true; 7066return 0 ; 7067} 7068 7069auto to_read = len ; 7070if ( has_content_length ) { 7071auto remaining = content_length - bytes_read ; 7072to_read = (std:: min )( len , remaining ); 7073} 7074auto n = stream -> read ( buf , to_read ); 7075 7076if ( n < 0 ) { 7077last_error = stream -> get_error (); 7078if ( last_error == Error :: Success ) { last_error = Error :: Read ; } 7079eof = true; 7080return n ; 7081} 7082if ( n == 0 ) { 7083// Unexpected EOF before content_length 7084last_error = stream -> get_error (); 7085if ( last_error == Error :: Success ) { last_error = Error :: Read ; } 7086eof = true; 7087return 0 ; 7088} 7089 7090bytes_read += static_cast < size_t > ( n ); 7091if ( has_content_length && bytes_read >= content_length ) { eof = true; } 7092if ( payload_max_length > 0 && bytes_read > payload_max_length ) { 7093last_error = Error :: ExceedMaxPayloadSize ; 7094eof = true; 7095return -1 ; 7096} 7097return n ; 7098} 7099 7100// Chunked transfer encoding: delegate to shared decoder instance. 7101if (! chunked_decoder ) { chunked_decoder . reset (new ChunkedDecoder ( * stream )); } 7102 7103size_t chunk_offset = 0 ; 7104size_t chunk_total = 0 ; 7105auto n = chunked_decoder -> read_payload ( buf , len , chunk_offset , chunk_total ); 7106if ( n < 0 ) { 7107last_error = stream -> get_error (); 7108if ( last_error == Error :: Success ) { last_error = Error :: Read ; } 7109eof = true; 7110return n ; 7111} 7112 7113if ( n == 0 ) { 7114// Final chunk observed. Leave trailer parsing to the caller (StreamHandle). 7115eof = true; 7116return 0 ; 7117} 7118 7119bytes_read += static_cast < size_t > ( n ); 7120if ( payload_max_length > 0 && bytes_read > payload_max_length ) { 7121last_error = Error :: ExceedMaxPayloadSize ; 7122eof = true; 7123return -1 ; 7124} 7125return n ; 7126} 7127 7128// ThreadPool implementation 7129ThreadPool :: ThreadPool ( size_t n , size_t max_n , size_t mqr , 7130time_t idle_timeout_sec ) 7131: base_thread_count_ ( n ), max_queued_requests_ ( mqr ), 7132idle_timeout_sec_ ( idle_timeout_sec ), idle_thread_count_ ( 0 ), 7133shutdown_ (false) { 7134#ifndef CPPHTTPLIB_NO_EXCEPTIONS 7135if ( max_n != 0 && max_n < n ) { 7136std:: string msg = "max_threads must be >= base_threads" ; 7137throw std:: invalid_argument ( msg ); 7138} 7139#endif 7140max_thread_count_ = max_n == 0 ? n : max_n ; 7141threads_ . reserve ( base_thread_count_ ); 7142#ifndef CPPHTTPLIB_NO_EXCEPTIONS 7143try { 7144#endif 7145for ( size_t i = 0 ; i < base_thread_count_ ; i ++ ) { 7146threads_ . emplace_back (std:: thread ([ this ]() { worker (false); })); 7147} 7148#ifndef CPPHTTPLIB_NO_EXCEPTIONS 7149} catch (...) { 7150// If thread creation fails partway (e.g., pthread_create returns EAGAIN), 7151// signal the workers we already spawned to exit and join them so the 7152// vector destructor does not see joinable threads (which would call 7153// std::terminate). Then rethrow so the caller learns of the failure. 7154{ 7155std:: unique_lock < std:: mutex > lock ( mutex_ ); 7156shutdown_ = true; 7157} 7158cond_ . notify_all (); 7159for ( auto & t : threads_ ) { 7160if ( t . joinable ()) { t . join (); } 7161} 7162throw; 7163} 7164#endif 7165} 7166 7167bool ThreadPool :: enqueue (std:: function < void () > fn ) { 7168{ 7169std:: unique_lock < std:: mutex > lock ( mutex_ ); 7170if ( shutdown_ ) { return false; } 7171if ( max_queued_requests_ > 0 && jobs_ . size () >= max_queued_requests_ ) { 7172return false; 7173} 7174jobs_ . push_back (std:: move ( fn )); 7175 7176// Spawn a dynamic thread if no idle threads and under max 7177if ( idle_thread_count_ == 0 && 7178threads_ . size () + dynamic_threads_ . size () < max_thread_count_ ) { 7179cleanup_finished_threads (); 7180dynamic_threads_ . emplace_back (std:: thread ([ this ]() { worker (true); })); 7181} 7182} 7183 7184cond_ . notify_one (); 7185return true; 7186} 7187 7188void ThreadPool :: shutdown () { 7189{ 7190std:: unique_lock < std:: mutex > lock ( mutex_ ); 7191shutdown_ = true; 7192} 7193 7194cond_ . notify_all (); 7195 7196for ( auto & t : threads_ ) { 7197if ( t . joinable ()) { t . join (); } 7198} 7199 7200// Move dynamic_threads_ to a local list under the lock to avoid racing 7201// with worker threads that call move_to_finished() concurrently. 7202std:: list < std:: thread > remaining_dynamic ; 7203{ 7204std:: unique_lock < std:: mutex > lock ( mutex_ ); 7205remaining_dynamic = std:: move ( dynamic_threads_ ); 7206} 7207for ( auto & t : remaining_dynamic ) { 7208if ( t . joinable ()) { t . join (); } 7209} 7210 7211std:: unique_lock < std:: mutex > lock ( mutex_ ); 7212cleanup_finished_threads (); 7213} 7214 7215void ThreadPool :: move_to_finished (std::thread:: id id ) { 7216// Must be called with mutex_ held 7217for ( auto it = dynamic_threads_ . begin (); it != dynamic_threads_ . end (); ++ it ) { 7218if ( it -> get_id () == id ) { 7219finished_threads_ . push_back (std:: move ( * it )); 7220dynamic_threads_ . erase ( it ); 7221return; 7222} 7223} 7224} 7225 7226void ThreadPool :: cleanup_finished_threads () { 7227// Must be called with mutex_ held 7228for ( auto & t : finished_threads_ ) { 7229if ( t . joinable ()) { t . join (); } 7230} 7231finished_threads_ . clear (); 7232} 7233 7234void ThreadPool :: worker ( bool is_dynamic ) { 7235for (;;) { 7236std:: function < void () > fn ; 7237{ 7238std:: unique_lock < std:: mutex > lock ( mutex_ ); 7239idle_thread_count_ ++ ; 7240 7241if ( is_dynamic ) { 7242auto has_work = 7243cond_ . wait_for ( lock , std::chrono:: seconds ( idle_timeout_sec_ ), 7244[ & ] { return ! jobs_ . empty () || shutdown_ ; }); 7245if (! has_work ) { 7246// Timed out with no work - exit this dynamic thread 7247idle_thread_count_ -- ; 7248move_to_finished (std::this_thread:: get_id ()); 7249break; 7250} 7251} else { 7252cond_ . wait ( lock , [ & ] { return ! jobs_ . empty () || shutdown_ ; }); 7253} 7254 7255idle_thread_count_ -- ; 7256 7257if ( shutdown_ && jobs_ . empty ()) { break; } 7258 7259fn = std:: move ( jobs_ . front ()); 7260jobs_ . pop_front (); 7261} 7262 7263assert (true == static_cast < bool > ( fn )); 7264fn (); 7265} 7266 7267#if defined( CPPHTTPLIB_OPENSSL_SUPPORT ) && !defined( OPENSSL_IS_BORINGSSL ) && \ 7268!defined( LIBRESSL_VERSION_NUMBER ) 7269OPENSSL_thread_stop (); 7270#endif 7271} 7272 7273/* 7274* Group 1 (continued): detail namespace - Stream implementations 7275*/ 7276 7277namespace detail { 7278 7279void calc_actual_timeout ( time_t max_timeout_msec , time_t duration_msec , 7280time_t timeout_sec , time_t timeout_usec , 7281time_t & actual_timeout_sec , 7282time_t & actual_timeout_usec ) { 7283auto timeout_msec = ( timeout_sec * 1000 ) + ( timeout_usec / 1000 ); 7284 7285auto actual_timeout_msec = 7286(std:: min )( max_timeout_msec - duration_msec , timeout_msec ); 7287 7288if ( actual_timeout_msec < 0 ) { actual_timeout_msec = 0 ; } 7289 7290actual_timeout_sec = actual_timeout_msec / 1000 ; 7291actual_timeout_usec = ( actual_timeout_msec % 1000 ) * 1000 ; 7292} 7293 7294// Socket stream implementation 7295SocketStream :: SocketStream ( 7296socket_t sock , time_t read_timeout_sec , time_t read_timeout_usec , 7297time_t write_timeout_sec , time_t write_timeout_usec , 7298time_t max_timeout_msec , 7299std::chrono:: time_point < std::chrono:: steady_clock > start_time ) 7300: sock_ ( sock ), read_timeout_sec_ ( read_timeout_sec ), 7301read_timeout_usec_ ( read_timeout_usec ), 7302write_timeout_sec_ ( write_timeout_sec ), 7303write_timeout_usec_ ( write_timeout_usec ), 7304max_timeout_msec_ ( max_timeout_msec ), start_time_ ( start_time ), 7305read_buff_ ( read_buff_size_ , 0 ) {} 7306 7307SocketStream ::~ SocketStream () = default ; 7308 7309bool SocketStream :: is_readable () const { 7310return read_buff_off_ < read_buff_content_size_ ; 7311} 7312 7313bool SocketStream :: wait_readable () const { 7314if ( max_timeout_msec_ <= 0 ) { 7315return select_read ( sock_ , read_timeout_sec_ , read_timeout_usec_ ) > 0 ; 7316} 7317 7318time_t read_timeout_sec ; 7319time_t read_timeout_usec ; 7320calc_actual_timeout ( max_timeout_msec_ , duration (), read_timeout_sec_ , 7321read_timeout_usec_ , read_timeout_sec , read_timeout_usec ); 7322 7323return select_read ( sock_ , read_timeout_sec , read_timeout_usec ) > 0 ; 7324} 7325 7326bool SocketStream :: wait_writable () const { 7327return select_write ( sock_ , write_timeout_sec_ , write_timeout_usec_ ) > 0 ; 7328} 7329 7330bool SocketStream :: ensure_readable () { 7331if ( readable_hint_ ) { 7332readable_hint_ = false; 7333return true; 7334} 7335return wait_readable (); 7336} 7337 7338const char * SocketStream :: buffered_data ( size_t & size ) const { 7339size = read_buff_content_size_ - read_buff_off_ ; 7340return size ? read_buff_ . data () + read_buff_off_ : nullptr ; 7341} 7342 7343void SocketStream :: consume_buffered ( size_t size ) { 7344assert ( size <= read_buff_content_size_ - read_buff_off_ ); 7345read_buff_off_ += size ; 7346} 7347 7348bool SocketStream :: is_peer_alive () const { 7349return detail:: is_socket_alive ( sock_ ); 7350} 7351 7352ssize_t SocketStream :: read ( char * ptr , size_t size ) { 7353#ifdef _WIN32 7354size = 7355(std:: min )( size , static_cast < size_t > ((std:: numeric_limits < int > :: max )())); 7356#else 7357size = (std:: min )( size , 7358static_cast < size_t > ((std:: numeric_limits < ssize_t > :: max )())); 7359#endif 7360 7361if ( read_buff_off_ < read_buff_content_size_ ) { 7362auto remaining_size = read_buff_content_size_ - read_buff_off_ ; 7363if ( size <= remaining_size ) { 7364memcpy ( ptr , read_buff_ . data () + read_buff_off_ , size ); 7365read_buff_off_ += size ; 7366return static_cast < ssize_t > ( size ); 7367} else { 7368memcpy ( ptr , read_buff_ . data () + read_buff_off_ , remaining_size ); 7369read_buff_off_ += remaining_size ; 7370return static_cast < ssize_t > ( remaining_size ); 7371} 7372} 7373 7374if (! ensure_readable ()) { 7375error_ = Error :: Timeout ; 7376return -1 ; 7377} 7378 7379read_buff_off_ = 0 ; 7380read_buff_content_size_ = 0 ; 7381 7382if ( size < read_buff_size_ ) { 7383auto n = read_socket ( sock_ , read_buff_ . data (), read_buff_size_ , 7384CPPHTTPLIB_RECV_FLAGS ); 7385if ( n <= 0 ) { 7386if ( n == 0 ) { 7387error_ = Error :: ConnectionClosed ; 7388} else { 7389error_ = Error :: Read ; 7390} 7391return n ; 7392} else if ( n <= static_cast < ssize_t > ( size )) { 7393memcpy ( ptr , read_buff_ . data (), static_cast < size_t > ( n )); 7394return n ; 7395} else { 7396memcpy ( ptr , read_buff_ . data (), size ); 7397read_buff_off_ = size ; 7398read_buff_content_size_ = static_cast < size_t > ( n ); 7399return static_cast < ssize_t > ( size ); 7400} 7401} else { 7402auto n = read_socket ( sock_ , ptr , size , CPPHTTPLIB_RECV_FLAGS ); 7403if ( n <= 0 ) { 7404if ( n == 0 ) { 7405error_ = Error :: ConnectionClosed ; 7406} else { 7407error_ = Error :: Read ; 7408} 7409} 7410return n ; 7411} 7412} 7413 7414ssize_t SocketStream :: write (const char * ptr , size_t size ) { 7415if (! wait_writable ()) { return -1 ; } 7416 7417#if defined( _WIN32 ) && !defined( _WIN64 ) 7418size = 7419(std:: min )( size , static_cast < size_t > ((std:: numeric_limits < int > :: max )())); 7420#endif 7421 7422return send_socket ( sock_ , ptr , size , CPPHTTPLIB_SEND_FLAGS ); 7423} 7424 7425void SocketStream :: get_remote_ip_and_port (std:: string & ip , 7426int & port ) const { 7427return detail:: get_remote_ip_and_port ( sock_ , ip , port ); 7428} 7429 7430void SocketStream :: get_local_ip_and_port (std:: string & ip , 7431int & port ) const { 7432return detail:: get_local_ip_and_port ( sock_ , ip , port ); 7433} 7434 7435socket_t SocketStream :: socket () const { return sock_ ; } 7436 7437time_t SocketStream :: duration () const { 7438return std::chrono:: duration_cast < std::chrono:: milliseconds > ( 7439std::chrono::steady_clock:: now () - start_time_ ) 7440. count (); 7441} 7442 7443void SocketStream :: set_read_timeout ( time_t sec , time_t usec ) { 7444read_timeout_sec_ = sec ; 7445read_timeout_usec_ = usec ; 7446} 7447 7448// Buffer stream implementation 7449bool BufferStream :: is_readable () const { return true; } 7450 7451bool BufferStream :: wait_readable () const { return true; } 7452 7453bool BufferStream :: wait_writable () const { return true; } 7454 7455ssize_t BufferStream :: read ( char * ptr , size_t size ) { 7456#if defined( _MSC_VER ) && _MSC_VER < 1910 7457auto len_read = buffer . _Copy_s ( ptr , size , size , position ); 7458#else 7459auto len_read = buffer . copy ( ptr , size , position ); 7460#endif 7461position += static_cast < size_t > ( len_read ); 7462return static_cast < ssize_t > ( len_read ); 7463} 7464 7465ssize_t BufferStream :: write (const char * ptr , size_t size ) { 7466buffer . append ( ptr , size ); 7467return static_cast < ssize_t > ( size ); 7468} 7469 7470void BufferStream :: get_remote_ip_and_port (std:: string & /*ip*/ , 7471int & /*port*/ ) const {} 7472 7473void BufferStream :: get_local_ip_and_port (std:: string & /*ip*/ , 7474int & /*port*/ ) const {} 7475 7476socket_t BufferStream :: socket () const { return 0 ; } 7477 7478time_t BufferStream :: duration () const { return 0 ; } 7479 7480const std:: string & BufferStream :: get_buffer () const { return buffer ; } 7481 7482PathParamsMatcher :: PathParamsMatcher (const std:: string & pattern ) 7483: MatcherBase ( pattern ) { 7484constexpr const char marker [] = "/:" ; 7485 7486// One past the last ending position of a path param substring 7487std:: size_t last_param_end = 0 ; 7488 7489#ifndef CPPHTTPLIB_NO_EXCEPTIONS 7490// Needed to ensure that parameter names are unique during matcher 7491// construction 7492// If exceptions are disabled, only last duplicate path 7493// parameter will be set 7494std:: unordered_set < std:: string > param_name_set ; 7495#endif 7496 7497while (true) { 7498const auto marker_pos = pattern . find ( 7499marker , last_param_end == 0 ? last_param_end : last_param_end - 1 ); 7500if ( marker_pos == std::string:: npos ) { break; } 7501 7502static_fragments_ . push_back ( 7503pattern . substr ( last_param_end , marker_pos - last_param_end + 1 )); 7504 7505const auto param_name_start = marker_pos + str_len ( marker ); 7506 7507auto sep_pos = pattern . find ( separator , param_name_start ); 7508if ( sep_pos == std::string:: npos ) { sep_pos = pattern . length (); } 7509 7510auto param_name = 7511pattern . substr ( param_name_start , sep_pos - param_name_start ); 7512 7513#ifndef CPPHTTPLIB_NO_EXCEPTIONS 7514if ( param_name_set . find ( param_name ) != param_name_set . cend ()) { 7515std:: string msg = "Encountered path parameter '" + param_name + 7516"' multiple times in route pattern '" + pattern + "'." ; 7517throw std:: invalid_argument ( msg ); 7518} 7519#endif 7520 7521param_names_ . push_back (std:: move ( param_name )); 7522 7523last_param_end = sep_pos + 1 ; 7524} 7525 7526if ( last_param_end < pattern . length ()) { 7527static_fragments_ . push_back ( pattern . substr ( last_param_end )); 7528} 7529} 7530 7531bool PathParamsMatcher :: match ( Request & request ) const { 7532request . matches = std:: smatch (); 7533request . path_params . clear (); 7534 7535// A pattern without parameters is just a literal path to compare against 7536if ( param_names_ . empty ()) { return request . path == pattern (); } 7537 7538request . path_params . reserve ( param_names_ . size ()); 7539 7540// One past the position at which the path matched the pattern last time 7541std:: size_t starting_pos = 0 ; 7542for ( size_t i = 0 ; i < static_fragments_ . size (); ++ i ) { 7543const auto & fragment = static_fragments_ [ i ]; 7544 7545if ( starting_pos + fragment . length () > request . path . length ()) { 7546return false; 7547} 7548 7549// Avoid unnecessary allocation by using strncmp instead of substr + 7550// comparison 7551if (std:: strncmp ( request . path . c_str () + starting_pos , fragment . c_str (), 7552fragment . length ()) != 0 ) { 7553return false; 7554} 7555 7556starting_pos += fragment . length (); 7557 7558// Should only happen when we have a static fragment after a param 7559// Example: '/users/:id/subscriptions' 7560// The 'subscriptions' fragment here does not have a corresponding param 7561if ( i >= param_names_ . size ()) { continue; } 7562 7563auto sep_pos = request . path . find ( separator , starting_pos ); 7564if ( sep_pos == std::string:: npos ) { sep_pos = request . path . length (); } 7565 7566const auto & param_name = param_names_ [ i ]; 7567 7568request . path_params . emplace ( 7569param_name , request . path . substr ( starting_pos , sep_pos - starting_pos )); 7570 7571// Mark everything up to '/' as matched 7572starting_pos = sep_pos + 1 ; 7573} 7574// Returns false if the path is longer than the pattern 7575return starting_pos >= request . path . length (); 7576} 7577 7578bool RegexMatcher :: match ( Request & request ) const { 7579request . path_params . clear (); 7580// See CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH: an overlong path is treated as 7581// a non-match rather than risking a stack overflow in std::regex_match. 7582if ( request . path . length () > CPPHTTPLIB_REGEX_ROUTE_PATH_MAX_LENGTH ) { 7583return false; 7584} 7585return std:: regex_match ( request . path , request . matches , regex_ ); 7586} 7587 7588// Enclose IPv6 address in brackets if needed 7589std:: string prepare_host_string (const std:: string & host ) { 7590// Enclose IPv6 address in brackets (but not if already enclosed) 7591if ( host . find ( ':' ) == std::string:: npos || 7592(! host . empty () && host [ 0 ] == '[' )) { 7593// IPv4, hostname, or already bracketed IPv6 7594return host ; 7595} else { 7596// IPv6 address without brackets 7597return "[" + host + "]" ; 7598} 7599} 7600 7601std:: string make_host_and_port_string (const std:: string & host , int port , 7602bool is_ssl ) { 7603auto result = prepare_host_string ( host ); 7604 7605// Append port if not default 7606if ((! is_ssl && port == 80 ) || ( is_ssl && port == 443 )) { 7607; // do nothing 7608} else { 7609result += ":" + std:: to_string ( port ); 7610} 7611 7612return result ; 7613} 7614 7615// Create "host:port" string always including port number (for CONNECT method) 7616std:: string 7617make_host_and_port_string_always_port (const std:: string & host , int port ) { 7618return prepare_host_string ( host ) + ":" + std:: to_string ( port ); 7619} 7620 7621// Value for the Host header a client sends when the caller supplied none. 7622// Only the value: callers decide where in their header list it goes. 7623std:: string make_default_host_header_value (const std:: string & host , 7624int port , bool is_ssl , 7625int address_family ) { 7626if ( address_family == AF_UNIX ) { return "localhost" ; } 7627return make_host_and_port_string ( host , port , is_ssl ); 7628} 7629 7630void add_default_user_agent_header ( Request & req ) { 7631#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT 7632if (! req . has_header ( "User-Agent" )) { 7633req . set_header ( "User-Agent" , 7634std:: string ( "cpp-httplib/" ) + CPPHTTPLIB_VERSION ); 7635} 7636#else 7637( void ) req ; 7638#endif 7639} 7640 7641bool parse_no_proxy_entry (const std:: string & token , NoProxyEntry & out ); 7642NormalizedTarget normalize_target (const std:: string & host ); 7643bool ip_in_cidr (const IPBytes & ip , const IPBytes & net , int prefix_bits ); 7644bool host_matches_no_proxy (const NormalizedTarget & target , 7645const std:: vector < NoProxyEntry > & entries ); 7646 7647bool ip_in_cidr (const IPBytes & ip , const IPBytes & net , int prefix_bits ) { 7648if ( prefix_bits < 0 || prefix_bits > 128 ) { return false; } 7649if ( prefix_bits == 0 ) { return true; } 7650int full_bytes = prefix_bits / 8 ; 7651int rem_bits = prefix_bits % 8 ; 7652if ( full_bytes > 0 && std:: memcmp ( ip . data (), net . data (), 7653static_cast < size_t > ( full_bytes )) != 0 ) { 7654return false; 7655} 7656if ( rem_bits == 0 ) { return true; } 7657auto i = static_cast < size_t > ( full_bytes ); 7658auto mask = static_cast < uint8_t > ( 0xFFu << ( 8 - rem_bits )); 7659return ( ip [ i ] & mask ) == ( net [ i ] & mask ); 7660} 7661 7662bool parse_no_proxy_entry (const std:: string & token , NoProxyEntry & out ) { 7663if ( token . empty ()) { return false; } 7664 7665if ( token == "*" ) { 7666out . kind = NoProxyKind :: Wildcard ; 7667return true; 7668} 7669 7670auto slash = token . find ( '/' ); 7671std:: string addr_part = 7672( slash == std::string:: npos ) ? token : token . substr ( 0 , slash ); 7673std:: string prefix_part = 7674( slash == std::string:: npos ) ? std:: string () : token . substr ( slash + 1 ); 7675 7676// A bare slash or trailing-slash CIDR like "10.0.0.0/" is malformed; 7677// don't silently treat it as a /32 (or /128). 7678if ( slash != std::string:: npos && prefix_part . empty ()) { return false; } 7679 7680// Accept the bracketed IPv6 form ("[::1]", "[fe80::]/10") as well as the 7681// bare form. Brackets have no meaning for IPv4, so skip the IPv4 attempt 7682// when brackets are present. 7683bool bracketed = addr_part . size () >= 2 && addr_part . front () == '[' && 7684addr_part . back () == ']' ; 7685if ( bracketed ) { addr_part = addr_part . substr ( 1 , addr_part . size () - 2 ); } 7686 7687if (! bracketed ) { 7688struct in_addr v4 ; 7689if ( inet_pton ( AF_INET , addr_part . c_str (), & v4 ) == 1 ) { 7690int prefix = 32 ; 7691if (! prefix_part . empty ()) { 7692auto r = from_chars ( prefix_part . data (), 7693prefix_part . data () + prefix_part . size (), prefix ); 7694if ( r . ec != std:: errc {} || 7695r . ptr != prefix_part . data () + prefix_part . size ()) { 7696return false; 7697} 7698if ( prefix < 0 || prefix > 32 ) { return false; } 7699} 7700out . kind = NoProxyKind :: IPv4Cidr ; 7701std:: memcpy ( out . net . data (), & v4 , sizeof( v4 )); 7702out . prefix_bits = prefix ; 7703return true; 7704} 7705} 7706 7707struct in6_addr v6 ; 7708if ( inet_pton ( AF_INET6 , addr_part . c_str (), & v6 ) == 1 ) { 7709int prefix = 128 ; 7710if (! prefix_part . empty ()) { 7711auto r = from_chars ( prefix_part . data (), 7712prefix_part . data () + prefix_part . size (), prefix ); 7713if ( r . ec != std:: errc {} || 7714r . ptr != prefix_part . data () + prefix_part . size ()) { 7715return false; 7716} 7717if ( prefix < 0 || prefix > 128 ) { return false; } 7718} 7719out . kind = NoProxyKind :: IPv6Cidr ; 7720std:: memcpy ( out . net . data (), & v6 , sizeof( v6 )); 7721out . prefix_bits = prefix ; 7722return true; 7723} 7724 7725// Bracketed entries can only be IPv6. If the IPv6 parse above failed, 7726// the entry is malformed — don't fall through to the hostname branch. 7727if ( bracketed ) { return false; } 7728 7729// A '/' on a non-IP token means a CIDR prefix without an address. Reject. 7730if ( slash != std::string:: npos ) { return false; } 7731// Port-specific entries (host:port) are not supported. 7732if ( token . find ( ':' ) != std::string:: npos ) { return false; } 7733 7734std:: string hostname = case_ignore:: to_lower ( token ); 7735while (! hostname . empty () && hostname . front () == '.' ) { 7736hostname . erase ( hostname . begin ()); 7737} 7738while (! hostname . empty () && hostname . back () == '.' ) { 7739hostname . pop_back (); 7740} 7741if ( hostname . empty ()) { return false; } 7742 7743out . kind = NoProxyKind :: HostnameSuffix ; 7744out . hostname_pattern = std:: move ( hostname ); 7745return true; 7746} 7747 7748NormalizedTarget normalize_target (const std:: string & host ) { 7749NormalizedTarget t ; 7750std:: string h = host ; 7751 7752if ( h . size () >= 2 && h . front () == '[' && h . back () == ']' ) { 7753h = h . substr ( 1 , h . size () - 2 ); 7754} 7755 7756// Strip a single trailing dot so "example.com." canonicalizes to 7757// "example.com". 7758if (! h . empty () && h . back () == '.' ) { h . pop_back (); } 7759 7760t . hostname = case_ignore:: to_lower ( h ); 7761 7762if (! t . hostname . empty ()) { 7763struct in_addr v4 ; 7764struct in6_addr v6 ; 7765if ( inet_pton ( AF_INET , t . hostname . c_str (), & v4 ) == 1 ) { 7766t . is_ipv4 = true; 7767std:: memcpy ( t . ip . data (), & v4 , sizeof( v4 )); 7768} else if ( inet_pton ( AF_INET6 , t . hostname . c_str (), & v6 ) == 1 ) { 7769t . is_ipv6 = true; 7770std:: memcpy ( t . ip . data (), & v6 , sizeof( v6 )); 7771} 7772} 7773return t ; 7774} 7775 7776bool host_matches_no_proxy (const NormalizedTarget & target , 7777const std:: vector < NoProxyEntry > & entries ) { 7778if ( target . hostname . empty ()) { return false; } 7779for (const auto & e : entries ) { 7780switch ( e . kind ) { 7781case NoProxyKind :: Wildcard : return true; 7782case NoProxyKind :: IPv4Cidr : 7783if ( target . is_ipv4 && ip_in_cidr ( target . ip , e . net , e . prefix_bits )) { 7784return true; 7785} 7786break; 7787case NoProxyKind :: IPv6Cidr : 7788if ( target . is_ipv6 && ip_in_cidr ( target . ip , e . net , e . prefix_bits )) { 7789return true; 7790} 7791break; 7792case NoProxyKind :: HostnameSuffix : 7793if ( target . is_ipv4 || target . is_ipv6 ) { break; } 7794if ( target . hostname == e . hostname_pattern ) { return true; } 7795// Dot-boundary suffix match: prevents "evilexample.com" from matching 7796// an entry of "example.com". 7797if ( target . hostname . size () > e . hostname_pattern . size () + 1 ) { 7798auto offset = target . hostname . size () - e . hostname_pattern . size (); 7799if ( target . hostname [ offset - 1 ] == '.' && 7800target . hostname . compare ( offset , e . hostname_pattern . size (), 7801e . hostname_pattern ) == 0 ) { 7802return true; 7803} 7804} 7805break; 7806} 7807} 7808return false; 7809} 7810 7811template < typename T > 7812bool check_and_write_headers ( Stream & strm , Headers & headers , 7813T header_writer , Error & error ) { 7814for (const auto & h : headers ) { 7815if (!detail::fields:: is_field_valid ( h . first , h . second )) { 7816error = Error :: InvalidHeaders ; 7817return false; 7818} 7819} 7820if ( header_writer ( strm , headers ) <= 0 ) { 7821error = Error :: Write ; 7822return false; 7823} 7824return true; 7825} 7826 7827} // namespace detail 7828 7829/* 7830* Group 2 (continued): detail namespace - SSLSocketStream implementation 7831*/ 7832 7833#ifdef CPPHTTPLIB_SSL_ENABLED 7834namespace detail { 7835 7836// SSL socket stream implementation 7837SSLSocketStream :: SSLSocketStream ( 7838socket_t sock , tls:: session_t session , time_t read_timeout_sec , 7839time_t read_timeout_usec , time_t write_timeout_sec , 7840time_t write_timeout_usec , time_t max_timeout_msec , 7841std::chrono:: time_point < std::chrono:: steady_clock > start_time ) 7842: sock_ ( sock ), session_ ( session ), read_timeout_sec_ ( read_timeout_sec ), 7843read_timeout_usec_ ( read_timeout_usec ), 7844write_timeout_sec_ ( write_timeout_sec ), 7845write_timeout_usec_ ( write_timeout_usec ), 7846max_timeout_msec_ ( max_timeout_msec ), start_time_ ( start_time ) { 7847#ifdef CPPHTTPLIB_OPENSSL_SUPPORT 7848// Clear AUTO_RETRY for proper non-blocking I/O timeout handling 7849// Note: create_session() also clears this, but SSLClient currently 7850// uses ssl_new() which does not. Until full TLS API migration is complete, 7851// we need to ensure AUTO_RETRY is cleared here regardless of how the 7852// SSL session was created. 7853SSL_clear_mode ( static_cast < SSL *> ( session ), SSL_MODE_AUTO_RETRY ); 7854#endif 7855} 7856 7857SSLSocketStream ::~ SSLSocketStream () = default ; 7858 7859bool SSLSocketStream :: is_readable () const { 7860return tls:: pending ( session_ ) > 0 ; 7861} 7862 7863bool SSLSocketStream :: wait_readable () const { 7864if ( max_timeout_msec_ <= 0 ) { 7865return select_read ( sock_ , read_timeout_sec_ , read_timeout_usec_ ) > 0 ; 7866} 7867 7868time_t read_timeout_sec ; 7869time_t read_timeout_usec ; 7870calc_actual_timeout ( max_timeout_msec_ , duration (), read_timeout_sec_ , 7871read_timeout_usec_ , read_timeout_sec , read_timeout_usec ); 7872 7873return select_read ( sock_ , read_timeout_sec , read_timeout_usec ) > 0 ; 7874} 7875 7876bool SSLSocketStream :: wait_writable () const { 7877return select_write ( sock_ , write_timeout_sec_ , write_timeout_usec_ ) > 0 && 7878!tls:: is_peer_closed ( session_ , sock_ ); 7879} 7880 7881bool SSLSocketStream :: ensure_readable () { 7882if ( readable_hint_ ) { 7883readable_hint_ = false; 7884return true; 7885} 7886return wait_readable (); 7887} 7888 7889bool SSLSocketStream :: is_peer_alive () const { 7890return !tls:: is_peer_closed ( session_ , sock_ ); 7891} 7892 7893ssize_t SSLSocketStream :: read ( char * ptr , size_t size ) { 7894if (tls:: pending ( session_ ) > 0 ) { 7895tls:: TlsError err ; 7896auto ret = tls:: read ( session_ , ptr , size , err ); 7897if ( ret == 0 || err . code == tls:: ErrorCode :: PeerClosed ) { 7898error_ = Error :: ConnectionClosed ; 7899} 7900return ret ; 7901} else if ( ensure_readable ()) { 7902tls:: TlsError err ; 7903auto ret = tls:: read ( session_ , ptr , size , err ); 7904if ( ret < 0 ) { 7905auto n = 1000 ; 7906#ifdef _WIN32 7907while ( -- n >= 0 && ( err . code == tls:: ErrorCode :: WantRead || 7908( err . code == tls:: ErrorCode :: SyscallError && 7909WSAGetLastError () == WSAETIMEDOUT ))) { 7910#else 7911while ( -- n >= 0 && err . code == tls:: ErrorCode :: WantRead ) { 7912#endif 7913if (tls:: pending ( session_ ) > 0 ) { 7914return tls:: read ( session_ , ptr , size , err ); 7915} else if ( wait_readable ()) { 7916std::this_thread:: sleep_for (std::chrono:: microseconds { 10 }); 7917ret = tls:: read ( session_ , ptr , size , err ); 7918if ( ret >= 0 ) { return ret ; } 7919} else { 7920break; 7921} 7922} 7923assert ( ret < 0 ); 7924} else if ( ret == 0 || err . code == tls:: ErrorCode :: PeerClosed ) { 7925error_ = Error :: ConnectionClosed ; 7926} 7927return ret ; 7928} else { 7929error_ = Error :: Timeout ; 7930return -1 ; 7931} 7932} 7933 7934ssize_t SSLSocketStream :: write (const char * ptr , size_t size ) { 7935if ( wait_writable ()) { 7936auto handle_size = 7937std:: min < size_t > ( size , (std:: numeric_limits < int > :: max )()); 7938 7939tls:: TlsError err ; 7940auto ret = tls:: write ( session_ , ptr , handle_size , err ); 7941if ( ret < 0 ) { 7942auto n = 1000 ; 7943#ifdef _WIN32 7944while ( -- n >= 0 && ( err . code == tls:: ErrorCode :: WantWrite || 7945( err . code == tls:: ErrorCode :: SyscallError && 7946WSAGetLastError () == WSAETIMEDOUT ))) { 7947#else 7948while ( -- n >= 0 && err . code == tls:: ErrorCode :: WantWrite ) { 7949#endif 7950if ( wait_writable ()) { 7951std::this_thread:: sleep_for (std::chrono:: microseconds { 10 }); 7952ret = tls:: write ( session_ , ptr , handle_size , err ); 7953if ( ret >= 0 ) { return ret ; } 7954} else { 7955break; 7956} 7957} 7958assert ( ret < 0 ); 7959} 7960return ret ; 7961} 7962return -1 ; 7963} 7964 7965void SSLSocketStream :: get_remote_ip_and_port (std:: string & ip , 7966int & port ) const { 7967detail:: get_remote_ip_and_port ( sock_ , ip , port ); 7968} 7969 7970void SSLSocketStream :: get_local_ip_and_port (std:: string & ip , 7971int & port ) const { 7972detail:: get_local_ip_and_port ( sock_ , ip , port ); 7973} 7974 7975socket_t SSLSocketStream :: socket () const { return sock_ ; } 7976 7977time_t SSLSocketStream :: duration () const { 7978return std::chrono:: duration_cast < std::chrono:: milliseconds > ( 7979std::chrono::steady_clock:: now () - start_time_ ) 7980. count (); 7981} 7982 7983void SSLSocketStream :: set_read_timeout ( time_t sec , time_t usec ) { 7984read_timeout_sec_ = sec ; 7985read_timeout_usec_ = usec ; 7986} 7987 7988WebSocketSSLStream :: WebSocketSSLStream ( socket_t sock , 7989tls:: session_t session , 7990time_t read_timeout_sec , 7991time_t read_timeout_usec , 7992time_t write_timeout_sec , 7993time_t write_timeout_usec ) 7994: sock_ ( sock ), session_ ( session ), read_timeout_sec_ ( read_timeout_sec ), 7995read_timeout_usec_ ( read_timeout_usec ), 7996write_timeout_sec_ ( write_timeout_sec ), 7997write_timeout_usec_ ( write_timeout_usec ), 7998start_time_ (std::chrono::steady_clock:: now ()) { 7999// The receive and send paths run on different threads, so each TLS call is 8000// driven in non-blocking mode and readiness is awaited with select() 8001// outside the session lock. Set the socket non-blocking once here; it is 8002// never flipped back, so no thread races on the flag. 8003detail:: set_nonblocking ( sock_ , true); 8004#ifdef CPPHTTPLIB_OPENSSL_SUPPORT 8005SSL_clear_mode ( static_cast < SSL *> ( session_ ), SSL_MODE_AUTO_RETRY ); 8006#endif 8007} 8008 8009WebSocketSSLStream ::~ WebSocketSSLStream () = default ; 8010 8011bool WebSocketSSLStream :: is_readable () const { 8012std:: lock_guard < std:: mutex > guard ( session_mutex_ ); 8013return tls:: pending ( session_ ) > 0 ; 8014} 8015 8016bool WebSocketSSLStream :: wait_readable () const { 8017return select_read ( sock_ , read_timeout_sec_ , read_timeout_usec_ ) > 0 ; 8018} 8019 8020bool WebSocketSSLStream :: wait_writable () const { 8021// Unlike SSLSocketStream, this deliberately does not call is_peer_closed(): 8022// that probe toggles the socket's blocking flag, which would race with the 8023// concurrent reader on a permanently non-blocking socket. 8024return select_write ( sock_ , write_timeout_sec_ , write_timeout_usec_ ) > 0 ; 8025} 8026 8027ssize_t WebSocketSSLStream :: read ( char * ptr , size_t size ) { 8028tls:: TlsError err ; 8029auto n = 1000 ; 8030while ( -- n >= 0 ) { 8031{ 8032std:: lock_guard < std:: mutex > guard ( session_mutex_ ); 8033auto ret = tls:: read ( session_ , ptr , size , err ); 8034if ( ret > 0 ) { return ret ; } 8035if ( ret == 0 || err . code == tls:: ErrorCode :: PeerClosed ) { 8036error_ = Error :: ConnectionClosed ; 8037return ret ; 8038} 8039} 8040// ret < 0. On a non-blocking socket a TLS read can stop needing either 8041// direction: the send path shares this session, so output it left pending 8042// has to be flushed before more input can be decrypted. Anything else is 8043// a hard error. 8044auto needs_readable = err . code == tls:: ErrorCode :: WantRead ; 8045#ifdef _WIN32 8046// On Windows a socket timeout surfaces as a syscall error, not WantRead. 8047needs_readable = 8048needs_readable || ( err . code == tls:: ErrorCode :: SyscallError && 8049WSAGetLastError () == WSAETIMEDOUT ); 8050#endif 8051if (! needs_readable && err . code != tls:: ErrorCode :: WantWrite ) { 8052error_ = Error :: Read ; 8053return -1 ; 8054} 8055if (!( needs_readable ? wait_readable () : wait_writable ())) { 8056error_ = Error :: Timeout ; 8057return -1 ; 8058} 8059} 8060// Out of retries. Recording a reason matters: a caller that reads get_error() 8061// to tell a timeout from a close would otherwise see whatever the previous 8062// failure left behind (error_ is never cleared on success). 8063error_ = Error :: Read ; 8064return -1 ; 8065} 8066 8067ssize_t WebSocketSSLStream :: write (const char * ptr , size_t size ) { 8068auto handle_size = std:: min < size_t > ( size , (std:: numeric_limits < int > :: max )()); 8069tls:: TlsError err ; 8070auto n = 1000 ; 8071while ( -- n >= 0 ) { 8072{ 8073std:: lock_guard < std:: mutex > guard ( session_mutex_ ); 8074auto ret = tls:: write ( session_ , ptr , handle_size , err ); 8075if ( ret >= 0 ) { return ret ; } 8076} 8077// ret < 0. As in read(), either direction can be needed: a renegotiation 8078// or a post-handshake message must be consumed before the record goes 8079// out. Anything else is a hard error. 8080auto needs_writable = err . code == tls:: ErrorCode :: WantWrite ; 8081#ifdef _WIN32 8082// On Windows a socket timeout surfaces as a syscall error, not WantWrite. 8083needs_writable = 8084needs_writable || ( err . code == tls:: ErrorCode :: SyscallError && 8085WSAGetLastError () == WSAETIMEDOUT ); 8086#endif 8087if (! needs_writable && err . code != tls:: ErrorCode :: WantRead ) { return -1 ; } 8088if (!( needs_writable ? wait_writable () : wait_readable ())) { return -1 ; } 8089} 8090return -1 ; 8091} 8092 8093void WebSocketSSLStream :: get_remote_ip_and_port (std:: string & ip , 8094int & port ) const { 8095detail:: get_remote_ip_and_port ( sock_ , ip , port ); 8096} 8097 8098void WebSocketSSLStream :: get_local_ip_and_port (std:: string & ip , 8099int & port ) const { 8100detail:: get_local_ip_and_port ( sock_ , ip , port ); 8101} 8102 8103socket_t WebSocketSSLStream :: socket () const { return sock_ ; } 8104 8105time_t WebSocketSSLStream :: duration () const { 8106return std::chrono:: duration_cast < std::chrono:: milliseconds > ( 8107std::chrono::steady_clock:: now () - start_time_ ) 8108. count (); 8109} 8110 8111void WebSocketSSLStream :: set_read_timeout ( time_t sec , time_t usec ) { 8112read_timeout_sec_ = sec ; 8113read_timeout_usec_ = usec ; 8114} 8115 8116} // namespace detail 8117#endif // CPPHTTPLIB_SSL_ENABLED 8118 8119/* 8120* Group 4: Server implementation 8121*/ 8122 8123// HTTP server implementation 8124Server :: Server () 8125: new_task_queue ([] { 8126return new ThreadPool ( CPPHTTPLIB_THREAD_POOL_COUNT , 8127CPPHTTPLIB_THREAD_POOL_MAX_COUNT ); 8128}) { 8129#ifndef _WIN32 8130signal ( SIGPIPE , SIG_IGN ); 8131#endif 8132} 8133 8134Server ::~ Server () = default ; 8135 8136std:: unique_ptr < detail:: MatcherBase > 8137Server :: make_matcher (const std:: string & pattern ) { 8138// Path params take precedence, so "/users/:id/(.*)" keeps being matched as 8139// a path params pattern 8140if ( pattern . find ( "/:" ) != std::string:: npos ) { 8141return detail:: make_unique < detail:: PathParamsMatcher > ( pattern ); 8142} 8143 8144// A pattern with no regex metacharacter only has to be compared literally, 8145// which is what PathParamsMatcher already does when it captures no 8146// parameter, so std::regex is only worth building for the patterns that 8147// actually need it 8148if ( pattern . find_first_of ( ".^$|()[]{}*+?\\" ) == std::string:: npos ) { 8149return detail:: make_unique < detail:: PathParamsMatcher > ( pattern ); 8150} 8151 8152return detail:: make_unique < detail:: RegexMatcher > ( pattern ); 8153} 8154 8155Server & Server :: Get (const std:: string & pattern , Handler handler ) { 8156return add_handler ( get_handlers_ , pattern , std:: move ( handler )); 8157} 8158 8159Server & Server :: Post (const std:: string & pattern , Handler handler ) { 8160return add_handler ( post_handlers_ , pattern , std:: move ( handler )); 8161} 8162 8163Server & Server :: Post (const std:: string & pattern , 8164HandlerWithContentReader handler ) { 8165return add_handler ( post_handlers_for_content_reader_ , pattern , 8166std:: move ( handler )); 8167} 8168 8169Server & Server :: Put (const std:: string & pattern , Handler handler ) { 8170return add_handler ( put_handlers_ , pattern , std:: move ( handler )); 8171} 8172 8173Server & Server :: Put (const std:: string & pattern , 8174HandlerWithContentReader handler ) { 8175return add_handler ( put_handlers_for_content_reader_ , pattern , 8176std:: move ( handler )); 8177} 8178 8179Server & Server :: Patch (const std:: string & pattern , Handler handler ) { 8180return add_handler ( patch_handlers_ , pattern , std:: move ( handler )); 8181} 8182 8183Server & Server :: Patch (const std:: string & pattern , 8184HandlerWithContentReader handler ) { 8185return add_handler ( patch_handlers_for_content_reader_ , pattern , 8186std:: move ( handler )); 8187} 8188 8189Server & Server :: Delete (const std:: string & pattern , Handler handler ) { 8190return add_handler ( delete_handlers_ , pattern , std:: move ( handler )); 8191} 8192 8193Server & Server :: Delete (const std:: string & pattern , 8194HandlerWithContentReader handler ) { 8195return add_handler ( delete_handlers_for_content_reader_ , pattern , 8196std:: move ( handler )); 8197} 8198 8199Server & Server :: Options (const std:: string & pattern , Handler handler ) { 8200return add_handler ( options_handlers_ , pattern , std:: move ( handler )); 8201} 8202 8203const std:: set < std:: string > & Server :: builtin_methods () { 8204thread_local const std:: set < std:: string > methods { 8205"GET" , "HEAD" , "POST" , "PUT" , "DELETE" , 8206"CONNECT" , "OPTIONS" , "TRACE" , "PATCH" , "PRI" }; 8207return methods ; 8208} 8209 8210Server :: CustomHandlerEntry * 8211Server :: custom_entry_for_registration (const std:: string & method ) { 8212// Built-in methods are refused for two different reasons. GET, HEAD, POST, 8213// PUT, DELETE, OPTIONS and PATCH are dispatched by the if/else chain in 8214// routing() before the custom tables are consulted, so a route registered 8215// for one of them could never fire. CONNECT, TRACE and PRI have no branch 8216// there and would be reachable, but they carry protocol-level meaning 8217// (tunnel setup, request echo, the HTTP/2 connection preface) that this 8218// library does not route. 8219if (!detail::fields:: is_token ( method ) || builtin_methods (). count ( method )) { 8220output_error_log ( Error :: InvalidHTTPMethod , nullptr ); 8221has_invalid_registration_ = true; 8222return nullptr ; 8223} 8224return & custom_handlers_ [ method ]; 8225} 8226 8227Server & Server :: CustomRoute (const std:: string & method , 8228const std:: string & pattern , 8229Handler handler ) { 8230auto * entry = custom_entry_for_registration ( method ); 8231if (! entry ) { return * this ; } 8232return add_handler ( entry -> handlers , pattern , std:: move ( handler )); 8233} 8234 8235Server & Server :: CustomRoute (const std:: string & method , 8236const std:: string & pattern , 8237HandlerWithContentReader handler ) { 8238auto * entry = custom_entry_for_registration ( method ); 8239if (! entry ) { return * this ; } 8240return add_handler ( entry -> handlers_for_content_reader , pattern , 8241std:: move ( handler )); 8242} 8243 8244const Server :: CustomHandlerEntry * 8245Server :: find_custom_entry (const std:: string & method ) const { 8246// find() alone would be correct here. The empty() check is what keeps the 8247// per-request cost off servers that never call CustomRoute(), which is the 8248// overwhelmingly common case; keep it rather than walking into the tree. 8249if ( custom_handlers_ . empty ()) { return nullptr ; } 8250auto it = custom_handlers_ . find ( method ); 8251return it == custom_handlers_ . end () ? nullptr : & it -> second ; 8252} 8253 8254Server & Server :: WebSocket (const std:: string & pattern , 8255WebSocketHandler handler ) { 8256websocket_handlers_ . push_back ( 8257{ make_matcher ( pattern ), std:: move ( handler ), nullptr }); 8258return * this ; 8259} 8260 8261Server & Server :: WebSocket (const std:: string & pattern , 8262WebSocketHandler handler , 8263SubProtocolSelector sub_protocol_selector ) { 8264websocket_handlers_ . push_back ({ make_matcher ( pattern ), std:: move ( handler ), 8265std:: move ( sub_protocol_selector )}); 8266return * this ; 8267} 8268 8269bool Server :: set_base_dir (const std:: string & dir , 8270const std:: string & mount_point ) { 8271return set_mount_point ( mount_point , dir ); 8272} 8273 8274bool Server :: set_mount_point (const std:: string & mount_point , 8275const std:: string & dir , Headers headers ) { 8276detail:: FileStat stat ( dir ); 8277if ( stat . is_dir ()) { 8278std:: string mnt = ! mount_point . empty () ? mount_point : "/" ; 8279if (! mnt . empty () && mnt [ 0 ] == '/' ) { 8280std:: string resolved_base ; 8281if (detail:: canonicalize_path ( dir . c_str (), resolved_base )) { 8282#if defined( _WIN32 ) 8283if ( resolved_base . back () != '\\' && resolved_base . back () != '/' ) { 8284resolved_base += '\\' ; 8285} 8286#else 8287if ( resolved_base . back () != '/' ) { resolved_base += '/' ; } 8288#endif 8289} 8290base_dirs_ . push_back ( 8291{std:: move ( mnt ), dir , std:: move ( resolved_base ), std:: move ( headers )}); 8292return true; 8293} 8294} 8295return false; 8296} 8297 8298bool Server :: remove_mount_point (const std:: string & mount_point ) { 8299for ( auto it = base_dirs_ . begin (); it != base_dirs_ . end (); ++ it ) { 8300if ( it -> mount_point == mount_point ) { 8301base_dirs_ . erase ( it ); 8302return true; 8303} 8304} 8305return false; 8306} 8307 8308Server & 8309Server :: set_file_extension_and_mimetype_mapping (const std:: string & ext , 8310const std:: string & mime ) { 8311file_extension_and_mimetype_map_ [ ext ] = mime ; 8312return * this ; 8313} 8314 8315Server & Server :: set_default_file_mimetype (const std:: string & mime ) { 8316default_file_mimetype_ = mime ; 8317return * this ; 8318} 8319 8320Server & Server :: set_file_request_handler ( Handler handler ) { 8321file_request_handler_ = std:: move ( handler ); 8322return * this ; 8323} 8324 8325Server & Server :: set_error_handler_core ( HandlerWithResponse handler , 8326std:: true_type ) { 8327error_handler_ = std:: move ( handler ); 8328return * this ; 8329} 8330 8331Server & Server :: set_error_handler_core ( Handler handler , 8332std:: false_type ) { 8333error_handler_ = [ handler ](const Request & req , Response & res ) { 8334handler ( req , res ); 8335return HandlerResponse :: Handled ; 8336}; 8337return * this ; 8338} 8339 8340Server & Server :: set_exception_handler ( ExceptionHandler handler ) { 8341exception_handler_ = std:: move ( handler ); 8342return * this ; 8343} 8344 8345Server & Server :: set_pre_routing_handler ( HandlerWithResponse handler ) { 8346pre_routing_handler_ = std:: move ( handler ); 8347return * this ; 8348} 8349 8350Server & Server :: set_post_routing_handler ( Handler handler ) { 8351post_routing_handler_ = std:: move ( handler ); 8352return * this ; 8353} 8354 8355Server & Server :: set_pre_request_handler ( HandlerWithResponse handler ) { 8356pre_request_handler_ = std:: move ( handler ); 8357return * this ; 8358} 8359 8360Server & Server :: set_logger ( Logger logger ) { 8361logger_ = std:: move ( logger ); 8362return * this ; 8363} 8364 8365Server & Server :: set_error_logger ( ErrorLogger error_logger ) { 8366error_logger_ = std:: move ( error_logger ); 8367return * this ; 8368} 8369 8370Server & Server :: set_pre_compression_logger ( Logger logger ) { 8371pre_compression_logger_ = std:: move ( logger ); 8372return * this ; 8373} 8374 8375Server & 8376Server :: set_expect_100_continue_handler ( Expect100ContinueHandler handler ) { 8377expect_100_continue_handler_ = std:: move ( handler ); 8378return * this ; 8379} 8380 8381Server & Server :: set_start_handler ( StartHandler handler ) { 8382start_handler_ = std:: move ( handler ); 8383return * this ; 8384} 8385 8386Server & Server :: set_address_family ( int family ) { 8387address_family_ = family ; 8388return * this ; 8389} 8390 8391Server & Server :: set_tcp_nodelay ( bool on ) { 8392tcp_nodelay_ = on ; 8393return * this ; 8394} 8395 8396Server & Server :: set_ipv6_v6only ( bool on ) { 8397ipv6_v6only_ = on ; 8398return * this ; 8399} 8400 8401Server & Server :: set_socket_options ( SocketOptions socket_options ) { 8402socket_options_ = std:: move ( socket_options ); 8403return * this ; 8404} 8405 8406Server & Server :: set_default_headers ( Headers headers ) { 8407default_headers_ = std:: move ( headers ); 8408return * this ; 8409} 8410 8411Server & Server :: set_header_writer ( 8412std:: function < ssize_t ( Stream & , Headers & ) > const & writer ) { 8413header_writer_ = writer ; 8414return * this ; 8415} 8416 8417Server & 8418Server :: set_trusted_proxies (const std:: vector < std:: string > & proxies ) { 8419trusted_proxies_ = proxies ; 8420return * this ; 8421} 8422 8423Server & Server :: set_keep_alive_max_count ( size_t count ) { 8424keep_alive_max_count_ = count ; 8425return * this ; 8426} 8427 8428Server & Server :: set_keep_alive_timeout ( time_t sec ) { 8429keep_alive_timeout_sec_ = sec ; 8430return * this ; 8431} 8432 8433template < class Rep , class Period > 8434Server & Server :: set_keep_alive_timeout ( 8435const std::chrono:: duration < Rep , Period > & duration ) { 8436detail:: duration_to_sec_and_usec ( duration , [ & ]( time_t sec , time_t /*usec*/ ) { 8437set_keep_alive_timeout ( sec ); 8438}); 8439return * this ; 8440} 8441 8442Server & Server :: set_read_timeout ( time_t sec , time_t usec ) { 8443read_timeout_sec_ = sec ; 8444read_timeout_usec_ = usec ; 8445return * this ; 8446} 8447 8448Server & Server :: set_write_timeout ( time_t sec , time_t usec ) { 8449write_timeout_sec_ = sec ; 8450write_timeout_usec_ = usec ; 8451return * this ; 8452} 8453 8454Server & Server :: set_idle_interval ( time_t sec , time_t usec ) { 8455idle_interval_sec_ = sec ; 8456idle_interval_usec_ = usec ; 8457return * this ; 8458} 8459 8460Server & Server :: set_payload_max_length ( size_t length ) { 8461payload_max_length_ = length ; 8462return * this ; 8463} 8464 8465Server & Server :: set_static_file_compression ( bool on ) { 8466static_file_compression_ = on ; 8467return * this ; 8468} 8469 8470Server & Server :: set_static_file_compression_min_length ( size_t length ) { 8471static_file_compression_min_length_ = length ; 8472return * this ; 8473} 8474 8475Server & Server :: set_static_file_compression_max_length ( size_t length ) { 8476static_file_compression_max_length_ = length ; 8477return * this ; 8478} 8479 8480Server & Server :: set_websocket_max_missed_pongs ( int count ) { 8481websocket_max_missed_pongs_ = count ; 8482return * this ; 8483} 8484 8485Server & Server :: set_websocket_ping_interval ( time_t sec ) { 8486websocket_ping_interval_sec_ = sec ; 8487return * this ; 8488} 8489 8490template < class Rep , class Period > 8491Server & Server :: set_websocket_ping_interval ( 8492const std::chrono:: duration < Rep , Period > & duration ) { 8493detail:: duration_to_sec_and_usec ( duration , [ & ]( time_t sec , time_t /*usec*/ ) { 8494set_websocket_ping_interval ( sec ); 8495}); 8496return * this ; 8497} 8498 8499bool Server :: bind_to_port (const std:: string & host , int port , 8500int socket_flags ) { 8501auto ret = bind_internal ( host , port , socket_flags ); 8502if ( ret == -1 ) { is_decommissioned = true; } 8503return ret >= 0 ; 8504} 8505int Server :: bind_to_any_port (const std:: string & host , int socket_flags ) { 8506auto ret = bind_internal ( host , 0 , socket_flags ); 8507if ( ret == -1 ) { is_decommissioned = true; } 8508return ret ; 8509} 8510 8511bool Server :: listen_after_bind () { return listen_internal (); } 8512 8513bool Server :: listen (const std:: string & host , int port , 8514int socket_flags ) { 8515return bind_to_port ( host , port , socket_flags ) && listen_internal (); 8516} 8517 8518bool Server :: is_running () const { return is_running_ ; } 8519 8520void Server :: wait_until_ready () const { 8521while (! is_running_ && ! is_decommissioned ) { 8522std::this_thread:: sleep_for (std::chrono:: milliseconds { 1 }); 8523} 8524} 8525 8526void Server :: stop () noexcept { 8527// Release the listening socket whether or not the accept loop is running: 8528// bind_to_port() without listen_after_bind() still owns the descriptor. The 8529// exchange is what makes this safe to call concurrently with the accept loop. 8530socket_t sock = svr_sock_ . exchange ( INVALID_SOCKET ); 8531if ( sock != INVALID_SOCKET ) { 8532detail:: shutdown_socket ( sock ); 8533detail:: close_socket ( sock ); 8534} 8535is_decommissioned = false; 8536} 8537 8538void Server :: decommission () { is_decommissioned = true; } 8539 8540bool Server :: parse_request_line (const char * s , Request & req ) const { 8541auto len = strlen ( s ); 8542if ( len < 2 || s [ len - 2 ] != '\r' || s [ len - 1 ] != '\n' ) { return false; } 8543len -= 2 ; 8544 8545{ 8546size_t count = 0 ; 8547 8548detail:: split ( s , s + len , ' ' , [ & ](const char * b , const char * e ) { 8549switch ( count ) { 8550case 0 : req . method = std:: string ( b , e ); break; 8551case 1 : req . target = std:: string ( b , e ); break; 8552case 2 : req . version = std:: string ( b , e ); break; 8553default: break; 8554} 8555count ++ ; 8556}); 8557 8558if ( count != 3 ) { return false; } 8559} 8560 8561// A method outside the built-in set is accepted only when a handler has been 8562// registered for it with CustomRoute(). 8563const auto & methods = builtin_methods (); 8564 8565if ( methods . find ( req . method ) == methods . end () && 8566! find_custom_entry ( req . method )) { 8567output_error_log ( Error :: InvalidHTTPMethod , & req ); 8568return false; 8569} 8570 8571if ( req . version != "HTTP/1.1" && req . version != "HTTP/1.0" ) { 8572output_error_log ( Error :: InvalidHTTPVersion , & req ); 8573return false; 8574} 8575 8576{ 8577// Skip URL fragment 8578for ( size_t i = 0 ; i < req . target . size (); i ++ ) { 8579if ( req . target [ i ] == '#' ) { 8580req . target . erase ( i ); 8581break; 8582} 8583} 8584 8585detail:: divide ( req . target , '?' , 8586[ & ](const char * lhs_data , std:: size_t lhs_size , 8587const char * rhs_data , std:: size_t rhs_size ) { 8588req . path = 8589decode_path_component (std:: string ( lhs_data , lhs_size )); 8590detail:: parse_query_text ( rhs_data , rhs_size , req . params ); 8591}); 8592} 8593 8594return true; 8595} 8596 8597bool Server :: write_response ( Stream & strm , bool close_connection , 8598Request & req , Response & res ) { 8599// NOTE: `req.ranges` should be empty, otherwise it will be applied 8600// incorrectly to the error content. 8601req . ranges . clear (); 8602return write_response_core ( strm , close_connection , req , res , false); 8603} 8604 8605bool Server :: write_response_with_content ( Stream & strm , 8606bool close_connection , 8607const Request & req , 8608Response & res ) { 8609return write_response_core ( strm , close_connection , req , res , true); 8610} 8611 8612bool Server :: write_response_core ( Stream & strm , bool close_connection , 8613const Request & req , Response & res , 8614bool need_apply_ranges ) { 8615assert ( res . status != -1 ); 8616 8617if ( 400 <= res . status && error_handler_ && 8618error_handler_ ( req , res ) == HandlerResponse :: Handled ) { 8619need_apply_ranges = true; 8620} 8621 8622std:: string content_type ; 8623std:: string boundary ; 8624if ( need_apply_ranges ) { apply_ranges ( req , res , content_type , boundary ); } 8625 8626// Prepare additional headers 8627if ( close_connection || 8628detail:: has_header_token ( req . headers , "Connection" , "close" ) || 8629400 <= res . status ) { // Don't leave connections open after errors 8630res . set_header ( "Connection" , "close" ); 8631} else { 8632std:: string s = "timeout=" ; 8633s += std:: to_string ( keep_alive_timeout_sec_ ); 8634s += ", max=" ; 8635s += std:: to_string ( keep_alive_max_count_ ); 8636res . set_header ( "Keep-Alive" , s ); 8637} 8638 8639if ((! res . body . empty () || res . content_length_ > 0 || res . content_provider_ ) && 8640! res . has_header ( "Content-Type" )) { 8641res . set_header ( "Content-Type" , "text/plain" ); 8642} 8643 8644if ( res . body . empty () && ! res . content_length_ && ! res . content_provider_ && 8645! res . has_header ( "Content-Length" )) { 8646res . set_header ( "Content-Length" , "0" ); 8647} 8648 8649if ( req . method == "HEAD" && ! res . has_header ( "Accept-Ranges" )) { 8650res . set_header ( "Accept-Ranges" , "bytes" ); 8651} 8652 8653if ( post_routing_handler_ ) { post_routing_handler_ ( req , res ); } 8654 8655// Response line and headers 8656detail:: BufferStream bstrm ; 8657if (!detail:: write_response_line ( bstrm , res . status )) { return false; } 8658if ( header_writer_ ( bstrm , res . headers ) <= 0 ) { return false; } 8659 8660// Combine small body with headers to reduce write syscalls 8661if ( req . method != "HEAD" && ! res . body . empty () && ! res . content_provider_ ) { 8662bstrm . write ( res . body . data (), res . body . size ()); 8663} 8664 8665// Log before writing to avoid race condition with client-side code that 8666// accesses logger-captured data immediately after receiving the response. 8667output_log ( req , res ); 8668 8669// Flush buffer 8670auto & data = bstrm . get_buffer (); 8671if (!detail:: write_data ( strm , data . data (), data . size ())) { return false; } 8672 8673// Streaming body 8674auto ret = true; 8675if ( req . method != "HEAD" && res . content_provider_ ) { 8676if ( write_content_with_provider ( strm , req , res , boundary , content_type )) { 8677res . content_provider_success_ = true; 8678} else { 8679ret = false; 8680} 8681} 8682 8683return ret ; 8684} 8685 8686bool 8687Server :: write_content_with_provider ( Stream & strm , const Request & req , 8688Response & res , const std:: string & boundary , 8689const std:: string & content_type ) { 8690auto is_shutting_down = [ this ]() { 8691return this -> svr_sock_ == INVALID_SOCKET ; 8692}; 8693 8694if ( res . content_length_ > 0 ) { 8695// Only a 206 response is served as a partial representation, matching the 8696// condition `apply_ranges()` used to decide the Content-Length and the 8697// multipart boundary. Since `detail::range_error()` validates `req.ranges` 8698// only for a 2xx status, slicing under any other status would write a body 8699// that disagrees with the header already sent, from an unchecked offset. 8700auto is_partial = 8701! req . ranges . empty () && res . status == StatusCode :: PartialContent_206 ; 8702 8703if (! is_partial ) { 8704return detail:: write_content ( strm , res . content_provider_ , 0 , 8705res . content_length_ , is_shutting_down ); 8706} else if ( req . ranges . size () == 1 ) { 8707auto offset_and_length = detail:: get_range_offset_and_length ( 8708req . ranges [ 0 ], res . content_length_ ); 8709 8710return detail:: write_content ( strm , res . content_provider_ , 8711offset_and_length . first , 8712offset_and_length . second , is_shutting_down ); 8713} else { 8714return detail:: write_multipart_ranges_data ( 8715strm , req , res , boundary , content_type , res . content_length_ , 8716is_shutting_down ); 8717} 8718} else { 8719if ( res . is_chunked_content_provider_ ) { 8720// Use the coding `apply_ranges()` chose when it wrote the headers; 8721// re-negotiating here would disagree with them, e.g. once a handler's 8722// own Content-Encoding header suppresses the negotiation. 8723auto compressor = detail:: make_compressor ( res . content_coding_ ); 8724if (! compressor ) { 8725compressor = detail:: make_unique < detail:: nocompressor > (); 8726} 8727 8728return detail:: write_content_chunked ( strm , res . content_provider_ , 8729is_shutting_down , * compressor ); 8730} else { 8731return detail:: write_content_without_length ( strm , res . content_provider_ , 8732is_shutting_down ); 8733} 8734} 8735} 8736 8737bool Server :: read_content ( Stream & strm , Request & req , Response & res ) { 8738FormFields :: iterator cur_field ; 8739FormFiles :: iterator cur_file ; 8740auto is_text_field = false; 8741size_t count = 0 ; 8742if ( read_content_core ( 8743strm , req , res , 8744// Regular 8745[ & ](const char * buf , size_t n ) { 8746// Prevent arithmetic overflow when checking sizes. 8747// Avoid computing (req.body.size() + n) directly because 8748// adding two unsigned `size_t` values can wrap around and 8749// produce a small result instead of indicating overflow. 8750// Instead, check using subtraction: ensure `n` does not 8751// exceed the remaining capacity `max_size() - size()`. 8752if ( req . body . size () >= req . body . max_size () || 8753n > req . body . max_size () - req . body . size ()) { 8754return false; 8755} 8756 8757// Limit decompressed body size to payload_max_length_ to protect 8758// against "zip bomb" attacks where a small compressed payload 8759// decompresses to a massive size. 8760if ( payload_max_length_ > 0 && 8761( req . body . size () >= payload_max_length_ || 8762n > payload_max_length_ - req . body . size ())) { 8763return false; 8764} 8765 8766req . body . append ( buf , n ); 8767return true; 8768}, 8769// Multipart FormData 8770[ & ](const FormData & file ) { 8771if ( count ++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT ) { 8772output_error_log ( Error :: TooManyFormDataFiles , & req ); 8773return false; 8774} 8775 8776if ( file . filename . empty ()) { 8777cur_field = req . form . fields . emplace ( 8778file . name , FormField { file . name , file . content , file . headers }); 8779is_text_field = true; 8780} else { 8781cur_file = req . form . files . emplace ( file . name , file ); 8782is_text_field = false; 8783} 8784return true; 8785}, 8786[ & ](const char * buf , size_t n ) { 8787if ( is_text_field ) { 8788auto & content = cur_field -> second . content ; 8789if ( content . size () + n > content . max_size ()) { return false; } 8790content . append ( buf , n ); 8791} else { 8792auto & content = cur_file -> second . content ; 8793if ( content . size () + n > content . max_size ()) { return false; } 8794content . append ( buf , n ); 8795} 8796return true; 8797})) { 8798const auto & content_type = req . get_header_value ( "Content-Type" ); 8799if (detail:: extract_media_type ( content_type ) == 8800"application/x-www-form-urlencoded" ) { 8801if ( req . body . size () > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH ) { 8802res . status = StatusCode :: PayloadTooLarge_413 ; // NOTE: should be 414? 8803output_error_log ( Error :: ExceedMaxPayloadSize , & req ); 8804return false; 8805} 8806detail:: parse_query_text ( req . body , req . params ); 8807} 8808return true; 8809} 8810return false; 8811} 8812 8813bool Server :: read_content_with_content_receiver ( 8814Stream & strm , Request & req , Response & res , ContentReceiver receiver , 8815FormDataHeader multipart_header , ContentReceiver multipart_receiver ) { 8816return read_content_core ( strm , req , res , std:: move ( receiver ), 8817std:: move ( multipart_header ), 8818std:: move ( multipart_receiver )); 8819} 8820 8821bool Server :: read_content_core ( 8822Stream & strm , Request & req , Response & res , ContentReceiver receiver , 8823FormDataHeader multipart_header , ContentReceiver multipart_receiver ) const { 8824detail:: FormDataParser multipart_form_data_parser ; 8825ContentReceiverWithProgress out ; 8826 8827if ( req . is_multipart_form_data ()) { 8828const auto & content_type = req . get_header_value ( "Content-Type" ); 8829std:: string boundary ; 8830if (!detail:: parse_multipart_boundary ( content_type , boundary )) { 8831res . status = StatusCode :: BadRequest_400 ; 8832output_error_log ( Error :: MultipartParsing , & req ); 8833return false; 8834} 8835 8836multipart_form_data_parser . set_boundary (std:: move ( boundary )); 8837out = [ & ](const char * buf , size_t n , size_t /*off*/ , size_t /*len*/ ) { 8838return multipart_form_data_parser . parse ( buf , n , multipart_header , 8839multipart_receiver ); 8840}; 8841} else { 8842out = [ receiver ](const char * buf , size_t n , size_t /*off*/ , 8843size_t /*len*/ ) { return receiver ( buf , n ); }; 8844} 8845 8846// RFC 9112 §6: no Transfer-Encoding and no Content-Length means no body. 8847// For non-SSL builds we still scan non-persistent connections for stray 8848// body bytes so the payload limit is enforced (413). On keep-alive, 8849// pending bytes may be the next request (issue #2450), so skip. 8850#if !defined( CPPHTTPLIB_SSL_ENABLED ) 8851if (! req . has_header ( "Content-Length" ) && 8852!detail:: is_chunked_transfer_encoding ( req . headers )) { 8853if (!detail:: is_connection_persistent ( req ) && payload_max_length_ > 0 && 8854payload_max_length_ < (std:: numeric_limits < size_t > :: max )()) { 8855auto has_data = strm . is_readable (); 8856if (! has_data ) { 8857auto s = strm . socket (); 8858if ( s != INVALID_SOCKET ) { 8859has_data = detail:: select_read ( s , 0 , 0 ) > 0 ; 8860} 8861} 8862if ( has_data ) { 8863// Route through the same decompressing reader used by the 8864// length-framed and chunked paths below, so payload_max_length_ is 8865// enforced on the decompressed size here too instead of only on the 8866// compressed wire bytes. 8867return detail:: read_content ( strm , req , payload_max_length_ , res . status , 8868nullptr , out , true); 8869} 8870} 8871return true; 8872} 8873#else 8874if (! req . has_header ( "Content-Length" ) && 8875!detail:: is_chunked_transfer_encoding ( req . headers )) { 8876return true; 8877} 8878#endif 8879 8880if (!detail:: read_content ( strm , req , payload_max_length_ , res . status , nullptr , 8881out , true)) { 8882return false; 8883} 8884 8885req . body_consumed_ = true; 8886 8887if ( req . is_multipart_form_data ()) { 8888if (! multipart_form_data_parser . is_valid ()) { 8889res . status = StatusCode :: BadRequest_400 ; 8890output_error_log ( Error :: MultipartParsing , & req ); 8891return false; 8892} 8893} 8894 8895return true; 8896} 8897 8898bool Server :: handle_file_request ( Request & req , Response & res ) { 8899for (const auto & entry : base_dirs_ ) { 8900// Prefix match, on a path segment boundary. A mount point of "/mount" 8901// covers "/mount" and "/mount/...", but must not swallow "/mountdir/...". 8902// One that already ends in '/' (the root mount among them) carries its own 8903// boundary; set_mount_point() guarantees the mount point is not empty. 8904if (! req . path . compare ( 0 , entry . mount_point . size (), entry . mount_point ) && 8905( entry . mount_point . back () == '/' || 8906req . path . size () == entry . mount_point . size () || 8907req . path [ entry . mount_point . size ()] == '/' )) { 8908std:: string sub_path = "/" + req . path . substr ( entry . mount_point . size ()); 8909if (detail:: is_valid_path ( sub_path )) { 8910auto path = entry . base_dir + sub_path ; 8911if ( path . back () == '/' ) { path += "index.html" ; } 8912 8913// Defense-in-depth: is_valid_path blocks ".." traversal in the URL, 8914// but symlinks/junctions can still escape the base directory. 8915if (! entry . resolved_base_dir . empty ()) { 8916std:: string resolved_path ; 8917if (detail:: canonicalize_path ( path . c_str (), resolved_path ) && 8918!detail:: is_path_within_base ( resolved_path , 8919entry . resolved_base_dir )) { 8920res . status = StatusCode :: Forbidden_403 ; 8921return true; 8922} 8923} 8924 8925detail:: FileStat stat ( path ); 8926 8927if ( stat . is_dir ()) { 8928res . set_redirect ( sub_path + "/" , StatusCode :: MovedPermanently_301 ); 8929return true; 8930} 8931 8932if ( stat . is_file ()) { 8933for (const auto & kv : entry . headers ) { 8934res . set_header ( kv . first , kv . second ); 8935} 8936 8937auto content_type_of = [ & ]() { 8938return detail:: find_content_type ( 8939path , file_extension_and_mimetype_map_ , default_file_mimetype_ ); 8940}; 8941 8942// Only the ETag needs the content type this early, and only to name 8943// the coding. Deciding it here would otherwise put a regex in front 8944// of the 304 below, which serving a file never used to pay for. 8945std:: string content_type ; 8946auto encoding = detail:: EncodingType :: None ; 8947if ( static_file_compression_ ) { 8948content_type = content_type_of (); 8949encoding = 8950static_file_encoding ( req , res , content_type , stat . size ()); 8951} 8952 8953// The ETag names the representation actually sent, so a client that 8954// cached the compressed form revalidates against the compressed ETag 8955// and still gets a 304, while one that took identity keeps the plain 8956// ETag. 8957auto etag = detail:: compute_etag ( 8958stat , encoding == detail:: EncodingType :: None 8959? std:: string () 8960: std:: string ( "-" ) + detail:: encoding_name ( encoding )); 8961if (! etag . empty ()) { res . set_header ( "ETag" , etag ); } 8962 8963auto mtime = stat . mtime (); 8964 8965auto last_modified = detail:: file_mtime_to_http_date ( mtime ); 8966if (! last_modified . empty ()) { 8967res . set_header ( "Last-Modified" , last_modified ); 8968} 8969 8970if ( check_if_not_modified ( req , res , etag , mtime )) { return true; } 8971 8972check_if_range ( req , etag , mtime ); 8973 8974auto mm = std:: make_shared < detail:: mmap > ( path . c_str ()); 8975if (! mm -> is_open ()) { 8976output_error_log ( Error :: OpenFile , & req ); 8977return false; 8978} 8979 8980if (! static_file_compression_ ) { content_type = content_type_of (); } 8981 8982detail:: set_file_content_provider ( res , mm , content_type , encoding ); 8983 8984if ( req . method != "HEAD" && file_request_handler_ ) { 8985file_request_handler_ ( req , res ); 8986} 8987 8988return true; 8989} else { 8990output_error_log ( Error :: OpenFile , & req ); 8991} 8992} 8993} 8994} 8995return false; 8996} 8997 8998bool Server :: check_if_not_modified (const Request & req , Response & res , 8999const std:: string & etag , 9000time_t mtime ) const { 9001// Handle conditional GET: 9002// 1. If-None-Match takes precedence (RFC 9110 Section 13.1.2) 9003// 2. If-Modified-Since is checked only when If-None-Match is absent 9004if ( req . has_header ( "If-None-Match" )) { 9005if (! etag . empty ()) { 9006auto val = 9007detail:: get_combined_header_value ( req . headers , "If-None-Match" ); 9008 9009// NOTE: We use exact string matching here. This works correctly 9010// because our server always generates weak ETags (W/"..."), and 9011// clients typically send back the same ETag they received. 9012// RFC 9110 Section 8.8.3.2 allows weak comparison for 9013// If-None-Match, where W/"x" and "x" would match, but this 9014// simplified implementation requires exact matches. 9015auto ret = detail:: split_find ( val . data (), val . data () + val . size (), ',' , 9016[ & ](const char * b , const char * e ) { 9017auto seg_len = static_cast < size_t > ( e - b ); 9018return ( seg_len == 1 && * b == '*' ) || 9019( seg_len == etag . size () && 9020std:: equal ( b , e , etag . begin ())); 9021}); 9022 9023if ( ret ) { 9024res . status = StatusCode :: NotModified_304 ; 9025return true; 9026} 9027} 9028} else if ( req . has_header ( "If-Modified-Since" )) { 9029auto val = req . get_header_value ( "If-Modified-Since" ); 9030auto t = detail:: parse_http_date ( val ); 9031 9032if ( t != static_cast < time_t > ( -1 ) && mtime <= t ) { 9033res . status = StatusCode :: NotModified_304 ; 9034return true; 9035} 9036} 9037return false; 9038} 9039 9040bool Server :: check_if_range ( Request & req , const std:: string & etag , 9041time_t mtime ) const { 9042// Handle If-Range for partial content requests (RFC 9110 9043// Section 13.1.5). If-Range is only evaluated when Range header is 9044// present. If the validator matches, serve partial content; otherwise 9045// serve full content. 9046if (! req . ranges . empty () && req . has_header ( "If-Range" )) { 9047auto val = req . get_header_value ( "If-Range" ); 9048 9049auto is_valid_range = [ & ]() { 9050if (detail:: is_strong_etag ( val )) { 9051// RFC 9110 Section 13.1.5: If-Range requires strong ETag 9052// comparison. 9053return (! etag . empty () && val == etag ); 9054} else if (detail:: is_weak_etag ( val )) { 9055// Weak ETags are not valid for If-Range (RFC 9110 Section 13.1.5) 9056return false; 9057} else { 9058// HTTP-date comparison 9059auto t = detail:: parse_http_date ( val ); 9060return ( t != static_cast < time_t > ( -1 ) && mtime <= t ); 9061} 9062}; 9063 9064if (! is_valid_range ()) { 9065// Validator doesn't match: ignore Range and serve full content 9066req . ranges . clear (); 9067return false; 9068} 9069} 9070 9071return true; 9072} 9073 9074socket_t 9075Server :: create_server_socket (const std:: string & host , int port , 9076int socket_flags , 9077SocketOptions socket_options ) const { 9078return detail:: create_socket ( 9079host , std:: string (), port , address_family_ , socket_flags , tcp_nodelay_ , 9080ipv6_v6only_ , std:: move ( socket_options ), 9081[ & ]( socket_t sock , struct addrinfo & ai , bool & /*quit*/ ) -> bool { 9082if (:: bind ( sock , ai . ai_addr , static_cast < socklen_t > ( ai . ai_addrlen ))) { 9083output_error_log ( Error :: BindIPAddress , nullptr ); 9084return false; 9085} 9086if (:: listen ( sock , CPPHTTPLIB_LISTEN_BACKLOG )) { 9087output_error_log ( Error :: Listen , nullptr ); 9088return false; 9089} 9090return true; 9091}); 9092} 9093 9094int Server :: bind_internal (const std:: string & host , int port , 9095int socket_flags ) { 9096if ( is_decommissioned ) { return -1 ; } 9097 9098if (! is_valid ()) { return -1 ; } 9099 9100svr_sock_ = create_server_socket ( host , port , socket_flags , socket_options_ ); 9101if ( svr_sock_ == INVALID_SOCKET ) { return -1 ; } 9102 9103if ( port == 0 ) { 9104struct sockaddr_storage addr ; 9105socklen_t addr_len = sizeof( addr ); 9106if ( getsockname ( svr_sock_ , reinterpret_cast < struct sockaddr *> ( & addr ), 9107& addr_len ) == -1 ) { 9108output_error_log ( Error :: GetSockName , nullptr ); 9109return -1 ; 9110} 9111if ( addr . ss_family == AF_INET ) { 9112return ntohs ( reinterpret_cast < struct sockaddr_in *> ( & addr ) -> sin_port ); 9113} else if ( addr . ss_family == AF_INET6 ) { 9114return ntohs ( reinterpret_cast < struct sockaddr_in6 *> ( & addr ) -> sin6_port ); 9115} else { 9116output_error_log ( Error :: UnsupportedAddressFamily , nullptr ); 9117return -1 ; 9118} 9119} else { 9120return port ; 9121} 9122} 9123 9124bool Server :: listen_internal () { 9125// A stop() between bind and listen leaves nothing to accept on. Report 9126// failure instead of returning success without ever serving, and mark the 9127// server decommissioned the way any failed listen does so that a concurrent 9128// wait_until_ready() wakes up instead of spinning forever. 9129if ( is_decommissioned || svr_sock_ == INVALID_SOCKET ) { 9130is_decommissioned = true; 9131return false; 9132} 9133 9134auto ret = true; 9135is_running_ = true; 9136auto se = detail:: scope_exit ([ & ]() { is_running_ = false; }); 9137 9138if ( start_handler_ ) { start_handler_ (); } 9139 9140{ 9141std:: unique_ptr < TaskQueue > task_queue ( new_task_queue ()); 9142 9143while ( svr_sock_ != INVALID_SOCKET ) { 9144#ifndef _WIN32 9145if ( idle_interval_sec_ > 0 || idle_interval_usec_ > 0 ) { 9146#endif 9147auto val = detail:: select_read ( svr_sock_ , idle_interval_sec_ , 9148idle_interval_usec_ ); 9149if ( val == 0 ) { // Timeout 9150task_queue -> on_idle (); 9151continue; 9152} 9153#ifndef _WIN32 9154} 9155#endif 9156 9157#if defined _WIN32 9158// sockets connected via WASAccept inherit flags NO_HANDLE_INHERIT, 9159// OVERLAPPED 9160socket_t sock = WSAAccept ( svr_sock_ , nullptr , nullptr , nullptr , 0 ); 9161#elif defined SOCK_CLOEXEC 9162socket_t sock = accept4 ( svr_sock_ , nullptr , nullptr , SOCK_CLOEXEC ); 9163#else 9164socket_t sock = accept ( svr_sock_ , nullptr , nullptr ); 9165#endif 9166 9167if ( sock == INVALID_SOCKET ) { 9168// NOTE: Winsock reports failures through WSAGetLastError() and never 9169// touches the CRT errno, so the two have to be asked platform by 9170// platform rather than by testing errno here. 9171if (detail:: is_accept_resource_error ()) { 9172// The per-process descriptor limit or the network stack's buffer 9173// space has been reached. Try to accept new connections after a 9174// short sleep. 9175std::this_thread:: sleep_for (std::chrono:: microseconds { 1 }); 9176continue; 9177} else if (detail:: is_accept_transient_error ()) { 9178continue; 9179} 9180// Take the descriptor out of svr_sock_ before closing it: a later 9181// stop() would otherwise shutdown()/close() a value the OS may have 9182// reused, and keep_alive() watches svr_sock_ to notice the server is 9183// gone. The exchange also settles the race with a concurrent stop(), 9184// since whichever side takes the descriptor closes it exactly once. 9185auto listen_sock = svr_sock_ . exchange ( INVALID_SOCKET ); 9186if ( listen_sock != INVALID_SOCKET ) { 9187detail:: close_socket ( listen_sock ); 9188ret = false; 9189output_error_log ( Error :: Connection , nullptr ); 9190} else { 9191; // The server socket was closed by user. 9192} 9193break; 9194} 9195 9196detail:: set_socket_opt_time ( sock , SOL_SOCKET , SO_RCVTIMEO , 9197read_timeout_sec_ , read_timeout_usec_ ); 9198detail:: set_socket_opt_time ( sock , SOL_SOCKET , SO_SNDTIMEO , 9199write_timeout_sec_ , write_timeout_usec_ ); 9200 9201if ( tcp_nodelay_ ) { set_socket_opt ( sock , IPPROTO_TCP , TCP_NODELAY , 1 ); } 9202 9203if (! task_queue -> enqueue ( 9204[ this , sock ]() { process_and_close_socket ( sock ); })) { 9205output_error_log ( Error :: ResourceExhaustion , nullptr ); 9206detail:: shutdown_socket ( sock ); 9207detail:: close_socket ( sock ); 9208} 9209} 9210 9211task_queue -> shutdown (); 9212} 9213 9214is_decommissioned = ! ret ; 9215return ret ; 9216} 9217 9218bool Server :: routing ( Request & req , Response & res , Stream & strm ) { 9219if ( pre_routing_handler_ && 9220pre_routing_handler_ ( req , res ) == HandlerResponse :: Handled ) { 9221return true; 9222} 9223 9224// File handler 9225if (( req . method == "GET" || req . method == "HEAD" ) && 9226handle_file_request ( req , res )) { 9227return true; 9228} 9229 9230const auto * custom = find_custom_entry ( req . method ); 9231 9232// The second clause mirrors what expect_content() does unconditionally for 9233// POST/PUT/PATCH/DELETE: a content reader route fires even when the request 9234// carries no body. Without it a body-less PROPFIND (RFC 4918 treats one as 9235// `allprop`) would skip its handler and fall through to 404. 9236if (detail:: expect_content ( req ) || 9237( custom && ! custom -> handlers_for_content_reader . empty ())) { 9238// Content reader handler 9239{ 9240// Track whether the ContentReader was aborted due to the decompressed 9241// payload exceeding `payload_max_length_`. 9242// The user handler runs after the lambda returns, so we must restore the 9243// 413 status if the handler overwrites it. 9244bool content_reader_payload_too_large = false; 9245 9246ContentReader reader ( 9247[ & ]( ContentReceiver receiver ) { 9248auto result = read_content_with_content_receiver ( 9249strm , req , res , std:: move ( receiver ), nullptr , nullptr ); 9250if (! result ) { 9251output_error_log ( Error :: Read , & req ); 9252if ( res . status == StatusCode :: PayloadTooLarge_413 ) { 9253content_reader_payload_too_large = true; 9254} 9255} 9256return result ; 9257}, 9258[ & ]( FormDataHeader header , ContentReceiver receiver ) { 9259auto result = read_content_with_content_receiver ( 9260strm , req , res , nullptr , std:: move ( header ), 9261std:: move ( receiver )); 9262if (! result ) { 9263output_error_log ( Error :: Read , & req ); 9264if ( res . status == StatusCode :: PayloadTooLarge_413 ) { 9265content_reader_payload_too_large = true; 9266} 9267} 9268return result ; 9269}); 9270 9271bool dispatched = false; 9272if ( req . method == "POST" ) { 9273dispatched = dispatch_request_for_content_reader ( 9274req , res , std:: move ( reader ), post_handlers_for_content_reader_ ); 9275} else if ( req . method == "PUT" ) { 9276dispatched = dispatch_request_for_content_reader ( 9277req , res , std:: move ( reader ), put_handlers_for_content_reader_ ); 9278} else if ( req . method == "PATCH" ) { 9279dispatched = dispatch_request_for_content_reader ( 9280req , res , std:: move ( reader ), patch_handlers_for_content_reader_ ); 9281} else if ( req . method == "DELETE" ) { 9282dispatched = dispatch_request_for_content_reader ( 9283req , res , std:: move ( reader ), delete_handlers_for_content_reader_ ); 9284} else if ( custom ) { 9285dispatched = dispatch_request_for_content_reader ( 9286req , res , std:: move ( reader ), custom -> handlers_for_content_reader ); 9287} 9288 9289if ( dispatched ) { 9290if ( content_reader_payload_too_large ) { 9291// Enforce the limit: override any status the handler may have set 9292// and return false so the error path sends a plain 413 response. 9293res . status = StatusCode :: PayloadTooLarge_413 ; 9294res . body . clear (); 9295res . content_length_ = 0 ; 9296res . content_provider_ = nullptr ; 9297return false; 9298} 9299return true; 9300} 9301} 9302 9303// NOTE: `req.body` is not read here. For a regular handler the body is 9304// read inside dispatch_request(), after the route has matched and the 9305// pre-request handler has approved the request, so that a rejected 9306// request (e.g. failed authentication) never forces us to buffer a 9307// potentially large body. 9308} 9309 9310// Regular handler 9311if ( req . method == "GET" || req . method == "HEAD" ) { 9312return dispatch_request ( req , res , get_handlers_ , strm ); 9313} else if ( req . method == "POST" ) { 9314return dispatch_request ( req , res , post_handlers_ , strm ); 9315} else if ( req . method == "PUT" ) { 9316return dispatch_request ( req , res , put_handlers_ , strm ); 9317} else if ( req . method == "DELETE" ) { 9318return dispatch_request ( req , res , delete_handlers_ , strm ); 9319} else if ( req . method == "OPTIONS" ) { 9320return dispatch_request ( req , res , options_handlers_ , strm ); 9321} else if ( req . method == "PATCH" ) { 9322return dispatch_request ( req , res , patch_handlers_ , strm ); 9323} else if ( custom ) { 9324return dispatch_request ( req , res , custom -> handlers , strm ); 9325} 9326 9327res . status = StatusCode :: BadRequest_400 ; 9328return false; 9329} 9330 9331bool Server :: dispatch_request ( Request & req , Response & res , 9332const Handlers & handlers , Stream & strm ) { 9333for (const auto & x : handlers ) { 9334const auto & matcher = x . first ; 9335const auto & handler = x . second ; 9336 9337if ( matcher -> match ( req )) { 9338req . matched_route = matcher -> pattern (); 9339 9340// Run the pre-request handler before reading the body so a rejected 9341// request (e.g. failed authentication) never forces us to buffer a 9342// potentially large body. `req.matched_route` is available here. 9343if ( pre_request_handler_ && 9344pre_request_handler_ ( req , res ) == HandlerResponse :: Handled ) { 9345return true; 9346} 9347 9348// The route matched and the request was approved; read the body now. 9349if (detail:: expect_content ( req ) && ! read_content ( strm , req , res )) { 9350output_error_log ( Error :: Read , & req ); 9351return false; 9352} 9353 9354handler ( req , res ); 9355return true; 9356} 9357} 9358return false; 9359} 9360 9361// Decides the content coding for a response served straight from a file. Both 9362// the ETag, which has to name the representation actually sent, and 9363// `apply_static_file_compression()` go through this, so the two cannot drift 9364// apart. 9365detail:: EncodingType 9366Server :: static_file_encoding (const Request & req , const Response & res , 9367const std:: string & content_type , 9368size_t length ) const { 9369if (! static_file_compression_ ) { return detail:: EncodingType :: None ; } 9370 9371// Nothing to compress, and an empty file already answers with 9372// `Content-Length: 0`. Checked on its own so that a zero floor still cannot 9373// turn an empty body into a 20-byte gzip stream. 9374if ( length == 0 ) { return detail:: EncodingType :: None ; } 9375 9376// A file that already fits in a single packet gains nothing from being made 9377// smaller, since it still travels in that one segment, and a file of a few 9378// bytes comes out larger than it went in. 9379if ( length < static_file_compression_min_length_ ) { 9380return detail:: EncodingType :: None ; 9381} 9382 9383// RFC 9110 applies Range to the representation after content coding, so a 9384// compressed 206 would mean compressing the whole file and then slicing it. 9385// Serve ranges from the identity representation instead. 9386if (! req . ranges . empty ()) { return detail:: EncodingType :: None ; } 9387 9388if ( static_file_compression_max_length_ > 0 && 9389length > static_file_compression_max_length_ ) { 9390return detail:: EncodingType :: None ; 9391} 9392 9393return detail:: encoding_type ( req , res , content_type ); 9394} 9395 9396// Compresses a file-backed content provider into `res.body` and takes over the 9397// framing headers. Returns false when the response is left untouched. 9398bool Server :: apply_static_file_compression (const Request & req , 9399Response & res ) const { 9400auto type = res . content_coding_ ; 9401if ( type == detail:: EncodingType :: None || ! res . content_provider_ ) { 9402return false; 9403} 9404 9405auto compressor = detail:: make_compressor ( type ); 9406if (! compressor ) { return false; } 9407 9408output_pre_compression_log ( req , res ); 9409 9410std:: string compressed ; 9411if (!detail:: compress_content_provider ( res . content_provider_ , 9412res . content_length_ , * compressor , 9413compressed )) { 9414return false; 9415} 9416 9417res . body . swap ( compressed ); 9418 9419// The provider was consumed in full, so a resource releaser registered with 9420// it should hear about a success when the response goes away. 9421res . content_provider_success_ = true; 9422res . content_provider_ = nullptr ; 9423res . content_length_ = 0 ; 9424res . content_coding_ = detail:: EncodingType :: None ; 9425 9426res . set_header ( "Content-Encoding" , detail:: encoding_name ( type )); 9427res . set_header ( "Vary" , "Accept-Encoding" ); 9428res . set_header ( "Content-Length" , std:: to_string ( res . body . size ())); 9429 9430return true; 9431} 9432 9433void Server :: apply_ranges (const Request & req , Response & res , 9434std:: string & content_type , 9435std:: string & boundary ) const { 9436// A known-length content provider leaves `res.body` empty, so the compressor 9437// at the end of this function never runs for one (issue #2545). A file-backed 9438// provider is fully readable right here, so compress it and answer with an 9439// ordinary body: `Content-Length` and HEAD keep working, and the response 9440// takes the same path as `set_content()` from here on. Range requests never 9441// get a content coding, so `Content-Range` still names identity bytes and 9442// none of the framing below applies. 9443if ( apply_static_file_compression ( req , res )) { return; } 9444 9445if ( req . ranges . size () > 1 && res . status == StatusCode :: PartialContent_206 ) { 9446auto it = res . headers . find ( "Content-Type" ); 9447if ( it != res . headers . end ()) { 9448content_type = it -> second ; 9449res . headers . erase ( it ); 9450} 9451 9452boundary = detail:: make_multipart_data_boundary (); 9453 9454res . set_header ( "Content-Type" , 9455"multipart/byteranges; boundary=" + boundary ); 9456} 9457 9458auto type = detail:: encoding_type ( req , res ); 9459 9460if ( res . body . empty ()) { 9461if ( res . content_length_ > 0 ) { 9462size_t length = 0 ; 9463if ( req . ranges . empty () || res . status != StatusCode :: PartialContent_206 ) { 9464length = res . content_length_ ; 9465} else if ( req . ranges . size () == 1 ) { 9466auto offset_and_length = detail:: get_range_offset_and_length ( 9467req . ranges [ 0 ], res . content_length_ ); 9468 9469length = offset_and_length . second ; 9470 9471auto content_range = detail:: make_content_range_header_field ( 9472offset_and_length , res . content_length_ ); 9473res . set_header ( "Content-Range" , content_range ); 9474} else { 9475length = detail:: get_multipart_ranges_data_length ( 9476req , boundary , content_type , res . content_length_ ); 9477} 9478res . set_header ( "Content-Length" , std:: to_string ( length )); 9479} else { 9480if ( res . content_provider_ ) { 9481if ( res . is_chunked_content_provider_ ) { 9482res . set_header ( "Transfer-Encoding" , "chunked" ); 9483res . content_coding_ = type ; 9484if ( type != detail:: EncodingType :: None ) { 9485res . set_header ( "Content-Encoding" , detail:: encoding_name ( type )); 9486res . set_header ( "Vary" , "Accept-Encoding" ); 9487} 9488} 9489} 9490} 9491} else { 9492if ( req . ranges . empty () || res . status != StatusCode :: PartialContent_206 ) { 9493; 9494} else if ( req . ranges . size () == 1 ) { 9495auto offset_and_length = 9496detail:: get_range_offset_and_length ( req . ranges [ 0 ], res . body . size ()); 9497auto offset = offset_and_length . first ; 9498auto length = offset_and_length . second ; 9499 9500auto content_range = detail:: make_content_range_header_field ( 9501offset_and_length , res . body . size ()); 9502res . set_header ( "Content-Range" , content_range ); 9503 9504assert ( offset + length <= res . body . size ()); 9505res . body = res . body . substr ( offset , length ); 9506} else { 9507std:: string data ; 9508detail:: make_multipart_ranges_data ( req , res , boundary , content_type , 9509res . body . size (), data ); 9510res . body . swap ( data ); 9511} 9512 9513if ( type != detail:: EncodingType :: None ) { 9514output_pre_compression_log ( req , res ); 9515 9516if ( auto compressor = detail:: make_compressor ( type )) { 9517std:: string compressed ; 9518if ( compressor -> compress ( res . body . data (), res . body . size (), true, 9519[ & ](const char * data , size_t data_len ) { 9520compressed . append ( data , data_len ); 9521return true; 9522})) { 9523res . body . swap ( compressed ); 9524res . set_header ( "Content-Encoding" , detail:: encoding_name ( type )); 9525res . set_header ( "Vary" , "Accept-Encoding" ); 9526} 9527} 9528} 9529 9530res . content_length_ = res . body . size (); 9531res . set_header ( "Content-Length" , std:: to_string ( res . content_length_ )); 9532} 9533} 9534 9535bool Server :: dispatch_request_for_content_reader ( 9536Request & req , Response & res , ContentReader content_reader , 9537const HandlersForContentReader & handlers ) const { 9538for (const auto & x : handlers ) { 9539const auto & matcher = x . first ; 9540const auto & handler = x . second ; 9541 9542if ( matcher -> match ( req )) { 9543req . matched_route = matcher -> pattern (); 9544if (! pre_request_handler_ || 9545pre_request_handler_ ( req , res ) != HandlerResponse :: Handled ) { 9546handler ( req , res , content_reader ); 9547} 9548return true; 9549} 9550} 9551return false; 9552} 9553 9554std:: string 9555get_client_ip (const std:: string & x_forwarded_for , 9556const std:: vector < std:: string > & trusted_proxies ) { 9557// X-Forwarded-For is a comma-separated list per RFC 7239 9558std:: vector < std:: string > ip_list ; 9559detail:: split ( x_forwarded_for . data (), 9560x_forwarded_for . data () + x_forwarded_for . size (), ',' , 9561[ & ](const char * b , const char * e ) { 9562auto r = detail:: trim ( b , e , 0 , static_cast < size_t > ( e - b )); 9563ip_list . emplace_back (std:: string ( b + r . first , b + r . second )); 9564}); 9565 9566// A malformed X-Forwarded-For (empty, comma-only, whitespace-only) yields 9567// no segments. Signal "no client IP derived" with an empty string so the 9568// caller can fall back to the connection-level remote address. 9569if ( ip_list . empty ()) { return std:: string (); } 9570 9571// Each hop appends the address it received the request from, so the rightmost 9572// entries are the ones written by our own infrastructure while the leftmost 9573// are whatever the original client chose to send. Walk from the right and 9574// skip trusted proxies; the first address that is not a trusted proxy is the 9575// furthest point still attributable to a real hop, i.e. the client. Scanning 9576// from the left instead lets a client forge an arbitrary address by following 9577// it with a trusted proxy's address, which the left-to-right scan then 9578// returned as the client. 9579for ( size_t i = ip_list . size (); i -- > 0 ;) { 9580const auto & ip = ip_list [ i ]; 9581 9582auto is_trusted_proxy = 9583std:: any_of ( trusted_proxies . begin (), trusted_proxies . end (), 9584[ & ](const std:: string & proxy ) { return ip == proxy ; }); 9585 9586if (! is_trusted_proxy ) { return ip ; } 9587} 9588 9589// Every hop was a trusted proxy; fall back to the first entry. 9590return ip_list . front (); 9591} 9592 9593bool 9594Server :: process_request ( Stream & strm , const std:: string & remote_addr , 9595int remote_port , const std:: string & local_addr , 9596int local_port , bool close_connection , 9597bool & connection_closed , 9598const std:: function < void ( Request & ) > & setup_request , 9599bool * websocket_upgraded ) { 9600std:: array < char , 2048 > buf {}; 9601 9602detail:: stream_line_reader line_reader ( strm , buf . data (), buf . size ()); 9603 9604// Connection has been closed on client 9605if (! line_reader . getline ()) { return false; } 9606 9607Request req ; 9608req . start_time_ = std::chrono::steady_clock:: now (); 9609req . remote_addr = remote_addr ; 9610req . remote_port = remote_port ; 9611req . local_addr = local_addr ; 9612req . local_port = local_port ; 9613 9614Response res ; 9615res . version = "HTTP/1.1" ; 9616res . headers = default_headers_ ; 9617 9618// Request line and headers 9619if (! parse_request_line ( line_reader . ptr (), req )) { 9620res . status = StatusCode :: BadRequest_400 ; 9621output_error_log ( Error :: InvalidRequestLine , & req ); 9622return write_response ( strm , close_connection , req , res ); 9623} 9624 9625// Request headers 9626if (!detail:: read_headers ( strm , req . headers )) { 9627res . status = StatusCode :: BadRequest_400 ; 9628output_error_log ( Error :: InvalidHeaders , & req ); 9629return write_response ( strm , close_connection , req , res ); 9630} 9631 9632// RFC 9112 §6.3: Reject requests whose framing is ambiguous, which would 9633// otherwise let an intermediary and this parser disagree on where the body 9634// ends and enable request smuggling. Two cases: a non-zero Content-Length 9635// alongside any Transfer-Encoding (Content-Length: 0 is tolerated for 9636// compatibility with existing clients), and a Transfer-Encoding whose final 9637// coding is not chunked, which leaves the body length undeterminable. The 9638// latter must not fall through to the "no body" path, or the body bytes are 9639// parsed as the next request on a persistent connection. 9640if (detail:: has_conflicting_content_length ( req . headers ) || 9641( req . has_header ( "Transfer-Encoding" ) && 9642!detail:: is_chunked_transfer_encoding ( req . headers ))) { 9643connection_closed = true; 9644res . status = StatusCode :: BadRequest_400 ; 9645return write_response ( strm , close_connection , req , res ); 9646} 9647 9648// Check if the request URI doesn't exceed the limit 9649if ( req . target . size () > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH ) { 9650connection_closed = true; 9651res . status = StatusCode :: UriTooLong_414 ; 9652output_error_log ( Error :: ExceedUriMaxLength , & req ); 9653return write_response ( strm , close_connection , req , res ); 9654} 9655 9656if (detail:: has_header_token ( req . headers , "Connection" , "close" )) { 9657connection_closed = true; 9658} 9659 9660if ( req . version == "HTTP/1.0" && 9661!detail:: has_header_token ( req . headers , "Connection" , "keep-alive" )) { 9662connection_closed = true; 9663} 9664 9665// Only honor X-Forwarded-For if the peer on the actual TCP connection is 9666// itself a trusted proxy. Otherwise any direct client could spoof 9667// remote_addr simply by sending an arbitrary X-Forwarded-For header. 9668auto is_trusted_peer = std:: any_of ( 9669trusted_proxies_ . begin (), trusted_proxies_ . end (), 9670[ & ](const std:: string & proxy ) { return proxy == remote_addr ; }); 9671 9672if ( is_trusted_peer && req . has_header ( "X-Forwarded-For" )) { 9673// Some proxies append the address they observed as a separate 9674// X-Forwarded-For field line instead of extending the one the client sent 9675// (e.g. HAProxy's "option forwardfor"), so the whole combined value has to 9676// be scanned. Reading only the first occurrence would hand back the 9677// client-supplied, and therefore forgeable, value. 9678auto x_forwarded_for = 9679detail:: get_combined_header_value ( req . headers , "X-Forwarded-For" ); 9680auto derived = get_client_ip ( x_forwarded_for , trusted_proxies_ ); 9681req . remote_addr = derived . empty () ? remote_addr : derived ; 9682} else { 9683req . remote_addr = remote_addr ; 9684} 9685req . remote_port = remote_port ; 9686 9687req . local_addr = local_addr ; 9688req . local_port = local_port ; 9689 9690if ( req . has_header ( "Accept" )) { 9691auto accept_header = 9692detail:: get_combined_header_value ( req . headers , "Accept" ); 9693if (!detail:: parse_accept_header ( accept_header , req . accept_content_types )) { 9694connection_closed = true; 9695res . status = StatusCode :: BadRequest_400 ; 9696output_error_log ( Error :: HTTPParsing , & req ); 9697return write_response ( strm , close_connection , req , res ); 9698} 9699} 9700 9701if ( req . has_header ( "Range" )) { 9702const auto & range_header_value = req . get_header_value ( "Range" ); 9703if (!detail:: parse_range_header ( range_header_value , req . ranges )) { 9704connection_closed = true; 9705res . status = StatusCode :: RangeNotSatisfiable_416 ; 9706output_error_log ( Error :: InvalidRangeHeader , & req ); 9707return write_response ( strm , close_connection , req , res ); 9708} 9709} 9710 9711if ( setup_request ) { setup_request ( req ); } 9712 9713// RFC 9110 10.1.1: Expect is a comma-separated list whose value is 9714// case-insensitive, and a 100-continue expectation in an HTTP/1.0 request 9715// must be ignored. An expectation we do not recognize is left alone; the 9716// 417 the section allows for one is a MAY, not a requirement. 9717if ( req . version != "HTTP/1.0" && 9718detail:: has_header_token ( req . headers , "Expect" , "100-continue" )) { 9719int status = StatusCode :: Continue_100 ; 9720if ( expect_100_continue_handler_ ) { 9721status = expect_100_continue_handler_ ( req , res ); 9722} 9723switch ( status ) { 9724case StatusCode :: Continue_100 : 9725case StatusCode :: ExpectationFailed_417 : 9726detail:: write_response_line ( strm , status ); 9727strm . write ( "\r\n" ); 9728break; 9729default: 9730connection_closed = true; 9731return write_response ( strm , true, req , res ); 9732} 9733} 9734 9735// Setup `is_connection_closed` method 9736auto sock = strm . socket (); 9737req . is_connection_closed = [ sock ]() { 9738return !detail:: is_socket_alive ( sock ); 9739}; 9740 9741// WebSocket upgrade 9742// Check pre_routing_handler_ before upgrading so that authentication 9743// and other middleware can reject the request with an HTTP response 9744// (e.g., 401) before the protocol switches. 9745if (detail:: is_websocket_upgrade ( req )) { 9746if ( pre_routing_handler_ && 9747pre_routing_handler_ ( req , res ) == HandlerResponse :: Handled ) { 9748if ( res . status == -1 ) { res . status = StatusCode :: OK_200 ; } 9749return write_response ( strm , close_connection , req , res ); 9750} 9751// Find matching WebSocket handler 9752for (const auto & entry : websocket_handlers_ ) { 9753if ( entry . matcher -> match ( req )) { 9754// Compute accept key 9755auto client_key = req . get_header_value ( "Sec-WebSocket-Key" ); 9756auto accept_key = detail:: websocket_accept_key ( client_key ); 9757 9758// Negotiate subprotocol 9759std:: string selected_subprotocol ; 9760if ( entry . sub_protocol_selector ) { 9761auto protocol_header = detail:: get_combined_header_value ( 9762req . headers , "Sec-WebSocket-Protocol" ); 9763if (! protocol_header . empty ()) { 9764std:: vector < std:: string > protocols ; 9765detail:: split ( protocol_header . data (), 9766protocol_header . data () + protocol_header . size (), ',' , 9767[ & ](const char * b , const char * e ) { 9768protocols . emplace_back ( b , e ); 9769}); 9770selected_subprotocol = entry . sub_protocol_selector ( protocols ); 9771} 9772} 9773 9774// Send 101 Switching Protocols 9775std:: string handshake_response = "HTTP/1.1 101 Switching Protocols\r\n" 9776"Upgrade: websocket\r\n" 9777"Connection: Upgrade\r\n" 9778"Sec-WebSocket-Accept: " + 9779accept_key + "\r\n" ; 9780if (! selected_subprotocol . empty ()) { 9781if (!detail::fields:: is_field_value ( selected_subprotocol )) { 9782return false; 9783} 9784handshake_response += 9785"Sec-WebSocket-Protocol: " + selected_subprotocol + "\r\n" ; 9786} 9787handshake_response += "\r\n" ; 9788if ( strm . write ( handshake_response . data (), handshake_response . size ()) < 97890 ) { 9790return false; 9791} 9792 9793connection_closed = true; 9794if ( websocket_upgraded ) { * websocket_upgraded = true; } 9795 9796{ 9797#ifdef CPPHTTPLIB_SSL_ENABLED 9798if ( req . ssl ) { 9799// wss: the heartbeat ping thread and the read path enter the same 9800// TLS session from different threads. Hand the WebSocket a stream 9801// that serializes every TLS call, so the shared SSLSocketStream on 9802// the plain HTTP/HTTPS paths stays untouched. 9803auto ws_strm = 9804std:: unique_ptr < Stream > (new detail:: WebSocketSSLStream ( 9805strm . socket (), const_cast < tls:: session_t > ( req . ssl ), 9806CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND , 0 , 9807write_timeout_sec_ , write_timeout_usec_ )); 9808ws:: WebSocket ws (std:: move ( ws_strm ), req , true, 9809websocket_ping_interval_sec_ , 9810websocket_max_missed_pongs_ ); 9811entry . handler ( req , ws ); 9812return true; 9813} 9814#endif 9815// Use WebSocket-specific read timeout instead of HTTP timeout 9816strm . set_read_timeout ( CPPHTTPLIB_WEBSOCKET_SERVER_READ_TIMEOUT_SECOND , 98170 ); 9818ws:: WebSocket ws ( strm , req , true , websocket_ping_interval_sec_ , 9819websocket_max_missed_pongs_ ); 9820entry . handler ( req , ws ); 9821} 9822return true; 9823} 9824} 9825// No matching handler - fall through to 404 9826} 9827 9828// Routing 9829auto routed = false; 9830#ifdef CPPHTTPLIB_NO_EXCEPTIONS 9831routed = routing ( req , res , strm ); 9832#else 9833try { 9834routed = routing ( req , res , strm ); 9835} catch (std:: exception & ) { 9836if ( exception_handler_ ) { 9837auto ep = std:: current_exception (); 9838exception_handler_ ( req , res , ep ); 9839routed = true; 9840} else { 9841res . status = StatusCode :: InternalServerError_500 ; 9842} 9843} catch (...) { 9844if ( exception_handler_ ) { 9845auto ep = std:: current_exception (); 9846exception_handler_ ( req , res , ep ); 9847routed = true; 9848} else { 9849res . status = StatusCode :: InternalServerError_500 ; 9850} 9851} 9852#endif 9853auto ret = false; 9854if ( routed ) { 9855if ( res . status == -1 ) { 9856res . status = req . ranges . empty () ? StatusCode :: OK_200 9857: StatusCode :: PartialContent_206 ; 9858} 9859 9860// Serve file content by using a content provider 9861auto file_open_error = false; 9862if (! res . file_content_path_ . empty ()) { 9863const auto & path = res . file_content_path_ ; 9864auto mm = std:: make_shared < detail:: mmap > ( path . c_str ()); 9865if (! mm -> is_open ()) { 9866res . body . clear (); 9867res . content_length_ = 0 ; 9868res . content_provider_ = nullptr ; 9869res . status = StatusCode :: NotFound_404 ; 9870output_error_log ( Error :: OpenFile , & req ); 9871file_open_error = true; 9872} else { 9873auto content_type = res . file_content_content_type_ ; 9874if ( content_type . empty ()) { 9875content_type = detail:: find_content_type ( 9876path , file_extension_and_mimetype_map_ , default_file_mimetype_ ); 9877} 9878 9879detail:: set_file_content_provider ( 9880res , mm , content_type , 9881static_file_encoding ( req , res , content_type , mm -> size ())); 9882} 9883} 9884 9885if ( file_open_error ) { 9886ret = write_response ( strm , close_connection , req , res ); 9887} else if (detail:: range_error ( req , res )) { 9888res . body . clear (); 9889res . content_length_ = 0 ; 9890res . content_provider_ = nullptr ; 9891res . status = StatusCode :: RangeNotSatisfiable_416 ; 9892ret = write_response ( strm , close_connection , req , res ); 9893} else { 9894ret = write_response_with_content ( strm , close_connection , req , res ); 9895} 9896} else { 9897if ( res . status == -1 ) { res . status = StatusCode :: NotFound_404 ; } 9898ret = write_response ( strm , close_connection , req , res ); 9899} 9900 9901// Drain any unconsumed framed body to prevent request smuggling on 9902// keep-alive. Without framing there is no body to drain — reading would 9903// consume the next request (issue #2450). If the response has committed the 9904// connection to close, there is no next request to protect. 9905if (! req . body_consumed_ && detail:: has_framed_body ( req )) { 9906if (detail:: has_header_token ( res . headers , "Connection" , "close" )) { 9907connection_closed = true; 9908} else { 9909int dummy_status ; 9910if (!detail:: read_content ( 9911strm , req , payload_max_length_ , dummy_status , nullptr , 9912[](const char * , size_t , size_t , size_t ) { return true; }, 9913false)) { 9914connection_closed = true; 9915} 9916} 9917} 9918 9919return ret ; 9920} 9921 9922bool Server :: is_valid () const { return ! has_invalid_registration_ ; } 9923 9924bool Server :: process_and_close_socket ( socket_t sock ) { 9925std:: string remote_addr ; 9926int remote_port = 0 ; 9927detail:: get_remote_ip_and_port ( sock , remote_addr , remote_port ); 9928 9929std:: string local_addr ; 9930int local_port = 0 ; 9931detail:: get_local_ip_and_port ( sock , local_addr , local_port ); 9932 9933bool websocket_upgraded = false; 9934auto ret = serve_guarded ([ & ]() { 9935return detail:: process_server_socket ( 9936svr_sock_ , sock , keep_alive_max_count_ , keep_alive_timeout_sec_ , 9937read_timeout_sec_ , read_timeout_usec_ , write_timeout_sec_ , 9938write_timeout_usec_ , 9939[ & ]( Stream & strm , bool close_connection , bool & connection_closed ) { 9940return process_request ( strm , remote_addr , remote_port , local_addr , 9941local_port , close_connection , 9942connection_closed , nullptr , 9943& websocket_upgraded ); 9944}); 9945}); 9946 9947detail:: drain_and_close_socket ( sock ); 9948return ret ; 9949} 9950 9951void Server :: output_log (const Request & req , const Response & res ) const { 9952if ( logger_ ) { 9953std:: lock_guard < std:: mutex > guard ( logger_mutex_ ); 9954logger_ ( req , res ); 9955} 9956} 9957 9958void Server :: output_pre_compression_log (const Request & req , 9959const Response & res ) const { 9960if ( pre_compression_logger_ ) { 9961std:: lock_guard < std:: mutex > guard ( logger_mutex_ ); 9962pre_compression_logger_ ( req , res ); 9963} 9964} 9965 9966void Server :: output_error_log (const Error & err , 9967const Request * req ) const { 9968if ( error_logger_ ) { 9969std:: lock_guard < std:: mutex > guard ( logger_mutex_ ); 9970error_logger_ ( err , req ); 9971} 9972} 9973 9974/* 9975* Group 5: ClientImpl and Client (Universal) implementation 9976*/ 9977// HTTP client implementation 9978ClientImpl :: ClientImpl (const std:: string & host ) 9979: ClientImpl ( host , 80 , std:: string (), std:: string ()) {} 9980 9981ClientImpl :: ClientImpl (const std:: string & host , int port ) 9982: ClientImpl ( host , port , std:: string (), std:: string ()) {} 9983 9984ClientImpl :: ClientImpl (const std:: string & host , int port , 9985const std:: string & client_cert_path , 9986const std:: string & client_key_path ) 9987: host_ (detail:: escape_abstract_namespace_unix_domain ( host )), port_ ( port ), 9988client_cert_path_ ( client_cert_path ), client_key_path_ ( client_key_path ) {} 9989 9990ClientImpl ::~ ClientImpl () { 9991// Wait until all the requests in flight are handled. 9992size_t retry_count = 10 ; 9993while ( retry_count -- > 0 ) { 9994{ 9995std:: lock_guard < std:: mutex > guard ( socket_mutex_ ); 9996if ( socket_requests_in_flight_ == 0 ) { break; } 9997} 9998std::this_thread:: sleep_for (std::chrono:: milliseconds { 1 }); 9999} 10000 10001std:: lock_guard < std:: mutex > guard ( socket_mutex_ ); 10002shutdown_socket ( socket_ ); 10003close_socket ( socket_ ); 10004} 10005 10006bool ClientImpl :: is_valid () const { return true; } 10007 10008void ClientImpl :: copy_settings (const ClientImpl & rhs ) { 10009client_cert_path_ = rhs . client_cert_path_ ; 10010client_key_path_ = rhs . client_key_path_ ; 10011connection_timeout_sec_ = rhs . connection_timeout_sec_ ; 10012read_timeout_sec_ = rhs . read_timeout_sec_ ; 10013read_timeout_usec_ = rhs . read_timeout_usec_ ; 10014write_timeout_sec_ = rhs . write_timeout_sec_ ; 10015write_timeout_usec_ = rhs . write_timeout_usec_ ; 10016max_timeout_msec_ = rhs . max_timeout_msec_ ; 10017basic_auth_username_ = rhs . basic_auth_username_ ; 10018basic_auth_password_ = rhs . basic_auth_password_ ; 10019bearer_token_auth_token_ = rhs . bearer_token_auth_token_ ; 10020keep_alive_ = rhs . keep_alive_ ; 10021follow_location_ = rhs . follow_location_ ; 10022path_encode_ = rhs . path_encode_ ; 10023address_family_ = rhs . address_family_ ; 10024tcp_nodelay_ = rhs . tcp_nodelay_ ; 10025ipv6_v6only_ = rhs . ipv6_v6only_ ; 10026socket_options_ = rhs . socket_options_ ; 10027compress_ = rhs . compress_ ; 10028decompress_ = rhs . decompress_ ; 10029payload_max_length_ = rhs . payload_max_length_ ; 10030has_payload_max_length_ = rhs . has_payload_max_length_ ; 10031interface_ = rhs . interface_ ; 10032proxy_host_ = rhs . proxy_host_ ; 10033proxy_port_ = rhs . proxy_port_ ; 10034proxy_basic_auth_username_ = rhs . proxy_basic_auth_username_ ; 10035proxy_basic_auth_password_ = rhs . proxy_basic_auth_password_ ; 10036proxy_bearer_token_auth_token_ = rhs . proxy_bearer_token_auth_token_ ; 10037no_proxy_entries_ = rhs . no_proxy_entries_ ; 10038logger_ = rhs . logger_ ; 10039error_logger_ = rhs . error_logger_ ; 10040 10041#ifdef CPPHTTPLIB_SSL_ENABLED 10042digest_auth_username_ = rhs . digest_auth_username_ ; 10043digest_auth_password_ = rhs . digest_auth_password_ ; 10044proxy_digest_auth_username_ = rhs . proxy_digest_auth_username_ ; 10045proxy_digest_auth_password_ = rhs . proxy_digest_auth_password_ ; 10046ca_cert_file_path_ = rhs . ca_cert_file_path_ ; 10047ca_cert_dir_path_ = rhs . ca_cert_dir_path_ ; 10048server_certificate_verification_ = rhs . server_certificate_verification_ ; 10049server_hostname_verification_ = rhs . server_hostname_verification_ ; 10050system_ca_mode_ = rhs . system_ca_mode_ ; 10051#endif 10052} 10053 10054bool 10055ClientImpl :: is_proxy_enabled_for_host (const std:: string & host ) const { 10056if ( proxy_host_ . empty () || proxy_port_ == -1 ) { return false; } 10057if ( no_proxy_entries_ . empty ()) { return true; } 10058// host_ is const so its normalized form is invariant; cache it. The 10059// cross-host path (setup_redirect_client passing next_host) re-normalizes. 10060if ( host == host_ ) { 10061if (! host_normalized_valid_ ) { 10062host_normalized_ = detail:: normalize_target ( host_ ); 10063host_normalized_valid_ = true; 10064} 10065return !detail:: host_matches_no_proxy ( host_normalized_ , no_proxy_entries_ ); 10066} 10067auto target = detail:: normalize_target ( host ); 10068return !detail:: host_matches_no_proxy ( target , no_proxy_entries_ ); 10069} 10070 10071socket_t ClientImpl :: create_client_socket ( Error & error ) const { 10072if ( is_proxy_enabled_for_host ( host_ )) { 10073return detail:: create_client_socket ( 10074proxy_host_ , std:: string (), proxy_port_ , address_family_ , tcp_nodelay_ , 10075ipv6_v6only_ , socket_options_ , connection_timeout_sec_ , 10076connection_timeout_usec_ , read_timeout_sec_ , read_timeout_usec_ , 10077write_timeout_sec_ , write_timeout_usec_ , interface_ , error ); 10078} 10079 10080// Check is custom IP or hostname specified for host_ 10081std:: string connect_host ; 10082std:: string ip ; 10083detail:: apply_addr_map ( addr_map_ , host_ , connect_host , ip ); 10084 10085return detail:: create_client_socket ( 10086connect_host , ip , port_ , address_family_ , tcp_nodelay_ , ipv6_v6only_ , 10087socket_options_ , connection_timeout_sec_ , connection_timeout_usec_ , 10088read_timeout_sec_ , read_timeout_usec_ , write_timeout_sec_ , 10089write_timeout_usec_ , interface_ , error ); 10090} 10091 10092bool ClientImpl :: create_and_connect_socket ( Socket & socket , 10093Error & error ) { 10094auto sock = create_client_socket ( error ); 10095if ( sock == INVALID_SOCKET ) { return false; } 10096socket . sock = sock ; 10097return true; 10098} 10099 10100bool ClientImpl :: ensure_socket_connection ( Socket & socket , Error & error ) { 10101return create_and_connect_socket ( socket , error ); 10102} 10103 10104bool ClientImpl :: setup_proxy_connection ( 10105Socket & /*socket*/ , 10106std::chrono:: time_point < std::chrono:: steady_clock > /*start_time*/ , 10107Response & /*res*/ , bool & /*success*/ , Error & /*error*/ ) { 10108return true; 10109} 10110 10111void ClientImpl :: shutdown_ssl ( Socket & /*socket*/ , 10112bool /*shutdown_gracefully*/ ) { 10113// If there are any requests in flight from threads other than us, then it's 10114// a thread-unsafe race because individual ssl* objects are not thread-safe. 10115assert ( socket_requests_in_flight_ == 0 || 10116socket_requests_are_from_thread_ == std::this_thread:: get_id ()); 10117} 10118 10119void ClientImpl :: shutdown_socket ( Socket & socket ) const { 10120if ( socket . sock == INVALID_SOCKET ) { return; } 10121detail:: shutdown_socket ( socket . sock ); 10122} 10123 10124void ClientImpl :: close_socket ( Socket & socket ) { 10125// If there are requests in flight in another thread, usually closing 10126// the socket will be fine and they will simply receive an error when 10127// using the closed socket, but it is still a bug since rarely the OS 10128// may reassign the socket id to be used for a new socket, and then 10129// suddenly they will be operating on a live socket that is different 10130// than the one they intended! 10131assert ( socket_requests_in_flight_ == 0 || 10132socket_requests_are_from_thread_ == std::this_thread:: get_id ()); 10133 10134// It is also a bug if this happens while SSL is still active 10135#ifdef CPPHTTPLIB_SSL_ENABLED 10136assert ( socket . ssl == nullptr ); 10137#endif 10138 10139if ( socket . sock == INVALID_SOCKET ) { return; } 10140detail:: close_socket ( socket . sock ); 10141socket . sock = INVALID_SOCKET ; 10142} 10143 10144void ClientImpl :: disconnect ( bool gracefully ) { 10145shutdown_ssl ( socket_ , gracefully ); 10146shutdown_socket ( socket_ ); 10147close_socket ( socket_ ); 10148} 10149 10150bool ClientImpl :: read_response_line ( Stream & strm , const Request & req , 10151Response & res , 10152bool skip_100_continue ) const { 10153std:: array < char , 2048 > buf {}; 10154 10155detail:: stream_line_reader line_reader ( strm , buf . data (), buf . size ()); 10156 10157if (! line_reader . getline ()) { return false; } 10158 10159if (!detail:: parse_status_line ( line_reader . ptr (), res . version , res . status , 10160res . reason )) { 10161return req . method == "CONNECT" ; 10162} 10163 10164// Ignore '100 Continue' (only when not using Expect: 100-continue explicitly) 10165while ( skip_100_continue && res . status == StatusCode :: Continue_100 ) { 10166if (! line_reader . getline ()) { return false; } // CRLF 10167if (! line_reader . getline ()) { return false; } // next response line 10168 10169if (!detail:: parse_status_line ( line_reader . ptr (), res . version , res . status , 10170res . reason )) { 10171return false; 10172} 10173} 10174 10175return true; 10176} 10177 10178bool ClientImpl :: send ( Request & req , Response & res , Error & error ) { 10179std:: lock_guard < std:: recursive_mutex > request_mutex_guard ( request_mutex_ ); 10180auto ret = send_ ( req , res , error ); 10181if ( error == Error :: SSLPeerCouldBeClosed_ ) { 10182assert (! ret ); 10183ret = send_ ( req , res , error ); 10184// If still failing with SSLPeerCouldBeClosed_, convert to Read error 10185if ( error == Error :: SSLPeerCouldBeClosed_ ) { error = Error :: Read ; } 10186} 10187return ret ; 10188} 10189 10190bool ClientImpl :: send_ ( Request & req , Response & res , Error & error ) { 10191{ 10192std:: lock_guard < std:: mutex > guard ( socket_mutex_ ); 10193 10194// Set this to false immediately - if it ever gets set to true by the end 10195// of the request, we know another thread instructed us to close the 10196// socket. 10197socket_should_be_closed_when_request_is_done_ = false; 10198 10199auto is_alive = false; 10200if ( socket_ . is_open ()) { 10201is_alive = detail:: is_socket_alive ( socket_ . sock ); 10202 10203#ifdef CPPHTTPLIB_SSL_ENABLED 10204if ( is_alive && is_ssl ()) { 10205if (tls:: is_peer_closed ( socket_ . ssl , socket_ . sock )) { 10206is_alive = false; 10207} 10208} 10209#endif 10210 10211if (! is_alive ) { 10212// Peer seems gone — non-graceful shutdown to avoid SIGPIPE. 10213disconnect ( /*gracefully=*/ false); 10214} 10215} 10216 10217if (! is_alive ) { 10218if (! ensure_socket_connection ( socket_ , error )) { 10219output_error_log ( error , & req ); 10220return false; 10221} 10222 10223{ 10224auto success = true; 10225if (! setup_proxy_connection ( socket_ , req . start_time_ , res , success , 10226error )) { 10227if (! success ) { output_error_log ( error , & req ); } 10228return success ; 10229} 10230} 10231} 10232 10233// Mark the current socket as being in use so that it cannot be closed by 10234// anyone else while this request is ongoing, even though we will be 10235// releasing the mutex. 10236if ( socket_requests_in_flight_ > 1 ) { 10237assert ( socket_requests_are_from_thread_ == std::this_thread:: get_id ()); 10238} 10239socket_requests_in_flight_ += 1 ; 10240socket_requests_are_from_thread_ = std::this_thread:: get_id (); 10241} 10242 10243for (const auto & header : default_headers_ ) { 10244if ( req . headers . find ( header . first ) == req . headers . end ()) { 10245req . headers . insert ( header ); 10246} 10247} 10248 10249auto ret = false; 10250auto close_connection = ! keep_alive_ ; 10251 10252auto se = detail:: scope_exit ([ & ]() { 10253// Briefly lock mutex in order to mark that a request is no longer ongoing 10254std:: lock_guard < std:: mutex > guard ( socket_mutex_ ); 10255socket_requests_in_flight_ -= 1 ; 10256if ( socket_requests_in_flight_ <= 0 ) { 10257assert ( socket_requests_in_flight_ == 0 ); 10258socket_requests_are_from_thread_ = std::thread:: id (); 10259} 10260 10261if ( socket_should_be_closed_when_request_is_done_ || close_connection || 10262! ret ) { 10263disconnect ( /*gracefully=*/ true); 10264} 10265}); 10266 10267ret = process_socket ( socket_ , req . start_time_ , [ & ]( Stream & strm ) { 10268return handle_request ( strm , req , res , close_connection , error ); 10269}); 10270 10271if (! ret ) { 10272if ( error == Error :: Success ) { 10273error = Error :: Unknown ; 10274output_error_log ( error , & req ); 10275} 10276} 10277 10278return ret ; 10279} 10280 10281Result ClientImpl :: send (const Request & req ) { 10282auto req2 = req ; 10283return send_ (std:: move ( req2 )); 10284} 10285 10286Result ClientImpl :: send_ ( Request && req ) { 10287auto res = detail:: make_unique < Response > (); 10288auto error = Error :: Success ; 10289auto ret = send ( req , * res , error ); 10290#ifdef CPPHTTPLIB_SSL_ENABLED 10291return Result { ret ? std:: move ( res ) : nullptr , error , std:: move ( req . headers ), 10292last_ssl_error_ , last_backend_error_ }; 10293#else 10294return Result { ret ? std:: move ( res ) : nullptr , error , std:: move ( req . headers )}; 10295#endif 10296} 10297 10298void ClientImpl :: prepare_default_headers ( Request & r , bool for_stream , 10299const std:: string & ct ) { 10300( void ) for_stream ; 10301// Default headers are meant for the origin and may carry its credentials, so 10302// keep them off the CONNECT request the proxy reads. 10303if ( r . method != "CONNECT" ) { 10304for (const auto & header : default_headers_ ) { 10305if (! r . has_header ( header . first )) { r . headers . insert ( header ); } 10306} 10307} 10308 10309// RFC 9110 5.3 recommends sending control data such as Host first, so 10310// prepend it rather than appending it after the caller's own fields. 10311if (! r . has_header ( "Host" )) { 10312r . headers . emplace_front ( 10313"Host" , detail:: make_default_host_header_value ( host_ , port_ , is_ssl (), 10314address_family_ )); 10315} 10316 10317if (! r . has_header ( "Accept" )) { r . headers . emplace ( "Accept" , "*/*" ); } 10318 10319if (! r . content_receiver ) { 10320if (! r . has_header ( "Accept-Encoding" )) { 10321std:: string accept_encoding ; 10322#ifdef CPPHTTPLIB_BROTLI_SUPPORT 10323accept_encoding = "br" ; 10324#endif 10325#ifdef CPPHTTPLIB_ZLIB_SUPPORT 10326if (! accept_encoding . empty ()) { accept_encoding += ", " ; } 10327accept_encoding += "gzip, deflate" ; 10328#endif 10329#ifdef CPPHTTPLIB_ZSTD_SUPPORT 10330if (! accept_encoding . empty ()) { accept_encoding += ", " ; } 10331accept_encoding += "zstd" ; 10332#endif 10333r . set_header ( "Accept-Encoding" , accept_encoding ); 10334} 10335 10336detail:: add_default_user_agent_header ( r ); 10337} 10338 10339if (! r . body . empty ()) { 10340if (! ct . empty () && ! r . has_header ( "Content-Type" )) { 10341r . headers . emplace ( "Content-Type" , ct ); 10342} 10343if (! r . has_header ( "Content-Length" )) { 10344r . headers . emplace ( "Content-Length" , std:: to_string ( r . body . size ())); 10345} 10346} 10347} 10348 10349ClientImpl :: StreamHandle 10350ClientImpl :: open_stream (const std:: string & method , const std:: string & path , 10351const Params & params , const Headers & headers , 10352const std:: string & body , 10353const std:: string & content_type ) { 10354StreamHandle handle ; 10355handle . response = detail:: make_unique < Response > (); 10356handle . error = Error :: Success ; 10357 10358// Encode the target exactly like the buffered send path does, so that the 10359// same `path` produces the same request line through either API. 10360auto raw_query_path = 10361params . empty () ? path : append_query_params ( path , params ); 10362auto query_path = detail:: encode_request_target ( raw_query_path , path_encode_ ); 10363 10364handle . connection_ = detail:: make_unique < ClientConnection > (); 10365 10366{ 10367std:: lock_guard < std:: mutex > guard ( socket_mutex_ ); 10368 10369auto is_alive = false; 10370if ( socket_ . is_open ()) { 10371is_alive = detail:: is_socket_alive ( socket_ . sock ); 10372#ifdef CPPHTTPLIB_SSL_ENABLED 10373if ( is_alive && is_ssl ()) { 10374if (tls:: is_peer_closed ( socket_ . ssl , socket_ . sock )) { 10375is_alive = false; 10376} 10377} 10378#endif 10379if (! is_alive ) { disconnect ( /*gracefully=*/ false); } 10380} 10381 10382if (! is_alive ) { 10383if (! ensure_socket_connection ( socket_ , handle . error )) { 10384handle . response . reset (); 10385return handle ; 10386} 10387 10388{ 10389auto success = true; 10390auto start_time = std::chrono::steady_clock:: now (); 10391if (! setup_proxy_connection ( socket_ , start_time , * handle . response , 10392success , handle . error )) { 10393if (! success ) { handle . response . reset (); } 10394return handle ; 10395} 10396} 10397} 10398 10399transfer_socket_ownership_to_handle ( handle ); 10400} 10401 10402#ifdef CPPHTTPLIB_SSL_ENABLED 10403if ( is_ssl () && handle . connection_ -> session ) { 10404handle . socket_stream_ = detail:: make_unique < detail:: SSLSocketStream > ( 10405handle . connection_ -> sock , handle . connection_ -> session , 10406read_timeout_sec_ , read_timeout_usec_ , write_timeout_sec_ , 10407write_timeout_usec_ ); 10408} else { 10409handle . socket_stream_ = detail:: make_unique < detail:: SocketStream > ( 10410handle . connection_ -> sock , read_timeout_sec_ , read_timeout_usec_ , 10411write_timeout_sec_ , write_timeout_usec_ ); 10412} 10413#else 10414handle . socket_stream_ = detail:: make_unique < detail:: SocketStream > ( 10415handle . connection_ -> sock , read_timeout_sec_ , read_timeout_usec_ , 10416write_timeout_sec_ , write_timeout_usec_ ); 10417#endif 10418handle . stream_ = handle . socket_stream_ . get (); 10419 10420Request req ; 10421req . method = method ; 10422req . path = query_path ; 10423req . headers = headers ; 10424req . body = body ; 10425 10426prepare_default_headers ( req , true, content_type ); 10427 10428auto & strm = * handle . stream_ ; 10429if (detail:: write_request_line ( strm , req . method , req . path ) < 0 ) { 10430handle . error = Error :: Write ; 10431handle . response . reset (); 10432return handle ; 10433} 10434 10435if (!detail:: check_and_write_headers ( strm , req . headers , header_writer_ , 10436handle . error )) { 10437handle . response . reset (); 10438return handle ; 10439} 10440 10441if (! body . empty ()) { 10442if ( strm . write ( body . data (), body . size ()) < 0 ) { 10443handle . error = Error :: Write ; 10444handle . response . reset (); 10445return handle ; 10446} 10447} 10448 10449if (! read_response_line ( strm , req , * handle . response ) || 10450!detail:: read_headers ( strm , handle . response -> headers )) { 10451handle . error = Error :: Read ; 10452handle . response . reset (); 10453return handle ; 10454} 10455 10456// Same framing check as ClientImpl::process_request(). A HEAD or bodyless 10457// (204/304) response legitimately carries framing headers with no body. 10458if ( method != "HEAD" && 10459handle . response -> status != StatusCode :: NoContent_204 && 10460handle . response -> status != StatusCode :: NotModified_304 && 10461detail:: has_conflicting_content_length ( handle . response -> headers )) { 10462handle . error = Error :: Read ; 10463handle . response . reset (); 10464return handle ; 10465} 10466 10467handle . body_reader_ . stream = handle . stream_ ; 10468handle . body_reader_ . payload_max_length = payload_max_length_ ; 10469 10470if ( handle . response -> has_header ( "Content-Length" )) { 10471bool is_invalid = false; 10472auto content_length = detail:: get_header_value_u64 ( 10473handle . response -> headers , "Content-Length" , 0 , 0 , is_invalid ); 10474if ( is_invalid ) { 10475handle . error = Error :: Read ; 10476handle . response . reset (); 10477return handle ; 10478} 10479handle . body_reader_ . has_content_length = true; 10480handle . body_reader_ . content_length = content_length ; 10481} 10482 10483handle . body_reader_ . chunked = 10484detail:: is_chunked_transfer_encoding ( handle . response -> headers ); 10485 10486auto content_encoding = detail:: get_combined_header_value ( 10487handle . response -> headers , "Content-Encoding" ); 10488if (! content_encoding . empty ()) { 10489// Same policy as prepare_content_receiver(): reject a coding we know about 10490// but were not built with, pass an unrecognized one through as-is. 10491handle . decompressor_ = detail:: create_decompressor ( content_encoding ); 10492if (! handle . decompressor_ ) { 10493if (detail:: is_known_content_encoding ( content_encoding )) { 10494handle . error = Error :: UnsupportedContentEncoding ; 10495handle . response . reset (); 10496return handle ; 10497} 10498} else if (! handle . decompressor_ -> is_valid ()) { 10499handle . error = Error :: Compression ; 10500handle . response . reset (); 10501return handle ; 10502} 10503} 10504 10505return handle ; 10506} 10507 10508ssize_t ClientImpl :: StreamHandle :: read ( char * buf , size_t len ) { 10509if (! is_valid () || ! response ) { return -1 ; } 10510 10511if ( decompressor_ ) { return read_with_decompression ( buf , len ); } 10512auto n = detail:: read_body_content ( stream_ , body_reader_ , buf , len ); 10513 10514if ( n <= 0 && body_reader_ . chunked && ! trailers_parsed_ && stream_ ) { 10515trailers_parsed_ = true; 10516if ( body_reader_ . chunked_decoder ) { 10517if (! body_reader_ . chunked_decoder -> parse_trailers_into ( 10518response -> trailers , response -> headers )) { 10519return n ; 10520} 10521} else { 10522detail:: ChunkedDecoder dec ( * stream_ ); 10523if (! dec . parse_trailers_into ( response -> trailers , response -> headers )) { 10524return n ; 10525} 10526} 10527} 10528 10529return n ; 10530} 10531 10532ssize_t ClientImpl :: StreamHandle :: read_with_decompression ( char * buf , 10533size_t len ) { 10534if ( decompress_offset_ < decompress_buffer_ . size ()) { 10535auto available = decompress_buffer_ . size () - decompress_offset_ ; 10536auto to_copy = (std:: min )( len , available ); 10537std:: memcpy ( buf , decompress_buffer_ . data () + decompress_offset_ , to_copy ); 10538decompress_offset_ += to_copy ; 10539decompressed_bytes_read_ += to_copy ; 10540return static_cast < ssize_t > ( to_copy ); 10541} 10542 10543decompress_buffer_ . clear (); 10544decompress_offset_ = 0 ; 10545 10546constexpr size_t kDecompressionBufferSize = 8192 ; 10547char compressed_buf [ kDecompressionBufferSize ]; 10548 10549while (true) { 10550auto n = detail:: read_body_content ( stream_ , body_reader_ , compressed_buf , 10551sizeof( compressed_buf )); 10552 10553if ( n <= 0 ) { return n ; } 10554 10555bool decompress_ok = decompressor_ -> decompress ( 10556compressed_buf , static_cast < size_t > ( n ), 10557[ this ](const char * data , size_t data_len ) { 10558decompress_buffer_ . append ( data , data_len ); 10559auto limit = body_reader_ . payload_max_length ; 10560if ( decompressed_bytes_read_ + decompress_buffer_ . size () > limit ) { 10561return false; 10562} 10563return true; 10564}); 10565 10566if (! decompress_ok ) { 10567body_reader_ . last_error = Error :: Read ; 10568return -1 ; 10569} 10570 10571if (! decompress_buffer_ . empty ()) { break; } 10572} 10573 10574auto to_copy = (std:: min )( len , decompress_buffer_ . size ()); 10575std:: memcpy ( buf , decompress_buffer_ . data (), to_copy ); 10576decompress_offset_ = to_copy ; 10577decompressed_bytes_read_ += to_copy ; 10578return static_cast < ssize_t > ( to_copy ); 10579} 10580 10581void ClientImpl :: StreamHandle :: parse_trailers_if_needed () { 10582if (! response || ! stream_ || ! body_reader_ . chunked || trailers_parsed_ ) { 10583return; 10584} 10585 10586trailers_parsed_ = true; 10587 10588const auto bufsiz = 128 ; 10589char line_buf [ bufsiz ]; 10590detail:: stream_line_reader line_reader ( * stream_ , line_buf , bufsiz ); 10591 10592if (! line_reader . getline ()) { return; } 10593 10594if (!detail:: parse_trailers ( line_reader , response -> trailers , 10595response -> headers )) { 10596return; 10597} 10598} 10599 10600namespace detail { 10601 10602ChunkedDecoder :: ChunkedDecoder ( Stream & s ) : strm ( s ) {} 10603 10604ssize_t ChunkedDecoder :: read_payload ( char * buf , size_t len , 10605size_t & out_chunk_offset , 10606size_t & out_chunk_total ) { 10607if ( finished ) { return 0 ; } 10608 10609if ( chunk_remaining == 0 ) { 10610stream_line_reader lr ( strm , line_buf , sizeof ( line_buf )); 10611if (! lr . getline ()) { return -1 ; } 10612 10613// RFC 9112 §7.1: chunk-size = 1*HEXDIG 10614const char * p = lr . ptr (); 10615int v = 0 ; 10616if (! is_hex ( * p , v )) { return -1 ; } 10617 10618size_t chunk_len = 0 ; 10619constexpr size_t chunk_len_max = (std:: numeric_limits < size_t > :: max )(); 10620for (; is_hex ( * p , v ); ++ p ) { 10621if ( chunk_len > ( chunk_len_max >> 4 )) { return -1 ; } 10622chunk_len = ( chunk_len << 4 ) | static_cast < size_t > ( v ); 10623} 10624 10625while ( is_space_or_tab ( * p )) { 10626++ p ; 10627} 10628if ( * p != '\0' && * p != ';' && * p != '\r' && * p != '\n' ) { return -1 ; } 10629 10630if ( chunk_len == 0 ) { 10631chunk_remaining = 0 ; 10632finished = true; 10633out_chunk_offset = 0 ; 10634out_chunk_total = 0 ; 10635return 0 ; 10636} 10637 10638chunk_remaining = chunk_len ; 10639last_chunk_total = chunk_remaining ; 10640last_chunk_offset = 0 ; 10641} 10642 10643auto to_read = (std:: min )( chunk_remaining , len ); 10644auto n = strm . read ( buf , to_read ); 10645if ( n <= 0 ) { return -1 ; } 10646 10647auto offset_before = last_chunk_offset ; 10648last_chunk_offset += static_cast < size_t > ( n ); 10649chunk_remaining -= static_cast < size_t > ( n ); 10650 10651out_chunk_offset = offset_before ; 10652out_chunk_total = last_chunk_total ; 10653 10654if ( chunk_remaining == 0 ) { 10655stream_line_reader lr ( strm , line_buf , sizeof ( line_buf )); 10656if (! lr . getline ()) { return -1 ; } 10657if (std:: strcmp ( lr . ptr (), "\r\n" ) != 0 ) { return -1 ; } 10658} 10659 10660return n ; 10661} 10662 10663bool ChunkedDecoder :: parse_trailers_into ( Headers & dest , 10664const Headers & src_headers ) { 10665stream_line_reader lr ( strm , line_buf , sizeof ( line_buf )); 10666if (! lr . getline ()) { return false; } 10667return parse_trailers ( lr , dest , src_headers ); 10668} 10669 10670} // namespace detail 10671 10672void 10673ClientImpl :: transfer_socket_ownership_to_handle ( StreamHandle & handle ) { 10674handle . connection_ -> sock = socket_ . sock ; 10675#ifdef CPPHTTPLIB_SSL_ENABLED 10676handle . connection_ -> session = socket_ . ssl ; 10677socket_ . ssl = nullptr ; 10678#endif 10679socket_ . sock = INVALID_SOCKET ; 10680} 10681 10682bool ClientImpl :: handle_request ( Stream & strm , Request & req , 10683Response & res , bool close_connection , 10684Error & error ) { 10685if ( req . path . empty ()) { 10686error = Error :: Connection ; 10687output_error_log ( error , & req ); 10688return false; 10689} 10690 10691auto req_save = req ; 10692 10693bool ret ; 10694 10695if (! is_ssl () && is_proxy_enabled_for_host ( host_ )) { 10696auto req2 = req ; 10697req2 . path = "http://" + 10698detail:: make_host_and_port_string ( host_ , port_ , false) + 10699req . path ; 10700ret = process_request ( strm , req2 , res , close_connection , error ); 10701req = std:: move ( req2 ); 10702req . path = req_save . path ; 10703} else { 10704ret = process_request ( strm , req , res , close_connection , error ); 10705} 10706 10707if (! ret ) { return false; } 10708 10709if (detail:: has_header_token ( res . headers , "Connection" , "close" ) || 10710( res . version == "HTTP/1.0" && res . reason != "Connection established" )) { 10711// NOTE: this requires a not-entirely-obvious chain of calls to be correct 10712// for this to be safe. 10713 10714// This is safe to call because handle_request is only called by send_ 10715// which locks the request mutex during the process. It would be a bug 10716// to call it from a different thread since it's a thread-safety issue 10717// to do these things to the socket if another thread is using the socket. 10718std:: lock_guard < std:: mutex > guard ( socket_mutex_ ); 10719disconnect ( /*gracefully=*/ true); 10720} 10721 10722if ( 300 < res . status && res . status < 400 && follow_location_ ) { 10723req = std:: move ( req_save ); 10724ret = redirect ( req , res , error ); 10725} 10726 10727#ifdef CPPHTTPLIB_SSL_ENABLED 10728if (( res . status == StatusCode :: Unauthorized_401 || 10729res . status == StatusCode :: ProxyAuthenticationRequired_407 ) && 10730req . authorization_count_ < 5 ) { 10731auto is_proxy = res . status == StatusCode :: ProxyAuthenticationRequired_407 ; 10732 10733// Only retry when the 407 actually came from a proxy hop: plain HTTP 10734// through an enabled proxy. HTTPS via CONNECT tunnels the 407 from the 10735// origin (#2457); direct/bypassed origins have no proxy hop at all. 10736if ( is_proxy && !(! is_ssl () && is_proxy_enabled_for_host ( host_ ))) { 10737return ret ; 10738} 10739 10740const auto & username = 10741is_proxy ? proxy_digest_auth_username_ : digest_auth_username_ ; 10742const auto & password = 10743is_proxy ? proxy_digest_auth_password_ : digest_auth_password_ ; 10744 10745if (! username . empty () && ! password . empty ()) { 10746std:: map < std:: string , std:: string > auth ; 10747if (detail:: parse_www_authenticate ( res , auth , is_proxy )) { 10748Request new_req = req ; 10749new_req . authorization_count_ += 1 ; 10750new_req . headers . erase ( is_proxy ? "Proxy-Authorization" 10751: "Authorization" ); 10752new_req . headers . insert (detail:: make_digest_authentication_header ( 10753req , auth , new_req . authorization_count_ , detail:: random_string ( 10 ), 10754username , password , is_proxy )); 10755 10756Response new_res ; 10757 10758ret = send ( new_req , new_res , error ); 10759if ( ret ) { res = std:: move ( new_res ); } 10760} 10761} 10762} 10763#endif 10764 10765return ret ; 10766} 10767 10768bool ClientImpl :: redirect ( Request & req , Response & res , Error & error ) { 10769if ( req . redirect_count_ == 0 ) { 10770error = Error :: ExceedRedirectCount ; 10771output_error_log ( error , & req ); 10772return false; 10773} 10774 10775auto location = res . get_header_value ( "location" ); 10776if ( location . empty ()) { return false; } 10777 10778detail:: UrlComponents uc ; 10779if (!detail:: parse_url ( location , uc )) { return false; } 10780 10781// Only follow http/https redirects 10782if (! uc . scheme . empty () && uc . scheme != "http" && uc . scheme != "https" ) { 10783return false; 10784} 10785 10786auto scheme = is_ssl () ? "https" : "http" ; 10787 10788auto next_scheme = std:: move ( uc . scheme ); 10789auto next_host = std:: move ( uc . host ); 10790auto port_str = std:: move ( uc . port ); 10791auto next_path = std:: move ( uc . path ); 10792auto next_query = std:: move ( uc . query ); 10793 10794auto next_port = port_ ; 10795if (! port_str . empty ()) { 10796if (!detail:: parse_port ( port_str , next_port )) { return false; } 10797} else if (! next_scheme . empty ()) { 10798next_port = next_scheme == "https" ? 443 : 80 ; 10799} 10800 10801if ( next_scheme . empty ()) { next_scheme = scheme ; } 10802if ( next_host . empty ()) { next_host = host_ ; } 10803if ( next_path . empty ()) { next_path = "/" ; } 10804 10805auto path = decode_path_component ( next_path ) + next_query ; 10806 10807// Same host redirect - use current client 10808if ( next_scheme == scheme && next_host == host_ && next_port == port_ ) { 10809return detail:: redirect ( * this , req , res , path , location , error ); 10810} 10811 10812// Cross-host/scheme redirect - create new client with robust setup 10813return create_redirect_client ( next_scheme , next_host , next_port , req , res , 10814path , location , error ); 10815} 10816 10817// New method for robust redirect client creation 10818bool ClientImpl :: create_redirect_client ( 10819const std:: string & scheme , const std:: string & host , int port , Request & req , 10820Response & res , const std:: string & path , const std:: string & location , 10821Error & error ) { 10822// Determine if we need SSL 10823auto need_ssl = ( scheme == "https" ); 10824 10825// Clean up request headers that are host/client specific 10826// Remove headers that should not be carried over to new host 10827auto headers_to_remove = std:: vector < std:: string > { 10828"Host" , "Proxy-Authorization" , "Authorization" , "Cookie" , "Cookie2" }; 10829 10830for (const auto & header_name : headers_to_remove ) { 10831auto it = req . headers . find ( header_name ); 10832while ( it != req . headers . end ()) { 10833it = req . headers . erase ( it ); 10834it = req . headers . find ( header_name ); 10835} 10836} 10837 10838// Create appropriate client type and handle redirect 10839if ( need_ssl ) { 10840#ifdef CPPHTTPLIB_SSL_ENABLED 10841// Create SSL client for HTTPS redirect 10842SSLClient redirect_client ( host , port ); 10843 10844// Setup basic client configuration first 10845setup_redirect_client ( redirect_client ); 10846 10847redirect_client . enable_server_certificate_verification ( 10848server_certificate_verification_ ); 10849redirect_client . enable_server_hostname_verification ( 10850server_hostname_verification_ ); 10851redirect_client . system_ca_mode_ = system_ca_mode_ ; 10852 10853// Transfer CA certificate to redirect client 10854if (! ca_cert_pem_ . empty ()) { 10855redirect_client . load_ca_cert_store ( ca_cert_pem_ . c_str (), 10856ca_cert_pem_ . size ()); 10857} 10858if (! ca_cert_file_path_ . empty ()) { 10859redirect_client . set_ca_cert_path ( ca_cert_file_path_ , ca_cert_dir_path_ ); 10860} 10861 10862// Client certificates are set through constructor for SSLClient 10863// NOTE: SSLClient constructor already takes client_cert_path and 10864// client_key_path so we need to create it properly if client certs are 10865// needed 10866 10867// Execute the redirect 10868return detail:: redirect ( redirect_client , req , res , path , location , error ); 10869#else 10870// SSL not supported - set appropriate error 10871error = Error :: SSLConnection ; 10872output_error_log ( error , & req ); 10873return false; 10874#endif 10875} else { 10876// HTTP redirect 10877ClientImpl redirect_client ( host , port ); 10878 10879// Setup client with robust configuration 10880setup_redirect_client ( redirect_client ); 10881 10882// Execute the redirect 10883return detail:: redirect ( redirect_client , req , res , path , location , error ); 10884} 10885} 10886 10887// New method for robust client setup (based on basic_manual_redirect.cpp 10888// logic) 10889template < typename ClientType > 10890void ClientImpl :: setup_redirect_client ( ClientType & client ) { 10891// Copy basic settings first 10892client . set_connection_timeout ( connection_timeout_sec_ ); 10893client . set_read_timeout ( read_timeout_sec_ , read_timeout_usec_ ); 10894client . set_write_timeout ( write_timeout_sec_ , write_timeout_usec_ ); 10895client . set_keep_alive ( keep_alive_ ); 10896client . set_follow_location ( 10897true); // Enable redirects to handle multi-step redirects 10898client . set_path_encode ( path_encode_ ); 10899client . set_compress ( compress_ ); 10900client . set_decompress ( decompress_ ); 10901 10902// NOTE: Authentication credentials (basic auth, bearer token, digest auth) 10903// are intentionally NOT copied to the redirect client. Per RFC 9110 Section 10904// 15.4, credentials must not be forwarded when redirecting to a different 10905// host. This function is only called for cross-host redirects; same-host 10906// redirects are handled directly in ClientImpl::redirect(). 10907 10908// Copy the proxy configuration unconditionally; the per-target bypass is 10909// re-evaluated at send time, so a later hop to a non-bypassed host can 10910// still use the proxy. 10911client . no_proxy_entries_ = no_proxy_entries_ ; 10912if (! proxy_host_ . empty () && proxy_port_ != -1 ) { 10913client . set_proxy ( proxy_host_ , proxy_port_ ); 10914 10915if (! proxy_basic_auth_username_ . empty ()) { 10916client . set_proxy_basic_auth ( proxy_basic_auth_username_ , 10917proxy_basic_auth_password_ ); 10918} 10919if (! proxy_bearer_token_auth_token_ . empty ()) { 10920client . set_proxy_bearer_token_auth ( proxy_bearer_token_auth_token_ ); 10921} 10922#ifdef CPPHTTPLIB_SSL_ENABLED 10923if (! proxy_digest_auth_username_ . empty ()) { 10924client . set_proxy_digest_auth ( proxy_digest_auth_username_ , 10925proxy_digest_auth_password_ ); 10926} 10927#endif 10928} 10929 10930// Copy network and socket settings 10931client . set_address_family ( address_family_ ); 10932client . set_tcp_nodelay ( tcp_nodelay_ ); 10933client . set_ipv6_v6only ( ipv6_v6only_ ); 10934if ( socket_options_ ) { client . set_socket_options ( socket_options_ ); } 10935if (! interface_ . empty ()) { client . set_interface ( interface_ ); } 10936 10937// Copy logging and headers 10938if ( logger_ ) { client . set_logger ( logger_ ); } 10939if ( error_logger_ ) { client . set_error_logger ( error_logger_ ); } 10940 10941// NOTE: DO NOT copy default_headers_ as they may contain stale Host headers 10942// Each new client should generate its own headers based on its target host 10943} 10944 10945bool ClientImpl :: write_content_with_provider ( Stream & strm , 10946const Request & req , 10947Error & error ) const { 10948auto is_shutting_down = []() { return false; }; 10949 10950if ( req . is_chunked_content_provider_ ) { 10951auto compressor = compress_ ? detail:: create_compressor (). first 10952: std:: unique_ptr < detail:: compressor > (); 10953if (! compressor ) { 10954compressor = detail:: make_unique < detail:: nocompressor > (); 10955} 10956 10957return detail:: write_content_chunked ( strm , req . content_provider_ , 10958is_shutting_down , * compressor , error ); 10959} else { 10960return detail:: write_content_with_progress ( 10961strm , req . content_provider_ , 0 , req . content_length_ , is_shutting_down , 10962req . upload_progress , error ); 10963} 10964} 10965 10966bool ClientImpl :: write_request ( Stream & strm , Request & req , 10967bool close_connection , Error & error , 10968bool skip_body ) { 10969// Prepare additional headers 10970if ( close_connection ) { 10971if (! req . has_header ( "Connection" )) { 10972req . set_header ( "Connection" , "close" ); 10973} 10974} 10975 10976std:: string ct_for_defaults ; 10977if (! req . has_header ( "Content-Type" ) && ! req . body . empty ()) { 10978ct_for_defaults = "text/plain" ; 10979} 10980prepare_default_headers ( req , false, ct_for_defaults ); 10981 10982if ( req . body . empty ()) { 10983if ( req . content_provider_ ) { 10984if (! req . is_chunked_content_provider_ ) { 10985if (! req . has_header ( "Content-Length" )) { 10986auto length = std:: to_string ( req . content_length_ ); 10987req . set_header ( "Content-Length" , length ); 10988} 10989} 10990} else { 10991if ( req . method == "POST" || req . method == "PUT" || 10992req . method == "PATCH" ) { 10993req . set_header ( "Content-Length" , "0" ); 10994} 10995} 10996} 10997 10998// A CONNECT request is read by the proxy; everything sent through the tunnel 10999// it opens is read by the origin. Each credential goes only to its own hop. 11000auto is_connect = req . method == "CONNECT" ; 11001 11002if (! is_connect && ! req . has_header ( "Authorization" )) { 11003if (! basic_auth_password_ . empty () || ! basic_auth_username_ . empty ()) { 11004req . headers . insert ( make_basic_authentication_header ( 11005basic_auth_username_ , basic_auth_password_ , false)); 11006} else if (! bearer_token_auth_token_ . empty ()) { 11007req . headers . insert ( make_bearer_token_authentication_header ( 11008bearer_token_auth_token_ , false)); 11009} 11010} 11011 11012// Proxy-Authorization is only sent when the proxy reads this message — 11013// otherwise NO_PROXY-matched requests, and requests inside a TLS tunnel, 11014// would leak proxy credentials to the destination server. 11015if ( is_proxy_enabled_for_host ( host_ ) && (! is_ssl () || is_connect )) { 11016if (! proxy_basic_auth_username_ . empty () && 11017! proxy_basic_auth_password_ . empty () && 11018! req . has_header ( "Proxy-Authorization" )) { 11019req . headers . insert ( make_basic_authentication_header ( 11020proxy_basic_auth_username_ , proxy_basic_auth_password_ , true)); 11021} 11022if (! proxy_bearer_token_auth_token_ . empty () && 11023! req . has_header ( "Proxy-Authorization" )) { 11024req . headers . insert ( make_bearer_token_authentication_header ( 11025proxy_bearer_token_auth_token_ , true)); 11026} 11027} 11028 11029// Request line and headers 11030{ 11031detail:: BufferStream bstrm ; 11032 11033// Extract the query from req.path. The encoding itself is delegated to 11034// `encode_request_target`; the raw query is still needed here to decide 11035// between populating `req.params` from it and falling back to building a 11036// query out of caller-supplied `req.params`. 11037auto query_pos = req . path . find ( '?' ); 11038auto query_part = query_pos == std::string:: npos 11039? std:: string () 11040: req . path . substr ( query_pos + 1 ); 11041 11042auto path_with_query = 11043detail:: encode_request_target ( req . path , path_encode_ ); 11044 11045if (! query_part . empty ()) { 11046// The query already came in through `req.path`; still populate 11047// `req.params` for handlers/users who read them. 11048detail:: parse_query_text ( query_part , req . params ); 11049} else if (! req . params . empty ()) { 11050// No query in `req.path`; build one from `req.params` so existing 11051// callers that pass `Params` separately continue to work. 11052path_with_query = append_query_params ( path_with_query , req . params ); 11053} 11054 11055// Write request line and headers 11056if (detail:: write_request_line ( bstrm , req . method , path_with_query ) < 0 ) { 11057// A rejected target (e.g. CR/LF smuggled in via a decoded redirect 11058// Location under set_path_encode(false)) must fail the request cleanly 11059// instead of emitting a request-line-less, header-injecting request. 11060error = Error :: Write ; 11061output_error_log ( error , & req ); 11062return false; 11063} 11064if (!detail:: check_and_write_headers ( bstrm , req . headers , header_writer_ , 11065error )) { 11066output_error_log ( error , & req ); 11067return false; 11068} 11069 11070// Flush buffer 11071auto & data = bstrm . get_buffer (); 11072if (!detail:: write_data ( strm , data . data (), data . size ())) { 11073error = Error :: Write ; 11074output_error_log ( error , & req ); 11075return false; 11076} 11077} 11078 11079// After sending request line and headers, wait briefly for an early server 11080// response (e.g. 4xx) and avoid sending a potentially large request body 11081// unnecessarily. This workaround is only enabled on Windows because Unix 11082// platforms surface write errors (EPIPE) earlier; on Windows kernel send 11083// buffering can accept large writes even when the peer already responded. 11084// Check the stream first (which covers SSL via `is_readable()`), then 11085// fall back to select on the socket. Only perform the wait for very large 11086// request bodies to avoid interfering with normal small requests and 11087// reduce side-effects. Poll briefly (up to 50ms as default) for an early 11088// response. Skip this check when using Expect: 100-continue, as the protocol 11089// handles early responses properly. 11090#if defined( _WIN32 ) 11091if (! skip_body && 11092req . body . size () > CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD && 11093req . path . size () > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH ) { 11094auto start = std::chrono::high_resolution_clock:: now (); 11095 11096for (;;) { 11097// Prefer socket-level readiness to avoid SSL_pending() false-positives 11098// from SSL internals. If the underlying socket is readable, assume an 11099// early response may be present. 11100auto sock = strm . socket (); 11101if ( sock != INVALID_SOCKET && detail:: select_read ( sock , 0 , 0 ) > 0 ) { 11102return false; 11103} 11104 11105// Fallback to stream-level check for non-socket streams or when the 11106// socket isn't reporting readable. Avoid using `is_readable()` for 11107// SSL, since `SSL_pending()` may report buffered records that do not 11108// indicate a complete application-level response yet. 11109if (! is_ssl () && strm . is_readable ()) { return false; } 11110 11111auto now = std::chrono::high_resolution_clock:: now (); 11112auto elapsed = 11113std::chrono:: duration_cast < std::chrono:: milliseconds > ( now - start ) 11114. count (); 11115if ( elapsed >= CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND ) { 11116break; 11117} 11118 11119std::this_thread:: sleep_for (std::chrono:: milliseconds ( 1 )); 11120} 11121} 11122#endif 11123 11124// Body 11125if ( skip_body ) { return true; } 11126 11127return write_request_body ( strm , req , error ); 11128} 11129 11130bool ClientImpl :: write_request_body ( Stream & strm , Request & req , 11131Error & error ) { 11132if ( req . body . empty ()) { 11133return write_content_with_provider ( strm , req , error ); 11134} 11135 11136if ( req . upload_progress ) { 11137auto body_size = req . body . size (); 11138size_t written = 0 ; 11139auto data = req . body . data (); 11140 11141while ( written < body_size ) { 11142size_t to_write = (std:: min )( CPPHTTPLIB_SEND_BUFSIZ , body_size - written ); 11143if (!detail:: write_data ( strm , data + written , to_write )) { 11144error = Error :: Write ; 11145output_error_log ( error , & req ); 11146return false; 11147} 11148written += to_write ; 11149 11150if (! req . upload_progress ( written , body_size )) { 11151error = Error :: Canceled ; 11152output_error_log ( error , & req ); 11153return false; 11154} 11155} 11156} else { 11157if (!detail:: write_data ( strm , req . body . data (), req . body . size ())) { 11158error = Error :: Write ; 11159output_error_log ( error , & req ); 11160return false; 11161} 11162} 11163 11164return true; 11165} 11166 11167std:: unique_ptr < Response > 11168ClientImpl :: send_with_content_provider_and_receiver ( 11169Request & req , const char * body , size_t content_length , 11170ContentProvider content_provider , 11171ContentProviderWithoutLength content_provider_without_length , 11172const std:: string & content_type , ContentReceiver content_receiver , 11173Error & error ) { 11174if (! content_type . empty ()) { req . set_header ( "Content-Type" , content_type ); } 11175 11176auto enc = compress_ 11177? detail:: create_compressor () 11178: std:: pair < std:: unique_ptr < detail:: compressor > , const char *> ( 11179nullptr , nullptr ); 11180 11181if ( enc . second ) { req . set_header ( "Content-Encoding" , enc . second ); } 11182 11183if ( enc . first && ! content_provider_without_length ) { 11184auto & compressor = enc . first ; 11185 11186if ( content_provider ) { 11187auto ok = true; 11188auto finished = false; 11189size_t offset = 0 ; 11190DataSink data_sink ; 11191 11192data_sink . write = [ & ](const char * data , size_t data_len ) -> bool { 11193if ( ok ) { 11194auto last = offset + data_len == content_length ; 11195 11196auto ret = compressor -> compress ( 11197data , data_len , last , 11198[ & ](const char * compressed_data , size_t compressed_data_len ) { 11199req . body . append ( compressed_data , compressed_data_len ); 11200return true; 11201}); 11202 11203if ( ret ) { 11204offset += data_len ; 11205} else { 11206ok = false; 11207} 11208} 11209return ok ; 11210}; 11211 11212// As in detail::write_content_with_progress(): the body is framed by 11213// content_length, so a provider that finishes early has truncated it. 11214// Stop and report that instead of calling the provider forever. 11215data_sink . done = [ & ]() { finished = true; }; 11216 11217while ( ok && ! finished && offset < content_length ) { 11218if (! content_provider ( offset , content_length - offset , data_sink )) { 11219error = Error :: Canceled ; 11220output_error_log ( error , & req ); 11221return nullptr ; 11222} 11223} 11224 11225// A short body here means either the provider stopped early or the 11226// compressor gave up. The branch below reports a failing compressor as 11227// Error::Compression, so keep the two distinguishable. 11228if ( offset < content_length ) { 11229error = ok ? Error :: Write : Error :: Compression ; 11230output_error_log ( error , & req ); 11231return nullptr ; 11232} 11233} else { 11234if (! compressor -> compress ( body , content_length , true, 11235[ & ](const char * data , size_t data_len ) { 11236req . body . append ( data , data_len ); 11237return true; 11238})) { 11239error = Error :: Compression ; 11240output_error_log ( error , & req ); 11241return nullptr ; 11242} 11243} 11244} else { 11245if ( content_provider ) { 11246req . content_length_ = content_length ; 11247req . content_provider_ = std:: move ( content_provider ); 11248req . is_chunked_content_provider_ = false; 11249} else if ( content_provider_without_length ) { 11250req . content_length_ = 0 ; 11251req . content_provider_ = detail:: ContentProviderAdapter ( 11252std:: move ( content_provider_without_length )); 11253req . is_chunked_content_provider_ = true; 11254req . set_header ( "Transfer-Encoding" , "chunked" ); 11255} else { 11256req . body . assign ( body , content_length ); 11257} 11258} 11259 11260if ( content_receiver ) { 11261req . content_receiver = 11262[ content_receiver ](const char * data , size_t data_length , 11263size_t /*offset*/ , size_t /*total_length*/ ) { 11264return content_receiver ( data , data_length ); 11265}; 11266} 11267 11268auto res = detail:: make_unique < Response > (); 11269return send ( req , * res , error ) ? std:: move ( res ) : nullptr ; 11270} 11271 11272Result ClientImpl :: send_with_content_provider_and_receiver ( 11273const std:: string & method , const std:: string & path , const Headers & headers , 11274const char * body , size_t content_length , ContentProvider content_provider , 11275ContentProviderWithoutLength content_provider_without_length , 11276const std:: string & content_type , ContentReceiver content_receiver , 11277UploadProgress progress ) { 11278Request req ; 11279req . method = method ; 11280req . headers = headers ; 11281req . path = path ; 11282req . upload_progress = std:: move ( progress ); 11283if ( max_timeout_msec_ > 0 ) { 11284req . start_time_ = std::chrono::steady_clock:: now (); 11285} 11286 11287auto error = Error :: Success ; 11288 11289auto res = send_with_content_provider_and_receiver ( 11290req , body , content_length , std:: move ( content_provider ), 11291std:: move ( content_provider_without_length ), content_type , 11292std:: move ( content_receiver ), error ); 11293 11294#ifdef CPPHTTPLIB_SSL_ENABLED 11295return Result {std:: move ( res ), error , std:: move ( req . headers ), last_ssl_error_ , 11296last_backend_error_ }; 11297#else 11298return Result {std:: move ( res ), error , std:: move ( req . headers )}; 11299#endif 11300} 11301 11302void ClientImpl :: output_log (const Request & req , 11303const Response & res ) const { 11304if ( logger_ ) { 11305std:: lock_guard < std:: mutex > guard ( logger_mutex_ ); 11306logger_ ( req , res ); 11307} 11308} 11309 11310void ClientImpl :: output_error_log (const Error & err , 11311const Request * req ) const { 11312if ( error_logger_ ) { 11313std:: lock_guard < std:: mutex > guard ( logger_mutex_ ); 11314error_logger_ ( err , req ); 11315} 11316} 11317 11318bool ClientImpl :: process_request ( Stream & strm , Request & req , 11319Response & res , bool close_connection , 11320Error & error ) { 11321// Auto-add Expect: 100-continue for large bodies 11322if ( CPPHTTPLIB_EXPECT_100_THRESHOLD > 0 && ! req . has_header ( "Expect" )) { 11323auto body_size = req . body . empty () ? req . content_length_ : req . body . size (); 11324if ( body_size >= CPPHTTPLIB_EXPECT_100_THRESHOLD ) { 11325req . set_header ( "Expect" , "100-continue" ); 11326} 11327} 11328 11329// Check for Expect: 100-continue 11330auto expect_100_continue = 11331detail:: has_header_token ( req . headers , "Expect" , "100-continue" ); 11332 11333// Send request (skip body if using Expect: 100-continue) 11334auto write_request_success = 11335write_request ( strm , req , close_connection , error , expect_100_continue ); 11336 11337#ifdef CPPHTTPLIB_SSL_ENABLED 11338if ( is_ssl () && ! expect_100_continue ) { 11339auto is_proxy_enabled = is_proxy_enabled_for_host ( host_ ); 11340if (! is_proxy_enabled ) { 11341if (tls:: is_peer_closed ( socket_ . ssl , socket_ . sock )) { 11342error = Error :: SSLPeerCouldBeClosed_ ; 11343output_error_log ( error , & req ); 11344return false; 11345} 11346} 11347} 11348#endif 11349 11350// Handle Expect: 100-continue. 11351// 11352// Wait for an interim/early response by attempting to read the status line 11353// under a short timeout, instead of trusting raw socket readability. Over 11354// TLS, post-handshake records (e.g. session tickets) make the socket 11355// readable without any HTTP response being available; relying on 11356// `select_read` there caused the body to be withheld forever and the 11357// request to fail with `Read` (#2458). If no status line arrives within the 11358// timeout, send the body anyway (matching curl's behavior). 11359auto status_line_read = false; 11360if ( expect_100_continue && write_request_success ) { 11361if ( CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND > 0 ) { 11362time_t sec = CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND / 1000 ; 11363time_t usec = ( CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND % 1000 ) * 1000 ; 11364strm . set_read_timeout ( sec , usec ); 11365status_line_read = read_response_line ( strm , req , res , false); 11366strm . set_read_timeout ( read_timeout_sec_ , read_timeout_usec_ ); 11367} 11368 11369if (! status_line_read ) { 11370// No interim response within the timeout: send the body and handle the 11371// response as usual. 11372if (! write_request_body ( strm , req , error )) { return false; } 11373expect_100_continue = false; // Switch to normal response handling 11374} 11375} 11376 11377// Receive response and headers 11378// When using Expect: 100-continue, don't auto-skip `100 Continue` response 11379if ((! status_line_read && 11380! read_response_line ( strm , req , res , ! expect_100_continue )) || 11381!detail:: read_headers ( strm , res . headers )) { 11382if ( write_request_success ) { error = Error :: Read ; } 11383output_error_log ( error , & req ); 11384return false; 11385} 11386 11387if (! write_request_success ) { return false; } 11388 11389// Handle Expect: 100-continue response 11390if ( expect_100_continue ) { 11391if ( res . status == StatusCode :: Continue_100 ) { 11392// Server accepted, send the body 11393if (! write_request_body ( strm , req , error )) { return false; } 11394 11395// Read the actual response 11396res . headers . clear (); 11397res . body . clear (); 11398if (! read_response_line ( strm , req , res ) || 11399!detail:: read_headers ( strm , res . headers )) { 11400error = Error :: Read ; 11401output_error_log ( error , & req ); 11402return false; 11403} 11404} 11405// If not 100 Continue, server returned an error; proceed with that response 11406} 11407 11408// Body 11409if (( res . status != StatusCode :: NoContent_204 ) && req . method != "HEAD" && 11410req . method != "CONNECT" ) { 11411// Reject ambiguous framing (RFC 9112 §6.3). Unlike a request, a response 11412// whose final transfer coding is not chunked is not ambiguous: its body 11413// runs until the server closes the connection, so it is not rejected. 11414// HEAD/204 are excluded above and a 304 carries no body. 11415if ( res . status != StatusCode :: NotModified_304 && 11416detail:: has_conflicting_content_length ( res . headers )) { 11417error = Error :: Read ; 11418output_error_log ( error , & req ); 11419return false; 11420} 11421 11422auto redirect = 300 < res . status && res . status < 400 && 11423res . status != StatusCode :: NotModified_304 && 11424follow_location_ ; 11425 11426if ( req . response_handler && ! redirect ) { 11427if (! req . response_handler ( res )) { 11428error = Error :: Canceled ; 11429output_error_log ( error , & req ); 11430return false; 11431} 11432} 11433 11434auto out = 11435req . content_receiver 11436? static_cast < ContentReceiverWithProgress > ( 11437[ & ](const char * buf , size_t n , size_t off , size_t len ) { 11438if ( redirect ) { return true; } 11439auto ret = req . content_receiver ( buf , n , off , len ); 11440if (! ret ) { 11441error = Error :: Canceled ; 11442output_error_log ( error , & req ); 11443} 11444return ret ; 11445}) 11446: static_cast < ContentReceiverWithProgress > ( 11447[ & ](const char * buf , size_t n , size_t /*off*/ , 11448size_t /*len*/ ) { 11449assert ( res . body . size () + n <= res . body . max_size ()); 11450if ( payload_max_length_ > 0 && 11451( res . body . size () >= payload_max_length_ || 11452n > payload_max_length_ - res . body . size ())) { 11453return false; 11454} 11455res . body . append ( buf , n ); 11456return true; 11457}); 11458 11459auto progress = [ & ]( size_t current , size_t total ) { 11460if (! req . download_progress || redirect ) { return true; } 11461auto ret = req . download_progress ( current , total ); 11462if (! ret ) { 11463error = Error :: Canceled ; 11464output_error_log ( error , & req ); 11465} 11466return ret ; 11467}; 11468 11469if ( res . has_header ( "Content-Length" )) { 11470if (! req . content_receiver ) { 11471auto len = res . get_header_value_u64 ( "Content-Length" ); 11472if ( len > res . body . max_size ()) { 11473error = Error :: Read ; 11474output_error_log ( error , & req ); 11475return false; 11476} 11477// Cap the reservation by payload_max_length_ to avoid OOM when a 11478// hostile or malformed server sends an enormous Content-Length. 11479// The actual body read below is bounded by payload_max_length_, 11480// so reserving more than that is never useful. 11481auto reserve_len = static_cast < size_t > ( len ); 11482if ( payload_max_length_ > 0 && reserve_len > payload_max_length_ ) { 11483reserve_len = payload_max_length_ ; 11484} 11485res . body . reserve ( reserve_len ); 11486} 11487} 11488 11489if ( res . status != StatusCode :: NotModified_304 ) { 11490auto content_status = 0 ; 11491auto max_length = (! has_payload_max_length_ && req . content_receiver ) 11492? (std:: numeric_limits < size_t > :: max )() 11493: payload_max_length_ ; 11494if (!detail:: read_content ( strm , res , max_length , content_status , 11495std:: move ( progress ), std:: move ( out ), 11496decompress_ )) { 11497if ( error != Error :: Canceled ) { 11498// Tell the caller apart from a plain read failure when the body could 11499// not be decoded because of its Content-Encoding. 11500switch ( content_status ) { 11501case StatusCode :: UnsupportedMediaType_415 : 11502error = Error :: UnsupportedContentEncoding ; 11503break; 11504case StatusCode :: InternalServerError_500 : 11505error = Error :: Compression ; 11506break; 11507default: error = Error :: Read ; break; 11508} 11509} 11510output_error_log ( error , & req ); 11511return false; 11512} 11513} 11514} 11515 11516// Log 11517output_log ( req , res ); 11518 11519return true; 11520} 11521 11522ContentProviderWithoutLength ClientImpl :: get_multipart_content_provider ( 11523const std:: string & boundary , const UploadFormDataItems & items , 11524const FormDataProviderItems & provider_items ) const { 11525size_t cur_item = 0 ; 11526size_t cur_start = 0 ; 11527// cur_item and cur_start are copied to within the std::function and 11528// maintain state between successive calls 11529return [ & , cur_item , cur_start ]( size_t offset , 11530DataSink & sink ) mutable -> bool { 11531if (! offset && ! items . empty ()) { 11532sink . os << detail:: serialize_multipart_formdata ( items , boundary , false); 11533return true; 11534} else if ( cur_item < provider_items . size ()) { 11535if (! cur_start ) { 11536const auto & begin = detail:: serialize_multipart_formdata_item_begin ( 11537provider_items [ cur_item ], boundary ); 11538offset += begin . size (); 11539cur_start = offset ; 11540sink . os << begin ; 11541} 11542 11543DataSink cur_sink ; 11544auto has_data = true; 11545cur_sink . write = sink . write ; 11546// Forward is_writable so a provider item asking whether it may keep 11547// going gets the outer sink's answer rather than the default `true`. 11548cur_sink . is_writable = sink . is_writable ; 11549cur_sink . done = [ & ]() { has_data = false; }; 11550 11551if (! provider_items [ cur_item ]. provider ( offset - cur_start , cur_sink )) { 11552return false; 11553} 11554 11555if (! has_data ) { 11556sink . os << detail:: serialize_multipart_formdata_item_end (); 11557cur_item ++ ; 11558cur_start = 0 ; 11559} 11560return true; 11561} else { 11562sink . os << detail:: serialize_multipart_formdata_finish ( boundary ); 11563sink . done (); 11564return true; 11565} 11566}; 11567} 11568 11569bool ClientImpl :: process_socket ( 11570const Socket & socket , 11571std::chrono:: time_point < std::chrono:: steady_clock > start_time , 11572std:: function < bool ( Stream & strm ) > callback ) { 11573return detail:: process_client_socket ( 11574socket . sock , read_timeout_sec_ , read_timeout_usec_ , write_timeout_sec_ , 11575write_timeout_usec_ , max_timeout_msec_ , start_time , std:: move ( callback )); 11576} 11577 11578bool ClientImpl :: is_ssl () const { return false; } 11579 11580Result ClientImpl :: Get (const std:: string & path , 11581DownloadProgress progress ) { 11582return Get ( path , Headers (), std:: move ( progress )); 11583} 11584 11585Result ClientImpl :: Get (const std:: string & path , const Params & params , 11586DownloadProgress progress ) { 11587return Get ( path , params , Headers (), std:: move ( progress )); 11588} 11589 11590Result ClientImpl :: Get (const std:: string & path , const Params & params , 11591const Headers & headers , 11592DownloadProgress progress ) { 11593if ( params . empty ()) { return Get ( path , headers ); } 11594 11595std:: string path_with_query = append_query_params ( path , params ); 11596return Get ( path_with_query , headers , std:: move ( progress )); 11597} 11598 11599Result ClientImpl :: Get (const std:: string & path , const Headers & headers , 11600DownloadProgress progress ) { 11601Request req ; 11602req . method = "GET" ; 11603req . path = path ; 11604req . headers = headers ; 11605req . download_progress = std:: move ( progress ); 11606if ( max_timeout_msec_ > 0 ) { 11607req . start_time_ = std::chrono::steady_clock:: now (); 11608} 11609 11610return send_ (std:: move ( req )); 11611} 11612 11613Result ClientImpl :: Get (const std:: string & path , 11614ContentReceiver content_receiver , 11615DownloadProgress progress ) { 11616return Get ( path , Headers (), nullptr , std:: move ( content_receiver ), 11617std:: move ( progress )); 11618} 11619 11620Result ClientImpl :: Get (const std:: string & path , const Headers & headers , 11621ContentReceiver content_receiver , 11622DownloadProgress progress ) { 11623return Get ( path , headers , nullptr , std:: move ( content_receiver ), 11624std:: move ( progress )); 11625} 11626 11627Result ClientImpl :: Get (const std:: string & path , 11628ResponseHandler response_handler , 11629ContentReceiver content_receiver , 11630DownloadProgress progress ) { 11631return Get ( path , Headers (), std:: move ( response_handler ), 11632std:: move ( content_receiver ), std:: move ( progress )); 11633} 11634 11635Result ClientImpl :: Get (const std:: string & path , const Headers & headers , 11636ResponseHandler response_handler , 11637ContentReceiver content_receiver , 11638DownloadProgress progress ) { 11639Request req ; 11640req . method = "GET" ; 11641req . path = path ; 11642req . headers = headers ; 11643req . response_handler = std:: move ( response_handler ); 11644req . content_receiver = 11645[ content_receiver ](const char * data , size_t data_length , 11646size_t /*offset*/ , size_t /*total_length*/ ) { 11647return content_receiver ( data , data_length ); 11648}; 11649req . download_progress = std:: move ( progress ); 11650if ( max_timeout_msec_ > 0 ) { 11651req . start_time_ = std::chrono::steady_clock:: now (); 11652} 11653 11654return send_ (std:: move ( req )); 11655} 11656 11657Result ClientImpl :: Get (const std:: string & path , const Params & params , 11658const Headers & headers , 11659ContentReceiver content_receiver , 11660DownloadProgress progress ) { 11661return Get ( path , params , headers , nullptr , std:: move ( content_receiver ), 11662std:: move ( progress )); 11663} 11664 11665Result ClientImpl :: Get (const std:: string & path , const Params & params , 11666const Headers & headers , 11667ResponseHandler response_handler , 11668ContentReceiver content_receiver , 11669DownloadProgress progress ) { 11670if ( params . empty ()) { 11671return Get ( path , headers , std:: move ( response_handler ), 11672std:: move ( content_receiver ), std:: move ( progress )); 11673} 11674 11675std:: string path_with_query = append_query_params ( path , params ); 11676return Get ( path_with_query , headers , std:: move ( response_handler ), 11677std:: move ( content_receiver ), std:: move ( progress )); 11678} 11679 11680Result ClientImpl :: Head (const std:: string & path ) { 11681return Head ( path , Headers ()); 11682} 11683 11684Result ClientImpl :: Head (const std:: string & path , 11685const Headers & headers ) { 11686Request req ; 11687req . method = "HEAD" ; 11688req . headers = headers ; 11689req . path = path ; 11690if ( max_timeout_msec_ > 0 ) { 11691req . start_time_ = std::chrono::steady_clock:: now (); 11692} 11693 11694return send_ (std:: move ( req )); 11695} 11696 11697Result ClientImpl :: Post (const std:: string & path ) { 11698return Post ( path , std:: string (), std:: string ()); 11699} 11700 11701Result ClientImpl :: Post (const std:: string & path , 11702const Headers & headers ) { 11703return Post ( path , headers , nullptr , 0 , std:: string ()); 11704} 11705 11706Result ClientImpl :: Post (const std:: string & path , const char * body , 11707size_t content_length , 11708const std:: string & content_type , 11709UploadProgress progress ) { 11710return Post ( path , Headers (), body , content_length , content_type , progress ); 11711} 11712 11713Result ClientImpl :: Post (const std:: string & path , const std:: string & body , 11714const std:: string & content_type , 11715UploadProgress progress ) { 11716return Post ( path , Headers (), body , content_type , progress ); 11717} 11718 11719Result ClientImpl :: Post (const std:: string & path , const Params & params ) { 11720return Post ( path , Headers (), params ); 11721} 11722 11723Result ClientImpl :: Post (const std:: string & path , size_t content_length , 11724ContentProvider content_provider , 11725const std:: string & content_type , 11726UploadProgress progress ) { 11727return Post ( path , Headers (), content_length , std:: move ( content_provider ), 11728content_type , progress ); 11729} 11730 11731Result ClientImpl :: Post (const std:: string & path , size_t content_length , 11732ContentProvider content_provider , 11733const std:: string & content_type , 11734ContentReceiver content_receiver , 11735UploadProgress progress ) { 11736return Post ( path , Headers (), content_length , std:: move ( content_provider ), 11737content_type , std:: move ( content_receiver ), progress ); 11738} 11739 11740Result ClientImpl :: Post (const std:: string & path , 11741ContentProviderWithoutLength content_provider , 11742const std:: string & content_type , 11743UploadProgress progress ) { 11744return Post ( path , Headers (), std:: move ( content_provider ), content_type , 11745progress ); 11746} 11747 11748Result ClientImpl :: Post (const std:: string & path , 11749ContentProviderWithoutLength content_provider , 11750const std:: string & content_type , 11751ContentReceiver content_receiver , 11752UploadProgress progress ) { 11753return Post ( path , Headers (), std:: move ( content_provider ), content_type , 11754std:: move ( content_receiver ), progress ); 11755} 11756 11757Result ClientImpl :: Post (const std:: string & path , const Headers & headers , 11758const Params & params ) { 11759auto query = detail:: params_to_query_str ( params ); 11760return Post ( path , headers , query , "application/x-www-form-urlencoded" ); 11761} 11762 11763Result ClientImpl :: Post (const std:: string & path , 11764const UploadFormDataItems & items , 11765UploadProgress progress ) { 11766return Post ( path , Headers (), items , progress ); 11767} 11768 11769Result ClientImpl :: Post (const std:: string & path , const Headers & headers , 11770const UploadFormDataItems & items , 11771UploadProgress progress ) { 11772const auto & boundary = detail:: make_multipart_data_boundary (); 11773const auto & content_type = 11774detail:: serialize_multipart_formdata_get_content_type ( boundary ); 11775auto content_length = detail:: get_multipart_content_length ( items , boundary ); 11776return Post ( path , headers , content_length , 11777detail:: make_multipart_content_provider ( items , boundary ), 11778content_type , progress ); 11779} 11780 11781Result ClientImpl :: Post (const std:: string & path , const Headers & headers , 11782const UploadFormDataItems & items , 11783const std:: string & boundary , 11784UploadProgress progress ) { 11785if (!detail:: is_multipart_boundary_chars_valid ( boundary )) { 11786return Result { nullptr , Error :: UnsupportedMultipartBoundaryChars }; 11787} 11788 11789const auto & content_type = 11790detail:: serialize_multipart_formdata_get_content_type ( boundary ); 11791auto content_length = detail:: get_multipart_content_length ( items , boundary ); 11792return Post ( path , headers , content_length , 11793detail:: make_multipart_content_provider ( items , boundary ), 11794content_type , progress ); 11795} 11796 11797Result ClientImpl :: Post (const std:: string & path , const Headers & headers , 11798const char * body , size_t content_length , 11799const std:: string & content_type , 11800UploadProgress progress ) { 11801return send_with_content_provider_and_receiver ( 11802"POST" , path , headers , body , content_length , nullptr , nullptr , 11803content_type , nullptr , progress ); 11804} 11805 11806Result ClientImpl :: Post (const std:: string & path , const Headers & headers , 11807const std:: string & body , 11808const std:: string & content_type , 11809UploadProgress progress ) { 11810return send_with_content_provider_and_receiver ( 11811"POST" , path , headers , body . data (), body . size (), nullptr , nullptr , 11812content_type , nullptr , progress ); 11813} 11814 11815Result ClientImpl :: Post (const std:: string & path , const Headers & headers , 11816size_t content_length , 11817ContentProvider content_provider , 11818const std:: string & content_type , 11819UploadProgress progress ) { 11820return send_with_content_provider_and_receiver ( 11821"POST" , path , headers , nullptr , content_length , 11822std:: move ( content_provider ), nullptr , content_type , nullptr , progress ); 11823} 11824 11825Result ClientImpl :: Post (const std:: string & path , const Headers & headers , 11826size_t content_length , 11827ContentProvider content_provider , 11828const std:: string & content_type , 11829ContentReceiver content_receiver , 11830DownloadProgress progress ) { 11831return send_with_content_provider_and_receiver ( 11832"POST" , path , headers , nullptr , content_length , 11833std:: move ( content_provider ), nullptr , content_type , 11834std:: move ( content_receiver ), std:: move ( progress )); 11835} 11836 11837Result ClientImpl :: Post (const std:: string & path , const Headers & headers , 11838ContentProviderWithoutLength content_provider , 11839const std:: string & content_type , 11840UploadProgress progress ) { 11841return send_with_content_provider_and_receiver ( 11842"POST" , path , headers , nullptr , 0 , nullptr , std:: move ( content_provider ), 11843content_type , nullptr , progress ); 11844} 11845 11846Result ClientImpl :: Post (const std:: string & path , const Headers & headers , 11847ContentProviderWithoutLength content_provider , 11848const std:: string & content_type , 11849ContentReceiver content_receiver , 11850DownloadProgress progress ) { 11851return send_with_content_provider_and_receiver ( 11852"POST" , path , headers , nullptr , 0 , nullptr , std:: move ( content_provider ), 11853content_type , std:: move ( content_receiver ), std:: move ( progress )); 11854} 11855 11856Result ClientImpl :: Post (const std:: string & path , const Headers & headers , 11857const UploadFormDataItems & items , 11858const FormDataProviderItems & provider_items , 11859UploadProgress progress ) { 11860const auto & boundary = detail:: make_multipart_data_boundary (); 11861const auto & content_type = 11862detail:: serialize_multipart_formdata_get_content_type ( boundary ); 11863return send_with_content_provider_and_receiver ( 11864"POST" , path , headers , nullptr , 0 , nullptr , 11865get_multipart_content_provider ( boundary , items , provider_items ), 11866content_type , nullptr , progress ); 11867} 11868 11869Result ClientImpl :: Post (const std:: string & path , const Headers & headers , 11870const std:: string & body , 11871const std:: string & content_type , 11872ContentReceiver content_receiver , 11873DownloadProgress progress ) { 11874Request req ; 11875req . method = "POST" ; 11876req . path = path ; 11877req . headers = headers ; 11878req . body = body ; 11879req . content_receiver = 11880[ content_receiver ](const char * data , size_t data_length , 11881size_t /*offset*/ , size_t /*total_length*/ ) { 11882return content_receiver ( data , data_length ); 11883}; 11884req . download_progress = std:: move ( progress ); 11885 11886if ( max_timeout_msec_ > 0 ) { 11887req . start_time_ = std::chrono::steady_clock:: now (); 11888} 11889 11890if (! content_type . empty ()) { req . set_header ( "Content-Type" , content_type ); } 11891 11892return send_ (std:: move ( req )); 11893} 11894 11895Result ClientImpl :: Put (const std:: string & path ) { 11896return Put ( path , std:: string (), std:: string ()); 11897} 11898 11899Result ClientImpl :: Put (const std:: string & path , const Headers & headers ) { 11900return Put ( path , headers , nullptr , 0 , std:: string ()); 11901} 11902 11903Result ClientImpl :: Put (const std:: string & path , const char * body , 11904size_t content_length , 11905const std:: string & content_type , 11906UploadProgress progress ) { 11907return Put ( path , Headers (), body , content_length , content_type , progress ); 11908} 11909 11910Result ClientImpl :: Put (const std:: string & path , const std:: string & body , 11911const std:: string & content_type , 11912UploadProgress progress ) { 11913return Put ( path , Headers (), body , content_type , progress ); 11914} 11915 11916Result ClientImpl :: Put (const std:: string & path , const Params & params ) { 11917return Put ( path , Headers (), params ); 11918} 11919 11920Result ClientImpl :: Put (const std:: string & path , size_t content_length , 11921ContentProvider content_provider , 11922const std:: string & content_type , 11923UploadProgress progress ) { 11924return Put ( path , Headers (), content_length , std:: move ( content_provider ), 11925content_type , progress ); 11926} 11927 11928Result ClientImpl :: Put (const std:: string & path , size_t content_length , 11929ContentProvider content_provider , 11930const std:: string & content_type , 11931ContentReceiver content_receiver , 11932UploadProgress progress ) { 11933return Put ( path , Headers (), content_length , std:: move ( content_provider ), 11934content_type , std:: move ( content_receiver ), progress ); 11935} 11936 11937Result ClientImpl :: Put (const std:: string & path , 11938ContentProviderWithoutLength content_provider , 11939const std:: string & content_type , 11940UploadProgress progress ) { 11941return Put ( path , Headers (), std:: move ( content_provider ), content_type , 11942progress ); 11943} 11944 11945Result ClientImpl :: Put (const std:: string & path , 11946ContentProviderWithoutLength content_provider , 11947const std:: string & content_type , 11948ContentReceiver content_receiver , 11949UploadProgress progress ) { 11950return Put ( path , Headers (), std:: move ( content_provider ), content_type , 11951std:: move ( content_receiver ), progress ); 11952} 11953 11954Result ClientImpl :: Put (const std:: string & path , const Headers & headers , 11955const Params & params ) { 11956auto query = detail:: params_to_query_str ( params ); 11957return Put ( path , headers , query , "application/x-www-form-urlencoded" ); 11958} 11959 11960Result ClientImpl :: Put (const std:: string & path , 11961const UploadFormDataItems & items , 11962UploadProgress progress ) { 11963return Put ( path , Headers (), items , progress ); 11964} 11965 11966Result ClientImpl :: Put (const std:: string & path , const Headers & headers , 11967const UploadFormDataItems & items , 11968UploadProgress progress ) { 11969const auto & boundary = detail:: make_multipart_data_boundary (); 11970const auto & content_type = 11971detail:: serialize_multipart_formdata_get_content_type ( boundary ); 11972auto content_length = detail:: get_multipart_content_length ( items , boundary ); 11973return Put ( path , headers , content_length , 11974detail:: make_multipart_content_provider ( items , boundary ), 11975content_type , progress ); 11976} 11977 11978Result ClientImpl :: Put (const std:: string & path , const Headers & headers , 11979const UploadFormDataItems & items , 11980const std:: string & boundary , 11981UploadProgress progress ) { 11982if (!detail:: is_multipart_boundary_chars_valid ( boundary )) { 11983return Result { nullptr , Error :: UnsupportedMultipartBoundaryChars }; 11984} 11985 11986const auto & content_type = 11987detail:: serialize_multipart_formdata_get_content_type ( boundary ); 11988auto content_length = detail:: get_multipart_content_length ( items , boundary ); 11989return Put ( path , headers , content_length , 11990detail:: make_multipart_content_provider ( items , boundary ), 11991content_type , progress ); 11992} 11993 11994Result ClientImpl :: Put (const std:: string & path , const Headers & headers , 11995const char * body , size_t content_length , 11996const std:: string & content_type , 11997UploadProgress progress ) { 11998return send_with_content_provider_and_receiver ( 11999"PUT" , path , headers , body , content_length , nullptr , nullptr , 12000content_type , nullptr , progress ); 12001} 12002 12003Result ClientImpl :: Put (const std:: string & path , const Headers & headers , 12004const std:: string & body , 12005const std:: string & content_type , 12006UploadProgress progress ) { 12007return send_with_content_provider_and_receiver ( 12008"PUT" , path , headers , body . data (), body . size (), nullptr , nullptr , 12009content_type , nullptr , progress ); 12010} 12011 12012Result ClientImpl :: Put (const std:: string & path , const Headers & headers , 12013size_t content_length , 12014ContentProvider content_provider , 12015const std:: string & content_type , 12016UploadProgress progress ) { 12017return send_with_content_provider_and_receiver ( 12018"PUT" , path , headers , nullptr , content_length , 12019std:: move ( content_provider ), nullptr , content_type , nullptr , progress ); 12020} 12021 12022Result ClientImpl :: Put (const std:: string & path , const Headers & headers , 12023size_t content_length , 12024ContentProvider content_provider , 12025const std:: string & content_type , 12026ContentReceiver content_receiver , 12027UploadProgress progress ) { 12028return send_with_content_provider_and_receiver ( 12029"PUT" , path , headers , nullptr , content_length , 12030std:: move ( content_provider ), nullptr , content_type , 12031std:: move ( content_receiver ), progress ); 12032} 12033 12034Result ClientImpl :: Put (const std:: string & path , const Headers & headers , 12035ContentProviderWithoutLength content_provider , 12036const std:: string & content_type , 12037UploadProgress progress ) { 12038return send_with_content_provider_and_receiver ( 12039"PUT" , path , headers , nullptr , 0 , nullptr , std:: move ( content_provider ), 12040content_type , nullptr , progress ); 12041} 12042 12043Result ClientImpl :: Put (const std:: string & path , const Headers & headers , 12044ContentProviderWithoutLength content_provider , 12045const std:: string & content_type , 12046ContentReceiver content_receiver , 12047UploadProgress progress ) { 12048return send_with_content_provider_and_receiver ( 12049"PUT" , path , headers , nullptr , 0 , nullptr , std:: move ( content_provider ), 12050content_type , std:: move ( content_receiver ), progress ); 12051} 12052 12053Result ClientImpl :: Put (const std:: string & path , const Headers & headers , 12054const UploadFormDataItems & items , 12055const FormDataProviderItems & provider_items , 12056UploadProgress progress ) { 12057const auto & boundary = detail:: make_multipart_data_boundary (); 12058const auto & content_type = 12059detail:: serialize_multipart_formdata_get_content_type ( boundary ); 12060return send_with_content_provider_and_receiver ( 12061"PUT" , path , headers , nullptr , 0 , nullptr , 12062get_multipart_content_provider ( boundary , items , provider_items ), 12063content_type , nullptr , progress ); 12064} 12065 12066Result ClientImpl :: Put (const std:: string & path , const Headers & headers , 12067const std:: string & body , 12068const std:: string & content_type , 12069ContentReceiver content_receiver , 12070DownloadProgress progress ) { 12071Request req ; 12072req . method = "PUT" ; 12073req . path = path ; 12074req . headers = headers ; 12075req . body = body ; 12076req . content_receiver = 12077[ content_receiver ](const char * data , size_t data_length , 12078size_t /*offset*/ , size_t /*total_length*/ ) { 12079return content_receiver ( data , data_length ); 12080}; 12081req . download_progress = std:: move ( progress ); 12082 12083if ( max_timeout_msec_ > 0 ) { 12084req . start_time_ = std::chrono::steady_clock:: now (); 12085} 12086 12087if (! content_type . empty ()) { req . set_header ( "Content-Type" , content_type ); } 12088 12089return send_ (std:: move ( req )); 12090} 12091 12092Result ClientImpl :: Patch (const std:: string & path ) { 12093return Patch ( path , std:: string (), std:: string ()); 12094} 12095 12096Result ClientImpl :: Patch (const std:: string & path , const Headers & headers , 12097UploadProgress progress ) { 12098return Patch ( path , headers , nullptr , 0 , std:: string (), progress ); 12099} 12100 12101Result ClientImpl :: Patch (const std:: string & path , const char * body , 12102size_t content_length , 12103const std:: string & content_type , 12104UploadProgress progress ) { 12105return Patch ( path , Headers (), body , content_length , content_type , progress ); 12106} 12107 12108Result ClientImpl :: Patch (const std:: string & path , 12109const std:: string & body , 12110const std:: string & content_type , 12111UploadProgress progress ) { 12112return Patch ( path , Headers (), body , content_type , progress ); 12113} 12114 12115Result ClientImpl :: Patch (const std:: string & path , const Params & params ) { 12116return Patch ( path , Headers (), params ); 12117} 12118 12119Result ClientImpl :: Patch (const std:: string & path , size_t content_length , 12120ContentProvider content_provider , 12121const std:: string & content_type , 12122UploadProgress progress ) { 12123return Patch ( path , Headers (), content_length , std:: move ( content_provider ), 12124content_type , progress ); 12125} 12126 12127Result ClientImpl :: Patch (const std:: string & path , size_t content_length , 12128ContentProvider content_provider , 12129const std:: string & content_type , 12130ContentReceiver content_receiver , 12131UploadProgress progress ) { 12132return Patch ( path , Headers (), content_length , std:: move ( content_provider ), 12133content_type , std:: move ( content_receiver ), progress ); 12134} 12135 12136Result ClientImpl :: Patch (const std:: string & path , 12137ContentProviderWithoutLength content_provider , 12138const std:: string & content_type , 12139UploadProgress progress ) { 12140return Patch ( path , Headers (), std:: move ( content_provider ), content_type , 12141progress ); 12142} 12143 12144Result ClientImpl :: Patch (const std:: string & path , 12145ContentProviderWithoutLength content_provider , 12146const std:: string & content_type , 12147ContentReceiver content_receiver , 12148UploadProgress progress ) { 12149return Patch ( path , Headers (), std:: move ( content_provider ), content_type , 12150std:: move ( content_receiver ), progress ); 12151} 12152 12153Result ClientImpl :: Patch (const std:: string & path , const Headers & headers , 12154const Params & params ) { 12155auto query = detail:: params_to_query_str ( params ); 12156return Patch ( path , headers , query , "application/x-www-form-urlencoded" ); 12157} 12158 12159Result ClientImpl :: Patch (const std:: string & path , 12160const UploadFormDataItems & items , 12161UploadProgress progress ) { 12162return Patch ( path , Headers (), items , progress ); 12163} 12164 12165Result ClientImpl :: Patch (const std:: string & path , const Headers & headers , 12166const UploadFormDataItems & items , 12167UploadProgress progress ) { 12168const auto & boundary = detail:: make_multipart_data_boundary (); 12169const auto & content_type = 12170detail:: serialize_multipart_formdata_get_content_type ( boundary ); 12171auto content_length = detail:: get_multipart_content_length ( items , boundary ); 12172return Patch ( path , headers , content_length , 12173detail:: make_multipart_content_provider ( items , boundary ), 12174content_type , progress ); 12175} 12176 12177Result ClientImpl :: Patch (const std:: string & path , const Headers & headers , 12178const UploadFormDataItems & items , 12179const std:: string & boundary , 12180UploadProgress progress ) { 12181if (!detail:: is_multipart_boundary_chars_valid ( boundary )) { 12182return Result { nullptr , Error :: UnsupportedMultipartBoundaryChars }; 12183} 12184 12185const auto & content_type = 12186detail:: serialize_multipart_formdata_get_content_type ( boundary ); 12187auto content_length = detail:: get_multipart_content_length ( items , boundary ); 12188return Patch ( path , headers , content_length , 12189detail:: make_multipart_content_provider ( items , boundary ), 12190content_type , progress ); 12191} 12192 12193Result ClientImpl :: Patch (const std:: string & path , const Headers & headers , 12194const char * body , size_t content_length , 12195const std:: string & content_type , 12196UploadProgress progress ) { 12197return send_with_content_provider_and_receiver ( 12198"PATCH" , path , headers , body , content_length , nullptr , nullptr , 12199content_type , nullptr , progress ); 12200} 12201 12202Result ClientImpl :: Patch (const std:: string & path , const Headers & headers , 12203const std:: string & body , 12204const std:: string & content_type , 12205UploadProgress progress ) { 12206return send_with_content_provider_and_receiver ( 12207"PATCH" , path , headers , body . data (), body . size (), nullptr , nullptr , 12208content_type , nullptr , progress ); 12209} 12210 12211Result ClientImpl :: Patch (const std:: string & path , const Headers & headers , 12212size_t content_length , 12213ContentProvider content_provider , 12214const std:: string & content_type , 12215UploadProgress progress ) { 12216return send_with_content_provider_and_receiver ( 12217"PATCH" , path , headers , nullptr , content_length , 12218std:: move ( content_provider ), nullptr , content_type , nullptr , progress ); 12219} 12220 12221Result ClientImpl :: Patch (const std:: string & path , const Headers & headers , 12222size_t content_length , 12223ContentProvider content_provider , 12224const std:: string & content_type , 12225ContentReceiver content_receiver , 12226UploadProgress progress ) { 12227return send_with_content_provider_and_receiver ( 12228"PATCH" , path , headers , nullptr , content_length , 12229std:: move ( content_provider ), nullptr , content_type , 12230std:: move ( content_receiver ), progress ); 12231} 12232 12233Result ClientImpl :: Patch (const std:: string & path , const Headers & headers , 12234ContentProviderWithoutLength content_provider , 12235const std:: string & content_type , 12236UploadProgress progress ) { 12237return send_with_content_provider_and_receiver ( 12238"PATCH" , path , headers , nullptr , 0 , nullptr , std:: move ( content_provider ), 12239content_type , nullptr , progress ); 12240} 12241 12242Result ClientImpl :: Patch (const std:: string & path , const Headers & headers , 12243ContentProviderWithoutLength content_provider , 12244const std:: string & content_type , 12245ContentReceiver content_receiver , 12246UploadProgress progress ) { 12247return send_with_content_provider_and_receiver ( 12248"PATCH" , path , headers , nullptr , 0 , nullptr , std:: move ( content_provider ), 12249content_type , std:: move ( content_receiver ), progress ); 12250} 12251 12252Result ClientImpl :: Patch (const std:: string & path , const Headers & headers , 12253const UploadFormDataItems & items , 12254const FormDataProviderItems & provider_items , 12255UploadProgress progress ) { 12256const auto & boundary = detail:: make_multipart_data_boundary (); 12257const auto & content_type = 12258detail:: serialize_multipart_formdata_get_content_type ( boundary ); 12259return send_with_content_provider_and_receiver ( 12260"PATCH" , path , headers , nullptr , 0 , nullptr , 12261get_multipart_content_provider ( boundary , items , provider_items ), 12262content_type , nullptr , progress ); 12263} 12264 12265Result ClientImpl :: Patch (const std:: string & path , const Headers & headers , 12266const std:: string & body , 12267const std:: string & content_type , 12268ContentReceiver content_receiver , 12269DownloadProgress progress ) { 12270Request req ; 12271req . method = "PATCH" ; 12272req . path = path ; 12273req . headers = headers ; 12274req . body = body ; 12275req . content_receiver = 12276[ content_receiver ](const char * data , size_t data_length , 12277size_t /*offset*/ , size_t /*total_length*/ ) { 12278return content_receiver ( data , data_length ); 12279}; 12280req . download_progress = std:: move ( progress ); 12281 12282if ( max_timeout_msec_ > 0 ) { 12283req . start_time_ = std::chrono::steady_clock:: now (); 12284} 12285 12286if (! content_type . empty ()) { req . set_header ( "Content-Type" , content_type ); } 12287 12288return send_ (std:: move ( req )); 12289} 12290 12291Result ClientImpl :: Delete (const std:: string & path , 12292DownloadProgress progress ) { 12293return Delete ( path , Headers (), std:: string (), std:: string (), progress ); 12294} 12295 12296Result ClientImpl :: Delete (const std:: string & path , 12297const Headers & headers , 12298DownloadProgress progress ) { 12299return Delete ( path , headers , std:: string (), std:: string (), progress ); 12300} 12301 12302Result ClientImpl :: Delete (const std:: string & path , const char * body , 12303size_t content_length , 12304const std:: string & content_type , 12305DownloadProgress progress ) { 12306return Delete ( path , Headers (), body , content_length , content_type , progress ); 12307} 12308 12309Result ClientImpl :: Delete (const std:: string & path , 12310const std:: string & body , 12311const std:: string & content_type , 12312DownloadProgress progress ) { 12313return Delete ( path , Headers (), body . data (), body . size (), content_type , 12314progress ); 12315} 12316 12317Result ClientImpl :: Delete (const std:: string & path , 12318const Headers & headers , 12319const std:: string & body , 12320const std:: string & content_type , 12321DownloadProgress progress ) { 12322return Delete ( path , headers , body . data (), body . size (), content_type , 12323progress ); 12324} 12325 12326Result ClientImpl :: Delete (const std:: string & path , const Params & params , 12327DownloadProgress progress ) { 12328return Delete ( path , Headers (), params , progress ); 12329} 12330 12331Result ClientImpl :: Delete (const std:: string & path , 12332const Headers & headers , const Params & params , 12333DownloadProgress progress ) { 12334auto query = detail:: params_to_query_str ( params ); 12335return Delete ( path , headers , query , "application/x-www-form-urlencoded" , 12336progress ); 12337} 12338 12339Result ClientImpl :: Delete (const std:: string & path , 12340const Headers & headers , const char * body , 12341size_t content_length , 12342const std:: string & content_type , 12343DownloadProgress progress ) { 12344Request req ; 12345req . method = "DELETE" ; 12346req . headers = headers ; 12347req . path = path ; 12348req . download_progress = std:: move ( progress ); 12349if ( max_timeout_msec_ > 0 ) { 12350req . start_time_ = std::chrono::steady_clock:: now (); 12351} 12352 12353if (! content_type . empty ()) { req . set_header ( "Content-Type" , content_type ); } 12354req . body . assign ( body , content_length ); 12355 12356return send_ (std:: move ( req )); 12357} 12358 12359Result ClientImpl :: Options (const std:: string & path ) { 12360return Options ( path , Headers ()); 12361} 12362 12363Result ClientImpl :: Options (const std:: string & path , 12364const Headers & headers ) { 12365Request req ; 12366req . method = "OPTIONS" ; 12367req . headers = headers ; 12368req . path = path ; 12369if ( max_timeout_msec_ > 0 ) { 12370req . start_time_ = std::chrono::steady_clock:: now (); 12371} 12372 12373return send_ (std:: move ( req )); 12374} 12375 12376void ClientImpl :: stop () { 12377std:: lock_guard < std:: mutex > guard ( socket_mutex_ ); 12378 12379// If there is anything ongoing right now, the ONLY thread-safe thing we can 12380// do is to shutdown_socket, so that threads using this socket suddenly 12381// discover they can't read/write any more and error out. Everything else 12382// (closing the socket, shutting ssl down) is unsafe because these actions 12383// are not thread-safe. 12384if ( socket_requests_in_flight_ > 0 ) { 12385shutdown_socket ( socket_ ); 12386 12387// Aside from that, we set a flag for the socket to be closed when we're 12388// done. 12389socket_should_be_closed_when_request_is_done_ = true; 12390return; 12391} 12392 12393disconnect ( /*gracefully=*/ true); 12394} 12395 12396std:: string ClientImpl :: host () const { return host_ ; } 12397 12398int ClientImpl :: port () const { return port_ ; } 12399 12400size_t ClientImpl :: is_socket_open () const { 12401std:: lock_guard < std:: mutex > guard ( socket_mutex_ ); 12402return socket_ . is_open (); 12403} 12404 12405socket_t ClientImpl :: socket () const { return socket_ . sock ; } 12406 12407void ClientImpl :: set_connection_timeout ( time_t sec , time_t usec ) { 12408connection_timeout_sec_ = sec ; 12409connection_timeout_usec_ = usec ; 12410} 12411 12412void ClientImpl :: set_read_timeout ( time_t sec , time_t usec ) { 12413read_timeout_sec_ = sec ; 12414read_timeout_usec_ = usec ; 12415} 12416 12417void ClientImpl :: set_write_timeout ( time_t sec , time_t usec ) { 12418write_timeout_sec_ = sec ; 12419write_timeout_usec_ = usec ; 12420} 12421 12422void ClientImpl :: set_max_timeout ( time_t msec ) { 12423max_timeout_msec_ = msec ; 12424} 12425 12426void ClientImpl :: set_basic_auth (const std:: string & username , 12427const std:: string & password ) { 12428basic_auth_username_ = username ; 12429basic_auth_password_ = password ; 12430} 12431 12432void ClientImpl :: set_bearer_token_auth (const std:: string & token ) { 12433bearer_token_auth_token_ = token ; 12434} 12435 12436void ClientImpl :: set_keep_alive ( bool on ) { keep_alive_ = on ; } 12437 12438void ClientImpl :: set_follow_location ( bool on ) { follow_location_ = on ; } 12439 12440void ClientImpl :: set_path_encode ( bool on ) { path_encode_ = on ; } 12441 12442void 12443ClientImpl :: set_hostname_addr_map (std:: map < std:: string , std:: string > addr_map ) { 12444addr_map_ = std:: move ( addr_map ); 12445} 12446 12447void ClientImpl :: set_default_headers ( Headers headers ) { 12448default_headers_ = std:: move ( headers ); 12449} 12450 12451void ClientImpl :: set_header_writer ( 12452std:: function < ssize_t ( Stream & , Headers & ) > const & writer ) { 12453header_writer_ = writer ; 12454} 12455 12456void ClientImpl :: set_address_family ( int family ) { 12457address_family_ = family ; 12458} 12459 12460void ClientImpl :: set_tcp_nodelay ( bool on ) { tcp_nodelay_ = on ; } 12461 12462void ClientImpl :: set_ipv6_v6only ( bool on ) { ipv6_v6only_ = on ; } 12463 12464void ClientImpl :: set_socket_options ( SocketOptions socket_options ) { 12465socket_options_ = std:: move ( socket_options ); 12466} 12467 12468void ClientImpl :: set_compress ( bool on ) { compress_ = on ; } 12469 12470void ClientImpl :: set_decompress ( bool on ) { decompress_ = on ; } 12471 12472void ClientImpl :: set_payload_max_length ( size_t length ) { 12473payload_max_length_ = length ; 12474has_payload_max_length_ = true; 12475} 12476 12477void ClientImpl :: set_interface (const std:: string & intf ) { 12478interface_ = intf ; 12479} 12480 12481void ClientImpl :: set_proxy (const std:: string & host , int port ) { 12482proxy_host_ = host ; 12483proxy_port_ = port ; 12484std:: lock_guard < std:: mutex > guard ( socket_mutex_ ); 12485disconnect ( /*gracefully=*/ true); 12486} 12487 12488void ClientImpl :: set_proxy_basic_auth (const std:: string & username , 12489const std:: string & password ) { 12490proxy_basic_auth_username_ = username ; 12491proxy_basic_auth_password_ = password ; 12492} 12493 12494void ClientImpl :: set_proxy_bearer_token_auth (const std:: string & token ) { 12495proxy_bearer_token_auth_token_ = token ; 12496} 12497 12498void ClientImpl :: set_no_proxy (const std:: vector < std:: string > & patterns ) { 12499std:: vector < detail:: NoProxyEntry > parsed ; 12500parsed . reserve ( patterns . size ()); 12501for (const auto & p : patterns ) { 12502auto trimmed = detail:: trim_copy ( p ); 12503if ( trimmed . empty ()) { continue; } 12504detail:: NoProxyEntry entry ; 12505if (detail:: parse_no_proxy_entry ( trimmed , entry )) { 12506parsed . push_back (std:: move ( entry )); 12507} 12508} 12509no_proxy_entries_ = std:: move ( parsed ); 12510std:: lock_guard < std:: mutex > guard ( socket_mutex_ ); 12511disconnect ( /*gracefully=*/ true); 12512} 12513 12514#ifdef CPPHTTPLIB_SSL_ENABLED 12515void ClientImpl :: set_digest_auth (const std:: string & username , 12516const std:: string & password ) { 12517digest_auth_username_ = username ; 12518digest_auth_password_ = password ; 12519} 12520 12521void ClientImpl :: set_ca_cert_path (const std:: string & ca_cert_file_path , 12522const std:: string & ca_cert_dir_path ) { 12523ca_cert_file_path_ = ca_cert_file_path ; 12524ca_cert_dir_path_ = ca_cert_dir_path ; 12525} 12526 12527void ClientImpl :: set_proxy_digest_auth (const std:: string & username , 12528const std:: string & password ) { 12529proxy_digest_auth_username_ = username ; 12530proxy_digest_auth_password_ = password ; 12531} 12532 12533void ClientImpl :: enable_server_certificate_verification ( bool enabled ) { 12534server_certificate_verification_ = enabled ; 12535} 12536 12537void ClientImpl :: enable_server_hostname_verification ( bool enabled ) { 12538server_hostname_verification_ = enabled ; 12539} 12540 12541void ClientImpl :: enable_system_ca ( bool enabled ) { 12542system_ca_mode_ = enabled ? SystemCAMode :: Enabled : SystemCAMode :: Disabled ; 12543} 12544#endif 12545 12546void ClientImpl :: set_logger ( Logger logger ) { 12547logger_ = std:: move ( logger ); 12548} 12549 12550void ClientImpl :: set_error_logger ( ErrorLogger error_logger ) { 12551error_logger_ = std:: move ( error_logger ); 12552} 12553 12554/* 12555* SSL/TLS Common Implementation 12556*/ 12557 12558ClientConnection ::~ ClientConnection () { 12559#ifdef CPPHTTPLIB_SSL_ENABLED 12560if ( session ) { 12561tls:: shutdown ( session , true); 12562tls:: free_session ( session ); 12563session = nullptr ; 12564} 12565#endif 12566 12567if ( sock != INVALID_SOCKET ) { 12568detail:: close_socket ( sock ); 12569sock = INVALID_SOCKET ; 12570} 12571} 12572 12573// Universal client implementation 12574Client :: Client (const std:: string & scheme_host_port ) 12575: Client ( scheme_host_port , std:: string (), std:: string ()) {} 12576 12577Client :: Client (const std:: string & scheme_host_port , 12578const std:: string & client_cert_path , 12579const std:: string & client_key_path ) { 12580detail:: UrlComponents uc ; 12581if (detail:: parse_url ( scheme_host_port , uc ) && ! uc . host . empty ()) { 12582auto & scheme = uc . scheme ; 12583 12584#ifdef CPPHTTPLIB_SSL_ENABLED 12585if (! scheme . empty () && ( scheme != "http" && scheme != "https" )) { 12586#else 12587if (! scheme . empty () && scheme != "http" ) { 12588#endif 12589#ifndef CPPHTTPLIB_NO_EXCEPTIONS 12590std:: string msg = "'" + scheme + "' scheme is not supported." ; 12591throw std:: invalid_argument ( msg ); 12592#endif 12593return; 12594} 12595 12596auto is_ssl = scheme == "https" ; 12597 12598auto host = std:: move ( uc . host ); 12599 12600auto port = is_ssl ? 443 : 80 ; 12601if (! uc . port . empty () && !detail:: parse_port ( uc . port , port )) { return; } 12602 12603if ( is_ssl ) { 12604#ifdef CPPHTTPLIB_SSL_ENABLED 12605cli_ = detail:: make_unique < SSLClient > ( host , port , client_cert_path , 12606client_key_path ); 12607is_ssl_ = is_ssl ; 12608#endif 12609} else { 12610cli_ = detail:: make_unique < ClientImpl > ( host , port , client_cert_path , 12611client_key_path ); 12612} 12613} else { 12614// NOTE: Update TEST(UniversalClientImplTest, Ipv6LiteralAddress) 12615// if port param below changes. 12616cli_ = detail:: make_unique < ClientImpl > ( scheme_host_port , 80 , 12617client_cert_path , client_key_path ); 12618} 12619} 12620 12621Client :: Client (const std:: string & host , int port ) 12622: Client ( host , port , std:: string (), std:: string ()) {} 12623 12624Client :: Client (const std:: string & host , int port , 12625const std:: string & client_cert_path , 12626const std:: string & client_key_path ) 12627: cli_ (detail:: make_unique < ClientImpl > ( host , port , client_cert_path , 12628client_key_path )) {} 12629 12630Client ::~ Client () = default ; 12631 12632bool Client :: is_valid () const { 12633return cli_ != nullptr && cli_ -> is_valid (); 12634} 12635 12636Result Client :: Get (const std:: string & path , DownloadProgress progress ) { 12637return cli_ -> Get ( path , std:: move ( progress )); 12638} 12639Result Client :: Get (const std:: string & path , const Headers & headers , 12640DownloadProgress progress ) { 12641return cli_ -> Get ( path , headers , std:: move ( progress )); 12642} 12643Result Client :: Get (const std:: string & path , 12644ContentReceiver content_receiver , 12645DownloadProgress progress ) { 12646return cli_ -> Get ( path , std:: move ( content_receiver ), std:: move ( progress )); 12647} 12648Result Client :: Get (const std:: string & path , const Headers & headers , 12649ContentReceiver content_receiver , 12650DownloadProgress progress ) { 12651return cli_ -> Get ( path , headers , std:: move ( content_receiver ), 12652std:: move ( progress )); 12653} 12654Result Client :: Get (const std:: string & path , 12655ResponseHandler response_handler , 12656ContentReceiver content_receiver , 12657DownloadProgress progress ) { 12658return cli_ -> Get ( path , std:: move ( response_handler ), 12659std:: move ( content_receiver ), std:: move ( progress )); 12660} 12661Result Client :: Get (const std:: string & path , const Headers & headers , 12662ResponseHandler response_handler , 12663ContentReceiver content_receiver , 12664DownloadProgress progress ) { 12665return cli_ -> Get ( path , headers , std:: move ( response_handler ), 12666std:: move ( content_receiver ), std:: move ( progress )); 12667} 12668Result Client :: Get (const std:: string & path , const Params & params , 12669DownloadProgress progress ) { 12670return cli_ -> Get ( path , params , std:: move ( progress )); 12671} 12672Result Client :: Get (const std:: string & path , const Params & params , 12673const Headers & headers , DownloadProgress progress ) { 12674return cli_ -> Get ( path , params , headers , std:: move ( progress )); 12675} 12676Result Client :: Get (const std:: string & path , const Params & params , 12677const Headers & headers , 12678ContentReceiver content_receiver , 12679DownloadProgress progress ) { 12680return cli_ -> Get ( path , params , headers , std:: move ( content_receiver ), 12681std:: move ( progress )); 12682} 12683Result Client :: Get (const std:: string & path , const Params & params , 12684const Headers & headers , 12685ResponseHandler response_handler , 12686ContentReceiver content_receiver , 12687DownloadProgress progress ) { 12688return cli_ -> Get ( path , params , headers , std:: move ( response_handler ), 12689std:: move ( content_receiver ), std:: move ( progress )); 12690} 12691 12692Result Client :: Head (const std:: string & path ) { return cli_ -> Head ( path ); } 12693Result Client :: Head (const std:: string & path , const Headers & headers ) { 12694return cli_ -> Head ( path , headers ); 12695} 12696 12697Result Client :: Post (const std:: string & path ) { return cli_ -> Post ( path ); } 12698Result Client :: Post (const std:: string & path , const Headers & headers ) { 12699return cli_ -> Post ( path , headers ); 12700} 12701Result Client :: Post (const std:: string & path , const char * body , 12702size_t content_length , 12703const std:: string & content_type , 12704UploadProgress progress ) { 12705return cli_ -> Post ( path , body , content_length , content_type , progress ); 12706} 12707Result Client :: Post (const std:: string & path , const Headers & headers , 12708const char * body , size_t content_length , 12709const std:: string & content_type , 12710UploadProgress progress ) { 12711return cli_ -> Post ( path , headers , body , content_length , content_type , 12712progress ); 12713} 12714Result Client :: Post (const std:: string & path , const std:: string & body , 12715const std:: string & content_type , 12716UploadProgress progress ) { 12717return cli_ -> Post ( path , body , content_type , progress ); 12718} 12719Result Client :: Post (const std:: string & path , const Headers & headers , 12720const std:: string & body , 12721const std:: string & content_type , 12722UploadProgress progress ) { 12723return cli_ -> Post ( path , headers , body , content_type , progress ); 12724} 12725Result Client :: Post (const std:: string & path , size_t content_length , 12726ContentProvider content_provider , 12727const std:: string & content_type , 12728UploadProgress progress ) { 12729return cli_ -> Post ( path , content_length , std:: move ( content_provider ), 12730content_type , progress ); 12731} 12732Result Client :: Post (const std:: string & path , size_t content_length , 12733ContentProvider content_provider , 12734const std:: string & content_type , 12735ContentReceiver content_receiver , 12736UploadProgress progress ) { 12737return cli_ -> Post ( path , content_length , std:: move ( content_provider ), 12738content_type , std:: move ( content_receiver ), progress ); 12739} 12740Result Client :: Post (const std:: string & path , 12741ContentProviderWithoutLength content_provider , 12742const std:: string & content_type , 12743UploadProgress progress ) { 12744return cli_ -> Post ( path , std:: move ( content_provider ), content_type , progress ); 12745} 12746Result Client :: Post (const std:: string & path , 12747ContentProviderWithoutLength content_provider , 12748const std:: string & content_type , 12749ContentReceiver content_receiver , 12750UploadProgress progress ) { 12751return cli_ -> Post ( path , std:: move ( content_provider ), content_type , 12752std:: move ( content_receiver ), progress ); 12753} 12754Result Client :: Post (const std:: string & path , const Headers & headers , 12755size_t content_length , 12756ContentProvider content_provider , 12757const std:: string & content_type , 12758UploadProgress progress ) { 12759return cli_ -> Post ( path , headers , content_length , std:: move ( content_provider ), 12760content_type , progress ); 12761} 12762Result Client :: Post (const std:: string & path , const Headers & headers , 12763size_t content_length , 12764ContentProvider content_provider , 12765const std:: string & content_type , 12766ContentReceiver content_receiver , 12767DownloadProgress progress ) { 12768return cli_ -> Post ( path , headers , content_length , std:: move ( content_provider ), 12769content_type , std:: move ( content_receiver ), progress ); 12770} 12771Result Client :: Post (const std:: string & path , const Headers & headers , 12772ContentProviderWithoutLength content_provider , 12773const std:: string & content_type , 12774UploadProgress progress ) { 12775return cli_ -> Post ( path , headers , std:: move ( content_provider ), content_type , 12776progress ); 12777} 12778Result Client :: Post (const std:: string & path , const Headers & headers , 12779ContentProviderWithoutLength content_provider , 12780const std:: string & content_type , 12781ContentReceiver content_receiver , 12782DownloadProgress progress ) { 12783return cli_ -> Post ( path , headers , std:: move ( content_provider ), content_type , 12784std:: move ( content_receiver ), progress ); 12785} 12786Result Client :: Post (const std:: string & path , const Params & params ) { 12787return cli_ -> Post ( path , params ); 12788} 12789Result Client :: Post (const std:: string & path , const Headers & headers , 12790const Params & params ) { 12791return cli_ -> Post ( path , headers , params ); 12792} 12793Result Client :: Post (const std:: string & path , 12794const UploadFormDataItems & items , 12795UploadProgress progress ) { 12796return cli_ -> Post ( path , items , progress ); 12797} 12798Result Client :: Post (const std:: string & path , const Headers & headers , 12799const UploadFormDataItems & items , 12800UploadProgress progress ) { 12801return cli_ -> Post ( path , headers , items , progress ); 12802} 12803Result Client :: Post (const std:: string & path , const Headers & headers , 12804const UploadFormDataItems & items , 12805const std:: string & boundary , 12806UploadProgress progress ) { 12807return cli_ -> Post ( path , headers , items , boundary , progress ); 12808} 12809Result Client :: Post (const std:: string & path , const Headers & headers , 12810const UploadFormDataItems & items , 12811const FormDataProviderItems & provider_items , 12812UploadProgress progress ) { 12813return cli_ -> Post ( path , headers , items , provider_items , progress ); 12814} 12815Result Client :: Post (const std:: string & path , const Headers & headers , 12816const std:: string & body , 12817const std:: string & content_type , 12818ContentReceiver content_receiver , 12819DownloadProgress progress ) { 12820return cli_ -> Post ( path , headers , body , content_type , 12821std:: move ( content_receiver ), progress ); 12822} 12823 12824Result Client :: Put (const std:: string & path ) { return cli_ -> Put ( path ); } 12825Result Client :: Put (const std:: string & path , const Headers & headers ) { 12826return cli_ -> Put ( path , headers ); 12827} 12828Result Client :: Put (const std:: string & path , const char * body , 12829size_t content_length , 12830const std:: string & content_type , 12831UploadProgress progress ) { 12832return cli_ -> Put ( path , body , content_length , content_type , progress ); 12833} 12834Result Client :: Put (const std:: string & path , const Headers & headers , 12835const char * body , size_t content_length , 12836const std:: string & content_type , 12837UploadProgress progress ) { 12838return cli_ -> Put ( path , headers , body , content_length , content_type , progress ); 12839} 12840Result Client :: Put (const std:: string & path , const std:: string & body , 12841const std:: string & content_type , 12842UploadProgress progress ) { 12843return cli_ -> Put ( path , body , content_type , progress ); 12844} 12845Result Client :: Put (const std:: string & path , const Headers & headers , 12846const std:: string & body , 12847const std:: string & content_type , 12848UploadProgress progress ) { 12849return cli_ -> Put ( path , headers , body , content_type , progress ); 12850} 12851Result Client :: Put (const std:: string & path , size_t content_length , 12852ContentProvider content_provider , 12853const std:: string & content_type , 12854UploadProgress progress ) { 12855return cli_ -> Put ( path , content_length , std:: move ( content_provider ), 12856content_type , progress ); 12857} 12858Result Client :: Put (const std:: string & path , size_t content_length , 12859ContentProvider content_provider , 12860const std:: string & content_type , 12861ContentReceiver content_receiver , 12862UploadProgress progress ) { 12863return cli_ -> Put ( path , content_length , std:: move ( content_provider ), 12864content_type , std:: move ( content_receiver ), progress ); 12865} 12866Result Client :: Put (const std:: string & path , 12867ContentProviderWithoutLength content_provider , 12868const std:: string & content_type , 12869UploadProgress progress ) { 12870return cli_ -> Put ( path , std:: move ( content_provider ), content_type , progress ); 12871} 12872Result Client :: Put (const std:: string & path , 12873ContentProviderWithoutLength content_provider , 12874const std:: string & content_type , 12875ContentReceiver content_receiver , 12876UploadProgress progress ) { 12877return cli_ -> Put ( path , std:: move ( content_provider ), content_type , 12878std:: move ( content_receiver ), progress ); 12879} 12880Result Client :: Put (const std:: string & path , const Headers & headers , 12881size_t content_length , 12882ContentProvider content_provider , 12883const std:: string & content_type , 12884UploadProgress progress ) { 12885return cli_ -> Put ( path , headers , content_length , std:: move ( content_provider ), 12886content_type , progress ); 12887} 12888Result Client :: Put (const std:: string & path , const Headers & headers , 12889size_t content_length , 12890ContentProvider content_provider , 12891const std:: string & content_type , 12892ContentReceiver content_receiver , 12893UploadProgress progress ) { 12894return cli_ -> Put ( path , headers , content_length , std:: move ( content_provider ), 12895content_type , std:: move ( content_receiver ), progress ); 12896} 12897Result Client :: Put (const std:: string & path , const Headers & headers , 12898ContentProviderWithoutLength content_provider , 12899const std:: string & content_type , 12900UploadProgress progress ) { 12901return cli_ -> Put ( path , headers , std:: move ( content_provider ), content_type , 12902progress ); 12903} 12904Result Client :: Put (const std:: string & path , const Headers & headers , 12905ContentProviderWithoutLength content_provider , 12906const std:: string & content_type , 12907ContentReceiver content_receiver , 12908UploadProgress progress ) { 12909return cli_ -> Put ( path , headers , std:: move ( content_provider ), content_type , 12910std:: move ( content_receiver ), progress ); 12911} 12912Result Client :: Put (const std:: string & path , const Params & params ) { 12913return cli_ -> Put ( path , params ); 12914} 12915Result Client :: Put (const std:: string & path , const Headers & headers , 12916const Params & params ) { 12917return cli_ -> Put ( path , headers , params ); 12918} 12919Result Client :: Put (const std:: string & path , 12920const UploadFormDataItems & items , 12921UploadProgress progress ) { 12922return cli_ -> Put ( path , items , progress ); 12923} 12924Result Client :: Put (const std:: string & path , const Headers & headers , 12925const UploadFormDataItems & items , 12926UploadProgress progress ) { 12927return cli_ -> Put ( path , headers , items , progress ); 12928} 12929Result Client :: Put (const std:: string & path , const Headers & headers , 12930const UploadFormDataItems & items , 12931const std:: string & boundary , 12932UploadProgress progress ) { 12933return cli_ -> Put ( path , headers , items , boundary , progress ); 12934} 12935Result Client :: Put (const std:: string & path , const Headers & headers , 12936const UploadFormDataItems & items , 12937const FormDataProviderItems & provider_items , 12938UploadProgress progress ) { 12939return cli_ -> Put ( path , headers , items , provider_items , progress ); 12940} 12941Result Client :: Put (const std:: string & path , const Headers & headers , 12942const std:: string & body , 12943const std:: string & content_type , 12944ContentReceiver content_receiver , 12945DownloadProgress progress ) { 12946return cli_ -> Put ( path , headers , body , content_type , content_receiver , 12947progress ); 12948} 12949 12950Result Client :: Patch (const std:: string & path ) { 12951return cli_ -> Patch ( path ); 12952} 12953Result Client :: Patch (const std:: string & path , const Headers & headers ) { 12954return cli_ -> Patch ( path , headers ); 12955} 12956Result Client :: Patch (const std:: string & path , const char * body , 12957size_t content_length , 12958const std:: string & content_type , 12959UploadProgress progress ) { 12960return cli_ -> Patch ( path , body , content_length , content_type , progress ); 12961} 12962Result Client :: Patch (const std:: string & path , const Headers & headers , 12963const char * body , size_t content_length , 12964const std:: string & content_type , 12965UploadProgress progress ) { 12966return cli_ -> Patch ( path , headers , body , content_length , content_type , 12967progress ); 12968} 12969Result Client :: Patch (const std:: string & path , const std:: string & body , 12970const std:: string & content_type , 12971UploadProgress progress ) { 12972return cli_ -> Patch ( path , body , content_type , progress ); 12973} 12974Result Client :: Patch (const std:: string & path , const Headers & headers , 12975const std:: string & body , 12976const std:: string & content_type , 12977UploadProgress progress ) { 12978return cli_ -> Patch ( path , headers , body , content_type , progress ); 12979} 12980Result Client :: Patch (const std:: string & path , size_t content_length , 12981ContentProvider content_provider , 12982const std:: string & content_type , 12983UploadProgress progress ) { 12984return cli_ -> Patch ( path , content_length , std:: move ( content_provider ), 12985content_type , progress ); 12986} 12987Result Client :: Patch (const std:: string & path , size_t content_length , 12988ContentProvider content_provider , 12989const std:: string & content_type , 12990ContentReceiver content_receiver , 12991UploadProgress progress ) { 12992return cli_ -> Patch ( path , content_length , std:: move ( content_provider ), 12993content_type , std:: move ( content_receiver ), progress ); 12994} 12995Result Client :: Patch (const std:: string & path , 12996ContentProviderWithoutLength content_provider , 12997const std:: string & content_type , 12998UploadProgress progress ) { 12999return cli_ -> Patch ( path , std:: move ( content_provider ), content_type , progress ); 13000} 13001Result Client :: Patch (const std:: string & path , 13002ContentProviderWithoutLength content_provider , 13003const std:: string & content_type , 13004ContentReceiver content_receiver , 13005UploadProgress progress ) { 13006return cli_ -> Patch ( path , std:: move ( content_provider ), content_type , 13007std:: move ( content_receiver ), progress ); 13008} 13009Result Client :: Patch (const std:: string & path , const Headers & headers , 13010size_t content_length , 13011ContentProvider content_provider , 13012const std:: string & content_type , 13013UploadProgress progress ) { 13014return cli_ -> Patch ( path , headers , content_length , std:: move ( content_provider ), 13015content_type , progress ); 13016} 13017Result Client :: Patch (const std:: string & path , const Headers & headers , 13018size_t content_length , 13019ContentProvider content_provider , 13020const std:: string & content_type , 13021ContentReceiver content_receiver , 13022UploadProgress progress ) { 13023return cli_ -> Patch ( path , headers , content_length , std:: move ( content_provider ), 13024content_type , std:: move ( content_receiver ), progress ); 13025} 13026Result Client :: Patch (const std:: string & path , const Headers & headers , 13027ContentProviderWithoutLength content_provider , 13028const std:: string & content_type , 13029UploadProgress progress ) { 13030return cli_ -> Patch ( path , headers , std:: move ( content_provider ), content_type , 13031progress ); 13032} 13033Result Client :: Patch (const std:: string & path , const Headers & headers , 13034ContentProviderWithoutLength content_provider , 13035const std:: string & content_type , 13036ContentReceiver content_receiver , 13037UploadProgress progress ) { 13038return cli_ -> Patch ( path , headers , std:: move ( content_provider ), content_type , 13039std:: move ( content_receiver ), progress ); 13040} 13041Result Client :: Patch (const std:: string & path , const Params & params ) { 13042return cli_ -> Patch ( path , params ); 13043} 13044Result Client :: Patch (const std:: string & path , const Headers & headers , 13045const Params & params ) { 13046return cli_ -> Patch ( path , headers , params ); 13047} 13048Result Client :: Patch (const std:: string & path , 13049const UploadFormDataItems & items , 13050UploadProgress progress ) { 13051return cli_ -> Patch ( path , items , progress ); 13052} 13053Result Client :: Patch (const std:: string & path , const Headers & headers , 13054const UploadFormDataItems & items , 13055UploadProgress progress ) { 13056return cli_ -> Patch ( path , headers , items , progress ); 13057} 13058Result Client :: Patch (const std:: string & path , const Headers & headers , 13059const UploadFormDataItems & items , 13060const std:: string & boundary , 13061UploadProgress progress ) { 13062return cli_ -> Patch ( path , headers , items , boundary , progress ); 13063} 13064Result Client :: Patch (const std:: string & path , const Headers & headers , 13065const UploadFormDataItems & items , 13066const FormDataProviderItems & provider_items , 13067UploadProgress progress ) { 13068return cli_ -> Patch ( path , headers , items , provider_items , progress ); 13069} 13070Result Client :: Patch (const std:: string & path , const Headers & headers , 13071const std:: string & body , 13072const std:: string & content_type , 13073ContentReceiver content_receiver , 13074DownloadProgress progress ) { 13075return cli_ -> Patch ( path , headers , body , content_type , content_receiver , 13076progress ); 13077} 13078 13079Result Client :: Delete (const std:: string & path , 13080DownloadProgress progress ) { 13081return cli_ -> Delete ( path , progress ); 13082} 13083Result Client :: Delete (const std:: string & path , const Headers & headers , 13084DownloadProgress progress ) { 13085return cli_ -> Delete ( path , headers , progress ); 13086} 13087Result Client :: Delete (const std:: string & path , const char * body , 13088size_t content_length , 13089const std:: string & content_type , 13090DownloadProgress progress ) { 13091return cli_ -> Delete ( path , body , content_length , content_type , progress ); 13092} 13093Result Client :: Delete (const std:: string & path , const Headers & headers , 13094const char * body , size_t content_length , 13095const std:: string & content_type , 13096DownloadProgress progress ) { 13097return cli_ -> Delete ( path , headers , body , content_length , content_type , 13098progress ); 13099} 13100Result Client :: Delete (const std:: string & path , const std:: string & body , 13101const std:: string & content_type , 13102DownloadProgress progress ) { 13103return cli_ -> Delete ( path , body , content_type , progress ); 13104} 13105Result Client :: Delete (const std:: string & path , const Headers & headers , 13106const std:: string & body , 13107const std:: string & content_type , 13108DownloadProgress progress ) { 13109return cli_ -> Delete ( path , headers , body , content_type , progress ); 13110} 13111Result Client :: Delete (const std:: string & path , const Params & params , 13112DownloadProgress progress ) { 13113return cli_ -> Delete ( path , params , progress ); 13114} 13115Result Client :: Delete (const std:: string & path , const Headers & headers , 13116const Params & params , DownloadProgress progress ) { 13117return cli_ -> Delete ( path , headers , params , progress ); 13118} 13119 13120Result Client :: Options (const std:: string & path ) { 13121return cli_ -> Options ( path ); 13122} 13123Result Client :: Options (const std:: string & path , const Headers & headers ) { 13124return cli_ -> Options ( path , headers ); 13125} 13126 13127ClientImpl :: StreamHandle 13128Client :: open_stream (const std:: string & method , const std:: string & path , 13129const Params & params , const Headers & headers , 13130const std:: string & body , const std:: string & content_type ) { 13131return cli_ -> open_stream ( method , path , params , headers , body , content_type ); 13132} 13133 13134bool Client :: send ( Request & req , Response & res , Error & error ) { 13135return cli_ -> send ( req , res , error ); 13136} 13137 13138Result Client :: send (const Request & req ) { return cli_ -> send ( req ); } 13139 13140void Client :: stop () { cli_ -> stop (); } 13141 13142std:: string Client :: host () const { return cli_ -> host (); } 13143 13144int Client :: port () const { return cli_ -> port (); } 13145 13146size_t Client :: is_socket_open () const { return cli_ -> is_socket_open (); } 13147 13148socket_t Client :: socket () const { return cli_ -> socket (); } 13149 13150void 13151Client :: set_hostname_addr_map (std:: map < std:: string , std:: string > addr_map ) { 13152cli_ -> set_hostname_addr_map (std:: move ( addr_map )); 13153} 13154 13155void Client :: set_default_headers ( Headers headers ) { 13156cli_ -> set_default_headers (std:: move ( headers )); 13157} 13158 13159void Client :: set_header_writer ( 13160std:: function < ssize_t ( Stream & , Headers & ) > const & writer ) { 13161cli_ -> set_header_writer ( writer ); 13162} 13163 13164void Client :: set_address_family ( int family ) { 13165cli_ -> set_address_family ( family ); 13166} 13167 13168void Client :: set_tcp_nodelay ( bool on ) { cli_ -> set_tcp_nodelay ( on ); } 13169 13170void Client :: set_socket_options ( SocketOptions socket_options ) { 13171cli_ -> set_socket_options (std:: move ( socket_options )); 13172} 13173 13174void Client :: set_connection_timeout ( time_t sec , time_t usec ) { 13175cli_ -> set_connection_timeout ( sec , usec ); 13176} 13177 13178void Client :: set_read_timeout ( time_t sec , time_t usec ) { 13179cli_ -> set_read_timeout ( sec , usec ); 13180} 13181 13182void Client :: set_write_timeout ( time_t sec , time_t usec ) { 13183cli_ -> set_write_timeout ( sec , usec ); 13184} 13185 13186void Client :: set_basic_auth (const std:: string & username , 13187const std:: string & password ) { 13188cli_ -> set_basic_auth ( username , password ); 13189} 13190void Client :: set_bearer_token_auth (const std:: string & token ) { 13191cli_ -> set_bearer_token_auth ( token ); 13192} 13193 13194void Client :: set_keep_alive ( bool on ) { cli_ -> set_keep_alive ( on ); } 13195void Client :: set_follow_location ( bool on ) { 13196cli_ -> set_follow_location ( on ); 13197} 13198 13199void Client :: set_path_encode ( bool on ) { cli_ -> set_path_encode ( on ); } 13200 13201void Client :: set_compress ( bool on ) { cli_ -> set_compress ( on ); } 13202 13203void Client :: set_decompress ( bool on ) { cli_ -> set_decompress ( on ); } 13204 13205void Client :: set_payload_max_length ( size_t length ) { 13206cli_ -> set_payload_max_length ( length ); 13207} 13208 13209void Client :: set_interface (const std:: string & intf ) { 13210cli_ -> set_interface ( intf ); 13211} 13212 13213void Client :: set_proxy (const std:: string & host , int port ) { 13214cli_ -> set_proxy ( host , port ); 13215} 13216void Client :: set_proxy_basic_auth (const std:: string & username , 13217const std:: string & password ) { 13218cli_ -> set_proxy_basic_auth ( username , password ); 13219} 13220void Client :: set_proxy_bearer_token_auth (const std:: string & token ) { 13221cli_ -> set_proxy_bearer_token_auth ( token ); 13222} 13223void Client :: set_no_proxy (const std:: vector < std:: string > & patterns ) { 13224cli_ -> set_no_proxy ( patterns ); 13225} 13226 13227void Client :: set_logger ( Logger logger ) { 13228cli_ -> set_logger (std:: move ( logger )); 13229} 13230 13231void Client :: set_error_logger ( ErrorLogger error_logger ) { 13232cli_ -> set_error_logger (std:: move ( error_logger )); 13233} 13234 13235/* 13236* Group 6: SSL Server and Client implementation 13237*/ 13238 13239#ifdef CPPHTTPLIB_SSL_ENABLED 13240 13241// SSL HTTP server implementation 13242SSLServer :: SSLServer (const char * cert_path , const char * private_key_path , 13243const char * client_ca_cert_file_path , 13244const char * client_ca_cert_dir_path , 13245const char * private_key_password ) { 13246using namespace tls ; 13247 13248ctx_ = create_server_context (); 13249if (! ctx_ ) { return; } 13250 13251// Load server certificate and private key 13252if (! set_server_cert_file ( ctx_ , cert_path , private_key_path , 13253private_key_password )) { 13254last_ssl_error_ = static_cast < int > ( get_error ()); 13255free_context ( ctx_ ); 13256ctx_ = nullptr ; 13257return; 13258} 13259 13260// Load client CA certificates for client authentication 13261if ( client_ca_cert_file_path || client_ca_cert_dir_path ) { 13262if (! set_client_ca_file ( ctx_ , client_ca_cert_file_path , 13263client_ca_cert_dir_path )) { 13264last_ssl_error_ = static_cast < int > ( get_error ()); 13265free_context ( ctx_ ); 13266ctx_ = nullptr ; 13267return; 13268} 13269// Enable client certificate verification 13270set_verify_client ( ctx_ , true); 13271} 13272} 13273 13274SSLServer :: SSLServer (const PemMemory & pem ) { 13275using namespace tls ; 13276ctx_ = create_server_context (); 13277if ( ctx_ ) { 13278if (! set_server_cert_pem ( ctx_ , pem . cert_pem , pem . key_pem , 13279pem . private_key_password )) { 13280last_ssl_error_ = static_cast < int > ( get_error ()); 13281free_context ( ctx_ ); 13282ctx_ = nullptr ; 13283} else if ( pem . client_ca_pem && pem . client_ca_pem_len > 0 ) { 13284if (! load_ca_pem ( ctx_ , pem . client_ca_pem , pem . client_ca_pem_len )) { 13285last_ssl_error_ = static_cast < int > ( get_error ()); 13286free_context ( ctx_ ); 13287ctx_ = nullptr ; 13288} else { 13289set_verify_client ( ctx_ , true); 13290} 13291} 13292} 13293} 13294 13295SSLServer :: SSLServer (const tls:: ContextSetupCallback & setup_callback ) { 13296using namespace tls ; 13297ctx_ = create_server_context (); 13298if ( ctx_ ) { 13299if (! setup_callback ( ctx_ )) { 13300free_context ( ctx_ ); 13301ctx_ = nullptr ; 13302} 13303} 13304} 13305 13306SSLServer ::~ SSLServer () { 13307if ( ctx_ ) { tls:: free_context ( ctx_ ); } 13308} 13309 13310bool SSLServer :: is_valid () const { 13311return ctx_ != nullptr && Server :: is_valid (); 13312} 13313 13314bool SSLServer :: process_and_close_socket ( socket_t sock ) { 13315using namespace tls ; 13316 13317// Create TLS session with mutex protection 13318session_t session = nullptr ; 13319{ 13320std:: lock_guard < std:: mutex > guard ( ctx_mutex_ ); 13321session = create_session ( static_cast < ctx_t > ( ctx_ ), sock ); 13322} 13323 13324if (! session ) { 13325last_ssl_error_ = static_cast < int > ( get_error ()); 13326detail:: shutdown_socket ( sock ); 13327detail:: close_socket ( sock ); 13328return false; 13329} 13330 13331// Use scope_exit to ensure cleanup on all paths (including exceptions) 13332bool handshake_done = false; 13333bool ret = false; 13334bool websocket_upgraded = false; 13335auto cleanup = detail:: scope_exit ([ & ] { 13336if ( handshake_done ) { shutdown ( session , ! websocket_upgraded && ret ); } 13337free_session ( session ); 13338detail:: shutdown_socket ( sock ); 13339detail:: close_socket ( sock ); 13340}); 13341 13342// Perform TLS accept handshake with timeout 13343TlsError tls_err ; 13344if (! accept_nonblocking ( session , sock , read_timeout_sec_ , read_timeout_usec_ , 13345& tls_err )) { 13346#ifdef CPPHTTPLIB_OPENSSL_SUPPORT 13347// Map TlsError to legacy ssl_error for backward compatibility 13348if ( tls_err . code == ErrorCode :: WantRead ) { 13349last_ssl_error_ = SSL_ERROR_WANT_READ ; 13350} else if ( tls_err . code == ErrorCode :: WantWrite ) { 13351last_ssl_error_ = SSL_ERROR_WANT_WRITE ; 13352} else { 13353last_ssl_error_ = SSL_ERROR_SSL ; 13354} 13355#else 13356last_ssl_error_ = static_cast < int > ( get_error ()); 13357#endif 13358return false; 13359} 13360 13361handshake_done = true; 13362 13363std:: string remote_addr ; 13364int remote_port = 0 ; 13365detail:: get_remote_ip_and_port ( sock , remote_addr , remote_port ); 13366 13367std:: string local_addr ; 13368int local_port = 0 ; 13369detail:: get_local_ip_and_port ( sock , local_addr , local_port ); 13370 13371ret = serve_guarded ([ & ]() { 13372return detail:: process_server_socket_ssl ( 13373svr_sock_ , session , sock , keep_alive_max_count_ , 13374keep_alive_timeout_sec_ , read_timeout_sec_ , read_timeout_usec_ , 13375write_timeout_sec_ , write_timeout_usec_ , 13376[ & ]( Stream & strm , bool close_connection , bool & connection_closed ) { 13377return process_request ( 13378strm , remote_addr , remote_port , local_addr , local_port , 13379close_connection , connection_closed , 13380[ & ]( Request & req ) { req . ssl = session ; }, & websocket_upgraded ); 13381}); 13382}); 13383 13384return ret ; 13385} 13386 13387bool SSLServer :: update_certs_pem (const char * cert_pem , 13388const char * key_pem , 13389const char * client_ca_pem , 13390const char * password ) { 13391if (! ctx_ ) { return false; } 13392std:: lock_guard < std:: mutex > guard ( ctx_mutex_ ); 13393if (!tls:: update_server_cert ( ctx_ , cert_pem , key_pem , password )) { 13394return false; 13395} 13396if ( client_ca_pem ) { 13397return tls:: update_server_client_ca ( ctx_ , client_ca_pem ); 13398} 13399return true; 13400} 13401 13402// SSL HTTP client implementation 13403SSLClient ::~ SSLClient () { 13404// Make sure to shut down SSL since shutdown_ssl will resolve to the 13405// base function rather than the derived function once we get to the 13406// base class destructor, and won't free the SSL (causing a leak). 13407// This must happen before the context is freed below: some backends 13408// (e.g. mbedTLS) have the SSL session borrow a raw pointer into the 13409// context, so freeing the context first leaves close_notify reading 13410// freed memory. 13411shutdown_ssl_impl ( socket_ , true); 13412if ( ctx_ ) { 13413tls:: free_context ( ctx_ ); 13414ctx_ = nullptr ; 13415} 13416} 13417 13418bool SSLClient :: is_valid () const { return ctx_ != nullptr ; } 13419 13420void SSLClient :: shutdown_ssl ( Socket & socket , bool shutdown_gracefully ) { 13421shutdown_ssl_impl ( socket , shutdown_gracefully ); 13422} 13423 13424void SSLClient :: shutdown_ssl_impl ( Socket & socket , 13425bool shutdown_gracefully ) { 13426if ( socket . sock == INVALID_SOCKET ) { 13427assert ( socket . ssl == nullptr ); 13428return; 13429} 13430if ( socket . ssl ) { 13431tls:: shutdown ( socket . ssl , shutdown_gracefully ); 13432{ 13433std:: lock_guard < std:: mutex > guard ( ctx_mutex_ ); 13434tls:: free_session ( socket . ssl ); 13435} 13436socket . ssl = nullptr ; 13437} 13438assert ( socket . ssl == nullptr ); 13439} 13440 13441bool SSLClient :: process_socket ( 13442const Socket & socket , 13443std::chrono:: time_point < std::chrono:: steady_clock > start_time , 13444std:: function < bool ( Stream & strm ) > callback ) { 13445assert ( socket . ssl ); 13446return detail:: process_client_socket_ssl ( 13447socket . ssl , socket . sock , read_timeout_sec_ , read_timeout_usec_ , 13448write_timeout_sec_ , write_timeout_usec_ , max_timeout_msec_ , start_time , 13449std:: move ( callback )); 13450} 13451 13452bool SSLClient :: is_ssl () const { return true; } 13453 13454bool SSLClient :: create_and_connect_socket ( Socket & socket , Error & error ) { 13455if (! is_valid ()) { 13456error = Error :: SSLConnection ; 13457return false; 13458} 13459return ClientImpl :: create_and_connect_socket ( socket , error ); 13460} 13461 13462bool SSLClient :: setup_proxy_connection ( 13463Socket & socket , 13464std::chrono:: time_point < std::chrono:: steady_clock > start_time , 13465Response & res , bool & success , Error & error ) { 13466if (! is_proxy_enabled_for_host ( host_ )) { return true; } 13467 13468if (! connect_with_proxy ( socket , start_time , res , success , error )) { 13469return false; 13470} 13471 13472if (! initialize_ssl ( socket , error )) { 13473success = false; 13474return false; 13475} 13476 13477return true; 13478} 13479 13480// Assumes that socket_mutex_ is locked and that there are no requests in 13481// flight 13482bool SSLClient :: connect_with_proxy ( 13483Socket & socket , 13484std::chrono:: time_point < std::chrono:: steady_clock > start_time , 13485Response & res , bool & success , Error & error ) { 13486success = true; 13487Response proxy_res ; 13488if (!detail:: process_client_socket ( 13489socket . sock , read_timeout_sec_ , read_timeout_usec_ , 13490write_timeout_sec_ , write_timeout_usec_ , max_timeout_msec_ , 13491start_time , [ & ]( Stream & strm ) { 13492Request req2 ; 13493req2 . method = "CONNECT" ; 13494req2 . path = 13495detail:: make_host_and_port_string_always_port ( host_ , port_ ); 13496if ( max_timeout_msec_ > 0 ) { 13497req2 . start_time_ = std::chrono::steady_clock:: now (); 13498} 13499return process_request ( strm , req2 , proxy_res , false, error ); 13500})) { 13501// Thread-safe to close everything because we are assuming there are no 13502// requests in flight 13503shutdown_ssl ( socket , true); 13504shutdown_socket ( socket ); 13505close_socket ( socket ); 13506success = false; 13507return false; 13508} 13509 13510if ( proxy_res . status == StatusCode :: ProxyAuthenticationRequired_407 ) { 13511if (! proxy_digest_auth_username_ . empty () && 13512! proxy_digest_auth_password_ . empty ()) { 13513std:: map < std:: string , std:: string > auth ; 13514if (detail:: parse_www_authenticate ( proxy_res , auth , true)) { 13515// Close the current socket and create a new one for the authenticated 13516// request 13517shutdown_ssl ( socket , true); 13518shutdown_socket ( socket ); 13519close_socket ( socket ); 13520 13521// Create a new socket for the authenticated CONNECT request 13522if (! ensure_socket_connection ( socket , error )) { 13523success = false; 13524output_error_log ( error , nullptr ); 13525return false; 13526} 13527 13528proxy_res = Response (); 13529if (!detail:: process_client_socket ( 13530socket . sock , read_timeout_sec_ , read_timeout_usec_ , 13531write_timeout_sec_ , write_timeout_usec_ , max_timeout_msec_ , 13532start_time , [ & ]( Stream & strm ) { 13533Request req3 ; 13534req3 . method = "CONNECT" ; 13535req3 . path = detail:: make_host_and_port_string_always_port ( 13536host_ , port_ ); 13537req3 . headers . insert (detail:: make_digest_authentication_header ( 13538req3 , auth , 1 , detail:: random_string ( 10 ), 13539proxy_digest_auth_username_ , proxy_digest_auth_password_ , 13540true)); 13541if ( max_timeout_msec_ > 0 ) { 13542req3 . start_time_ = std::chrono::steady_clock:: now (); 13543} 13544return process_request ( strm , req3 , proxy_res , false, error ); 13545})) { 13546// Thread-safe to close everything because we are assuming there are 13547// no requests in flight 13548shutdown_ssl ( socket , true); 13549shutdown_socket ( socket ); 13550close_socket ( socket ); 13551success = false; 13552return false; 13553} 13554} 13555} 13556} 13557 13558// If status code is not 200, proxy request is failed. 13559// Set error to ProxyConnection and return proxy response 13560// as the response of the request 13561if ( proxy_res . status != StatusCode :: OK_200 ) { 13562error = Error :: ProxyConnection ; 13563output_error_log ( error , nullptr ); 13564res = std:: move ( proxy_res ); 13565// Thread-safe to close everything because we are assuming there are 13566// no requests in flight 13567shutdown_ssl ( socket , true); 13568shutdown_socket ( socket ); 13569close_socket ( socket ); 13570return false; 13571} 13572 13573return true; 13574} 13575 13576bool SSLClient :: ensure_socket_connection ( Socket & socket , Error & error ) { 13577if (! ClientImpl :: ensure_socket_connection ( socket , error )) { return false; } 13578 13579if ( is_proxy_enabled_for_host ( host_ )) { return true; } 13580 13581if (! initialize_ssl ( socket , error )) { 13582shutdown_socket ( socket ); 13583close_socket ( socket ); 13584return false; 13585} 13586 13587return true; 13588} 13589 13590// SSL HTTP client implementation 13591SSLClient :: SSLClient (const std:: string & host ) 13592: SSLClient ( host , 443 , std:: string (), std:: string ()) {} 13593 13594SSLClient :: SSLClient (const std:: string & host , int port ) 13595: SSLClient ( host , port , std:: string (), std:: string ()) {} 13596 13597void SSLClient :: init_ctx () { 13598ctx_ = tls:: create_client_context (); 13599if ( ctx_ ) { tls:: set_min_version ( ctx_ , tls:: Version :: TLS1_2 ); } 13600} 13601 13602void SSLClient :: reset_ctx_on_error () { 13603last_backend_error_ = tls:: get_error (); 13604tls:: free_context ( ctx_ ); 13605ctx_ = nullptr ; 13606} 13607 13608SSLClient :: SSLClient (const std:: string & host , int port , 13609const std:: string & client_cert_path , 13610const std:: string & client_key_path , 13611const std:: string & private_key_password ) 13612: ClientImpl ( host , port , client_cert_path , client_key_path ) { 13613init_ctx (); 13614if (! ctx_ ) { return; } 13615 13616if (! client_cert_path . empty () && ! client_key_path . empty ()) { 13617const char * password = 13618private_key_password . empty () ? nullptr : private_key_password . c_str (); 13619if (!tls:: set_client_cert_file ( ctx_ , client_cert_path . c_str (), 13620client_key_path . c_str (), password )) { 13621reset_ctx_on_error (); 13622} 13623} 13624} 13625 13626SSLClient :: SSLClient (const std:: string & host , int port , 13627const PemMemory & pem ) 13628: ClientImpl ( host , port ) { 13629init_ctx (); 13630if (! ctx_ ) { return; } 13631 13632if ( pem . cert_pem && pem . key_pem ) { 13633if (!tls:: set_client_cert_pem ( ctx_ , pem . cert_pem , pem . key_pem , 13634pem . private_key_password )) { 13635reset_ctx_on_error (); 13636} 13637} 13638} 13639 13640void SSLClient :: set_ca_cert_store (tls:: ca_store_t ca_cert_store ) { 13641if ( ca_cert_store && ctx_ ) { 13642// set_ca_store takes ownership of ca_cert_store 13643tls:: set_ca_store ( ctx_ , ca_cert_store ); 13644ca_cert_store_set_ = true; 13645} else if ( ca_cert_store ) { 13646tls:: free_ca_store ( ca_cert_store ); 13647} 13648} 13649 13650void 13651SSLClient :: set_server_certificate_verifier (tls:: VerifyCallback verifier ) { 13652if (! ctx_ ) { return; } 13653tls:: set_verify_callback ( ctx_ , verifier ); 13654} 13655 13656void SSLClient :: set_session_verifier ( 13657std:: function < SSLVerifierResponse (tls:: session_t ) > verifier ) { 13658session_verifier_ = std:: move ( verifier ); 13659} 13660 13661#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE 13662void SSLClient :: enable_windows_certificate_verification ( bool enabled ) { 13663enable_windows_cert_verification_ = enabled ; 13664} 13665#endif 13666 13667void SSLClient :: load_ca_cert_store (const char * ca_cert , 13668std:: size_t size ) { 13669if ( ctx_ && ca_cert && size > 0 ) { 13670ca_cert_pem_ . assign ( ca_cert , size ); // Store for redirect transfer 13671tls:: load_ca_pem ( ctx_ , ca_cert , size ); 13672} 13673} 13674 13675bool SSLClient :: load_certs () { 13676auto ret = true; 13677 13678// call_once rather than the plain flag WebSocketClient::create_stream() uses: 13679// one client is shared across concurrent requests here. 13680std:: call_once ( initialize_cert_ , [ & ]() { 13681std:: lock_guard < std:: mutex > guard ( ctx_mutex_ ); 13682 13683ret = detail:: load_client_ca_config ( 13684ctx_ , ca_cert_file_path_ , ca_cert_dir_path_ , 13685! ca_cert_pem_ . empty () || ca_cert_store_set_ , system_ca_mode_ , 13686last_backend_error_ ); 13687}); 13688 13689return ret ; 13690} 13691 13692bool SSLClient :: initialize_ssl ( Socket & socket , Error & error ) { 13693// Load CA certificates if server verification is enabled 13694if ( server_certificate_verification_ ) { 13695if (! load_certs ()) { 13696error = Error :: SSLLoadingCerts ; 13697output_error_log ( error , nullptr ); 13698return false; 13699} 13700} 13701 13702detail:: ClientTlsSessionOptions options ; 13703options . server_hostname_verification = server_hostname_verification_ ; 13704options . session_verifier = session_verifier_ ; 13705options . ctx_mutex = & ctx_mutex_ ; 13706#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE 13707// Skip Schannel when a custom CA cert is specified, as the Windows 13708// certificate store would not know about user-provided CA certificates. 13709// Also skip when system CA trust is explicitly disabled. 13710options . windows_cert_verification = 13711enable_windows_cert_verification_ && 13712system_ca_mode_ != SystemCAMode :: Disabled && ca_cert_file_path_ . empty () && 13713ca_cert_dir_path_ . empty () && ca_cert_pem_ . empty () && ! ca_cert_store_set_ ; 13714#endif 13715 13716tls:: session_t session = nullptr ; 13717 13718// Use scope_exit to ensure session is freed on error paths 13719bool success = false; 13720auto session_guard = detail:: scope_exit ([ & ] { 13721if (! success ) { tls:: free_session ( session ); } 13722}); 13723 13724detail:: ClientTlsSessionError tls_error ; 13725if (!detail:: setup_client_tls_session ( 13726host_ , ctx_ , session , socket . sock , server_certificate_verification_ , 13727connection_timeout_sec_ , connection_timeout_usec_ , & tls_error , 13728options )) { 13729error = tls_error . error ; 13730last_ssl_error_ = tls_error . ssl_error ; 13731last_backend_error_ = tls_error . backend_error ; 13732output_error_log ( error , nullptr ); 13733return false; 13734} 13735 13736success = true; 13737socket . ssl = session ; 13738return true; 13739} 13740 13741void Client :: set_digest_auth (const std:: string & username , 13742const std:: string & password ) { 13743cli_ -> set_digest_auth ( username , password ); 13744} 13745 13746void Client :: set_proxy_digest_auth (const std:: string & username , 13747const std:: string & password ) { 13748cli_ -> set_proxy_digest_auth ( username , password ); 13749} 13750 13751void Client :: enable_server_certificate_verification ( bool enabled ) { 13752cli_ -> enable_server_certificate_verification ( enabled ); 13753} 13754 13755void Client :: enable_server_hostname_verification ( bool enabled ) { 13756cli_ -> enable_server_hostname_verification ( enabled ); 13757} 13758 13759void Client :: enable_system_ca ( bool enabled ) { 13760cli_ -> enable_system_ca ( enabled ); 13761} 13762 13763#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE 13764void Client :: enable_windows_certificate_verification ( bool enabled ) { 13765if ( is_ssl_ ) { 13766static_cast < SSLClient &> ( * cli_ ). enable_windows_certificate_verification ( 13767enabled ); 13768} 13769} 13770#endif 13771 13772void Client :: set_ca_cert_path (const std:: string & ca_cert_file_path , 13773const std:: string & ca_cert_dir_path ) { 13774cli_ -> set_ca_cert_path ( ca_cert_file_path , ca_cert_dir_path ); 13775} 13776 13777void Client :: set_ca_cert_store (tls:: ca_store_t ca_cert_store ) { 13778if ( is_ssl_ ) { 13779static_cast < SSLClient &> ( * cli_ ). set_ca_cert_store ( ca_cert_store ); 13780} else if ( ca_cert_store ) { 13781tls:: free_ca_store ( ca_cert_store ); 13782} 13783} 13784 13785void Client :: load_ca_cert_store (const char * ca_cert , std:: size_t size ) { 13786if ( is_ssl_ ) { 13787// Use the PEM-based path so the CA data is retained for redirect transfer 13788static_cast < SSLClient &> ( * cli_ ). load_ca_cert_store ( ca_cert , size ); 13789} 13790} 13791 13792void 13793Client :: set_server_certificate_verifier (tls:: VerifyCallback verifier ) { 13794if ( is_ssl_ ) { 13795static_cast < SSLClient &> ( * cli_ ). set_server_certificate_verifier ( 13796std:: move ( verifier )); 13797} 13798} 13799 13800void Client :: set_session_verifier ( 13801std:: function < SSLVerifierResponse (tls:: session_t ) > verifier ) { 13802if ( is_ssl_ ) { 13803static_cast < SSLClient &> ( * cli_ ). set_session_verifier (std:: move ( verifier )); 13804} 13805} 13806 13807tls:: ctx_t Client :: tls_context () const { 13808if ( is_ssl_ ) { return static_cast < SSLClient &> ( * cli_ ). tls_context (); } 13809return nullptr ; 13810} 13811 13812#endif // CPPHTTPLIB_SSL_ENABLED 13813 13814/* 13815* Group 7: TLS abstraction layer - Common API 13816*/ 13817 13818#ifdef CPPHTTPLIB_SSL_ENABLED 13819 13820namespace tls { 13821 13822// Helper for PeerCert construction 13823PeerCert get_peer_cert_from_session ( const_session_t session ) { 13824return PeerCert ( get_peer_cert ( session )); 13825} 13826 13827namespace impl { 13828 13829VerifyCallback & get_verify_callback () { 13830static thread_local VerifyCallback callback ; 13831return callback ; 13832} 13833 13834VerifyCallback & get_mbedtls_verify_callback () { 13835static thread_local VerifyCallback callback ; 13836return callback ; 13837} 13838 13839// Check if a string is an IPv4 address 13840bool is_ipv4_address (const std:: string & str ) { 13841int dots = 0 ; 13842for ( char c : str ) { 13843if ( c == '.' ) { 13844dots ++ ; 13845} else if (!detail:: is_ascii_digit ( c )) { 13846return false; 13847} 13848} 13849return dots == 3 ; 13850} 13851 13852// Parse IPv4 address string to bytes 13853bool parse_ipv4 (const std:: string & str , unsigned char * out ) { 13854const char * p = str . c_str (); 13855for ( int i = 0 ; i < 4 ; i ++ ) { 13856if ( i > 0 ) { 13857if ( * p != '.' ) { return false; } 13858p ++ ; 13859} 13860int val = 0 ; 13861int digits = 0 ; 13862while (detail:: is_ascii_digit ( * p )) { 13863val = val * 10 + ( * p - '0' ); 13864if ( val > 255 ) { return false; } 13865p ++ ; 13866digits ++ ; 13867} 13868if ( digits == 0 ) { return false; } 13869// Reject leading zeros (e.g., "01.002.03.04") to prevent ambiguity 13870if ( digits > 1 && * ( p - digits ) == '0' ) { return false; } 13871out [ i ] = static_cast < unsigned char > ( val ); 13872} 13873return * p == '\0' ; 13874} 13875 13876// Parse an IP literal (IPv4 or IPv6) into raw network-order bytes. 13877// `out` must have room for at least 16 bytes. Returns the address length 13878// (4 for IPv4, 16 for IPv6) on success, or 0 if the string is not an IP 13879// literal. Used to match a host against iPAddress SANs the same way the 13880// OpenSSL backend does via X509_check_ip. 13881size_t parse_ip_address (const std:: string & str , unsigned char * out ) { 13882if ( is_ipv4_address ( str )) { return parse_ipv4 ( str , out ) ? 4 : 0 ; } 13883struct in6_addr addr6 = {}; 13884if ( inet_pton ( AF_INET6 , str . c_str (), & addr6 ) == 1 ) { 13885memcpy ( out , & addr6 , 16 ); 13886return 16 ; 13887} 13888return 0 ; 13889} 13890 13891#ifdef _WIN32 13892// Enumerate Windows system certificates and call callback with DER data 13893template < typename Callback > 13894bool enumerate_windows_system_certs ( Callback cb ) { 13895bool loaded = false; 13896static const wchar_t * store_names [] = { L"ROOT" , L"CA" }; 13897for ( auto store_name : store_names ) { 13898HCERTSTORE hStore = CertOpenSystemStoreW ( 0 , store_name ); 13899if ( hStore ) { 13900PCCERT_CONTEXT pContext = nullptr ; 13901while (( pContext = CertEnumCertificatesInStore ( hStore , pContext )) != 13902nullptr ) { 13903if ( cb ( pContext -> pbCertEncoded , pContext -> cbCertEncoded )) { 13904loaded = true; 13905} 13906} 13907CertCloseStore ( hStore , 0 ); 13908} 13909} 13910return loaded ; 13911} 13912#endif 13913 13914#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN 13915// Enumerate macOS Keychain certificates and call callback with DER data 13916template < typename Callback > 13917bool enumerate_macos_keychain_certs ( Callback cb ) { 13918bool loaded = false; 13919const SecTrustSettingsDomain domains [] = { 13920kSecTrustSettingsDomainSystem , 13921kSecTrustSettingsDomainAdmin , 13922kSecTrustSettingsDomainUser , 13923}; 13924for ( auto domain : domains ) { 13925CFArrayRef certs = nullptr ; 13926OSStatus status = SecTrustSettingsCopyCertificates ( domain , & certs ); 13927if ( status != errSecSuccess || ! certs ) { 13928if ( certs ) CFRelease ( certs ); 13929continue; 13930} 13931CFIndex count = CFArrayGetCount ( certs ); 13932for ( CFIndex i = 0 ; i < count ; i ++ ) { 13933SecCertificateRef cert = 13934( SecCertificateRef ) CFArrayGetValueAtIndex ( certs , i ); 13935CFDataRef data = SecCertificateCopyData ( cert ); 13936if ( data ) { 13937if ( cb ( CFDataGetBytePtr ( data ), 13938static_cast < size_t > ( CFDataGetLength ( data )))) { 13939loaded = true; 13940} 13941CFRelease ( data ); 13942} 13943} 13944CFRelease ( certs ); 13945} 13946return loaded ; 13947} 13948#endif 13949 13950#if !defined( _WIN32 ) && !(defined( __APPLE__ ) && \ 13951defined( CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN )) 13952// Common CA certificate file paths on Linux/Unix 13953const char ** system_ca_paths () { 13954static const char * paths [] = { 13955"/etc/ssl/certs/ca-certificates.crt" , // Debian/Ubuntu 13956"/etc/pki/tls/certs/ca-bundle.crt" , // RHEL/CentOS 13957"/etc/ssl/ca-bundle.pem" , // OpenSUSE 13958"/etc/pki/tls/cacert.pem" , // OpenELEC 13959"/etc/ssl/cert.pem" , // Alpine, FreeBSD 13960nullptr }; 13961return paths ; 13962} 13963 13964// Common CA certificate directory paths on Linux/Unix 13965const char ** system_ca_dirs () { 13966static const char * dirs [] = { "/etc/ssl/certs" , // Debian/Ubuntu 13967"/etc/pki/tls/certs" , // RHEL/CentOS 13968"/usr/share/ca-certificates" , // Other 13969nullptr }; 13970return dirs ; 13971} 13972#endif 13973 13974} // namespace impl 13975 13976bool set_client_ca_file ( ctx_t ctx , const char * ca_file , 13977const char * ca_dir ) { 13978if (! ctx ) { return false; } 13979 13980bool success = true; 13981if ( ca_file && * ca_file ) { 13982if (! load_ca_file ( ctx , ca_file )) { success = false; } 13983} 13984if ( ca_dir && * ca_dir ) { 13985if (! load_ca_dir ( ctx , ca_dir )) { success = false; } 13986} 13987 13988#ifdef CPPHTTPLIB_OPENSSL_SUPPORT 13989// Set CA list for client certificate request (CertificateRequest message) 13990if ( ca_file && * ca_file ) { 13991auto list = SSL_load_client_CA_file ( ca_file ); 13992if ( list ) { SSL_CTX_set_client_CA_list ( static_cast < SSL_CTX *> ( ctx ), list ); } 13993} 13994#endif 13995 13996return success ; 13997} 13998 13999bool set_server_cert_pem ( ctx_t ctx , const char * cert , const char * key , 14000const char * password ) { 14001return set_client_cert_pem ( ctx , cert , key , password ); 14002} 14003 14004bool set_server_cert_file ( ctx_t ctx , const char * cert_path , 14005const char * key_path , const char * password ) { 14006return set_client_cert_file ( ctx , cert_path , key_path , password ); 14007} 14008 14009// PeerCert implementation 14010PeerCert :: PeerCert () = default ; 14011 14012PeerCert :: PeerCert ( cert_t cert ) : cert_ ( cert ) {} 14013 14014PeerCert :: PeerCert ( PeerCert && other ) noexcept : cert_ ( other . cert_ ) { 14015other . cert_ = nullptr ; 14016} 14017 14018PeerCert & PeerCert ::operator = ( PeerCert && other ) noexcept { 14019if ( this != & other ) { 14020if ( cert_ ) { free_cert ( cert_ ); } 14021cert_ = other . cert_ ; 14022other . cert_ = nullptr ; 14023} 14024return * this ; 14025} 14026 14027PeerCert ::~ PeerCert () { 14028if ( cert_ ) { free_cert ( cert_ ); } 14029} 14030 14031PeerCert ::operator bool () const { return cert_ != nullptr ; } 14032 14033std:: string PeerCert :: subject_cn () const { 14034return cert_ ? get_cert_subject_cn ( cert_ ) : std:: string (); 14035} 14036 14037std:: string PeerCert :: issuer_name () const { 14038return cert_ ? get_cert_issuer_name ( cert_ ) : std:: string (); 14039} 14040 14041bool PeerCert :: check_hostname (const char * hostname ) const { 14042return cert_ ? verify_hostname ( cert_ , hostname ) : false; 14043} 14044 14045std:: vector < SanEntry > PeerCert :: sans () const { 14046std:: vector < SanEntry > result ; 14047if ( cert_ ) { get_cert_sans ( cert_ , result ); } 14048return result ; 14049} 14050 14051bool PeerCert :: validity ( time_t & not_before , time_t & not_after ) const { 14052return cert_ ? get_cert_validity ( cert_ , not_before , not_after ) : false; 14053} 14054 14055std:: string PeerCert :: serial () const { 14056return cert_ ? get_cert_serial ( cert_ ) : std:: string (); 14057} 14058 14059// VerifyContext method implementations 14060std:: string VerifyContext :: subject_cn () const { 14061return cert ? get_cert_subject_cn ( cert ) : std:: string (); 14062} 14063 14064std:: string VerifyContext :: issuer_name () const { 14065return cert ? get_cert_issuer_name ( cert ) : std:: string (); 14066} 14067 14068bool VerifyContext :: check_hostname (const char * hostname ) const { 14069return cert ? verify_hostname ( cert , hostname ) : false; 14070} 14071 14072std:: vector < SanEntry > VerifyContext :: sans () const { 14073std:: vector < SanEntry > result ; 14074if ( cert ) { get_cert_sans ( cert , result ); } 14075return result ; 14076} 14077 14078bool VerifyContext :: validity ( time_t & not_before , 14079time_t & not_after ) const { 14080return cert ? get_cert_validity ( cert , not_before , not_after ) : false; 14081} 14082 14083std:: string VerifyContext :: serial () const { 14084return cert ? get_cert_serial ( cert ) : std:: string (); 14085} 14086 14087// TlsError static method implementation 14088std:: string TlsError :: verify_error_to_string ( long error_code ) { 14089return verify_error_string ( error_code ); 14090} 14091 14092} // namespace tls 14093 14094// Request::peer_cert() implementation 14095tls:: PeerCert Request :: peer_cert () const { 14096return tls:: get_peer_cert_from_session ( ssl ); 14097} 14098 14099// Request::sni() implementation 14100std:: string Request :: sni () const { 14101if (! ssl ) { return std:: string (); } 14102const char * s = tls:: get_sni ( ssl ); 14103return s ? std:: string ( s ) : std:: string (); 14104} 14105 14106#endif // CPPHTTPLIB_SSL_ENABLED 14107 14108/* 14109* Group 8: TLS abstraction layer - OpenSSL backend 14110*/ 14111 14112/* 14113* OpenSSL Backend Implementation 14114*/ 14115 14116#ifdef CPPHTTPLIB_OPENSSL_SUPPORT 14117namespace tls { 14118 14119namespace impl { 14120 14121// Helper to map OpenSSL SSL_get_error to ErrorCode 14122ErrorCode map_ssl_error ( int ssl_error , int & out_errno ) { 14123switch ( ssl_error ) { 14124case SSL_ERROR_NONE : return ErrorCode :: Success ; 14125case SSL_ERROR_WANT_READ : return ErrorCode :: WantRead ; 14126case SSL_ERROR_WANT_WRITE : return ErrorCode :: WantWrite ; 14127case SSL_ERROR_ZERO_RETURN : return ErrorCode :: PeerClosed ; 14128case SSL_ERROR_SYSCALL : out_errno = errno ; return ErrorCode :: SyscallError ; 14129case SSL_ERROR_SSL : 14130default: return ErrorCode :: Fatal ; 14131} 14132} 14133 14134// Helper: Create client CA list from PEM string 14135// Returns a new STACK_OF(X509_NAME)* or nullptr on failure 14136// Caller takes ownership of returned list 14137STACK_OF ( X509_NAME ) * 14138create_client_ca_list_from_pem ( const char * ca_pem ) { 14139if (! ca_pem ) { return nullptr ; } 14140 14141auto ca_list = sk_X509_NAME_new_null (); 14142if (! ca_list ) { return nullptr ; } 14143 14144BIO * bio = BIO_new_mem_buf ( ca_pem , -1 ); 14145if (! bio ) { 14146sk_X509_NAME_pop_free ( ca_list , X509_NAME_free ); 14147return nullptr ; 14148} 14149 14150X509 * cert = nullptr ; 14151while (( cert = PEM_read_bio_X509 ( bio , nullptr , nullptr , nullptr )) != 14152nullptr ) { 14153const X509_NAME * name = X509_get_subject_name ( cert ); 14154if ( name ) { 14155sk_X509_NAME_push ( ca_list , X509_NAME_dup ( const_cast < X509_NAME *> ( name ))); 14156} 14157X509_free ( cert ); 14158} 14159BIO_free ( bio ); 14160 14161return ca_list ; 14162} 14163 14164// OpenSSL verify callback wrapper 14165int openssl_verify_callback ( int preverify_ok , X509_STORE_CTX * ctx ) { 14166auto & callback = get_verify_callback (); 14167if (! callback ) { return preverify_ok ; } 14168 14169// Get SSL object from X509_STORE_CTX 14170auto ssl = static_cast < SSL *> ( 14171X509_STORE_CTX_get_ex_data ( ctx , SSL_get_ex_data_X509_STORE_CTX_idx ())); 14172if (! ssl ) { return preverify_ok ; } 14173 14174// Get current certificate and depth 14175auto cert = X509_STORE_CTX_get_current_cert ( ctx ); 14176int depth = X509_STORE_CTX_get_error_depth ( ctx ); 14177int error = X509_STORE_CTX_get_error ( ctx ); 14178 14179// Build context 14180VerifyContext verify_ctx ; 14181verify_ctx . session = static_cast < session_t > ( ssl ); 14182verify_ctx . cert = static_cast < cert_t > ( cert ); 14183verify_ctx . depth = depth ; 14184verify_ctx . preverify_ok = ( preverify_ok != 0 ); 14185verify_ctx . error_code = error ; 14186verify_ctx . error_string = 14187( error != X509_V_OK ) ? X509_verify_cert_error_string ( error ) : nullptr ; 14188 14189return callback ( verify_ctx ) ? 1 : 0 ; 14190} 14191 14192// X509_STORE_get0_objects is deprecated since OpenSSL 4.0 because it is not 14193// thread-safe; X509_STORE_get1_objects (OpenSSL 3.3+) returns a snapshot 14194// that must be released with release_store_objects 14195#if !defined( OPENSSL_IS_BORINGSSL ) && !defined( LIBRESSL_VERSION_NUMBER ) && \ 14196OPENSSL_VERSION_NUMBER >= 0x30300000L 14197#define CPPHTTPLIB_HAS_X509_STORE_GET1_OBJECTS 14198#endif 14199 14200STACK_OF ( X509_OBJECT ) * get_store_objects ( X509_STORE * store ) { 14201#ifdef CPPHTTPLIB_HAS_X509_STORE_GET1_OBJECTS 14202return X509_STORE_get1_objects ( store ); 14203#else 14204return X509_STORE_get0_objects ( store ); 14205#endif 14206} 14207 14208void release_store_objects ( STACK_OF ( X509_OBJECT ) * objs ) { 14209#ifdef CPPHTTPLIB_HAS_X509_STORE_GET1_OBJECTS 14210sk_X509_OBJECT_pop_free ( objs , X509_OBJECT_free ); 14211#else 14212( void ) objs ; // get0 variant returns an internal pointer; nothing to free 14213#endif 14214} 14215 14216} // namespace impl 14217 14218ctx_t create_client_context () { 14219SSL_CTX * ctx = SSL_CTX_new ( TLS_client_method ()); 14220if ( ctx ) { 14221// Disable auto-retry to properly handle non-blocking I/O 14222SSL_CTX_clear_mode ( ctx , SSL_MODE_AUTO_RETRY ); 14223// Set minimum TLS version 14224SSL_CTX_set_min_proto_version ( ctx , TLS1_2_VERSION ); 14225} 14226return static_cast < ctx_t > ( ctx ); 14227} 14228 14229void free_context ( ctx_t ctx ) { 14230if ( ctx ) { SSL_CTX_free ( static_cast < SSL_CTX *> ( ctx )); } 14231} 14232 14233bool set_min_version ( ctx_t ctx , Version version ) { 14234if (! ctx ) return false; 14235return SSL_CTX_set_min_proto_version ( static_cast < SSL_CTX *> ( ctx ), 14236static_cast < int > ( version )) == 1 ; 14237} 14238 14239bool load_ca_pem ( ctx_t ctx , const char * pem , size_t len ) { 14240if (! ctx || ! pem || len == 0 ) return false; 14241 14242auto ssl_ctx = static_cast < SSL_CTX *> ( ctx ); 14243auto store = SSL_CTX_get_cert_store ( ssl_ctx ); 14244if (! store ) return false; 14245 14246auto bio = BIO_new_mem_buf ( pem , static_cast < int > ( len )); 14247if (! bio ) return false; 14248 14249bool ok = true; 14250X509 * cert = nullptr ; 14251while (( cert = PEM_read_bio_X509 ( bio , nullptr , nullptr , nullptr )) != 14252nullptr ) { 14253if ( X509_STORE_add_cert ( store , cert ) != 1 ) { 14254// Ignore duplicate errors 14255auto err = ERR_peek_last_error (); 14256if ( ERR_GET_REASON ( err ) != X509_R_CERT_ALREADY_IN_HASH_TABLE ) { 14257ok = false; 14258} 14259} 14260X509_free ( cert ); 14261if (! ok ) break; 14262} 14263BIO_free ( bio ); 14264 14265// Clear any "no more certificates" errors 14266ERR_clear_error (); 14267return ok ; 14268} 14269 14270bool load_ca_file ( ctx_t ctx , const char * file_path ) { 14271if (! ctx || ! file_path ) return false; 14272return SSL_CTX_load_verify_locations ( static_cast < SSL_CTX *> ( ctx ), file_path , 14273nullptr ) == 1 ; 14274} 14275 14276bool load_ca_dir ( ctx_t ctx , const char * dir_path ) { 14277if (! ctx || ! dir_path ) return false; 14278return SSL_CTX_load_verify_locations ( static_cast < SSL_CTX *> ( ctx ), nullptr , 14279dir_path ) == 1 ; 14280} 14281 14282bool load_system_certs ( ctx_t ctx ) { 14283if (! ctx ) return false; 14284auto ssl_ctx = static_cast < SSL_CTX *> ( ctx ); 14285 14286#ifdef _WIN32 14287// Windows: Load from system certificate store (ROOT and CA) 14288auto store = SSL_CTX_get_cert_store ( ssl_ctx ); 14289if (! store ) return false; 14290 14291bool loaded_any = false; 14292static const wchar_t * store_names [] = { L"ROOT" , L"CA" }; 14293for ( auto store_name : store_names ) { 14294auto hStore = CertOpenSystemStoreW ( NULL , store_name ); 14295if (! hStore ) continue; 14296 14297PCCERT_CONTEXT pContext = nullptr ; 14298while (( pContext = CertEnumCertificatesInStore ( hStore , pContext )) != 14299nullptr ) { 14300const unsigned char * data = pContext -> pbCertEncoded ; 14301auto x509 = d2i_X509 ( nullptr , & data , pContext -> cbCertEncoded ); 14302if ( x509 ) { 14303if ( X509_STORE_add_cert ( store , x509 ) == 1 ) { loaded_any = true; } 14304X509_free ( x509 ); 14305} 14306} 14307CertCloseStore ( hStore , 0 ); 14308} 14309return loaded_any ; 14310 14311#elif defined( __APPLE__ ) 14312#ifdef CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN 14313// macOS: Load from Keychain 14314auto store = SSL_CTX_get_cert_store ( ssl_ctx ); 14315if (! store ) return false; 14316 14317bool loaded_any = false; 14318const SecTrustSettingsDomain domains [] = { 14319kSecTrustSettingsDomainSystem , 14320kSecTrustSettingsDomainAdmin , 14321kSecTrustSettingsDomainUser , 14322}; 14323for ( auto domain : domains ) { 14324CFArrayRef certs = nullptr ; 14325if ( SecTrustSettingsCopyCertificates ( domain , & certs ) != errSecSuccess || 14326! certs ) { 14327if ( certs ) CFRelease ( certs ); 14328continue; 14329} 14330auto count = CFArrayGetCount ( certs ); 14331for ( CFIndex i = 0 ; i < count ; i ++ ) { 14332auto cert = reinterpret_cast < SecCertificateRef > ( 14333const_cast < void *> ( CFArrayGetValueAtIndex ( certs , i ))); 14334CFDataRef der = SecCertificateCopyData ( cert ); 14335if ( der ) { 14336const unsigned char * data = CFDataGetBytePtr ( der ); 14337auto x509 = d2i_X509 ( nullptr , & data , CFDataGetLength ( der )); 14338if ( x509 ) { 14339if ( X509_STORE_add_cert ( store , x509 ) == 1 ) { loaded_any = true; } 14340X509_free ( x509 ); 14341} 14342CFRelease ( der ); 14343} 14344} 14345CFRelease ( certs ); 14346} 14347return loaded_any || SSL_CTX_set_default_verify_paths ( ssl_ctx ) == 1 ; 14348#else 14349return SSL_CTX_set_default_verify_paths ( ssl_ctx ) == 1 ; 14350#endif 14351 14352#else 14353// Other Unix: use default verify paths 14354return SSL_CTX_set_default_verify_paths ( ssl_ctx ) == 1 ; 14355#endif 14356} 14357 14358bool set_client_cert_pem ( ctx_t ctx , const char * cert , const char * key , 14359const char * password ) { 14360if (! ctx || ! cert || ! key ) return false; 14361 14362auto ssl_ctx = static_cast < SSL_CTX *> ( ctx ); 14363 14364// Load certificate 14365auto cert_bio = BIO_new_mem_buf ( cert , -1 ); 14366if (! cert_bio ) return false; 14367 14368auto x509 = PEM_read_bio_X509 ( cert_bio , nullptr , nullptr , nullptr ); 14369BIO_free ( cert_bio ); 14370if (! x509 ) return false; 14371 14372auto cert_ok = SSL_CTX_use_certificate ( ssl_ctx , x509 ) == 1 ; 14373X509_free ( x509 ); 14374if (! cert_ok ) return false; 14375 14376// Load private key 14377auto key_bio = BIO_new_mem_buf ( key , -1 ); 14378if (! key_bio ) return false; 14379 14380auto pkey = PEM_read_bio_PrivateKey ( key_bio , nullptr , nullptr , 14381password ? const_cast < char *> ( password ) 14382: nullptr ); 14383BIO_free ( key_bio ); 14384if (! pkey ) return false; 14385 14386auto key_ok = SSL_CTX_use_PrivateKey ( ssl_ctx , pkey ) == 1 ; 14387EVP_PKEY_free ( pkey ); 14388 14389return key_ok && SSL_CTX_check_private_key ( ssl_ctx ) == 1 ; 14390} 14391 14392bool set_client_cert_file ( ctx_t ctx , const char * cert_path , 14393const char * key_path , const char * password ) { 14394if (! ctx || ! cert_path || ! key_path ) return false; 14395 14396auto ssl_ctx = static_cast < SSL_CTX *> ( ctx ); 14397 14398if ( password && password [ 0 ] != '\0' ) { 14399SSL_CTX_set_default_passwd_cb_userdata ( 14400ssl_ctx , reinterpret_cast < void *> ( const_cast < char *> ( password ))); 14401} 14402 14403return SSL_CTX_use_certificate_chain_file ( ssl_ctx , cert_path ) == 1 && 14404SSL_CTX_use_PrivateKey_file ( ssl_ctx , key_path , SSL_FILETYPE_PEM ) == 1 ; 14405} 14406 14407ctx_t create_server_context () { 14408SSL_CTX * ctx = SSL_CTX_new ( TLS_server_method ()); 14409if ( ctx ) { 14410SSL_CTX_set_options ( ctx , SSL_OP_NO_COMPRESSION | 14411SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION ); 14412SSL_CTX_set_min_proto_version ( ctx , TLS1_2_VERSION ); 14413} 14414return static_cast < ctx_t > ( ctx ); 14415} 14416 14417void set_verify_client ( ctx_t ctx , bool require ) { 14418if (! ctx ) return; 14419SSL_CTX_set_verify ( static_cast < SSL_CTX *> ( ctx ), 14420require 14421? ( SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT ) 14422: SSL_VERIFY_NONE , 14423nullptr ); 14424} 14425 14426session_t create_session ( ctx_t ctx , socket_t sock ) { 14427if (! ctx || sock == INVALID_SOCKET ) return nullptr ; 14428 14429auto ssl_ctx = static_cast < SSL_CTX *> ( ctx ); 14430SSL * ssl = SSL_new ( ssl_ctx ); 14431if (! ssl ) return nullptr ; 14432 14433// Disable auto-retry for proper non-blocking I/O handling 14434SSL_clear_mode ( ssl , SSL_MODE_AUTO_RETRY ); 14435 14436auto bio = BIO_new_socket ( static_cast < int > ( sock ), BIO_NOCLOSE ); 14437if (! bio ) { 14438SSL_free ( ssl ); 14439return nullptr ; 14440} 14441 14442SSL_set_bio ( ssl , bio , bio ); 14443return static_cast < session_t > ( ssl ); 14444} 14445 14446void free_session ( session_t session ) { 14447if ( session ) { SSL_free ( static_cast < SSL *> ( session )); } 14448} 14449 14450bool set_sni ( session_t session , const char * hostname , 14451bool /*verify_hostname*/ ) { 14452if (! session || ! hostname ) return false; 14453 14454auto ssl = static_cast < SSL *> ( session ); 14455 14456// Set SNI (Server Name Indication) only - does not enable verification. 14457// OpenSSL never binds identity checking to SNI (that happens post- 14458// handshake in setup_client_tls_session()), so verify_hostname is unused. 14459#if defined( OPENSSL_IS_BORINGSSL ) 14460return SSL_set_tlsext_host_name ( ssl , hostname ) == 1 ; 14461#else 14462// Direct call instead of macro to suppress -Wold-style-cast warning 14463return SSL_ctrl ( ssl , SSL_CTRL_SET_TLSEXT_HOSTNAME , TLSEXT_NAMETYPE_host_name , 14464static_cast < void *> ( const_cast < char *> ( hostname ))) == 1 ; 14465#endif 14466} 14467 14468TlsError connect ( session_t session ) { 14469if (! session ) { return TlsError (); } 14470 14471auto ssl = static_cast < SSL *> ( session ); 14472auto ret = SSL_connect ( ssl ); 14473 14474TlsError err ; 14475if ( ret == 1 ) { 14476err . code = ErrorCode :: Success ; 14477} else { 14478auto ssl_err = SSL_get_error ( ssl , ret ); 14479err . code = impl:: map_ssl_error ( ssl_err , err . sys_errno ); 14480err . backend_code = ERR_get_error (); 14481} 14482return err ; 14483} 14484 14485TlsError accept ( session_t session ) { 14486if (! session ) { return TlsError (); } 14487 14488auto ssl = static_cast < SSL *> ( session ); 14489auto ret = SSL_accept ( ssl ); 14490 14491TlsError err ; 14492if ( ret == 1 ) { 14493err . code = ErrorCode :: Success ; 14494} else { 14495auto ssl_err = SSL_get_error ( ssl , ret ); 14496err . code = impl:: map_ssl_error ( ssl_err , err . sys_errno ); 14497err . backend_code = ERR_get_error (); 14498} 14499return err ; 14500} 14501 14502bool connect_nonblocking ( session_t session , socket_t sock , 14503time_t timeout_sec , time_t timeout_usec , 14504TlsError * err ) { 14505if (! session ) { 14506if ( err ) { err -> code = ErrorCode :: Fatal ; } 14507return false; 14508} 14509 14510auto ssl = static_cast < SSL *> ( session ); 14511auto bio = SSL_get_rbio ( ssl ); 14512 14513// Set non-blocking mode for handshake 14514detail:: set_nonblocking ( sock , true); 14515if ( bio ) { BIO_set_nbio ( bio , 1 ); } 14516 14517auto cleanup = detail:: scope_exit ([ & ]() { 14518// Restore blocking mode after handshake 14519if ( bio ) { BIO_set_nbio ( bio , 0 ); } 14520detail:: set_nonblocking ( sock , false); 14521}); 14522 14523auto res = 0 ; 14524while (( res = SSL_connect ( ssl )) != 1 ) { 14525auto ssl_err = SSL_get_error ( ssl , res ); 14526switch ( ssl_err ) { 14527case SSL_ERROR_WANT_READ : 14528if (detail:: select_read ( sock , timeout_sec , timeout_usec ) > 0 ) { 14529continue; 14530} 14531break; 14532case SSL_ERROR_WANT_WRITE : 14533if (detail:: select_write ( sock , timeout_sec , timeout_usec ) > 0 ) { 14534continue; 14535} 14536break; 14537default: break; 14538} 14539if ( err ) { 14540err -> code = impl:: map_ssl_error ( ssl_err , err -> sys_errno ); 14541err -> backend_code = ERR_get_error (); 14542} 14543return false; 14544} 14545if ( err ) { err -> code = ErrorCode :: Success ; } 14546return true; 14547} 14548 14549bool accept_nonblocking ( session_t session , socket_t sock , 14550time_t timeout_sec , time_t timeout_usec , 14551TlsError * err ) { 14552if (! session ) { 14553if ( err ) { err -> code = ErrorCode :: Fatal ; } 14554return false; 14555} 14556 14557auto ssl = static_cast < SSL *> ( session ); 14558auto bio = SSL_get_rbio ( ssl ); 14559 14560// Set non-blocking mode for handshake 14561detail:: set_nonblocking ( sock , true); 14562if ( bio ) { BIO_set_nbio ( bio , 1 ); } 14563 14564auto cleanup = detail:: scope_exit ([ & ]() { 14565// Restore blocking mode after handshake 14566if ( bio ) { BIO_set_nbio ( bio , 0 ); } 14567detail:: set_nonblocking ( sock , false); 14568}); 14569 14570auto res = 0 ; 14571while (( res = SSL_accept ( ssl )) != 1 ) { 14572auto ssl_err = SSL_get_error ( ssl , res ); 14573switch ( ssl_err ) { 14574case SSL_ERROR_WANT_READ : 14575if (detail:: select_read ( sock , timeout_sec , timeout_usec ) > 0 ) { 14576continue; 14577} 14578break; 14579case SSL_ERROR_WANT_WRITE : 14580if (detail:: select_write ( sock , timeout_sec , timeout_usec ) > 0 ) { 14581continue; 14582} 14583break; 14584default: break; 14585} 14586if ( err ) { 14587err -> code = impl:: map_ssl_error ( ssl_err , err -> sys_errno ); 14588err -> backend_code = ERR_get_error (); 14589} 14590return false; 14591} 14592if ( err ) { err -> code = ErrorCode :: Success ; } 14593return true; 14594} 14595 14596ssize_t read ( session_t session , void * buf , size_t len , TlsError & err ) { 14597if (! session || ! buf ) { 14598err . code = ErrorCode :: Fatal ; 14599return -1 ; 14600} 14601 14602auto ssl = static_cast < SSL *> ( session ); 14603constexpr auto max_len = 14604static_cast < size_t > ((std:: numeric_limits < int > :: max )()); 14605if ( len > max_len ) { len = max_len ; } 14606auto ret = SSL_read ( ssl , buf , static_cast < int > ( len )); 14607 14608if ( ret > 0 ) { 14609err . code = ErrorCode :: Success ; 14610return ret ; 14611} 14612 14613auto ssl_err = SSL_get_error ( ssl , ret ); 14614err . code = impl:: map_ssl_error ( ssl_err , err . sys_errno ); 14615if ( err . code == ErrorCode :: PeerClosed ) { 14616return 0 ; 14617} // Gracefully handle the peer closed state. 14618if ( err . code == ErrorCode :: Fatal ) { err . backend_code = ERR_get_error (); } 14619return -1 ; 14620} 14621 14622ssize_t write ( session_t session , const void * buf , size_t len , 14623TlsError & err ) { 14624if (! session || ! buf ) { 14625err . code = ErrorCode :: Fatal ; 14626return -1 ; 14627} 14628 14629auto ssl = static_cast < SSL *> ( session ); 14630auto ret = SSL_write ( ssl , buf , static_cast < int > ( len )); 14631 14632if ( ret > 0 ) { 14633err . code = ErrorCode :: Success ; 14634return ret ; 14635} 14636 14637auto ssl_err = SSL_get_error ( ssl , ret ); 14638err . code = impl:: map_ssl_error ( ssl_err , err . sys_errno ); 14639if ( err . code == ErrorCode :: Fatal ) { err . backend_code = ERR_get_error (); } 14640return -1 ; 14641} 14642 14643int pending ( const_session_t session ) { 14644if (! session ) return 0 ; 14645return SSL_pending ( static_cast < SSL *> ( const_cast < void *> ( session ))); 14646} 14647 14648void shutdown ( session_t session , bool graceful ) { 14649if (! session ) return; 14650 14651auto ssl = static_cast < SSL *> ( session ); 14652if ( graceful ) { 14653// First call sends close_notify 14654if ( SSL_shutdown ( ssl ) == 0 ) { 14655// Second call waits for peer's close_notify 14656SSL_shutdown ( ssl ); 14657} 14658} 14659} 14660 14661bool is_peer_closed ( session_t session , socket_t sock ) { 14662if (! session ) return true; 14663 14664// Temporarily set socket to non-blocking to avoid blocking on SSL_peek 14665detail:: set_nonblocking ( sock , true); 14666auto se = detail:: scope_exit ([ & ]() { detail:: set_nonblocking ( sock , false); }); 14667 14668auto ssl = static_cast < SSL *> ( session ); 14669char buf ; 14670auto ret = SSL_peek ( ssl , & buf , 1 ); 14671if ( ret > 0 ) return false; 14672 14673auto err = SSL_get_error ( ssl , ret ); 14674return err == SSL_ERROR_ZERO_RETURN ; 14675} 14676 14677cert_t get_peer_cert ( const_session_t session ) { 14678if (! session ) return nullptr ; 14679return static_cast < cert_t > ( SSL_get1_peer_certificate ( 14680static_cast < SSL *> ( const_cast < void *> ( session )))); 14681} 14682 14683void free_cert ( cert_t cert ) { 14684if ( cert ) { X509_free ( static_cast < X509 *> ( cert )); } 14685} 14686 14687bool verify_hostname ( cert_t cert , const char * hostname ) { 14688if (! cert || ! hostname ) return false; 14689 14690auto x509 = static_cast < X509 *> ( cert ); 14691 14692// Use X509_check_ip_asc for IP addresses, X509_check_host for DNS names 14693if (detail:: is_ip_address ( hostname )) { 14694return X509_check_ip_asc ( x509 , hostname , 0 ) == 1 ; 14695} 14696return X509_check_host ( x509 , hostname , strlen ( hostname ), 0 , nullptr ) == 1 ; 14697} 14698 14699uint64_t hostname_mismatch_code () { 14700return static_cast < uint64_t > ( X509_V_ERR_HOSTNAME_MISMATCH ); 14701} 14702 14703long get_verify_result ( const_session_t session ) { 14704if (! session ) return X509_V_ERR_UNSPECIFIED ; 14705return SSL_get_verify_result ( static_cast < SSL *> ( const_cast < void *> ( session ))); 14706} 14707 14708std:: string get_cert_subject_cn ( cert_t cert ) { 14709if (! cert ) return "" ; 14710auto x509 = static_cast < X509 *> ( cert ); 14711auto subject_name = X509_get_subject_name ( x509 ); 14712if (! subject_name ) return "" ; 14713 14714// X509_NAME_get_text_by_NID is deprecated since OpenSSL 4.0 14715auto idx = X509_NAME_get_index_by_NID ( subject_name , NID_commonName , -1 ); 14716if ( idx < 0 ) return "" ; 14717 14718auto entry = X509_NAME_get_entry ( subject_name , idx ); 14719if (! entry ) return "" ; 14720 14721auto data = X509_NAME_ENTRY_get_data ( entry ); 14722if (! data ) return "" ; 14723 14724return std:: string ( 14725reinterpret_cast < const char *> ( ASN1_STRING_get0_data ( data )), 14726static_cast < size_t > ( ASN1_STRING_length ( data ))); 14727} 14728 14729std:: string get_cert_issuer_name ( cert_t cert ) { 14730if (! cert ) return "" ; 14731auto x509 = static_cast < X509 *> ( cert ); 14732auto issuer_name = X509_get_issuer_name ( x509 ); 14733if (! issuer_name ) return "" ; 14734 14735char buf [ 256 ]; 14736X509_NAME_oneline ( issuer_name , buf , sizeof( buf )); 14737return std:: string ( buf ); 14738} 14739 14740bool get_cert_sans ( cert_t cert , std:: vector < SanEntry > & sans ) { 14741sans . clear (); 14742if (! cert ) return false; 14743auto x509 = static_cast < X509 *> ( cert ); 14744 14745auto names = static_cast < GENERAL_NAMES *> ( 14746X509_get_ext_d2i ( x509 , NID_subject_alt_name , nullptr , nullptr )); 14747if (! names ) return true; // No SANs is valid 14748 14749auto count = sk_GENERAL_NAME_num ( names ); 14750for (decltype( count ) i = 0 ; i < count ; i ++ ) { 14751auto gen = sk_GENERAL_NAME_value ( names , i ); 14752if (! gen ) continue; 14753 14754SanEntry entry ; 14755switch ( gen -> type ) { 14756case GEN_DNS : 14757entry . type = SanType :: DNS ; 14758if ( gen -> d . dNSName ) { 14759entry . value = std:: string ( 14760reinterpret_cast < const char *> ( 14761ASN1_STRING_get0_data ( gen -> d . dNSName )), 14762static_cast < size_t > ( ASN1_STRING_length ( gen -> d . dNSName ))); 14763} 14764break; 14765case GEN_IPADD : 14766entry . type = SanType :: IP ; 14767if ( gen -> d . iPAddress ) { 14768auto data = ASN1_STRING_get0_data ( gen -> d . iPAddress ); 14769auto len = ASN1_STRING_length ( gen -> d . iPAddress ); 14770if ( len == 4 ) { 14771// IPv4 14772char buf [ INET_ADDRSTRLEN ]; 14773inet_ntop ( AF_INET , data , buf , sizeof( buf )); 14774entry . value = buf ; 14775} else if ( len == 16 ) { 14776// IPv6 14777char buf [ INET6_ADDRSTRLEN ]; 14778inet_ntop ( AF_INET6 , data , buf , sizeof( buf )); 14779entry . value = buf ; 14780} 14781} 14782break; 14783case GEN_EMAIL : 14784entry . type = SanType :: ; 14785if ( gen -> d . rfc822Name ) { 14786entry . value = std:: string ( 14787reinterpret_cast < const char *> ( 14788ASN1_STRING_get0_data ( gen -> d . rfc822Name )), 14789static_cast < size_t > ( ASN1_STRING_length ( gen -> d . rfc822Name ))); 14790} 14791break; 14792case GEN_URI : 14793entry . type = SanType :: URI ; 14794if ( gen -> d . uniformResourceIdentifier ) { 14795entry . value = std:: string ( 14796reinterpret_cast < const char *> ( 14797ASN1_STRING_get0_data ( gen -> d . uniformResourceIdentifier )), 14798static_cast < size_t > ( 14799ASN1_STRING_length ( gen -> d . uniformResourceIdentifier ))); 14800} 14801break; 14802default: entry . type = SanType :: OTHER ; break; 14803} 14804 14805if (! entry . value . empty ()) { sans . push_back (std:: move ( entry )); } 14806} 14807 14808GENERAL_NAMES_free ( names ); 14809return true; 14810} 14811 14812bool get_cert_validity ( cert_t cert , time_t & not_before , 14813time_t & not_after ) { 14814if (! cert ) return false; 14815auto x509 = static_cast < X509 *> ( cert ); 14816 14817auto nb = X509_get0_notBefore ( x509 ); 14818auto na = X509_get0_notAfter ( x509 ); 14819if (! nb || ! na ) return false; 14820 14821ASN1_TIME * epoch = ASN1_TIME_new (); 14822if (! epoch ) return false; 14823auto se = detail:: scope_exit ([ & ] { ASN1_TIME_free ( epoch ); }); 14824 14825if (! ASN1_TIME_set ( epoch , 0 )) return false; 14826 14827int pday , psec ; 14828 14829if (! ASN1_TIME_diff ( & pday , & psec , epoch , nb )) return false; 14830not_before = 86400 * ( time_t ) pday + psec ; 14831 14832if (! ASN1_TIME_diff ( & pday , & psec , epoch , na )) return false; 14833not_after = 86400 * ( time_t ) pday + psec ; 14834 14835return true; 14836} 14837 14838std:: string get_cert_serial ( cert_t cert ) { 14839if (! cert ) return "" ; 14840auto x509 = static_cast < X509 *> ( cert ); 14841 14842auto serial = X509_get_serialNumber ( x509 ); 14843if (! serial ) return "" ; 14844 14845auto bn = ASN1_INTEGER_to_BN ( serial , nullptr ); 14846if (! bn ) return "" ; 14847 14848auto hex = BN_bn2hex ( bn ); 14849BN_free ( bn ); 14850if (! hex ) return "" ; 14851 14852std:: string result ( hex ); 14853OPENSSL_free ( hex ); 14854return result ; 14855} 14856 14857bool get_cert_der ( cert_t cert , std:: vector < unsigned char > & der ) { 14858if (! cert ) return false; 14859auto x509 = static_cast < X509 *> ( cert ); 14860auto len = i2d_X509 ( x509 , nullptr ); 14861if ( len < 0 ) return false; 14862der . resize ( static_cast < size_t > ( len )); 14863auto p = der . data (); 14864i2d_X509 ( x509 , & p ); 14865return true; 14866} 14867 14868const char * get_sni ( const_session_t session ) { 14869if (! session ) return nullptr ; 14870auto ssl = static_cast < SSL *> ( const_cast < void *> ( session )); 14871return SSL_get_servername ( ssl , TLSEXT_NAMETYPE_host_name ); 14872} 14873 14874uint64_t peek_error () { return ERR_peek_last_error (); } 14875 14876uint64_t get_error () { return ERR_get_error (); } 14877 14878std:: string error_string ( uint64_t code ) { 14879char buf [ 256 ]; 14880ERR_error_string_n ( static_cast < unsigned long > ( code ), buf , sizeof( buf )); 14881return std:: string ( buf ); 14882} 14883 14884ca_store_t create_ca_store (const char * pem , size_t len ) { 14885auto mem = BIO_new_mem_buf ( pem , static_cast < int > ( len )); 14886if (! mem ) { return nullptr ; } 14887auto mem_guard = detail:: scope_exit ([ & ] { BIO_free_all ( mem ); }); 14888 14889auto inf = PEM_X509_INFO_read_bio ( mem , nullptr , nullptr , nullptr ); 14890if (! inf ) { return nullptr ; } 14891 14892auto store = X509_STORE_new (); 14893if ( store ) { 14894for ( auto i = 0 ; i < static_cast < int > ( sk_X509_INFO_num ( inf )); i ++ ) { 14895auto itmp = sk_X509_INFO_value ( inf , i ); 14896if (! itmp ) { continue; } 14897if ( itmp -> x509 ) { X509_STORE_add_cert ( store , itmp -> x509 ); } 14898if ( itmp -> crl ) { X509_STORE_add_crl ( store , itmp -> crl ); } 14899} 14900} 14901 14902sk_X509_INFO_pop_free ( inf , X509_INFO_free ); 14903return static_cast < ca_store_t > ( store ); 14904} 14905 14906void free_ca_store ( ca_store_t store ) { 14907if ( store ) { X509_STORE_free ( static_cast < X509_STORE *> ( store )); } 14908} 14909 14910bool set_ca_store ( ctx_t ctx , ca_store_t store ) { 14911if (! ctx || ! store ) { return false; } 14912auto ssl_ctx = static_cast < SSL_CTX *> ( ctx ); 14913auto x509_store = static_cast < X509_STORE *> ( store ); 14914 14915// Check if same store is already set 14916if ( SSL_CTX_get_cert_store ( ssl_ctx ) == x509_store ) { return true; } 14917 14918// SSL_CTX_set_cert_store takes ownership and frees the old store 14919SSL_CTX_set_cert_store ( ssl_ctx , x509_store ); 14920return true; 14921} 14922 14923size_t get_ca_certs ( ctx_t ctx , std:: vector < cert_t > & certs ) { 14924certs . clear (); 14925if (! ctx ) { return 0 ; } 14926auto ssl_ctx = static_cast < SSL_CTX *> ( ctx ); 14927 14928auto store = SSL_CTX_get_cert_store ( ssl_ctx ); 14929if (! store ) { return 0 ; } 14930 14931auto objs = impl:: get_store_objects ( store ); 14932if (! objs ) { return 0 ; } 14933auto se = detail:: scope_exit ([ & ] { impl:: release_store_objects ( objs ); }); 14934 14935auto count = sk_X509_OBJECT_num ( objs ); 14936for (decltype( count ) i = 0 ; i < count ; i ++ ) { 14937auto obj = sk_X509_OBJECT_value ( objs , i ); 14938if (! obj ) { continue; } 14939if ( X509_OBJECT_get_type ( obj ) == X509_LU_X509 ) { 14940auto x509 = X509_OBJECT_get0_X509 ( obj ); 14941if ( x509 ) { 14942// Increment reference count so caller can free it 14943X509_up_ref ( x509 ); 14944certs . push_back ( static_cast < cert_t > ( x509 )); 14945} 14946} 14947} 14948return certs . size (); 14949} 14950 14951std:: vector < std:: string > get_ca_names ( ctx_t ctx ) { 14952std:: vector < std:: string > names ; 14953if (! ctx ) { return names ; } 14954auto ssl_ctx = static_cast < SSL_CTX *> ( ctx ); 14955 14956auto store = SSL_CTX_get_cert_store ( ssl_ctx ); 14957if (! store ) { return names ; } 14958 14959auto objs = impl:: get_store_objects ( store ); 14960if (! objs ) { return names ; } 14961auto se = detail:: scope_exit ([ & ] { impl:: release_store_objects ( objs ); }); 14962 14963auto count = sk_X509_OBJECT_num ( objs ); 14964for (decltype( count ) i = 0 ; i < count ; i ++ ) { 14965auto obj = sk_X509_OBJECT_value ( objs , i ); 14966if (! obj ) { continue; } 14967if ( X509_OBJECT_get_type ( obj ) == X509_LU_X509 ) { 14968auto x509 = X509_OBJECT_get0_X509 ( obj ); 14969if ( x509 ) { 14970auto subject = X509_get_subject_name ( x509 ); 14971if ( subject ) { 14972char buf [ 512 ]; 14973X509_NAME_oneline ( subject , buf , sizeof( buf )); 14974names . push_back ( buf ); 14975} 14976} 14977} 14978} 14979return names ; 14980} 14981 14982bool update_server_cert ( ctx_t ctx , const char * cert_pem , 14983const char * key_pem , const char * password ) { 14984if (! ctx || ! cert_pem || ! key_pem ) { return false; } 14985auto ssl_ctx = static_cast < SSL_CTX *> ( ctx ); 14986 14987// Load certificate from PEM 14988auto cert_bio = BIO_new_mem_buf ( cert_pem , -1 ); 14989if (! cert_bio ) { return false; } 14990auto cert = PEM_read_bio_X509 ( cert_bio , nullptr , nullptr , nullptr ); 14991BIO_free ( cert_bio ); 14992if (! cert ) { return false; } 14993 14994// Load private key from PEM 14995auto key_bio = BIO_new_mem_buf ( key_pem , -1 ); 14996if (! key_bio ) { 14997X509_free ( cert ); 14998return false; 14999} 15000auto key = PEM_read_bio_PrivateKey ( key_bio , nullptr , nullptr , 15001password ? const_cast < char *> ( password ) 15002: nullptr ); 15003BIO_free ( key_bio ); 15004if (! key ) { 15005X509_free ( cert ); 15006return false; 15007} 15008 15009// Update certificate and key 15010auto ret = SSL_CTX_use_certificate ( ssl_ctx , cert ) == 1 && 15011SSL_CTX_use_PrivateKey ( ssl_ctx , key ) == 1 ; 15012 15013X509_free ( cert ); 15014EVP_PKEY_free ( key ); 15015return ret ; 15016} 15017 15018bool update_server_client_ca ( ctx_t ctx , const char * ca_pem ) { 15019if (! ctx || ! ca_pem ) { return false; } 15020auto ssl_ctx = static_cast < SSL_CTX *> ( ctx ); 15021 15022// Create new X509_STORE from PEM 15023auto store = create_ca_store ( ca_pem , strlen ( ca_pem )); 15024if (! store ) { return false; } 15025 15026// SSL_CTX_set_cert_store takes ownership 15027SSL_CTX_set_cert_store ( ssl_ctx , static_cast < X509_STORE *> ( store )); 15028 15029// Set client CA list for client certificate request 15030auto ca_list = impl:: create_client_ca_list_from_pem ( ca_pem ); 15031if ( ca_list ) { 15032// SSL_CTX_set_client_CA_list takes ownership of ca_list 15033SSL_CTX_set_client_CA_list ( ssl_ctx , ca_list ); 15034} 15035 15036return true; 15037} 15038 15039bool set_verify_callback ( ctx_t ctx , VerifyCallback callback ) { 15040if (! ctx ) { return false; } 15041auto ssl_ctx = static_cast < SSL_CTX *> ( ctx ); 15042 15043impl:: get_verify_callback () = std:: move ( callback ); 15044 15045if (impl:: get_verify_callback ()) { 15046SSL_CTX_set_verify ( ssl_ctx , SSL_VERIFY_PEER , impl:: openssl_verify_callback ); 15047} else { 15048SSL_CTX_set_verify ( ssl_ctx , SSL_VERIFY_PEER , nullptr ); 15049} 15050return true; 15051} 15052 15053long get_verify_error ( const_session_t session ) { 15054if (! session ) { return -1 ; } 15055auto ssl = static_cast < SSL *> ( const_cast < void *> ( session )); 15056return SSL_get_verify_result ( ssl ); 15057} 15058 15059std:: string verify_error_string ( long error_code ) { 15060if ( error_code == X509_V_OK ) { return "" ; } 15061const char * str = X509_verify_cert_error_string ( static_cast < int > ( error_code )); 15062return str ? str : "unknown error" ; 15063} 15064 15065} // namespace tls 15066 15067#endif // CPPHTTPLIB_OPENSSL_SUPPORT 15068 15069/* 15070* Group 9: TLS abstraction layer - Mbed TLS backend 15071*/ 15072 15073/* 15074* Mbed TLS Backend Implementation 15075*/ 15076 15077#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT 15078namespace tls { 15079 15080namespace impl { 15081 15082// Mbed TLS session wrapper 15083struct MbedTlsSession { 15084mbedtls_ssl_context ssl ; 15085socket_t sock = INVALID_SOCKET ; 15086std:: string hostname ; // For client: set via set_sni 15087std:: string sni_hostname ; // For server: received from client via SNI callback 15088 15089// Mbed TLS has no SSL_peek() equivalent, so is_peer_closed() must probe with 15090// a real 1-byte mbedtls_ssl_read(). If that probe lands on application data 15091// (e.g. a response that arrived while this side was still in its post-write 15092// check), the byte is pushed back here and served by the next read(). 15093unsigned char peeked_byte = 0 ; 15094bool has_peeked_byte = false; 15095 15096// Set by set_sni() when the caller disabled hostname verification, so the 15097// verify callback can clear the CN/SAN mismatch flag while still enforcing 15098// the rest of the chain (Mbed TLS ties SNI and identity checking together; 15099// OpenSSL and wolfSSL keep them independent). 15100bool suppress_hostname_mismatch = false; 15101 15102// Copied from the owning MbedTlsContext at creation. set_sni() uses this to 15103// decide which verify callback to install when hostname verification is 15104// disabled: mbedtls_verify_callback() when a user callback is genuinely 15105// wired for this context, or a self-contained one otherwise, so a session 15106// that never opted into a callback never consults the process-wide 15107// set_verify_callback() slot (which some other, unrelated client may have 15108// populated). 15109bool has_verify_callback = false; 15110 15111MbedTlsSession () { mbedtls_ssl_init ( & ssl ); } 15112 15113~ MbedTlsSession () { mbedtls_ssl_free ( & ssl ); } 15114 15115MbedTlsSession (const MbedTlsSession & ) = delete; 15116MbedTlsSession & operator = (const MbedTlsSession & ) = delete; 15117}; 15118 15119// Thread-local error code accessor for Mbed TLS (since it doesn't have an error 15120// queue) 15121int & mbedtls_last_error () { 15122static thread_local int err = 0 ; 15123return err ; 15124} 15125 15126// Helper to map Mbed TLS error to ErrorCode 15127ErrorCode map_mbedtls_error ( int ret , int & out_errno , 15128uint32_t verify_flags ) { 15129if ( ret == 0 ) { return ErrorCode :: Success ; } 15130if ( ret == MBEDTLS_ERR_SSL_WANT_READ ) { return ErrorCode :: WantRead ; } 15131if ( ret == MBEDTLS_ERR_SSL_WANT_WRITE ) { return ErrorCode :: WantWrite ; } 15132if ( ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY ) { 15133return ErrorCode :: PeerClosed ; 15134} 15135if ( ret == MBEDTLS_ERR_NET_CONN_RESET || ret == MBEDTLS_ERR_NET_SEND_FAILED || 15136ret == MBEDTLS_ERR_NET_RECV_FAILED ) { 15137out_errno = errno ; 15138return ErrorCode :: SyscallError ; 15139} 15140if ( ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED ) { 15141// Unlike OpenSSL/wolfSSL, Mbed TLS folds the CN/SAN identity check into 15142// the handshake's chain verification (see set_sni()); a mismatch there 15143// is reported the same way as any other verify_flags bit. Report it as 15144// HostnameMismatch, matching the other backends and the post-handshake 15145// identity check below, but only when naming is the sole problem - 15146// if the chain itself is also untrusted/expired/etc., that takes 15147// priority over the naming detail. 15148if ( verify_flags == static_cast < uint32_t > ( hostname_mismatch_code ())) { 15149return ErrorCode :: HostnameMismatch ; 15150} 15151return ErrorCode :: CertVerifyFailed ; 15152} 15153return ErrorCode :: Fatal ; 15154} 15155 15156// Populates a TlsError from a failed (non-zero) mbedtls_ssl_handshake() 15157// return value, including the verify-flags-dependent HostnameMismatch 15158// mapping; shared by connect() and connect_nonblocking() so the 15159// backend_code policy for that mapping only lives in one place. 15160void fill_mbedtls_tls_error ( TlsError & err , mbedtls_ssl_context & ssl , 15161int ret ) { 15162auto verify_flags = mbedtls_ssl_get_verify_result ( & ssl ); 15163err . code = map_mbedtls_error ( ret , err . sys_errno , verify_flags ); 15164err . backend_code = err . code == ErrorCode :: HostnameMismatch 15165? static_cast < uint64_t > ( verify_flags ) 15166: static_cast < uint64_t > ( - ret ); 15167} 15168 15169// A TLS 1.3 NewSessionTicket (signaled by default on Mbed TLS 4.x) is a 15170// non-fatal notification delivered between records, not an error and not 15171// application data, so I/O calls that see it should just be retried. Kept in 15172// one helper so the retry loops keep an intact "do { } while (...)" instead of 15173// splitting the closing brace across an #if. 15174bool mbedtls_is_session_ticket ( int ret ) { 15175#if defined( MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET ) 15176return ret == MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET ; 15177#else 15178( void ) ret ; 15179return false; 15180#endif 15181} 15182 15183// BIO-like send callback for Mbed TLS 15184int mbedtls_net_send_cb ( void * ctx , const unsigned char * buf , 15185size_t len ) { 15186auto sock = * static_cast < socket_t *> ( ctx ); 15187#ifdef _WIN32 15188auto ret = 15189send ( sock , reinterpret_cast < const char *> ( buf ), static_cast < int > ( len ), 0 ); 15190if ( ret == SOCKET_ERROR ) { 15191int err = WSAGetLastError (); 15192if ( err == WSAEWOULDBLOCK ) { return MBEDTLS_ERR_SSL_WANT_WRITE ; } 15193return MBEDTLS_ERR_NET_SEND_FAILED ; 15194} 15195#else 15196auto ret = send ( sock , buf , len , 0 ); 15197if ( ret < 0 ) { 15198if ( errno == EAGAIN || errno == EWOULDBLOCK ) { 15199return MBEDTLS_ERR_SSL_WANT_WRITE ; 15200} 15201return MBEDTLS_ERR_NET_SEND_FAILED ; 15202} 15203#endif 15204return static_cast < int > ( ret ); 15205} 15206 15207// BIO-like recv callback for Mbed TLS 15208int mbedtls_net_recv_cb ( void * ctx , unsigned char * buf , size_t len ) { 15209auto sock = * static_cast < socket_t *> ( ctx ); 15210#ifdef _WIN32 15211auto ret = 15212recv ( sock , reinterpret_cast < char *> ( buf ), static_cast < int > ( len ), 0 ); 15213if ( ret == SOCKET_ERROR ) { 15214int err = WSAGetLastError (); 15215if ( err == WSAEWOULDBLOCK ) { return MBEDTLS_ERR_SSL_WANT_READ ; } 15216return MBEDTLS_ERR_NET_RECV_FAILED ; 15217} 15218#else 15219auto ret = recv ( sock , buf , len , 0 ); 15220if ( ret < 0 ) { 15221if ( errno == EAGAIN || errno == EWOULDBLOCK ) { 15222return MBEDTLS_ERR_SSL_WANT_READ ; 15223} 15224return MBEDTLS_ERR_NET_RECV_FAILED ; 15225} 15226#endif 15227if ( ret == 0 ) { return MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY ; } 15228return static_cast < int > ( ret ); 15229} 15230 15231// MbedTlsContext constructor/destructor implementations 15232MbedTlsContext :: MbedTlsContext () { 15233mbedtls_ssl_config_init ( & conf ); 15234#ifndef CPPHTTPLIB_MBEDTLS_V4 15235mbedtls_entropy_init ( & entropy ); 15236mbedtls_ctr_drbg_init ( & ctr_drbg ); 15237#endif 15238mbedtls_x509_crt_init ( & ca_chain ); 15239mbedtls_x509_crt_init ( & own_cert ); 15240mbedtls_pk_init ( & own_key ); 15241} 15242 15243MbedTlsContext ::~ MbedTlsContext () { 15244mbedtls_pk_free ( & own_key ); 15245mbedtls_x509_crt_free ( & own_cert ); 15246mbedtls_x509_crt_free ( & ca_chain ); 15247#ifndef CPPHTTPLIB_MBEDTLS_V4 15248mbedtls_ctr_drbg_free ( & ctr_drbg ); 15249mbedtls_entropy_free ( & entropy ); 15250#endif 15251mbedtls_ssl_config_free ( & conf ); 15252} 15253 15254// Thread-local storage for SNI captured during handshake 15255// This is needed because the SNI callback doesn't have a way to pass 15256// session-specific data before the session is fully set up 15257std:: string & mbedpending_sni () { 15258static thread_local std:: string sni ; 15259return sni ; 15260} 15261 15262// SNI callback for Mbed TLS server to capture client's SNI hostname 15263int mbedtls_sni_callback ( void * p_ctx , mbedtls_ssl_context * ssl , 15264const unsigned char * name , size_t name_len ) { 15265( void ) p_ctx ; 15266( void ) ssl ; 15267 15268// Store SNI name in thread-local storage 15269// It will be retrieved and stored in the session after handshake 15270if ( name && name_len > 0 ) { 15271mbedpending_sni (). assign ( reinterpret_cast < const char *> ( name ), name_len ); 15272} else { 15273mbedpending_sni (). clear (); 15274} 15275return 0 ; // Accept any SNI 15276} 15277 15278void mbedtls_clear_cn_mismatch ( uint32_t * flags ) { 15279* flags &= ~ static_cast < uint32_t > ( hostname_mismatch_code ()); 15280} 15281 15282// Verify callback used when hostname verification is disabled for a session 15283// that has no user-supplied verify callback of its own (MbedTlsSession:: 15284// has_verify_callback is false). Deliberately does not consult 15285// get_verify_callback(): that slot is process-wide, so reading it here would 15286// pick up whatever another, unrelated client last installed there. 15287int mbedtls_mask_hostname_mismatch_callback ( void * data , 15288mbedtls_x509_crt * , int , 15289uint32_t * flags ) { 15290( void ) data ; 15291mbedtls_clear_cn_mismatch ( flags ); 15292return 0 ; 15293} 15294 15295int mbedtls_verify_callback ( void * data , mbedtls_x509_crt * crt , 15296int cert_depth , uint32_t * flags ); 15297 15298// MbedTLS verify callback wrapper 15299int mbedtls_verify_callback ( void * data , mbedtls_x509_crt * crt , 15300int cert_depth , uint32_t * flags ) { 15301// data points to the MbedTlsSession 15302auto * session = static_cast < MbedTlsSession *> ( data ); 15303 15304// set_sni() disabled hostname verification for this session: drop the 15305// CN/SAN mismatch flag so it doesn't fail the chain check below, mirroring 15306// the OpenSSL/wolfSSL backends where identity checking is independent of 15307// SNI. The final pass/fail decision still comes from the remaining flags 15308// (or, below, from the user's own verify callback). 15309if ( session && session -> suppress_hostname_mismatch ) { 15310mbedtls_clear_cn_mismatch ( flags ); 15311} 15312 15313auto & callback = get_verify_callback (); 15314if (! callback ) { return 0 ; } // Continue with default verification 15315 15316// Build context 15317VerifyContext verify_ctx ; 15318verify_ctx . session = static_cast < session_t > ( session ); 15319verify_ctx . cert = static_cast < cert_t > ( crt ); 15320verify_ctx . depth = cert_depth ; 15321verify_ctx . preverify_ok = ( * flags == 0 ); 15322verify_ctx . error_code = static_cast < long > ( * flags ); 15323 15324// Convert Mbed TLS flags to error string 15325static thread_local char error_buf [ 256 ]; 15326if ( * flags != 0 ) { 15327mbedtls_x509_crt_verify_info ( error_buf , sizeof( error_buf ), "" , * flags ); 15328verify_ctx . error_string = error_buf ; 15329} else { 15330verify_ctx . error_string = nullptr ; 15331} 15332 15333bool accepted = callback ( verify_ctx ); 15334 15335if ( accepted ) { 15336* flags = 0 ; // Clear all error flags 15337return 0 ; 15338} 15339return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED ; 15340} 15341 15342} // namespace impl 15343 15344ctx_t create_client_context () { 15345auto ctx = new (std:: nothrow ) impl:: MbedTlsContext (); 15346if (! ctx ) { return nullptr ; } 15347 15348ctx -> is_server = false; 15349 15350#ifdef CPPHTTPLIB_MBEDTLS_V4 15351// Mbed TLS 4.x draws randomness from PSA Crypto; just ensure it is ready. 15352if (!detail:: ensure_mbedtls_psa_crypto ()) { 15353delete ctx ; 15354return nullptr ; 15355} 15356int ret ; 15357#else 15358// Seed the random number generator 15359const char * pers = "httplib_client" ; 15360int ret = mbedtls_ctr_drbg_seed ( 15361& ctx -> ctr_drbg , mbedtls_entropy_func , & ctx -> entropy , 15362reinterpret_cast < const unsigned char *> ( pers ), strlen ( pers )); 15363if ( ret != 0 ) { 15364impl:: mbedtls_last_error () = ret ; 15365delete ctx ; 15366return nullptr ; 15367} 15368#endif 15369 15370// Set up SSL config for client 15371ret = mbedtls_ssl_config_defaults ( & ctx -> conf , MBEDTLS_SSL_IS_CLIENT , 15372MBEDTLS_SSL_TRANSPORT_STREAM , 15373MBEDTLS_SSL_PRESET_DEFAULT ); 15374if ( ret != 0 ) { 15375impl:: mbedtls_last_error () = ret ; 15376delete ctx ; 15377return nullptr ; 15378} 15379 15380#ifndef CPPHTTPLIB_MBEDTLS_V4 15381// Set random number generator (Mbed TLS 4.x uses the PSA RNG implicitly) 15382mbedtls_ssl_conf_rng ( & ctx -> conf , mbedtls_ctr_drbg_random , & ctx -> ctr_drbg ); 15383#endif 15384 15385// Default: verify peer certificate 15386mbedtls_ssl_conf_authmode ( & ctx -> conf , MBEDTLS_SSL_VERIFY_REQUIRED ); 15387 15388// Set minimum TLS version to 1.2 15389#ifdef CPPHTTPLIB_MBEDTLS_V3 15390mbedtls_ssl_conf_min_tls_version ( & ctx -> conf , MBEDTLS_SSL_VERSION_TLS1_2 ); 15391#else 15392mbedtls_ssl_conf_min_version ( & ctx -> conf , MBEDTLS_SSL_MAJOR_VERSION_3 , 15393MBEDTLS_SSL_MINOR_VERSION_3 ); 15394#endif 15395 15396return static_cast < ctx_t > ( ctx ); 15397} 15398 15399ctx_t create_server_context () { 15400auto ctx = new (std:: nothrow ) impl:: MbedTlsContext (); 15401if (! ctx ) { return nullptr ; } 15402 15403ctx -> is_server = true; 15404 15405#ifdef CPPHTTPLIB_MBEDTLS_V4 15406// Mbed TLS 4.x draws randomness from PSA Crypto; just ensure it is ready. 15407if (!detail:: ensure_mbedtls_psa_crypto ()) { 15408delete ctx ; 15409return nullptr ; 15410} 15411int ret ; 15412#else 15413// Seed the random number generator 15414const char * pers = "httplib_server" ; 15415int ret = mbedtls_ctr_drbg_seed ( 15416& ctx -> ctr_drbg , mbedtls_entropy_func , & ctx -> entropy , 15417reinterpret_cast < const unsigned char *> ( pers ), strlen ( pers )); 15418if ( ret != 0 ) { 15419impl:: mbedtls_last_error () = ret ; 15420delete ctx ; 15421return nullptr ; 15422} 15423#endif 15424 15425// Set up SSL config for server 15426ret = mbedtls_ssl_config_defaults ( & ctx -> conf , MBEDTLS_SSL_IS_SERVER , 15427MBEDTLS_SSL_TRANSPORT_STREAM , 15428MBEDTLS_SSL_PRESET_DEFAULT ); 15429if ( ret != 0 ) { 15430impl:: mbedtls_last_error () = ret ; 15431delete ctx ; 15432return nullptr ; 15433} 15434 15435#ifndef CPPHTTPLIB_MBEDTLS_V4 15436// Set random number generator (Mbed TLS 4.x uses the PSA RNG implicitly) 15437mbedtls_ssl_conf_rng ( & ctx -> conf , mbedtls_ctr_drbg_random , & ctx -> ctr_drbg ); 15438#endif 15439 15440// Default: don't verify client 15441mbedtls_ssl_conf_authmode ( & ctx -> conf , MBEDTLS_SSL_VERIFY_NONE ); 15442 15443// Set minimum TLS version to 1.2 15444#ifdef CPPHTTPLIB_MBEDTLS_V3 15445mbedtls_ssl_conf_min_tls_version ( & ctx -> conf , MBEDTLS_SSL_VERSION_TLS1_2 ); 15446#else 15447mbedtls_ssl_conf_min_version ( & ctx -> conf , MBEDTLS_SSL_MAJOR_VERSION_3 , 15448MBEDTLS_SSL_MINOR_VERSION_3 ); 15449#endif 15450 15451// Set SNI callback to capture client's SNI hostname 15452mbedtls_ssl_conf_sni ( & ctx -> conf , impl:: mbedtls_sni_callback , nullptr ); 15453 15454return static_cast < ctx_t > ( ctx ); 15455} 15456 15457void free_context ( ctx_t ctx ) { 15458if ( ctx ) { delete static_cast < impl:: MbedTlsContext *> ( ctx ); } 15459} 15460 15461bool set_min_version ( ctx_t ctx , Version version ) { 15462if (! ctx ) { return false; } 15463auto mctx = static_cast < impl:: MbedTlsContext *> ( ctx ); 15464 15465#ifdef CPPHTTPLIB_MBEDTLS_V3 15466// Mbed TLS 3.x uses mbedtls_ssl_protocol_version enum 15467mbedtls_ssl_protocol_version min_ver = MBEDTLS_SSL_VERSION_TLS1_2 ; 15468if ( version >= Version :: TLS1_3 ) { 15469#if defined( MBEDTLS_SSL_PROTO_TLS1_3 ) 15470min_ver = MBEDTLS_SSL_VERSION_TLS1_3 ; 15471#endif 15472} 15473mbedtls_ssl_conf_min_tls_version ( & mctx -> conf , min_ver ); 15474#else 15475// Mbed TLS 2.x uses major/minor version numbers 15476int major = MBEDTLS_SSL_MAJOR_VERSION_3 ; 15477int minor = MBEDTLS_SSL_MINOR_VERSION_3 ; // TLS 1.2 15478if ( version >= Version :: TLS1_3 ) { 15479#if defined( MBEDTLS_SSL_PROTO_TLS1_3 ) 15480minor = MBEDTLS_SSL_MINOR_VERSION_4 ; // TLS 1.3 15481#else 15482minor = MBEDTLS_SSL_MINOR_VERSION_3 ; // Fall back to TLS 1.2 15483#endif 15484} 15485mbedtls_ssl_conf_min_version ( & mctx -> conf , major , minor ); 15486#endif 15487return true; 15488} 15489 15490bool load_ca_pem ( ctx_t ctx , const char * pem , size_t len ) { 15491if (! ctx || ! pem ) { return false; } 15492auto mctx = static_cast < impl:: MbedTlsContext *> ( ctx ); 15493 15494// mbedtls_x509_crt_parse expects null-terminated string for PEM 15495// Add null terminator if not present 15496std:: string pem_str ( pem , len ); 15497int ret = mbedtls_x509_crt_parse ( 15498& mctx -> ca_chain , reinterpret_cast < const unsigned char *> ( pem_str . c_str ()), 15499pem_str . size () + 1 ); 15500if ( ret != 0 ) { 15501impl:: mbedtls_last_error () = ret ; 15502return false; 15503} 15504 15505mbedtls_ssl_conf_ca_chain ( & mctx -> conf , & mctx -> ca_chain , nullptr ); 15506return true; 15507} 15508 15509bool load_ca_file ( ctx_t ctx , const char * file_path ) { 15510if (! ctx || ! file_path ) { return false; } 15511auto mctx = static_cast < impl:: MbedTlsContext *> ( ctx ); 15512 15513int ret = mbedtls_x509_crt_parse_file ( & mctx -> ca_chain , file_path ); 15514if ( ret != 0 ) { 15515impl:: mbedtls_last_error () = ret ; 15516return false; 15517} 15518 15519mbedtls_ssl_conf_ca_chain ( & mctx -> conf , & mctx -> ca_chain , nullptr ); 15520return true; 15521} 15522 15523bool load_ca_dir ( ctx_t ctx , const char * dir_path ) { 15524if (! ctx || ! dir_path ) { return false; } 15525auto mctx = static_cast < impl:: MbedTlsContext *> ( ctx ); 15526 15527int ret = mbedtls_x509_crt_parse_path ( & mctx -> ca_chain , dir_path ); 15528if ( ret < 0 ) { // Returns number of certs on success, negative on error 15529impl:: mbedtls_last_error () = ret ; 15530return false; 15531} 15532 15533mbedtls_ssl_conf_ca_chain ( & mctx -> conf , & mctx -> ca_chain , nullptr ); 15534return true; 15535} 15536 15537bool load_system_certs ( ctx_t ctx ) { 15538if (! ctx ) { return false; } 15539auto mctx = static_cast < impl:: MbedTlsContext *> ( ctx ); 15540bool loaded = false; 15541 15542#ifdef _WIN32 15543loaded = impl:: enumerate_windows_system_certs ( 15544[ & ](const unsigned char * data , size_t len ) { 15545return mbedtls_x509_crt_parse_der ( & mctx -> ca_chain , data , len ) == 0 ; 15546}); 15547#elif defined( __APPLE__ ) && defined( CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN ) 15548loaded = impl:: enumerate_macos_keychain_certs ( 15549[ & ](const unsigned char * data , size_t len ) { 15550return mbedtls_x509_crt_parse_der ( & mctx -> ca_chain , data , len ) == 0 ; 15551}); 15552#else 15553for ( auto path = impl:: system_ca_paths (); * path ; ++ path ) { 15554if ( mbedtls_x509_crt_parse_file ( & mctx -> ca_chain , * path ) >= 0 ) { 15555loaded = true; 15556break; 15557} 15558} 15559 15560if (! loaded ) { 15561for ( auto dir = impl:: system_ca_dirs (); * dir ; ++ dir ) { 15562if ( mbedtls_x509_crt_parse_path ( & mctx -> ca_chain , * dir ) >= 0 ) { 15563loaded = true; 15564break; 15565} 15566} 15567} 15568#endif 15569 15570if ( loaded ) { 15571mbedtls_ssl_conf_ca_chain ( & mctx -> conf , & mctx -> ca_chain , nullptr ); 15572} 15573return loaded ; 15574} 15575 15576bool set_client_cert_pem ( ctx_t ctx , const char * cert , const char * key , 15577const char * password ) { 15578if (! ctx || ! cert || ! key ) { return false; } 15579auto mctx = static_cast < impl:: MbedTlsContext *> ( ctx ); 15580 15581// Parse certificate 15582std:: string cert_str ( cert ); 15583int ret = mbedtls_x509_crt_parse ( 15584& mctx -> own_cert , 15585reinterpret_cast < const unsigned char *> ( cert_str . c_str ()), 15586cert_str . size () + 1 ); 15587if ( ret != 0 ) { 15588impl:: mbedtls_last_error () = ret ; 15589return false; 15590} 15591 15592// Parse private key 15593std:: string key_str ( key ); 15594const unsigned char * pwd = 15595password ? reinterpret_cast < const unsigned char *> ( password ) : nullptr ; 15596size_t pwd_len = password ? strlen ( password ) : 0 ; 15597 15598#if defined( CPPHTTPLIB_MBEDTLS_V3 ) && !defined( CPPHTTPLIB_MBEDTLS_V4 ) 15599ret = mbedtls_pk_parse_key ( 15600& mctx -> own_key , reinterpret_cast < const unsigned char *> ( key_str . c_str ()), 15601key_str . size () + 1 , pwd , pwd_len , mbedtls_ctr_drbg_random , 15602& mctx -> ctr_drbg ); 15603#else 15604ret = mbedtls_pk_parse_key ( 15605& mctx -> own_key , reinterpret_cast < const unsigned char *> ( key_str . c_str ()), 15606key_str . size () + 1 , pwd , pwd_len ); 15607#endif 15608if ( ret != 0 ) { 15609impl:: mbedtls_last_error () = ret ; 15610return false; 15611} 15612 15613// Verify that the certificate and private key match. 15614// Mbed TLS 4.x: mbedtls_pk_check_pair() reports a spurious mismatch for 15615// PSA-backed keys, so skip it and let the handshake surface a real mismatch. 15616#ifndef CPPHTTPLIB_MBEDTLS_V4 15617#ifdef CPPHTTPLIB_MBEDTLS_V3 15618ret = mbedtls_pk_check_pair ( & mctx -> own_cert . pk , & mctx -> own_key , 15619mbedtls_ctr_drbg_random , & mctx -> ctr_drbg ); 15620#else 15621ret = mbedtls_pk_check_pair ( & mctx -> own_cert . pk , & mctx -> own_key ); 15622#endif 15623if ( ret != 0 ) { 15624impl:: mbedtls_last_error () = ret ; 15625return false; 15626} 15627#endif 15628 15629ret = mbedtls_ssl_conf_own_cert ( & mctx -> conf , & mctx -> own_cert , & mctx -> own_key ); 15630if ( ret != 0 ) { 15631impl:: mbedtls_last_error () = ret ; 15632return false; 15633} 15634 15635return true; 15636} 15637 15638bool set_client_cert_file ( ctx_t ctx , const char * cert_path , 15639const char * key_path , const char * password ) { 15640if (! ctx || ! cert_path || ! key_path ) { return false; } 15641auto mctx = static_cast < impl:: MbedTlsContext *> ( ctx ); 15642 15643// Parse certificate file 15644int ret = mbedtls_x509_crt_parse_file ( & mctx -> own_cert , cert_path ); 15645if ( ret != 0 ) { 15646impl:: mbedtls_last_error () = ret ; 15647return false; 15648} 15649 15650// Parse private key file 15651#if defined( CPPHTTPLIB_MBEDTLS_V3 ) && !defined( CPPHTTPLIB_MBEDTLS_V4 ) 15652ret = mbedtls_pk_parse_keyfile ( & mctx -> own_key , key_path , password , 15653mbedtls_ctr_drbg_random , & mctx -> ctr_drbg ); 15654#else 15655ret = mbedtls_pk_parse_keyfile ( & mctx -> own_key , key_path , password ); 15656#endif 15657if ( ret != 0 ) { 15658impl:: mbedtls_last_error () = ret ; 15659return false; 15660} 15661 15662// Verify that the certificate and private key match. 15663// Mbed TLS 4.x: see set_client_cert() — skip the spurious check_pair. 15664#ifndef CPPHTTPLIB_MBEDTLS_V4 15665#ifdef CPPHTTPLIB_MBEDTLS_V3 15666ret = mbedtls_pk_check_pair ( & mctx -> own_cert . pk , & mctx -> own_key , 15667mbedtls_ctr_drbg_random , & mctx -> ctr_drbg ); 15668#else 15669ret = mbedtls_pk_check_pair ( & mctx -> own_cert . pk , & mctx -> own_key ); 15670#endif 15671if ( ret != 0 ) { 15672impl:: mbedtls_last_error () = ret ; 15673return false; 15674} 15675#endif 15676 15677ret = mbedtls_ssl_conf_own_cert ( & mctx -> conf , & mctx -> own_cert , & mctx -> own_key ); 15678if ( ret != 0 ) { 15679impl:: mbedtls_last_error () = ret ; 15680return false; 15681} 15682 15683return true; 15684} 15685 15686void set_verify_client ( ctx_t ctx , bool require ) { 15687if (! ctx ) { return; } 15688auto mctx = static_cast < impl:: MbedTlsContext *> ( ctx ); 15689mctx -> verify_client = require ; 15690if ( require ) { 15691mbedtls_ssl_conf_authmode ( & mctx -> conf , MBEDTLS_SSL_VERIFY_REQUIRED ); 15692} else { 15693// If a verify callback is set, use OPTIONAL mode to ensure the callback 15694// is called (matching OpenSSL behavior). Otherwise use NONE. 15695mbedtls_ssl_conf_authmode ( & mctx -> conf , mctx -> has_verify_callback 15696? MBEDTLS_SSL_VERIFY_OPTIONAL 15697: MBEDTLS_SSL_VERIFY_NONE ); 15698} 15699} 15700 15701session_t create_session ( ctx_t ctx , socket_t sock ) { 15702if (! ctx || sock == INVALID_SOCKET ) { return nullptr ; } 15703auto mctx = static_cast < impl:: MbedTlsContext *> ( ctx ); 15704 15705auto session = new (std:: nothrow ) impl:: MbedTlsSession (); 15706if (! session ) { return nullptr ; } 15707 15708session -> sock = sock ; 15709 15710int ret = mbedtls_ssl_setup ( & session -> ssl , & mctx -> conf ); 15711if ( ret != 0 ) { 15712impl:: mbedtls_last_error () = ret ; 15713delete session ; 15714return nullptr ; 15715} 15716 15717// Explicitly opt out of in-handshake hostname verification by default; 15718// since Mbed TLS 3.6.4 a client handshake with certificate verification 15719// fails outright when no hostname was set. set_sni() installs the real 15720// hostname for DNS hosts; for IP hosts (where SNI must not be set) the 15721// caller verifies the certificate identity post-handshake via 15722// verify_hostname(). 15723mbedtls_ssl_set_hostname ( & session -> ssl , nullptr ); 15724 15725// Set BIO callbacks 15726mbedtls_ssl_set_bio ( & session -> ssl , & session -> sock , impl:: mbedtls_net_send_cb , 15727impl:: mbedtls_net_recv_cb , nullptr ); 15728 15729// Set per-session verify callback with session pointer if callback is 15730// registered 15731session -> has_verify_callback = mctx -> has_verify_callback ; 15732if ( mctx -> has_verify_callback ) { 15733mbedtls_ssl_set_verify ( & session -> ssl , impl:: mbedtls_verify_callback , 15734session ); 15735} 15736 15737return static_cast < session_t > ( session ); 15738} 15739 15740void free_session ( session_t session ) { 15741if ( session ) { delete static_cast < impl:: MbedTlsSession *> ( session ); } 15742} 15743 15744bool set_sni ( session_t session , const char * hostname , 15745bool verify_hostname ) { 15746if (! session || ! hostname ) { return false; } 15747auto msession = static_cast < impl:: MbedTlsSession *> ( session ); 15748 15749// mbedtls_ssl_set_hostname() both sends the SNI extension and binds the 15750// handshake-time CN/SAN check to `hostname`; the two can't be requested 15751// independently, so a disabled hostname check is handled below by masking 15752// the resulting mismatch flag instead of skipping this call. 15753int ret = mbedtls_ssl_set_hostname ( & msession -> ssl , hostname ); 15754if ( ret != 0 ) { 15755impl:: mbedtls_last_error () = ret ; 15756return false; 15757} 15758 15759msession -> hostname = hostname ; 15760 15761if (! verify_hostname ) { 15762msession -> suppress_hostname_mismatch = true; 15763// If a user verify callback is already wired for this session, 15764// mbedtls_verify_callback() masks the mismatch flag itself before 15765// consulting it (see suppress_hostname_mismatch above) - reinstalling it 15766// here would be redundant. Otherwise install the self-contained masking 15767// callback, which never touches the process-wide callback slot. 15768if (! msession -> has_verify_callback ) { 15769mbedtls_ssl_set_verify ( & msession -> ssl , 15770impl:: mbedtls_mask_hostname_mismatch_callback , 15771msession ); 15772} 15773} 15774 15775return true; 15776} 15777 15778TlsError connect ( session_t session ) { 15779TlsError err ; 15780if (! session ) { 15781err . code = ErrorCode :: Fatal ; 15782return err ; 15783} 15784 15785auto msession = static_cast < impl:: MbedTlsSession *> ( session ); 15786int ret ; 15787do { 15788ret = mbedtls_ssl_handshake ( & msession -> ssl ); 15789} while (impl:: mbedtls_is_session_ticket ( ret )); 15790 15791if ( ret == 0 ) { 15792err . code = ErrorCode :: Success ; 15793} else { 15794impl:: fill_mbedtls_tls_error ( err , msession -> ssl , ret ); 15795impl:: mbedtls_last_error () = ret ; 15796} 15797 15798return err ; 15799} 15800 15801TlsError accept ( session_t session ) { 15802// Same as connect for Mbed TLS - handshake works for both client and server 15803auto result = connect ( session ); 15804 15805// After successful handshake, capture SNI from thread-local storage 15806if ( result . code == ErrorCode :: Success && session ) { 15807auto msession = static_cast < impl:: MbedTlsSession *> ( session ); 15808msession -> sni_hostname = std:: move (impl:: mbedpending_sni ()); 15809impl:: mbedpending_sni (). clear (); 15810} 15811 15812return result ; 15813} 15814 15815bool connect_nonblocking ( session_t session , socket_t sock , 15816time_t timeout_sec , time_t timeout_usec , 15817TlsError * err ) { 15818if (! session ) { 15819if ( err ) { err -> code = ErrorCode :: Fatal ; } 15820return false; 15821} 15822 15823auto msession = static_cast < impl:: MbedTlsSession *> ( session ); 15824 15825// Set socket to non-blocking mode 15826detail:: set_nonblocking ( sock , true); 15827auto cleanup = 15828detail:: scope_exit ([ & ]() { detail:: set_nonblocking ( sock , false); }); 15829 15830int ret ; 15831while (( ret = mbedtls_ssl_handshake ( & msession -> ssl )) != 0 ) { 15832// Non-fatal TLS 1.3 ticket; retry immediately. 15833if (impl:: mbedtls_is_session_ticket ( ret )) { continue; } 15834if ( ret == MBEDTLS_ERR_SSL_WANT_READ ) { 15835if (detail:: select_read ( sock , timeout_sec , timeout_usec ) > 0 ) { 15836continue; 15837} 15838} else if ( ret == MBEDTLS_ERR_SSL_WANT_WRITE ) { 15839if (detail:: select_write ( sock , timeout_sec , timeout_usec ) > 0 ) { 15840continue; 15841} 15842} 15843 15844// TlsError or timeout 15845if ( err ) { impl:: fill_mbedtls_tls_error ( * err , msession -> ssl , ret ); } 15846impl:: mbedtls_last_error () = ret ; 15847return false; 15848} 15849 15850if ( err ) { err -> code = ErrorCode :: Success ; } 15851return true; 15852} 15853 15854bool accept_nonblocking ( session_t session , socket_t sock , 15855time_t timeout_sec , time_t timeout_usec , 15856TlsError * err ) { 15857// Same implementation as connect for Mbed TLS 15858bool result = 15859connect_nonblocking ( session , sock , timeout_sec , timeout_usec , err ); 15860 15861// After successful handshake, capture SNI from thread-local storage 15862if ( result && session ) { 15863auto msession = static_cast < impl:: MbedTlsSession *> ( session ); 15864msession -> sni_hostname = std:: move (impl:: mbedpending_sni ()); 15865impl:: mbedpending_sni (). clear (); 15866} 15867 15868return result ; 15869} 15870 15871ssize_t read ( session_t session , void * buf , size_t len , TlsError & err ) { 15872if (! session || ! buf ) { 15873err . code = ErrorCode :: Fatal ; 15874return -1 ; 15875} 15876 15877auto msession = static_cast < impl:: MbedTlsSession *> ( session ); 15878 15879// Serve a byte consumed by the is_peer_closed() probe before reading more. 15880if ( msession -> has_peeked_byte ) { 15881if ( len == 0 ) { return 0 ; } 15882auto p = static_cast < unsigned char *> ( buf ); 15883p [ 0 ] = msession -> peeked_byte ; 15884msession -> has_peeked_byte = false; 15885size_t n = 1 ; 15886// Top up with any already-decrypted bytes without risking a block. 15887if ( len > 1 && mbedtls_ssl_get_bytes_avail ( & msession -> ssl ) > 0 ) { 15888int extra = mbedtls_ssl_read ( & msession -> ssl , p + 1 , len - 1 ); 15889if ( extra > 0 ) { n += static_cast < size_t > ( extra ); } 15890} 15891err . code = ErrorCode :: Success ; 15892return static_cast < ssize_t > ( n ); 15893} 15894 15895int ret ; 15896do { 15897ret = mbedtls_ssl_read ( & msession -> ssl , static_cast < unsigned char *> ( buf ), 15898len ); 15899} while (impl:: mbedtls_is_session_ticket ( ret )); 15900 15901if ( ret > 0 ) { 15902err . code = ErrorCode :: Success ; 15903return static_cast < ssize_t > ( ret ); 15904} 15905 15906if ( ret == 0 ) { 15907err . code = ErrorCode :: PeerClosed ; 15908return 0 ; 15909} 15910 15911err . code = impl:: map_mbedtls_error ( ret , err . sys_errno , 0 ); 15912err . backend_code = static_cast < uint64_t > ( - ret ); 15913impl:: mbedtls_last_error () = ret ; 15914// mbedTLS signals a clean close_notify via a negative error code rather 15915// than 0; surface it as a clean EOF the way OpenSSL/wolfSSL do. 15916if ( err . code == ErrorCode :: PeerClosed ) { return 0 ; } 15917return -1 ; 15918} 15919 15920ssize_t write ( session_t session , const void * buf , size_t len , 15921TlsError & err ) { 15922if (! session || ! buf ) { 15923err . code = ErrorCode :: Fatal ; 15924return -1 ; 15925} 15926 15927auto msession = static_cast < impl:: MbedTlsSession *> ( session ); 15928int ret ; 15929do { 15930ret = mbedtls_ssl_write ( & msession -> ssl , 15931static_cast < const unsigned char *> ( buf ), len ); 15932} while (impl:: mbedtls_is_session_ticket ( ret )); 15933 15934if ( ret > 0 ) { 15935err . code = ErrorCode :: Success ; 15936return static_cast < ssize_t > ( ret ); 15937} 15938 15939if ( ret == 0 ) { 15940err . code = ErrorCode :: PeerClosed ; 15941return 0 ; 15942} 15943 15944err . code = impl:: map_mbedtls_error ( ret , err . sys_errno , 0 ); 15945err . backend_code = static_cast < uint64_t > ( - ret ); 15946impl:: mbedtls_last_error () = ret ; 15947return -1 ; 15948} 15949 15950int pending ( const_session_t session ) { 15951if (! session ) { return 0 ; } 15952auto msession = 15953static_cast < impl:: MbedTlsSession *> ( const_cast < void *> ( session )); 15954return static_cast < int > ( mbedtls_ssl_get_bytes_avail ( & msession -> ssl )) + 15955( msession -> has_peeked_byte ? 1 : 0 ); 15956} 15957 15958void shutdown ( session_t session , bool graceful ) { 15959if (! session ) { return; } 15960auto msession = static_cast < impl:: MbedTlsSession *> ( session ); 15961 15962if ( graceful ) { 15963// Try to send close_notify, but don't block forever 15964int ret ; 15965int attempts = 0 ; 15966while (( ret = mbedtls_ssl_close_notify ( & msession -> ssl )) != 0 && 15967attempts < 3 ) { 15968if ( ret != MBEDTLS_ERR_SSL_WANT_READ && 15969ret != MBEDTLS_ERR_SSL_WANT_WRITE ) { 15970break; 15971} 15972attempts ++ ; 15973} 15974} 15975} 15976 15977bool is_peer_closed ( session_t session , socket_t sock ) { 15978if (! session || sock == INVALID_SOCKET ) { return true; } 15979auto msession = static_cast < impl:: MbedTlsSession *> ( session ); 15980 15981// Check if there's already decrypted or pushed-back data available. 15982// If so, the connection is definitely alive. 15983if ( msession -> has_peeked_byte || 15984mbedtls_ssl_get_bytes_avail ( & msession -> ssl ) > 0 ) { 15985return false; 15986} 15987 15988// Set socket to non-blocking to avoid blocking on read 15989detail:: set_nonblocking ( sock , true); 15990auto cleanup = 15991detail:: scope_exit ([ & ]() { detail:: set_nonblocking ( sock , false); }); 15992 15993// Probe with a 1-byte read (Mbed TLS has no peek API). If the probe lands 15994// on application data — e.g. a response that already arrived — push the 15995// byte back so the next read() delivers it instead of losing it. 15996unsigned char buf ; 15997int ret ; 15998do { 15999ret = mbedtls_ssl_read ( & msession -> ssl , & buf , 1 ); 16000} while (impl:: mbedtls_is_session_ticket ( ret )); 16001 16002// If we got data or WANT_READ (would block), connection is alive 16003if ( ret > 0 ) { 16004msession -> peeked_byte = buf ; 16005msession -> has_peeked_byte = true; 16006return false; 16007} 16008if ( ret == MBEDTLS_ERR_SSL_WANT_READ ) { return false; } 16009 16010// If we get a peer close notify or a connection reset, the peer is closed 16011return ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY || 16012ret == MBEDTLS_ERR_NET_CONN_RESET || ret == 0 ; 16013} 16014 16015cert_t get_peer_cert ( const_session_t session ) { 16016if (! session ) { return nullptr ; } 16017auto msession = 16018static_cast < impl:: MbedTlsSession *> ( const_cast < void *> ( session )); 16019 16020// Mbed TLS returns a pointer to the internal peer cert chain. 16021// WARNING: This pointer is only valid while the session is active. 16022// Do not use the certificate after calling free_session(). 16023const mbedtls_x509_crt * cert = mbedtls_ssl_get_peer_cert ( & msession -> ssl ); 16024return const_cast < mbedtls_x509_crt *> ( cert ); 16025} 16026 16027void free_cert ( cert_t cert ) { 16028// Mbed TLS: peer certificate is owned by the SSL context. 16029// No-op here, but callers should still call this for cross-backend 16030// portability. 16031( void ) cert ; 16032} 16033 16034bool verify_hostname ( cert_t cert , const char * hostname ) { 16035if (! cert || ! hostname ) { return false; } 16036auto mcert = static_cast < const mbedtls_x509_crt *> ( cert ); 16037std:: string host_str ( hostname ); 16038 16039// Check if hostname is an IP address (IPv4 or IPv6) 16040unsigned char ip_bytes [ 16 ]; 16041auto ip_len = impl:: parse_ip_address ( host_str , ip_bytes ); 16042auto is_ip = ip_len > 0 ; 16043 16044// Check Subject Alternative Names (SAN) 16045// In Mbed TLS 3.x, subject_alt_names contains raw values without ASN.1 tags 16046// - DNS names: raw string bytes 16047// - IP addresses: raw IP bytes (4 for IPv4, 16 for IPv6) 16048const mbedtls_x509_sequence * san = & mcert -> subject_alt_names ; 16049while ( san != nullptr && san -> buf . p != nullptr && san -> buf . len > 0 ) { 16050const unsigned char * p = san -> buf . p ; 16051size_t len = san -> buf . len ; 16052 16053if ( is_ip ) { 16054// For an IP host, only a matching iPAddress SAN of the same family 16055// (4 bytes for IPv4, 16 bytes for IPv6) may authenticate it. 16056if ( len == ip_len && memcmp ( p , ip_bytes , ip_len ) == 0 ) { return true; } 16057} else { 16058// Check if this SAN is a DNS name (printable ASCII string) 16059bool is_dns = len > 0 ; 16060for ( size_t i = 0 ; i < len && is_dns ; i ++ ) { 16061if ( p [ i ] < 32 || p [ i ] > 126 ) { is_dns = false; } 16062} 16063if ( is_dns ) { 16064std:: string san_name ( reinterpret_cast < const char *> ( p ), len ); 16065if (detail:: match_hostname ( san_name , host_str )) { return true; } 16066} 16067} 16068san = san -> next ; 16069} 16070 16071// Fallback: Check Common Name (CN) in subject. Skipped for IP-literal hosts: 16072// an IP identity is only valid via an iPAddress SAN, never the CN (RFC 9110; 16073// the OpenSSL backend's X509_check_ip behaves the same way). 16074if (! is_ip ) { 16075char cn [ 256 ]; 16076int ret = mbedtls_x509_dn_gets ( cn , sizeof( cn ), & mcert -> subject ); 16077if ( ret > 0 ) { 16078std:: string cn_str ( cn ); 16079 16080// Look for "CN=" in the DN string 16081size_t cn_pos = cn_str . find ( "CN=" ); 16082if ( cn_pos != std::string:: npos ) { 16083size_t start = cn_pos + 3 ; 16084size_t end = cn_str . find ( ',' , start ); 16085std:: string cn_value = 16086cn_str . substr ( start , end == std::string:: npos ? end : end - start ); 16087 16088if (detail:: match_hostname ( cn_value , host_str )) { return true; } 16089} 16090} 16091} 16092 16093return false; 16094} 16095 16096uint64_t hostname_mismatch_code () { 16097return static_cast < uint64_t > ( MBEDTLS_X509_BADCERT_CN_MISMATCH ); 16098} 16099 16100long get_verify_result ( const_session_t session ) { 16101if (! session ) { return -1 ; } 16102auto msession = 16103static_cast < impl:: MbedTlsSession *> ( const_cast < void *> ( session )); 16104uint32_t flags = mbedtls_ssl_get_verify_result ( & msession -> ssl ); 16105// Return 0 (X509_V_OK equivalent) if verification passed 16106return flags == 0 ? 0 : static_cast < long > ( flags ); 16107} 16108 16109std:: string get_cert_subject_cn ( cert_t cert ) { 16110if (! cert ) return "" ; 16111auto x509 = static_cast < mbedtls_x509_crt *> ( cert ); 16112 16113// Find the CN in the subject 16114const mbedtls_x509_name * name = & x509 -> subject ; 16115while ( name != nullptr ) { 16116if ( MBEDTLS_OID_CMP ( MBEDTLS_OID_AT_CN , & name -> oid ) == 0 ) { 16117return std:: string ( reinterpret_cast < const char *> ( name -> val . p ), 16118name -> val . len ); 16119} 16120name = name -> next ; 16121} 16122return "" ; 16123} 16124 16125std:: string get_cert_issuer_name ( cert_t cert ) { 16126if (! cert ) return "" ; 16127auto x509 = static_cast < mbedtls_x509_crt *> ( cert ); 16128 16129// Build a human-readable issuer name string 16130char buf [ 512 ]; 16131int ret = mbedtls_x509_dn_gets ( buf , sizeof( buf ), & x509 -> issuer ); 16132if ( ret < 0 ) return "" ; 16133return std:: string ( buf ); 16134} 16135 16136bool get_cert_sans ( cert_t cert , std:: vector < SanEntry > & sans ) { 16137sans . clear (); 16138if (! cert ) return false; 16139auto x509 = static_cast < mbedtls_x509_crt *> ( cert ); 16140 16141// Parse the Subject Alternative Name extension 16142const mbedtls_x509_sequence * cur = & x509 -> subject_alt_names ; 16143while ( cur != nullptr ) { 16144if ( cur -> buf . len > 0 ) { 16145// Mbed TLS stores SAN as ASN.1 sequences 16146// The tag byte indicates the type 16147const unsigned char * p = cur -> buf . p ; 16148size_t len = cur -> buf . len ; 16149 16150// First byte is the tag 16151unsigned char tag = * p ; 16152p ++ ; 16153len -- ; 16154 16155// Parse length (simple single-byte length assumed) 16156if ( len > 0 && * p < 0x80 ) { 16157size_t value_len = * p ; 16158p ++ ; 16159len -- ; 16160 16161if ( value_len <= len ) { 16162SanEntry entry ; 16163// ASN.1 context tags for GeneralName 16164switch ( tag & 0x1F ) { 16165case 2 : // dNSName 16166entry . type = SanType :: DNS ; 16167entry . value = 16168std:: string ( reinterpret_cast < const char *> ( p ), value_len ); 16169break; 16170case 7 : // iPAddress 16171entry . type = SanType :: IP ; 16172if ( value_len == 4 ) { 16173// IPv4 16174char buf [ 16 ]; 16175snprintf ( buf , sizeof( buf ), "%d.%d.%d.%d" , p [ 0 ], p [ 1 ], p [ 2 ], p [ 3 ]); 16176entry . value = buf ; 16177} else if ( value_len == 16 ) { 16178// IPv6 16179char buf [ 64 ]; 16180snprintf ( buf , sizeof( buf ), 16181"%02x%02x:%02x%02x:%02x%02x:%02x%02x:" 16182"%02x%02x:%02x%02x:%02x%02x:%02x%02x" , 16183p [ 0 ], p [ 1 ], p [ 2 ], p [ 3 ], p [ 4 ], p [ 5 ], p [ 6 ], p [ 7 ], p [ 8 ], 16184p [ 9 ], p [ 10 ], p [ 11 ], p [ 12 ], p [ 13 ], p [ 14 ], p [ 15 ]); 16185entry . value = buf ; 16186} 16187break; 16188case 1 : // rfc822Name (email) 16189entry . type = SanType :: ; 16190entry . value = 16191std:: string ( reinterpret_cast < const char *> ( p ), value_len ); 16192break; 16193case 6 : // uniformResourceIdentifier 16194entry . type = SanType :: URI ; 16195entry . value = 16196std:: string ( reinterpret_cast < const char *> ( p ), value_len ); 16197break; 16198default: entry . type = SanType :: OTHER ; break; 16199} 16200 16201if (! entry . value . empty ()) { sans . push_back (std:: move ( entry )); } 16202} 16203} 16204} 16205cur = cur -> next ; 16206} 16207return true; 16208} 16209 16210bool get_cert_validity ( cert_t cert , time_t & not_before , 16211time_t & not_after ) { 16212if (! cert ) return false; 16213auto x509 = static_cast < mbedtls_x509_crt *> ( cert ); 16214 16215// Convert mbedtls_x509_time to time_t 16216auto to_time_t = [](const mbedtls_x509_time & t ) -> time_t { 16217struct tm tm_time = {}; 16218tm_time . tm_year = t . year - 1900 ; 16219tm_time . tm_mon = t . mon - 1 ; 16220tm_time . tm_mday = t . day ; 16221tm_time . tm_hour = t . hour ; 16222tm_time . tm_min = t . min ; 16223tm_time . tm_sec = t . sec ; 16224#ifdef _WIN32 16225return _mkgmtime ( & tm_time ); 16226#else 16227return timegm ( & tm_time ); 16228#endif 16229}; 16230 16231not_before = to_time_t ( x509 -> valid_from ); 16232not_after = to_time_t ( x509 -> valid_to ); 16233return true; 16234} 16235 16236std:: string get_cert_serial ( cert_t cert ) { 16237if (! cert ) return "" ; 16238auto x509 = static_cast < mbedtls_x509_crt *> ( cert ); 16239 16240// Convert serial number to hex string 16241std:: string result ; 16242result . reserve ( x509 -> serial . len * 2 ); 16243for ( size_t i = 0 ; i < x509 -> serial . len ; i ++ ) { 16244char hex [ 3 ]; 16245snprintf ( hex , sizeof( hex ), "%02X" , x509 -> serial . p [ i ]); 16246result += hex ; 16247} 16248return result ; 16249} 16250 16251bool get_cert_der ( cert_t cert , std:: vector < unsigned char > & der ) { 16252if (! cert ) return false; 16253auto crt = static_cast < mbedtls_x509_crt *> ( cert ); 16254if (! crt -> raw . p || crt -> raw . len == 0 ) return false; 16255der . assign ( crt -> raw . p , crt -> raw . p + crt -> raw . len ); 16256return true; 16257} 16258 16259const char * get_sni ( const_session_t session ) { 16260if (! session ) return nullptr ; 16261auto msession = static_cast < const impl:: MbedTlsSession *> ( session ); 16262 16263// For server: return SNI received from client during handshake 16264if (! msession -> sni_hostname . empty ()) { 16265return msession -> sni_hostname . c_str (); 16266} 16267 16268// For client: return the hostname set via set_sni 16269if (! msession -> hostname . empty ()) { return msession -> hostname . c_str (); } 16270 16271return nullptr ; 16272} 16273 16274uint64_t peek_error () { 16275// Mbed TLS doesn't have an error queue, return the last error 16276return static_cast < uint64_t > ( - impl:: mbedtls_last_error ()); 16277} 16278 16279uint64_t get_error () { 16280// Mbed TLS doesn't have an error queue, return and clear the last error 16281uint64_t err = static_cast < uint64_t > ( - impl:: mbedtls_last_error ()); 16282impl:: mbedtls_last_error () = 0 ; 16283return err ; 16284} 16285 16286std:: string error_string ( uint64_t code ) { 16287char buf [ 256 ]; 16288mbedtls_strerror ( - static_cast < int > ( code ), buf , sizeof( buf )); 16289return std:: string ( buf ); 16290} 16291 16292ca_store_t create_ca_store (const char * pem , size_t len ) { 16293auto * ca_chain = new (std:: nothrow ) mbedtls_x509_crt ; 16294if (! ca_chain ) { return nullptr ; } 16295 16296mbedtls_x509_crt_init ( ca_chain ); 16297 16298// mbedtls_x509_crt_parse expects null-terminated PEM 16299int ret = mbedtls_x509_crt_parse ( ca_chain , 16300reinterpret_cast < const unsigned char *> ( pem ), 16301len + 1 ); // +1 for null terminator 16302if ( ret != 0 ) { 16303// Try without +1 in case PEM is already null-terminated 16304ret = mbedtls_x509_crt_parse ( 16305ca_chain , reinterpret_cast < const unsigned char *> ( pem ), len ); 16306if ( ret != 0 ) { 16307mbedtls_x509_crt_free ( ca_chain ); 16308delete ca_chain ; 16309return nullptr ; 16310} 16311} 16312 16313return static_cast < ca_store_t > ( ca_chain ); 16314} 16315 16316void free_ca_store ( ca_store_t store ) { 16317if ( store ) { 16318auto * ca_chain = static_cast < mbedtls_x509_crt *> ( store ); 16319mbedtls_x509_crt_free ( ca_chain ); 16320delete ca_chain ; 16321} 16322} 16323 16324bool set_ca_store ( ctx_t ctx , ca_store_t store ) { 16325if (! ctx || ! store ) { return false; } 16326auto * mbed_ctx = static_cast < impl:: MbedTlsContext *> ( ctx ); 16327auto * ca_chain = static_cast < mbedtls_x509_crt *> ( store ); 16328 16329// Free existing CA chain 16330mbedtls_x509_crt_free ( & mbed_ctx -> ca_chain ); 16331mbedtls_x509_crt_init ( & mbed_ctx -> ca_chain ); 16332 16333// Copy the CA chain (deep copy) 16334// Parse from the raw data of the source cert 16335mbedtls_x509_crt * src = ca_chain ; 16336while ( src != nullptr ) { 16337int ret = mbedtls_x509_crt_parse_der ( & mbed_ctx -> ca_chain , src -> raw . p , 16338src -> raw . len ); 16339if ( ret != 0 ) { 16340free_ca_store ( store ); 16341return false; 16342} 16343src = src -> next ; 16344} 16345 16346// This function takes ownership of the store; the chain was deep-copied 16347// above, so release the source 16348free_ca_store ( store ); 16349 16350// Update the SSL config to use the new CA chain 16351mbedtls_ssl_conf_ca_chain ( & mbed_ctx -> conf , & mbed_ctx -> ca_chain , nullptr ); 16352return true; 16353} 16354 16355size_t get_ca_certs ( ctx_t ctx , std:: vector < cert_t > & certs ) { 16356certs . clear (); 16357if (! ctx ) { return 0 ; } 16358auto * mbed_ctx = static_cast < impl:: MbedTlsContext *> ( ctx ); 16359 16360// Iterate through the CA chain 16361mbedtls_x509_crt * cert = & mbed_ctx -> ca_chain ; 16362while ( cert != nullptr && cert -> raw . len > 0 ) { 16363// Create a copy of the certificate for the caller 16364auto * copy = new mbedtls_x509_crt ; 16365mbedtls_x509_crt_init ( copy ); 16366int ret = mbedtls_x509_crt_parse_der ( copy , cert -> raw . p , cert -> raw . len ); 16367if ( ret == 0 ) { 16368certs . push_back ( static_cast < cert_t > ( copy )); 16369} else { 16370mbedtls_x509_crt_free ( copy ); 16371delete copy ; 16372} 16373cert = cert -> next ; 16374} 16375return certs . size (); 16376} 16377 16378std:: vector < std:: string > get_ca_names ( ctx_t ctx ) { 16379std:: vector < std:: string > names ; 16380if (! ctx ) { return names ; } 16381auto * mbed_ctx = static_cast < impl:: MbedTlsContext *> ( ctx ); 16382 16383// Iterate through the CA chain 16384mbedtls_x509_crt * cert = & mbed_ctx -> ca_chain ; 16385while ( cert != nullptr && cert -> raw . len > 0 ) { 16386char buf [ 512 ]; 16387int ret = mbedtls_x509_dn_gets ( buf , sizeof( buf ), & cert -> subject ); 16388if ( ret > 0 ) { names . push_back ( buf ); } 16389cert = cert -> next ; 16390} 16391return names ; 16392} 16393 16394bool update_server_cert ( ctx_t ctx , const char * cert_pem , 16395const char * key_pem , const char * password ) { 16396if (! ctx || ! cert_pem || ! key_pem ) { return false; } 16397auto * mbed_ctx = static_cast < impl:: MbedTlsContext *> ( ctx ); 16398 16399// Free existing certificate and key 16400mbedtls_x509_crt_free ( & mbed_ctx -> own_cert ); 16401mbedtls_pk_free ( & mbed_ctx -> own_key ); 16402mbedtls_x509_crt_init ( & mbed_ctx -> own_cert ); 16403mbedtls_pk_init ( & mbed_ctx -> own_key ); 16404 16405// Parse certificate PEM 16406int ret = mbedtls_x509_crt_parse ( 16407& mbed_ctx -> own_cert , reinterpret_cast < const unsigned char *> ( cert_pem ), 16408strlen ( cert_pem ) + 1 ); 16409if ( ret != 0 ) { 16410impl:: mbedtls_last_error () = ret ; 16411return false; 16412} 16413 16414// Parse private key PEM 16415#if defined( CPPHTTPLIB_MBEDTLS_V3 ) && !defined( CPPHTTPLIB_MBEDTLS_V4 ) 16416ret = mbedtls_pk_parse_key ( 16417& mbed_ctx -> own_key , reinterpret_cast < const unsigned char *> ( key_pem ), 16418strlen ( key_pem ) + 1 , 16419password ? reinterpret_cast < const unsigned char *> ( password ) : nullptr , 16420password ? strlen ( password ) : 0 , mbedtls_ctr_drbg_random , 16421& mbed_ctx -> ctr_drbg ); 16422#else 16423ret = mbedtls_pk_parse_key ( 16424& mbed_ctx -> own_key , reinterpret_cast < const unsigned char *> ( key_pem ), 16425strlen ( key_pem ) + 1 , 16426password ? reinterpret_cast < const unsigned char *> ( password ) : nullptr , 16427password ? strlen ( password ) : 0 ); 16428#endif 16429if ( ret != 0 ) { 16430impl:: mbedtls_last_error () = ret ; 16431return false; 16432} 16433 16434// Configure SSL to use the new certificate and key 16435ret = mbedtls_ssl_conf_own_cert ( & mbed_ctx -> conf , & mbed_ctx -> own_cert , 16436& mbed_ctx -> own_key ); 16437if ( ret != 0 ) { 16438impl:: mbedtls_last_error () = ret ; 16439return false; 16440} 16441 16442return true; 16443} 16444 16445bool update_server_client_ca ( ctx_t ctx , const char * ca_pem ) { 16446if (! ctx || ! ca_pem ) { return false; } 16447auto * mbed_ctx = static_cast < impl:: MbedTlsContext *> ( ctx ); 16448 16449// Free existing CA chain 16450mbedtls_x509_crt_free ( & mbed_ctx -> ca_chain ); 16451mbedtls_x509_crt_init ( & mbed_ctx -> ca_chain ); 16452 16453// Parse CA PEM 16454int ret = mbedtls_x509_crt_parse ( 16455& mbed_ctx -> ca_chain , reinterpret_cast < const unsigned char *> ( ca_pem ), 16456strlen ( ca_pem ) + 1 ); 16457if ( ret != 0 ) { 16458impl:: mbedtls_last_error () = ret ; 16459return false; 16460} 16461 16462// Update SSL config to use new CA chain 16463mbedtls_ssl_conf_ca_chain ( & mbed_ctx -> conf , & mbed_ctx -> ca_chain , nullptr ); 16464return true; 16465} 16466 16467bool set_verify_callback ( ctx_t ctx , VerifyCallback callback ) { 16468if (! ctx ) { return false; } 16469auto * mbed_ctx = static_cast < impl:: MbedTlsContext *> ( ctx ); 16470 16471impl:: get_verify_callback () = std:: move ( callback ); 16472mbed_ctx -> has_verify_callback = 16473static_cast < bool > (impl:: get_verify_callback ()); 16474 16475if ( mbed_ctx -> has_verify_callback ) { 16476// Set OPTIONAL mode to ensure callback is called even when verification 16477// is disabled (matching OpenSSL behavior where SSL_VERIFY_PEER is set) 16478mbedtls_ssl_conf_authmode ( & mbed_ctx -> conf , MBEDTLS_SSL_VERIFY_OPTIONAL ); 16479mbedtls_ssl_conf_verify ( & mbed_ctx -> conf , impl:: mbedtls_verify_callback , 16480nullptr ); 16481} else { 16482mbedtls_ssl_conf_verify ( & mbed_ctx -> conf , nullptr , nullptr ); 16483} 16484return true; 16485} 16486 16487long get_verify_error ( const_session_t session ) { 16488if (! session ) { return -1 ; } 16489auto * msession = 16490static_cast < impl:: MbedTlsSession *> ( const_cast < void *> ( session )); 16491return static_cast < long > ( mbedtls_ssl_get_verify_result ( & msession -> ssl )); 16492} 16493 16494std:: string verify_error_string ( long error_code ) { 16495if ( error_code == 0 ) { return "" ; } 16496char buf [ 256 ]; 16497mbedtls_x509_crt_verify_info ( buf , sizeof( buf ), "" , 16498static_cast < uint32_t > ( error_code )); 16499// Remove trailing newline if present 16500std:: string result ( buf ); 16501while (! result . empty () && ( result . back () == '\n' || result . back () == ' ' )) { 16502result . pop_back (); 16503} 16504return result ; 16505} 16506 16507} // namespace tls 16508 16509#endif // CPPHTTPLIB_MBEDTLS_SUPPORT 16510 16511/* 16512* Group 10: TLS abstraction layer - wolfSSL backend 16513*/ 16514 16515/* 16516* wolfSSL Backend Implementation 16517*/ 16518 16519#ifdef CPPHTTPLIB_WOLFSSL_SUPPORT 16520namespace tls { 16521 16522namespace impl { 16523 16524// wolfSSL session wrapper 16525struct WolfSSLSession { 16526WOLFSSL * ssl = nullptr ; 16527socket_t sock = INVALID_SOCKET ; 16528std:: string hostname ; // For client: set via set_sni 16529std:: string sni_hostname ; // For server: received from client via SNI callback 16530 16531WolfSSLSession () = default; 16532 16533~ WolfSSLSession () { 16534if ( ssl ) { wolfSSL_free ( ssl ); } 16535} 16536 16537WolfSSLSession (const WolfSSLSession & ) = delete; 16538WolfSSLSession & operator = (const WolfSSLSession & ) = delete; 16539}; 16540 16541// Thread-local error code accessor for wolfSSL 16542uint64_t & wolfssl_last_error () { 16543static thread_local uint64_t err = 0 ; 16544return err ; 16545} 16546 16547// Helper to map wolfSSL error to ErrorCode. 16548// ssl_error is the value from wolfSSL_get_error(). 16549// raw_ret is the raw return value from the wolfSSL call (for low-level error). 16550ErrorCode map_wolfssl_error ( WOLFSSL * ssl , int ssl_error , 16551int & out_errno ) { 16552switch ( ssl_error ) { 16553case SSL_ERROR_NONE : return ErrorCode :: Success ; 16554case SSL_ERROR_WANT_READ : return ErrorCode :: WantRead ; 16555case SSL_ERROR_WANT_WRITE : return ErrorCode :: WantWrite ; 16556case SSL_ERROR_ZERO_RETURN : return ErrorCode :: PeerClosed ; 16557case SSL_ERROR_SYSCALL : out_errno = errno ; return ErrorCode :: SyscallError ; 16558default: 16559if ( ssl ) { 16560// wolfSSL stores the low-level error code as a negative value. 16561// DOMAIN_NAME_MISMATCH (-322) indicates hostname verification failure. 16562int low_err = ssl_error ; // wolfSSL_get_error returns the low-level code 16563if ( low_err == DOMAIN_NAME_MISMATCH ) { 16564return ErrorCode :: HostnameMismatch ; 16565} 16566// Check verify result to distinguish cert verification from generic SSL 16567// errors. 16568long vr = wolfSSL_get_verify_result ( ssl ); 16569if ( vr != 0 ) { return ErrorCode :: CertVerifyFailed ; } 16570} 16571return ErrorCode :: Fatal ; 16572} 16573} 16574 16575// WolfSSLContext constructor/destructor implementations 16576WolfSSLContext :: WolfSSLContext () { wolfSSL_Init (); } 16577 16578WolfSSLContext ::~ WolfSSLContext () { 16579if ( ctx ) { wolfSSL_CTX_free ( ctx ); } 16580} 16581 16582// Thread-local storage for SNI captured during handshake 16583std:: string & wolfssl_pending_sni () { 16584static thread_local std:: string sni ; 16585return sni ; 16586} 16587 16588// SNI callback for wolfSSL server to capture client's SNI hostname 16589int wolfssl_sni_callback ( WOLFSSL * ssl , int * ret , void * exArg ) { 16590( void ) ret ; 16591( void ) exArg ; 16592 16593void * name_data = nullptr ; 16594unsigned short name_len = 16595wolfSSL_SNI_GetRequest ( ssl , WOLFSSL_SNI_HOST_NAME , & name_data ); 16596 16597if ( name_data && name_len > 0 ) { 16598wolfssl_pending_sni (). assign ( static_cast < const char *> ( name_data ), 16599name_len ); 16600} else { 16601wolfssl_pending_sni (). clear (); 16602} 16603return 0 ; // Continue regardless 16604} 16605 16606// wolfSSL verify callback wrapper 16607int wolfssl_verify_callback ( int preverify_ok , 16608WOLFSSL_X509_STORE_CTX * x509_ctx ) { 16609auto & callback = get_verify_callback (); 16610if (! callback ) { return preverify_ok ; } 16611 16612WOLFSSL_X509 * cert = wolfSSL_X509_STORE_CTX_get_current_cert ( x509_ctx ); 16613int depth = wolfSSL_X509_STORE_CTX_get_error_depth ( x509_ctx ); 16614int err = wolfSSL_X509_STORE_CTX_get_error ( x509_ctx ); 16615 16616// Get the WOLFSSL object from the X509_STORE_CTX 16617WOLFSSL * ssl = static_cast < WOLFSSL *> ( wolfSSL_X509_STORE_CTX_get_ex_data ( 16618x509_ctx , wolfSSL_get_ex_data_X509_STORE_CTX_idx ())); 16619 16620VerifyContext verify_ctx ; 16621verify_ctx . session = static_cast < session_t > ( ssl ); 16622verify_ctx . cert = static_cast < cert_t > ( cert ); 16623verify_ctx . depth = depth ; 16624verify_ctx . preverify_ok = ( preverify_ok != 0 ); 16625verify_ctx . error_code = static_cast < long > ( err ); 16626 16627if ( err != 0 ) { 16628verify_ctx . error_string = wolfSSL_X509_verify_cert_error_string ( err ); 16629} else { 16630verify_ctx . error_string = nullptr ; 16631} 16632 16633bool accepted = callback ( verify_ctx ); 16634return accepted ? 1 : 0 ; 16635} 16636 16637void set_wolfssl_password_cb ( WOLFSSL_CTX * ctx , const char * password ) { 16638wolfSSL_CTX_set_default_passwd_cb_userdata ( ctx , const_cast < char *> ( password )); 16639wolfSSL_CTX_set_default_passwd_cb ( 16640ctx , []( char * buf , int size , int /*rwflag*/ , void * userdata ) -> int { 16641auto * pwd = static_cast < const char *> ( userdata ); 16642if (! pwd ) return 0 ; 16643auto len = static_cast < int > ( strlen ( pwd )); 16644if ( len > size ) len = size ; 16645memcpy ( buf , pwd , static_cast < size_t > ( len )); 16646return len ; 16647}); 16648} 16649 16650} // namespace impl 16651 16652ctx_t create_client_context () { 16653auto ctx = new (std:: nothrow ) impl:: WolfSSLContext (); 16654if (! ctx ) { return nullptr ; } 16655 16656ctx -> is_server = false; 16657 16658WOLFSSL_METHOD * method = wolfTLSv1_2_client_method (); 16659if (! method ) { 16660delete ctx ; 16661return nullptr ; 16662} 16663 16664ctx -> ctx = wolfSSL_CTX_new ( method ); 16665if (! ctx -> ctx ) { 16666delete ctx ; 16667return nullptr ; 16668} 16669 16670// Default: verify peer certificate 16671wolfSSL_CTX_set_verify ( ctx -> ctx , SSL_VERIFY_PEER , nullptr ); 16672 16673return static_cast < ctx_t > ( ctx ); 16674} 16675 16676ctx_t create_server_context () { 16677auto ctx = new (std:: nothrow ) impl:: WolfSSLContext (); 16678if (! ctx ) { return nullptr ; } 16679 16680ctx -> is_server = true; 16681 16682WOLFSSL_METHOD * method = wolfTLSv1_2_server_method (); 16683if (! method ) { 16684delete ctx ; 16685return nullptr ; 16686} 16687 16688ctx -> ctx = wolfSSL_CTX_new ( method ); 16689if (! ctx -> ctx ) { 16690delete ctx ; 16691return nullptr ; 16692} 16693 16694// Default: don't verify client 16695wolfSSL_CTX_set_verify ( ctx -> ctx , SSL_VERIFY_NONE , nullptr ); 16696 16697// Enable SNI on server 16698wolfSSL_CTX_SNI_SetOptions ( ctx -> ctx , WOLFSSL_SNI_HOST_NAME , 16699WOLFSSL_SNI_CONTINUE_ON_MISMATCH ); 16700wolfSSL_CTX_set_servername_callback ( ctx -> ctx , impl:: wolfssl_sni_callback ); 16701 16702return static_cast < ctx_t > ( ctx ); 16703} 16704 16705void free_context ( ctx_t ctx ) { 16706if ( ctx ) { delete static_cast < impl:: WolfSSLContext *> ( ctx ); } 16707} 16708 16709bool set_min_version ( ctx_t ctx , Version version ) { 16710if (! ctx ) { return false; } 16711auto wctx = static_cast < impl:: WolfSSLContext *> ( ctx ); 16712 16713int min_ver = WOLFSSL_TLSV1_2 ; 16714if ( version >= Version :: TLS1_3 ) { min_ver = WOLFSSL_TLSV1_3 ; } 16715 16716return wolfSSL_CTX_SetMinVersion ( wctx -> ctx , min_ver ) == WOLFSSL_SUCCESS ; 16717} 16718 16719bool load_ca_pem ( ctx_t ctx , const char * pem , size_t len ) { 16720if (! ctx || ! pem ) { return false; } 16721auto wctx = static_cast < impl:: WolfSSLContext *> ( ctx ); 16722 16723int ret = wolfSSL_CTX_load_verify_buffer ( 16724wctx -> ctx , reinterpret_cast < const unsigned char *> ( pem ), 16725static_cast < long > ( len ), SSL_FILETYPE_PEM ); 16726if ( ret != SSL_SUCCESS ) { 16727impl:: wolfssl_last_error () = 16728static_cast < uint64_t > ( wolfSSL_ERR_peek_last_error ()); 16729return false; 16730} 16731wctx -> ca_pem_data_ . append ( pem , len ); 16732return true; 16733} 16734 16735bool load_ca_file ( ctx_t ctx , const char * file_path ) { 16736if (! ctx || ! file_path ) { return false; } 16737auto wctx = static_cast < impl:: WolfSSLContext *> ( ctx ); 16738 16739int ret = wolfSSL_CTX_load_verify_locations ( wctx -> ctx , file_path , nullptr ); 16740if ( ret != SSL_SUCCESS ) { 16741impl:: wolfssl_last_error () = 16742static_cast < uint64_t > ( wolfSSL_ERR_peek_last_error ()); 16743return false; 16744} 16745return true; 16746} 16747 16748bool load_ca_dir ( ctx_t ctx , const char * dir_path ) { 16749if (! ctx || ! dir_path ) { return false; } 16750auto wctx = static_cast < impl:: WolfSSLContext *> ( ctx ); 16751 16752int ret = wolfSSL_CTX_load_verify_locations ( wctx -> ctx , nullptr , dir_path ); 16753// wolfSSL may fail if the directory doesn't contain properly hashed certs. 16754// Unlike OpenSSL which lazily loads certs from directories, wolfSSL scans 16755// immediately. Return true even on failure since the CA file may have 16756// already been loaded, matching OpenSSL's lenient behavior. 16757( void ) ret ; 16758return true; 16759} 16760 16761bool load_system_certs ( ctx_t ctx ) { 16762if (! ctx ) { return false; } 16763auto wctx = static_cast < impl:: WolfSSLContext *> ( ctx ); 16764bool loaded = false; 16765 16766#ifdef _WIN32 16767loaded = impl:: enumerate_windows_system_certs ( 16768[ & ](const unsigned char * data , size_t len ) { 16769return wolfSSL_CTX_load_verify_buffer ( wctx -> ctx , data , 16770static_cast < long > ( len ), 16771SSL_FILETYPE_ASN1 ) == SSL_SUCCESS ; 16772}); 16773#elif defined( __APPLE__ ) && defined( CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN ) 16774loaded = impl:: enumerate_macos_keychain_certs ( 16775[ & ](const unsigned char * data , size_t len ) { 16776return wolfSSL_CTX_load_verify_buffer ( wctx -> ctx , data , 16777static_cast < long > ( len ), 16778SSL_FILETYPE_ASN1 ) == SSL_SUCCESS ; 16779}); 16780#else 16781for ( auto path = impl:: system_ca_paths (); * path ; ++ path ) { 16782if ( wolfSSL_CTX_load_verify_locations ( wctx -> ctx , * path , nullptr ) == 16783SSL_SUCCESS ) { 16784loaded = true; 16785break; 16786} 16787} 16788 16789if (! loaded ) { 16790for ( auto dir = impl:: system_ca_dirs (); * dir ; ++ dir ) { 16791if ( wolfSSL_CTX_load_verify_locations ( wctx -> ctx , nullptr , * dir ) == 16792SSL_SUCCESS ) { 16793loaded = true; 16794break; 16795} 16796} 16797} 16798#endif 16799 16800return loaded ; 16801} 16802 16803bool set_client_cert_pem ( ctx_t ctx , const char * cert , const char * key , 16804const char * password ) { 16805if (! ctx || ! cert || ! key ) { return false; } 16806auto wctx = static_cast < impl:: WolfSSLContext *> ( ctx ); 16807 16808// Load certificate 16809int ret = wolfSSL_CTX_use_certificate_buffer ( 16810wctx -> ctx , reinterpret_cast < const unsigned char *> ( cert ), 16811static_cast < long > ( strlen ( cert )), SSL_FILETYPE_PEM ); 16812if ( ret != SSL_SUCCESS ) { 16813impl:: wolfssl_last_error () = 16814static_cast < uint64_t > ( wolfSSL_ERR_peek_last_error ()); 16815return false; 16816} 16817 16818// Set password callback if password is provided 16819if ( password ) { impl:: set_wolfssl_password_cb ( wctx -> ctx , password ); } 16820 16821// Load private key 16822ret = wolfSSL_CTX_use_PrivateKey_buffer ( 16823wctx -> ctx , reinterpret_cast < const unsigned char *> ( key ), 16824static_cast < long > ( strlen ( key )), SSL_FILETYPE_PEM ); 16825if ( ret != SSL_SUCCESS ) { 16826impl:: wolfssl_last_error () = 16827static_cast < uint64_t > ( wolfSSL_ERR_peek_last_error ()); 16828return false; 16829} 16830 16831// Verify that the certificate and private key match 16832return wolfSSL_CTX_check_private_key ( wctx -> ctx ) == SSL_SUCCESS ; 16833} 16834 16835bool set_client_cert_file ( ctx_t ctx , const char * cert_path , 16836const char * key_path , const char * password ) { 16837if (! ctx || ! cert_path || ! key_path ) { return false; } 16838auto wctx = static_cast < impl:: WolfSSLContext *> ( ctx ); 16839 16840// Load certificate file 16841int ret = 16842wolfSSL_CTX_use_certificate_file ( wctx -> ctx , cert_path , SSL_FILETYPE_PEM ); 16843if ( ret != SSL_SUCCESS ) { 16844impl:: wolfssl_last_error () = 16845static_cast < uint64_t > ( wolfSSL_ERR_peek_last_error ()); 16846return false; 16847} 16848 16849// Set password callback if password is provided 16850if ( password ) { impl:: set_wolfssl_password_cb ( wctx -> ctx , password ); } 16851 16852// Load private key file 16853ret = wolfSSL_CTX_use_PrivateKey_file ( wctx -> ctx , key_path , SSL_FILETYPE_PEM ); 16854if ( ret != SSL_SUCCESS ) { 16855impl:: wolfssl_last_error () = 16856static_cast < uint64_t > ( wolfSSL_ERR_peek_last_error ()); 16857return false; 16858} 16859 16860// Verify that the certificate and private key match 16861return wolfSSL_CTX_check_private_key ( wctx -> ctx ) == SSL_SUCCESS ; 16862} 16863 16864void set_verify_client ( ctx_t ctx , bool require ) { 16865if (! ctx ) { return; } 16866auto wctx = static_cast < impl:: WolfSSLContext *> ( ctx ); 16867wctx -> verify_client = require ; 16868if ( require ) { 16869wolfSSL_CTX_set_verify ( 16870wctx -> ctx , SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT , 16871wctx -> has_verify_callback ? impl:: wolfssl_verify_callback : nullptr ); 16872} else { 16873if ( wctx -> has_verify_callback ) { 16874wolfSSL_CTX_set_verify ( wctx -> ctx , SSL_VERIFY_PEER , 16875impl:: wolfssl_verify_callback ); 16876} else { 16877wolfSSL_CTX_set_verify ( wctx -> ctx , SSL_VERIFY_NONE , nullptr ); 16878} 16879} 16880} 16881 16882session_t create_session ( ctx_t ctx , socket_t sock ) { 16883if (! ctx || sock == INVALID_SOCKET ) { return nullptr ; } 16884auto wctx = static_cast < impl:: WolfSSLContext *> ( ctx ); 16885 16886auto session = new (std:: nothrow ) impl:: WolfSSLSession (); 16887if (! session ) { return nullptr ; } 16888 16889session -> sock = sock ; 16890session -> ssl = wolfSSL_new ( wctx -> ctx ); 16891if (! session -> ssl ) { 16892impl:: wolfssl_last_error () = 16893static_cast < uint64_t > ( wolfSSL_ERR_peek_last_error ()); 16894delete session ; 16895return nullptr ; 16896} 16897 16898wolfSSL_set_fd ( session -> ssl , static_cast < int > ( sock )); 16899 16900return static_cast < session_t > ( session ); 16901} 16902 16903void free_session ( session_t session ) { 16904if ( session ) { delete static_cast < impl:: WolfSSLSession *> ( session ); } 16905} 16906 16907bool set_sni ( session_t session , const char * hostname , 16908bool verify_hostname ) { 16909if (! session || ! hostname ) { return false; } 16910auto wsession = static_cast < impl:: WolfSSLSession *> ( session ); 16911 16912int ret = wolfSSL_UseSNI ( wsession -> ssl , WOLFSSL_SNI_HOST_NAME , hostname , 16913static_cast < word16 > ( strlen ( hostname ))); 16914if ( ret != WOLFSSL_SUCCESS ) { 16915impl:: wolfssl_last_error () = 16916static_cast < uint64_t > ( wolfSSL_ERR_peek_last_error ()); 16917return false; 16918} 16919 16920// wolfSSL_check_domain_name binds identity checking to the handshake, 16921// separately from the SNI extension sent above; skip it when hostname 16922// verification is disabled so only the chain is checked, matching OpenSSL. 16923if ( verify_hostname ) { wolfSSL_check_domain_name ( wsession -> ssl , hostname ); } 16924 16925wsession -> hostname = hostname ; 16926return true; 16927} 16928 16929TlsError connect ( session_t session ) { 16930TlsError err ; 16931if (! session ) { 16932err . code = ErrorCode :: Fatal ; 16933return err ; 16934} 16935 16936auto wsession = static_cast < impl:: WolfSSLSession *> ( session ); 16937int ret = wolfSSL_connect ( wsession -> ssl ); 16938 16939if ( ret == SSL_SUCCESS ) { 16940err . code = ErrorCode :: Success ; 16941} else { 16942int ssl_error = wolfSSL_get_error ( wsession -> ssl , ret ); 16943err . code = impl:: map_wolfssl_error ( wsession -> ssl , ssl_error , err . sys_errno ); 16944err . backend_code = static_cast < uint64_t > ( ssl_error ); 16945impl:: wolfssl_last_error () = err . backend_code ; 16946} 16947 16948return err ; 16949} 16950 16951TlsError accept ( session_t session ) { 16952TlsError err ; 16953if (! session ) { 16954err . code = ErrorCode :: Fatal ; 16955return err ; 16956} 16957 16958auto wsession = static_cast < impl:: WolfSSLSession *> ( session ); 16959int ret = wolfSSL_accept ( wsession -> ssl ); 16960 16961if ( ret == SSL_SUCCESS ) { 16962err . code = ErrorCode :: Success ; 16963// Capture SNI from thread-local storage after successful handshake 16964wsession -> sni_hostname = std:: move (impl:: wolfssl_pending_sni ()); 16965impl:: wolfssl_pending_sni (). clear (); 16966} else { 16967int ssl_error = wolfSSL_get_error ( wsession -> ssl , ret ); 16968err . code = impl:: map_wolfssl_error ( wsession -> ssl , ssl_error , err . sys_errno ); 16969err . backend_code = static_cast < uint64_t > ( ssl_error ); 16970impl:: wolfssl_last_error () = err . backend_code ; 16971} 16972 16973return err ; 16974} 16975 16976bool connect_nonblocking ( session_t session , socket_t sock , 16977time_t timeout_sec , time_t timeout_usec , 16978TlsError * err ) { 16979if (! session ) { 16980if ( err ) { err -> code = ErrorCode :: Fatal ; } 16981return false; 16982} 16983 16984auto wsession = static_cast < impl:: WolfSSLSession *> ( session ); 16985 16986// Set socket to non-blocking mode 16987detail:: set_nonblocking ( sock , true); 16988auto cleanup = 16989detail:: scope_exit ([ & ]() { detail:: set_nonblocking ( sock , false); }); 16990 16991int ret ; 16992while (( ret = wolfSSL_connect ( wsession -> ssl )) != SSL_SUCCESS ) { 16993int ssl_error = wolfSSL_get_error ( wsession -> ssl , ret ); 16994if ( ssl_error == SSL_ERROR_WANT_READ ) { 16995if (detail:: select_read ( sock , timeout_sec , timeout_usec ) > 0 ) { 16996continue; 16997} 16998} else if ( ssl_error == SSL_ERROR_WANT_WRITE ) { 16999if (detail:: select_write ( sock , timeout_sec , timeout_usec ) > 0 ) { 17000continue; 17001} 17002} 17003 17004// Error or timeout 17005if ( err ) { 17006err -> code = 17007impl:: map_wolfssl_error ( wsession -> ssl , ssl_error , err -> sys_errno ); 17008err -> backend_code = static_cast < uint64_t > ( ssl_error ); 17009} 17010impl:: wolfssl_last_error () = static_cast < uint64_t > ( ssl_error ); 17011return false; 17012} 17013 17014if ( err ) { err -> code = ErrorCode :: Success ; } 17015return true; 17016} 17017 17018bool accept_nonblocking ( session_t session , socket_t sock , 17019time_t timeout_sec , time_t timeout_usec , 17020TlsError * err ) { 17021if (! session ) { 17022if ( err ) { err -> code = ErrorCode :: Fatal ; } 17023return false; 17024} 17025 17026auto wsession = static_cast < impl:: WolfSSLSession *> ( session ); 17027 17028// Set socket to non-blocking mode 17029detail:: set_nonblocking ( sock , true); 17030auto cleanup = 17031detail:: scope_exit ([ & ]() { detail:: set_nonblocking ( sock , false); }); 17032 17033int ret ; 17034while (( ret = wolfSSL_accept ( wsession -> ssl )) != SSL_SUCCESS ) { 17035int ssl_error = wolfSSL_get_error ( wsession -> ssl , ret ); 17036if ( ssl_error == SSL_ERROR_WANT_READ ) { 17037if (detail:: select_read ( sock , timeout_sec , timeout_usec ) > 0 ) { 17038continue; 17039} 17040} else if ( ssl_error == SSL_ERROR_WANT_WRITE ) { 17041if (detail:: select_write ( sock , timeout_sec , timeout_usec ) > 0 ) { 17042continue; 17043} 17044} 17045 17046// Error or timeout 17047if ( err ) { 17048err -> code = 17049impl:: map_wolfssl_error ( wsession -> ssl , ssl_error , err -> sys_errno ); 17050err -> backend_code = static_cast < uint64_t > ( ssl_error ); 17051} 17052impl:: wolfssl_last_error () = static_cast < uint64_t > ( ssl_error ); 17053return false; 17054} 17055 17056if ( err ) { err -> code = ErrorCode :: Success ; } 17057 17058// Capture SNI from thread-local storage after successful handshake 17059wsession -> sni_hostname = std:: move (impl:: wolfssl_pending_sni ()); 17060impl:: wolfssl_pending_sni (). clear (); 17061 17062return true; 17063} 17064 17065ssize_t read ( session_t session , void * buf , size_t len , TlsError & err ) { 17066if (! session || ! buf ) { 17067err . code = ErrorCode :: Fatal ; 17068return -1 ; 17069} 17070 17071auto wsession = static_cast < impl:: WolfSSLSession *> ( session ); 17072int ret = wolfSSL_read ( wsession -> ssl , buf , static_cast < int > ( len )); 17073 17074if ( ret > 0 ) { 17075err . code = ErrorCode :: Success ; 17076return static_cast < ssize_t > ( ret ); 17077} 17078 17079if ( ret == 0 ) { 17080err . code = ErrorCode :: PeerClosed ; 17081return 0 ; 17082} 17083 17084int ssl_error = wolfSSL_get_error ( wsession -> ssl , ret ); 17085err . code = impl:: map_wolfssl_error ( wsession -> ssl , ssl_error , err . sys_errno ); 17086err . backend_code = static_cast < uint64_t > ( ssl_error ); 17087impl:: wolfssl_last_error () = err . backend_code ; 17088return -1 ; 17089} 17090 17091ssize_t write ( session_t session , const void * buf , size_t len , 17092TlsError & err ) { 17093if (! session || ! buf ) { 17094err . code = ErrorCode :: Fatal ; 17095return -1 ; 17096} 17097 17098auto wsession = static_cast < impl:: WolfSSLSession *> ( session ); 17099int ret = wolfSSL_write ( wsession -> ssl , buf , static_cast < int > ( len )); 17100 17101if ( ret > 0 ) { 17102err . code = ErrorCode :: Success ; 17103return static_cast < ssize_t > ( ret ); 17104} 17105 17106// wolfSSL_write returns 0 when the peer has sent a close_notify. 17107// Treat this as an error (return -1) so callers don't spin in a 17108// write loop adding zero to the offset. 17109if ( ret == 0 ) { 17110err . code = ErrorCode :: PeerClosed ; 17111return -1 ; 17112} 17113 17114int ssl_error = wolfSSL_get_error ( wsession -> ssl , ret ); 17115err . code = impl:: map_wolfssl_error ( wsession -> ssl , ssl_error , err . sys_errno ); 17116err . backend_code = static_cast < uint64_t > ( ssl_error ); 17117impl:: wolfssl_last_error () = err . backend_code ; 17118return -1 ; 17119} 17120 17121int pending ( const_session_t session ) { 17122if (! session ) { return 0 ; } 17123auto wsession = 17124static_cast < impl:: WolfSSLSession *> ( const_cast < void *> ( session )); 17125return wolfSSL_pending ( wsession -> ssl ); 17126} 17127 17128void shutdown ( session_t session , bool graceful ) { 17129if (! session ) { return; } 17130auto wsession = static_cast < impl:: WolfSSLSession *> ( session ); 17131 17132if ( graceful ) { 17133int ret ; 17134int attempts = 0 ; 17135while (( ret = wolfSSL_shutdown ( wsession -> ssl )) != SSL_SUCCESS && 17136attempts < 3 ) { 17137int ssl_error = wolfSSL_get_error ( wsession -> ssl , ret ); 17138if ( ssl_error != SSL_ERROR_WANT_READ && 17139ssl_error != SSL_ERROR_WANT_WRITE ) { 17140break; 17141} 17142attempts ++ ; 17143} 17144} else { 17145wolfSSL_shutdown ( wsession -> ssl ); 17146} 17147} 17148 17149bool is_peer_closed ( session_t session , socket_t sock ) { 17150if (! session || sock == INVALID_SOCKET ) { return true; } 17151auto wsession = static_cast < impl:: WolfSSLSession *> ( session ); 17152 17153// Check if there's already decrypted data available 17154if ( wolfSSL_pending ( wsession -> ssl ) > 0 ) { return false; } 17155 17156// Set socket to non-blocking to avoid blocking on read 17157detail:: set_nonblocking ( sock , true); 17158auto cleanup = 17159detail:: scope_exit ([ & ]() { detail:: set_nonblocking ( sock , false); }); 17160 17161// Peek 1 byte to check connection status without consuming data 17162unsigned char buf ; 17163int ret = wolfSSL_peek ( wsession -> ssl , & buf , 1 ); 17164 17165// If we got data or WANT_READ (would block), connection is alive 17166if ( ret > 0 ) { return false; } 17167 17168int ssl_error = wolfSSL_get_error ( wsession -> ssl , ret ); 17169if ( ssl_error == SSL_ERROR_WANT_READ ) { return false; } 17170 17171return ssl_error == SSL_ERROR_ZERO_RETURN || ssl_error == SSL_ERROR_SYSCALL || 17172ret == 0 ; 17173} 17174 17175cert_t get_peer_cert ( const_session_t session ) { 17176if (! session ) { return nullptr ; } 17177auto wsession = 17178static_cast < impl:: WolfSSLSession *> ( const_cast < void *> ( session )); 17179 17180WOLFSSL_X509 * cert = wolfSSL_get_peer_certificate ( wsession -> ssl ); 17181return static_cast < cert_t > ( cert ); 17182} 17183 17184void free_cert ( cert_t cert ) { 17185if ( cert ) { wolfSSL_X509_free ( static_cast < WOLFSSL_X509 *> ( cert )); } 17186} 17187 17188bool verify_hostname ( cert_t cert , const char * hostname ) { 17189if (! cert || ! hostname ) { return false; } 17190auto x509 = static_cast < WOLFSSL_X509 *> ( cert ); 17191std:: string host_str ( hostname ); 17192 17193// Check if hostname is an IP address (IPv4 or IPv6) 17194unsigned char ip_bytes [ 16 ]; 17195auto ip_len = impl:: parse_ip_address ( host_str , ip_bytes ); 17196auto is_ip = ip_len > 0 ; 17197 17198// Check Subject Alternative Names 17199auto * san_names = static_cast < WOLF_STACK_OF ( WOLFSSL_GENERAL_NAME ) * >( 17200wolfSSL_X509_get_ext_d2i (x509, NID_subject_alt_name, nullptr , nullptr )); 17201 17202if (san_names) { 17203int san_count = wolfSSL_sk_num (san_names); 17204for ( int i = 0 ; i < san_count; i ++ ) { 17205auto * names = 17206static_cast < WOLFSSL_GENERAL_NAME *> ( wolfSSL_sk_value (san_names, i)); 17207if (!names) continue ; 17208 17209if (!is_ip && names -> type == WOLFSSL_GEN_DNS ) { 17210// DNS name 17211unsigned char * dns_name = nullptr ; 17212int dns_len = wolfSSL_ASN1_STRING_to_UTF8 ( & dns_name, names -> d . dNSName ); 17213if (dns_name && dns_len > 0 ) { 17214std:: string san_name( reinterpret_cast < char *> (dns_name), 17215static_cast < size_t > (dns_len)); 17216XFREE (dns_name, nullptr , DYNAMIC_TYPE_OPENSSL ); 17217if (detail:: match_hostname (san_name, host_str)) { 17218wolfSSL_sk_free (san_names); 17219return true; 17220} 17221} 17222} else if (is_ip && names -> type == WOLFSSL_GEN_IPADD ) { 17223// IP address: only an iPAddress SAN of the same family (4 bytes for 17224// IPv4, 16 bytes for IPv6) may authenticate the host. 17225unsigned char * ip_data = wolfSSL_ASN1_STRING_data (names -> d . iPAddress ); 17226auto san_ip_len = wolfSSL_ASN1_STRING_length (names -> d . iPAddress ); 17227if (ip_data && san_ip_len == static_cast < int > (ip_len) && 17228memcmp (ip_data, ip_bytes, ip_len) == 0 ) { 17229wolfSSL_sk_free (san_names); 17230return true; 17231} 17232} 17233} 17234wolfSSL_sk_free (san_names); 17235} 17236 17237// Fallback: Check Common Name (CN) in subject. Skipped for IP-literal hosts: 17238// an IP identity is only valid via an iPAddress SAN, never the CN (RFC 9110; 17239// the OpenSSL backend's X509_check_ip behaves the same way). 17240auto subject = is_ip ? nullptr : wolfSSL_X509_get_subject_name (x509); 17241if (subject) { 17242char cn[ 256 ] = {}; 17243int cn_len = wolfSSL_X509_NAME_get_text_by_NID (subject, NID_commonName, cn, 17244sizeof (cn)); 17245if (cn_len > 0 ) { 17246std:: string cn_str(cn, static_cast < size_t > (cn_len)); 17247if (detail:: match_hostname (cn_str, host_str)) { return true; } 17248} 17249} 17250 17251return false; 17252} 17253 17254uint64_t hostname_mismatch_code () { 17255return static_cast < uint64_t > ( DOMAIN_NAME_MISMATCH ); 17256} 17257 17258long get_verify_result ( const_session_t session) { 17259if (!session) { return -1 ; } 17260auto wsession = 17261static_cast < impl:: WolfSSLSession *> ( const_cast < void *> (session)); 17262long result = wolfSSL_get_verify_result (wsession -> ssl ); 17263return result; 17264} 17265 17266std:: string get_cert_subject_cn ( cert_t cert) { 17267if (!cert) return "" ; 17268auto x509 = static_cast < WOLFSSL_X509 *> (cert); 17269 17270WOLFSSL_X509_NAME * subject = wolfSSL_X509_get_subject_name (x509); 17271if (!subject) return "" ; 17272 17273char cn[ 256 ] = {}; 17274int cn_len = wolfSSL_X509_NAME_get_text_by_NID (subject, NID_commonName, cn, 17275sizeof (cn)); 17276if (cn_len <= 0 ) return "" ; 17277return std:: string (cn, static_cast < size_t > (cn_len)); 17278} 17279 17280std:: string get_cert_issuer_name ( cert_t cert) { 17281if (!cert) return "" ; 17282auto x509 = static_cast < WOLFSSL_X509 *> (cert); 17283 17284WOLFSSL_X509_NAME * issuer = wolfSSL_X509_get_issuer_name (x509); 17285if (!issuer) return "" ; 17286 17287char * name_str = wolfSSL_X509_NAME_oneline (issuer, nullptr , 0 ); 17288if (!name_str) return "" ; 17289 17290std:: string result ( name_str ); 17291XFREE (name_str, nullptr , DYNAMIC_TYPE_OPENSSL ); 17292return result; 17293} 17294 17295bool get_cert_sans ( cert_t cert, std:: vector < SanEntry > & sans) { 17296sans. clear (); 17297if (!cert) return false; 17298auto x509 = static_cast < WOLFSSL_X509 *> (cert); 17299 17300auto * san_names = static_cast < WOLF_STACK_OF ( WOLFSSL_GENERAL_NAME ) * >( 17301wolfSSL_X509_get_ext_d2i (x509, NID_subject_alt_name, nullptr , nullptr )); 17302if (!san_names) return true; // No SANs is not an error 17303 17304int count = wolfSSL_sk_num (san_names); 17305for ( int i = 0 ; i < count; i ++ ) { 17306auto * name = 17307static_cast < WOLFSSL_GENERAL_NAME *> ( wolfSSL_sk_value (san_names, i)); 17308if (!name) continue ; 17309 17310SanEntry entry; 17311switch (name -> type ) { 17312case WOLFSSL_GEN_DNS : { 17313entry. type = SanType :: DNS ; 17314unsigned char * dns_name = nullptr ; 17315int dns_len = wolfSSL_ASN1_STRING_to_UTF8 ( & dns_name, name -> d . dNSName ); 17316if (dns_name && dns_len > 0 ) { 17317entry. value = std:: string ( reinterpret_cast < char *> (dns_name), 17318static_cast < size_t > (dns_len)); 17319XFREE (dns_name, nullptr , DYNAMIC_TYPE_OPENSSL ); 17320} 17321break ; 17322} 17323case WOLFSSL_GEN_IPADD : { 17324entry. type = SanType :: IP ; 17325unsigned char * ip_data = wolfSSL_ASN1_STRING_data (name -> d . iPAddress ); 17326int ip_len = wolfSSL_ASN1_STRING_length (name -> d . iPAddress ); 17327if (ip_data && ip_len == 4 ) { 17328char buf[ 16 ]; 17329snprintf (buf, sizeof (buf), "%d.%d.%d.%d" , ip_data[ 0 ], ip_data[ 1 ], 17330ip_data[ 2 ], ip_data[ 3 ]); 17331entry. value = buf; 17332} else if (ip_data && ip_len == 16 ) { 17333char buf[ 64 ]; 17334snprintf (buf, sizeof (buf), 17335"%02x%02x:%02x%02x:%02x%02x:%02x%02x:" 17336"%02x%02x:%02x%02x:%02x%02x:%02x%02x" , 17337ip_data[ 0 ], ip_data[ 1 ], ip_data[ 2 ], ip_data[ 3 ], ip_data[ 4 ], 17338ip_data[ 5 ], ip_data[ 6 ], ip_data[ 7 ], ip_data[ 8 ], ip_data[ 9 ], 17339ip_data[ 10 ], ip_data[ 11 ], ip_data[ 12 ], ip_data[ 13 ], 17340ip_data[ 14 ], ip_data[ 15 ]); 17341entry. value = buf; 17342} 17343break ; 17344} 17345case WOLFSSL_GEN_EMAIL : 17346entry. type = SanType :: ; 17347{ 17348unsigned char * = nullptr ; 17349int email_len = wolfSSL_ASN1_STRING_to_UTF8 ( & email, name -> d . rfc822Name ); 17350if && email_len > 0 ) { 17351entry. value = std:: string ( reinterpret_cast < char *> (email), 17352static_cast < size_t > (email_len)); 17353XFREE (email, nullptr , DYNAMIC_TYPE_OPENSSL ); 17354} 17355} 17356break ; 17357case WOLFSSL_GEN_URI : 17358entry. type = SanType :: URI ; 17359{ 17360unsigned char * uri = nullptr ; 17361int uri_len = wolfSSL_ASN1_STRING_to_UTF8 ( 17362& uri, name -> d . uniformResourceIdentifier ); 17363if (uri && uri_len > 0 ) { 17364entry. value = std:: string ( reinterpret_cast < char *> (uri), 17365static_cast < size_t > (uri_len)); 17366XFREE (uri, nullptr , DYNAMIC_TYPE_OPENSSL ); 17367} 17368} 17369break ; 17370default : entry. type = SanType :: OTHER ; break ; 17371} 17372 17373if (!entry. value . empty ()) { sans. push_back (std:: move (entry)); } 17374} 17375wolfSSL_sk_free (san_names); 17376return true; 17377} 17378 17379bool get_cert_validity ( cert_t cert, time_t & not_before, 17380time_t & not_after) { 17381if (!cert) return false; 17382auto x509 = static_cast < WOLFSSL_X509 *> (cert); 17383 17384const WOLFSSL_ASN1_TIME * nb = wolfSSL_X509_get_notBefore (x509); 17385const WOLFSSL_ASN1_TIME * na = wolfSSL_X509_get_notAfter (x509); 17386 17387if (!nb || !na) return false; 17388 17389// wolfSSL_ASN1_TIME_to_tm is available 17390struct tm tm_nb = {}, tm_na = {}; 17391if ( wolfSSL_ASN1_TIME_to_tm (nb, & tm_nb) != WOLFSSL_SUCCESS ) return false; 17392if ( wolfSSL_ASN1_TIME_to_tm (na, & tm_na) != WOLFSSL_SUCCESS ) return false; 17393 17394#ifdef _WIN32 17395not_before = _mkgmtime ( & tm_nb); 17396not_after = _mkgmtime ( & tm_na); 17397#else 17398not_before = timegm ( & tm_nb); 17399not_after = timegm ( & tm_na); 17400#endif 17401return true; 17402} 17403 17404std:: string get_cert_serial ( cert_t cert) { 17405if (!cert) return "" ; 17406auto x509 = static_cast < WOLFSSL_X509 *> (cert); 17407 17408WOLFSSL_ASN1_INTEGER * serial_asn1 = wolfSSL_X509_get_serialNumber (x509); 17409if (!serial_asn1) return "" ; 17410 17411// Get the serial number data 17412int len = serial_asn1 -> length ; 17413unsigned char * data = serial_asn1 -> data ; 17414if (!data || len <= 0 ) return "" ; 17415 17416std:: string result; 17417result. reserve ( static_cast < size_t > (len) * 2 ); 17418for ( int i = 0 ; i < len; i ++ ) { 17419char hex[ 3 ]; 17420snprintf (hex, sizeof (hex), "%02X" , data[i]); 17421result += hex; 17422} 17423return result; 17424} 17425 17426bool get_cert_der ( cert_t cert, std:: vector < unsigned char > & der) { 17427if (!cert) return false; 17428auto x509 = static_cast < WOLFSSL_X509 *> (cert); 17429 17430int der_len = 0 ; 17431const unsigned char * der_data = wolfSSL_X509_get_der (x509, & der_len); 17432if (!der_data || der_len <= 0 ) return false; 17433 17434der. assign (der_data, der_data + der_len); 17435return true; 17436} 17437 17438const char * get_sni ( const_session_t session) { 17439if (!session) return nullptr ; 17440auto wsession = static_cast < const impl:: WolfSSLSession *> (session); 17441 17442// For server: return SNI received from client during handshake 17443if (!wsession -> sni_hostname . empty ()) { 17444return wsession -> sni_hostname . c_str (); 17445} 17446 17447// For client: return the hostname set via set_sni 17448if (!wsession -> hostname . empty ()) { return wsession -> hostname . c_str (); } 17449 17450return nullptr ; 17451} 17452 17453uint64_t peek_error () { 17454return static_cast < uint64_t > ( wolfSSL_ERR_peek_last_error ()); 17455} 17456 17457uint64_t get_error () { 17458uint64_t err = impl:: wolfssl_last_error (); 17459impl:: wolfssl_last_error () = 0 ; 17460return err; 17461} 17462 17463std:: string error_string ( uint64_t code) { 17464char buf[ 256 ]; 17465wolfSSL_ERR_error_string ( static_cast < unsigned long > (code), buf); 17466return std:: string (buf); 17467} 17468 17469ca_store_t create_ca_store ( const char * pem, size_t len) { 17470if (!pem || len == 0 ) { return nullptr ; } 17471// Validate by attempting to load into a temporary ctx 17472WOLFSSL_CTX * tmp_ctx = wolfSSL_CTX_new ( wolfTLSv1_2_client_method ()); 17473if (!tmp_ctx) { return nullptr ; } 17474int ret = wolfSSL_CTX_load_verify_buffer ( 17475tmp_ctx, reinterpret_cast < const unsigned char *> (pem), 17476static_cast < long > (len), SSL_FILETYPE_PEM ); 17477wolfSSL_CTX_free (tmp_ctx); 17478if (ret != SSL_SUCCESS ) { return nullptr ; } 17479return static_cast < ca_store_t > ( 17480new impl:: WolfSSLCAStore {std:: string (pem, len)}); 17481} 17482 17483void free_ca_store ( ca_store_t store) { 17484delete static_cast < impl:: WolfSSLCAStore *> (store); 17485} 17486 17487bool set_ca_store ( ctx_t ctx, ca_store_t store) { 17488if (!ctx || !store) { return false; } 17489auto * wctx = static_cast < impl:: WolfSSLContext *> (ctx); 17490auto * ca = static_cast < impl:: WolfSSLCAStore *> (store); 17491int ret = wolfSSL_CTX_load_verify_buffer ( 17492wctx -> ctx , reinterpret_cast < const unsigned char *> (ca -> pem_data . data ()), 17493static_cast < long > (ca -> pem_data . size ()), SSL_FILETYPE_PEM ); 17494if (ret == SSL_SUCCESS ) { wctx -> ca_pem_data_ += ca -> pem_data ; } 17495// This function takes ownership of the store; the PEM data was copied into 17496// the context, so release the source 17497free_ca_store (store); 17498return ret == SSL_SUCCESS ; 17499} 17500 17501size_t get_ca_certs ( ctx_t ctx, std:: vector < cert_t > & certs) { 17502certs. clear (); 17503if (!ctx) { return 0 ; } 17504auto * wctx = static_cast < impl:: WolfSSLContext *> (ctx); 17505if (wctx -> ca_pem_data_ . empty ()) { return 0 ; } 17506 17507const std:: string & pem = wctx -> ca_pem_data_ ; 17508const std:: string begin_marker = "-----BEGIN CERTIFICATE-----" ; 17509const std:: string end_marker = "-----END CERTIFICATE-----" ; 17510size_t pos = 0 ; 17511while ((pos = pem. find (begin_marker, pos)) != std::string::npos) { 17512size_t end_pos = pem. find (end_marker, pos); 17513if (end_pos == std::string::npos) { break ; } 17514end_pos += end_marker. size (); 17515std:: string cert_pem = pem. substr (pos, end_pos - pos); 17516WOLFSSL_X509 * x509 = wolfSSL_X509_load_certificate_buffer ( 17517reinterpret_cast < const unsigned char *> (cert_pem. data ()), 17518static_cast < int > (cert_pem. size ()), WOLFSSL_FILETYPE_PEM ); 17519if (x509) { certs. push_back ( static_cast < cert_t > (x509)); } 17520pos = end_pos; 17521} 17522return certs. size (); 17523} 17524 17525std:: vector < std:: string > get_ca_names ( ctx_t ctx) { 17526std:: vector < std:: string > names; 17527if (!ctx) { return names; } 17528auto * wctx = static_cast < impl:: WolfSSLContext *> (ctx); 17529if (wctx -> ca_pem_data_ . empty ()) { return names; } 17530 17531const std:: string & pem = wctx -> ca_pem_data_ ; 17532const std:: string begin_marker = "-----BEGIN CERTIFICATE-----" ; 17533const std:: string end_marker = "-----END CERTIFICATE-----" ; 17534size_t pos = 0 ; 17535while ((pos = pem. find (begin_marker, pos)) != std::string::npos) { 17536size_t end_pos = pem. find (end_marker, pos); 17537if (end_pos == std::string::npos) { break ; } 17538end_pos += end_marker. size (); 17539std:: string cert_pem = pem. substr (pos, end_pos - pos); 17540WOLFSSL_X509 * x509 = wolfSSL_X509_load_certificate_buffer ( 17541reinterpret_cast < const unsigned char *> (cert_pem. data ()), 17542static_cast < int > (cert_pem. size ()), WOLFSSL_FILETYPE_PEM ); 17543if (x509) { 17544WOLFSSL_X509_NAME * subject = wolfSSL_X509_get_subject_name (x509); 17545if (subject) { 17546char * name_str = wolfSSL_X509_NAME_oneline (subject, nullptr , 0 ); 17547if (name_str) { 17548names. push_back (name_str); 17549XFREE (name_str, nullptr , DYNAMIC_TYPE_OPENSSL ); 17550} 17551} 17552wolfSSL_X509_free (x509); 17553} 17554pos = end_pos; 17555} 17556return names; 17557} 17558 17559bool update_server_cert ( ctx_t ctx, const char * cert_pem, 17560const char * key_pem, const char * password) { 17561if (!ctx || !cert_pem || !key_pem) { return false; } 17562auto * wctx = static_cast < impl:: WolfSSLContext *> (ctx); 17563 17564// Load new certificate 17565int ret = wolfSSL_CTX_use_certificate_buffer ( 17566wctx -> ctx , reinterpret_cast < const unsigned char *> (cert_pem), 17567static_cast < long > ( strlen (cert_pem)), SSL_FILETYPE_PEM ); 17568if (ret != SSL_SUCCESS ) { 17569impl:: wolfssl_last_error () = 17570static_cast < uint64_t > ( wolfSSL_ERR_peek_last_error ()); 17571return false; 17572} 17573 17574// Set password if provided 17575if (password) { impl:: set_wolfssl_password_cb (wctx -> ctx , password); } 17576 17577// Load new private key 17578ret = wolfSSL_CTX_use_PrivateKey_buffer ( 17579wctx -> ctx , reinterpret_cast < const unsigned char *> (key_pem), 17580static_cast < long > ( strlen (key_pem)), SSL_FILETYPE_PEM ); 17581if (ret != SSL_SUCCESS ) { 17582impl:: wolfssl_last_error () = 17583static_cast < uint64_t > ( wolfSSL_ERR_peek_last_error ()); 17584return false; 17585} 17586 17587return true; 17588} 17589 17590bool update_server_client_ca ( ctx_t ctx, const char * ca_pem) { 17591if (!ctx || !ca_pem) { return false; } 17592auto * wctx = static_cast < impl:: WolfSSLContext *> (ctx); 17593 17594int ret = wolfSSL_CTX_load_verify_buffer ( 17595wctx -> ctx , reinterpret_cast < const unsigned char *> (ca_pem), 17596static_cast < long > ( strlen (ca_pem)), SSL_FILETYPE_PEM ); 17597if (ret != SSL_SUCCESS ) { 17598impl:: wolfssl_last_error () = 17599static_cast < uint64_t > ( wolfSSL_ERR_peek_last_error ()); 17600return false; 17601} 17602return true; 17603} 17604 17605bool set_verify_callback ( ctx_t ctx, VerifyCallback callback) { 17606if (!ctx) { return false; } 17607auto * wctx = static_cast < impl:: WolfSSLContext *> (ctx); 17608 17609impl:: get_verify_callback () = std:: move (callback); 17610wctx -> has_verify_callback = static_cast < bool > (impl:: get_verify_callback ()); 17611 17612if (wctx -> has_verify_callback ) { 17613wolfSSL_CTX_set_verify (wctx -> ctx , SSL_VERIFY_PEER , 17614impl::wolfssl_verify_callback); 17615} else { 17616wolfSSL_CTX_set_verify ( 17617wctx -> ctx , 17618wctx -> verify_client 17619? ( SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT ) 17620: SSL_VERIFY_NONE , 17621nullptr ); 17622} 17623return true; 17624} 17625 17626long get_verify_error ( const_session_t session) { 17627if (!session) { return -1 ; } 17628auto * wsession = 17629static_cast < impl:: WolfSSLSession *> ( const_cast < void *> (session)); 17630return wolfSSL_get_verify_result (wsession -> ssl ); 17631} 17632 17633std:: string verify_error_string ( long error_code) { 17634if (error_code == 0 ) { return "" ; } 17635const char * str = 17636wolfSSL_X509_verify_cert_error_string ( static_cast < int > (error_code)); 17637return str ? std:: string (str) : std:: string (); 17638} 17639 17640} // namespace tls 17641 17642#endif // CPPHTTPLIB_WOLFSSL_SUPPORT 17643 17644// WebSocket implementation 17645namespace ws { 17646 17647bool WebSocket :: send_frame ( Opcode op, const char * data, size_t len, 17648bool fin) { 17649std:: lock_guard < std:: mutex > lock ( write_mutex_ ); 17650if (closed_) { return false; } 17651return detail:: write_websocket_frame (strm_, op, data, len, fin, !is_server_); 17652} 17653 17654ReadResult WebSocket :: read (std:: string & msg) { 17655std:: unique_lock < std:: mutex > read_lock ( read_mutex_ ); 17656while (!closed_) { 17657Opcode opcode; 17658std:: string payload; 17659bool fin; 17660 17661impl:: FrameRead r = 17662impl:: read_websocket_frame (strm_, opcode, payload, fin, is_server_, 17663CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH ); 17664// A timeout landed on a frame boundary: the connection is untouched and 17665// still usable, so hand control back without closing it. That is only 17666// useful to a caller who asked for the timeout; the compile-time default 17667// is a backstop against a peer gone quiet, and elapsing it closes the 17668// connection so a plain `while (ws.read(msg))` loop ends. 17669if (r == impl:: FrameRead ::Timeout && read_timeout_set_) { return Timeout; } 17670if (r != impl:: FrameRead ::Ok) { 17671closed_ = true; 17672return Fail; 17673} 17674 17675switch (opcode) { 17676case Opcode ::Ping: { 17677std:: lock_guard < std:: mutex > lock ( write_mutex_ ); 17678detail:: write_websocket_frame (strm_, Opcode ::Pong, payload. data (), 17679payload. size (), true, !is_server_); 17680continue ; 17681} 17682case Opcode ::Pong: { 17683std:: lock_guard < std:: mutex > lock ( ping_mutex_ ); 17684unacked_pings_ = 0 ; 17685continue ; 17686} 17687case Opcode ::Close: { 17688if (!closed_. exchange (true)) { 17689// Echo close frame back 17690std:: lock_guard < std:: mutex > lock ( write_mutex_ ); 17691detail:: write_websocket_frame (strm_, Opcode ::Close, payload. data (), 17692payload. size (), true, !is_server_); 17693} 17694return Fail; 17695} 17696case Opcode ::Text: 17697case Opcode ::Binary: { 17698auto result = opcode == Opcode ::Text ? Text : Binary; 17699msg = std:: move (payload); 17700 17701// Handle fragmentation 17702if (!fin) { 17703while (true) { 17704Opcode cont_opcode; 17705std:: string cont_payload; 17706bool cont_fin; 17707// A timeout is not reportable here: half of a fragmented message is 17708// already in `msg` and read() has no way to resume it, so it is a 17709// failure like any other. Timeouts are only ever seen on a message 17710// boundary. 17711if (impl:: read_websocket_frame ( 17712strm_, cont_opcode, cont_payload, cont_fin, is_server_, 17713CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH ) != 17714impl:: FrameRead ::Ok) { 17715closed_ = true; 17716return Fail; 17717} 17718if (cont_opcode == Opcode ::Ping) { 17719std:: lock_guard < std:: mutex > lock ( write_mutex_ ); 17720detail:: write_websocket_frame ( 17721strm_, Opcode ::Pong, cont_payload. data (), cont_payload. size (), 17722true, !is_server_); 17723continue ; 17724} 17725if (cont_opcode == Opcode ::Pong) { 17726std:: lock_guard < std:: mutex > lock ( ping_mutex_ ); 17727unacked_pings_ = 0 ; 17728continue ; 17729} 17730if (cont_opcode == Opcode ::Close) { 17731if (!closed_. exchange (true)) { 17732std:: lock_guard < std:: mutex > lock ( write_mutex_ ); 17733detail:: write_websocket_frame ( 17734strm_, Opcode ::Close, cont_payload. data (), 17735cont_payload. size (), true, !is_server_); 17736} 17737return Fail; 17738} 17739// RFC 6455: continuation frames must use opcode 0x0 17740if (cont_opcode != Opcode ::Continuation) { 17741closed_ = true; 17742return Fail; 17743} 17744msg += cont_payload; 17745if (msg. size () > CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH ) { 17746closed_ = true; 17747return Fail; 17748} 17749if (cont_fin) { break ; } 17750} 17751} 17752// RFC 6455 Section 5.6: text frames must contain valid UTF-8 17753if (result == Text && !impl:: is_valid_utf8 (msg)) { 17754// close() takes the read lock to wait for the peer's Close reply, so 17755// it must not run while this thread still holds it. 17756read_lock. unlock (); 17757close ( CloseStatus ::InvalidPayload, "invalid UTF-8" ); 17758return Fail; 17759} 17760return result; 17761} 17762default : closed_ = true; return Fail; 17763} 17764} 17765return Fail; 17766} 17767 17768bool WebSocket :: send ( const std:: string & data) { 17769return send_frame ( Opcode ::Text, data. data (), data. size ()); 17770} 17771 17772bool WebSocket :: send ( const char * data, size_t len) { 17773return send_frame ( Opcode ::Binary, data, len); 17774} 17775 17776void WebSocket :: close ( CloseStatus status, const std:: string & reason) { 17777if (closed_. exchange (true)) { return ; } 17778ping_cv_. notify_all (); 17779std:: string payload; 17780auto code = static_cast < uint16_t > (status); 17781payload. push_back ( static_cast < char > ((code >> 8 ) & 0xFF )); 17782payload. push_back ( static_cast < char > (code & 0xFF )); 17783// RFC 6455 Section 5.5: control frame payload must not exceed 125 bytes 17784// Close frame has 2-byte status code, so reason is limited to 123 bytes 17785payload += reason. substr ( 0 , 123 ); 17786{ 17787std:: lock_guard < std:: mutex > lock ( write_mutex_ ); 17788detail:: write_websocket_frame (strm_, Opcode ::Close, payload. data (), 17789payload. size (), true, !is_server_); 17790} 17791 17792// RFC 6455 Section 7.1.1: after sending a Close frame, wait for the peer's 17793// Close response before closing the TCP connection. 17794// 17795// Wait only when no other thread is parsing frames. When one is, it is the 17796// thread positioned to see the peer's reply, and reading here would take 17797// bytes out of the message it is assembling. Bailing out also leaves the 17798// stream, including its read timeout, entirely to that thread. 17799std:: unique_lock < std:: mutex > read_lock ( read_mutex_ , std:: try_to_lock ); 17800if (!read_lock. owns_lock ()) { return ; } 17801 17802// Use a short timeout to avoid hanging if the peer doesn't respond. 17803strm_. set_read_timeout ( CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND , 0 ); 17804 17805Opcode op; 17806std:: string resp; 17807bool fin; 17808while (impl:: read_websocket_frame (strm_, op, resp, fin, is_server_, 125 ) == 17809impl:: FrameRead ::Ok) { 17810if (op == Opcode ::Close) { break ; } 17811} 17812} 17813 17814WebSocket ::~WebSocket() { 17815{ 17816std:: lock_guard < std:: mutex > lock ( ping_mutex_ ); 17817closed_ = true; 17818} 17819ping_cv_. notify_all (); 17820if (ping_thread_. joinable ()) { ping_thread_. join (); } 17821} 17822 17823void WebSocket :: start_heartbeat () { 17824if (ping_interval_sec_ == 0 ) { return ; } 17825ping_thread_ = std:: thread ([this]() { 17826std:: unique_lock < std:: mutex > lock ( ping_mutex_ ); 17827while (!closed_) { 17828ping_cv_. wait_for (lock, std::chrono::seconds(ping_interval_sec_)); 17829if (closed_) { break ; } 17830// If the peer has failed to respond to the previous pings, give up. 17831// RFC 6455 does not define a pong-timeout mechanism; this is an 17832// opt-in liveness check controlled by max_missed_pongs_. 17833if (max_missed_pongs_ > 0 && unacked_pings_ >= max_missed_pongs_) { 17834lock. unlock (); 17835close ( CloseStatus ::GoingAway, "pong timeout" ); 17836return ; 17837} 17838lock. unlock (); 17839if (! send_frame ( Opcode ::Ping, nullptr , 0 )) { 17840lock. lock (); 17841closed_ = true; 17842break ; 17843} 17844lock. lock (); 17845unacked_pings_ ++ ; 17846} 17847}); 17848} 17849 17850const Request & WebSocket :: request () const { return req_; } 17851 17852bool WebSocket :: is_open () const { return !closed_; } 17853 17854void WebSocket :: set_read_timeout ( time_t sec, time_t usec) { 17855// 0 waits forever here, as it does for SO_RCVTIMEO. The stream waits with 17856// poll(), where 0 would instead mean "return immediately", so hand it the 17857// negative poll uses for an unbounded wait. 17858if (sec == 0 && usec == 0 ) { sec = -1 ; } 17859strm_. set_read_timeout (sec, usec); 17860read_timeout_set_ = true; 17861} 17862 17863// WebSocketClient implementation 17864WebSocketClient :: WebSocketClient ( 17865const std:: string & scheme_host_port_path, const Headers & headers) 17866: headers_ (headers) { 17867detail:: UrlComponents uc; 17868if (detail:: parse_url (scheme_host_port_path, uc) && !uc. scheme . empty () && 17869!uc. host . empty () && !uc. path . empty ()) { 17870auto & scheme = uc. scheme ; 17871 17872#ifdef CPPHTTPLIB_SSL_ENABLED 17873if (scheme != "ws" && scheme != "wss" ) { 17874#else 17875if (scheme != "ws" ) { 17876#endif 17877#ifndef CPPHTTPLIB_NO_EXCEPTIONS 17878std:: string msg = "'" + scheme + "' scheme is not supported." ; 17879throw std:: invalid_argument (msg); 17880#endif 17881return ; 17882} 17883 17884auto is_ssl = scheme == "wss" ; 17885 17886host_ = std:: move (uc. host ); 17887 17888port_ = is_ssl ? 443 : 80 ; 17889if (!uc. port . empty () && !detail:: parse_port (uc. port , port_)) { return ; } 17890 17891path_ = std:: move (uc. path ); 17892if (!uc. query . empty ()) { path_ += uc. query ; } 17893 17894#ifdef CPPHTTPLIB_SSL_ENABLED 17895is_ssl_ = is_ssl; 17896if (is_ssl_) { 17897// The context lives as long as the client so that CA configuration 17898// survives reconnects; sessions are created per connection. 17899tls_ctx_ = tls:: create_client_context (); 17900if (!tls_ctx_) { return ; } 17901} 17902#else 17903if (is_ssl) { return ; } 17904#endif 17905 17906is_valid_ = true; 17907} 17908} 17909 17910#ifdef CPPHTTPLIB_SSL_ENABLED 17911WebSocketClient :: WebSocketClient ( 17912const std:: string & scheme_host_port_path, const PemMemory & pem, 17913const Headers & headers) 17914: WebSocketClient (scheme_host_port_path, headers) { 17915// For ws:// URLs the client certificate is silently ignored, consistent 17916// with the TLS-only setters such as set_ca_cert_path(). 17917if (is_valid_ && is_ssl_ && pem. cert_pem && pem. key_pem ) { 17918if (!tls:: set_client_cert_pem (tls_ctx_, pem. cert_pem , pem. key_pem , 17919pem. private_key_password )) { 17920tls:: free_context (tls_ctx_); 17921tls_ctx_ = nullptr ; 17922is_valid_ = false; 17923} 17924} 17925} 17926#endif 17927 17928WebSocketClient ::~WebSocketClient() { 17929shutdown_and_close (); 17930#ifdef CPPHTTPLIB_SSL_ENABLED 17931if (tls_ctx_) { 17932tls:: free_context (tls_ctx_); 17933tls_ctx_ = nullptr ; 17934} 17935#endif 17936} 17937 17938bool WebSocketClient :: is_valid () const { return is_valid_; } 17939 17940void WebSocketClient :: shutdown_and_close () { 17941// Send the close frame while the TLS session is still alive: ws_ holds an 17942// SSLSocketStream that keeps a raw pointer to tls_session_, so the session 17943// must outlive ws_->close() and ws_.reset() to avoid a use-after-free. 17944if (ws_ && ws_ -> is_open ()) { ws_ -> close (); } 17945ws_. reset (); 17946#ifdef CPPHTTPLIB_SSL_ENABLED 17947if (is_ssl_) { 17948if (tls_session_) { 17949tls:: shutdown (tls_session_, true); 17950tls:: free_session (tls_session_); 17951tls_session_ = nullptr ; 17952} 17953} 17954#endif 17955if (sock_ != INVALID_SOCKET ) { 17956detail:: shutdown_socket (sock_); 17957detail:: close_socket (sock_); 17958sock_ = INVALID_SOCKET ; 17959} 17960} 17961 17962bool WebSocketClient :: create_stream (std:: unique_ptr < Stream > & strm, 17963Error & error, int & ssl_error, 17964uint64_t & ssl_backend_error) { 17965// A read timeout of 0 means "wait forever", the way SO_RCVTIMEO reads it. 17966// The streams wait with poll(), where 0 instead means "return immediately", 17967// so they are given the negative poll uses for an unbounded wait. 17968auto unbounded = read_timeout_sec_ == 0 && read_timeout_usec_ == 0 ; 17969time_t strm_read_sec = unbounded ? -1 : read_timeout_sec_; 17970time_t strm_read_usec = unbounded ? 0 : read_timeout_usec_; 17971// The handshake belongs to establishing the connection, so an unset read 17972// timeout leaves it bounded by the connection timeout instead of forever. 17973time_t hs_sec = unbounded ? connection_timeout_sec_ : read_timeout_sec_; 17974time_t hs_usec = unbounded ? connection_timeout_usec_ : read_timeout_usec_; 17975#ifdef CPPHTTPLIB_SSL_ENABLED 17976if (is_ssl_) { 17977// A plain flag rather than SSLClient::load_certs()'s call_once: connect() 17978// is not safe to call concurrently on one client to begin with, since 17979// nothing else here is guarded either. 17980if (server_certificate_verification_ && !certs_loaded_) { 17981uint64_t backend_error = 0 ; 17982detail:: load_client_ca_config (tls_ctx_, ca_cert_file_path_, 17983ca_cert_dir_path_, custom_ca_loaded_, 17984system_ca_mode_, backend_error); 17985certs_loaded_ = true; 17986} 17987 17988detail:: ClientTlsSessionOptions options; 17989options. server_hostname_verification = server_hostname_verification_; 17990 17991detail:: ClientTlsSessionError tls_error; 17992if (!detail:: setup_client_tls_session (host_, tls_ctx_, tls_session_, sock_, 17993server_certificate_verification_, 17994hs_sec, hs_usec, & tls_error, 17995options)) { 17996error = tls_error. error ; 17997ssl_error = tls_error. ssl_error ; 17998ssl_backend_error = tls_error. backend_error ; 17999return false; 18000} 18001 18002strm = std:: unique_ptr < Stream > ( new detail:: WebSocketSSLStream ( 18003sock_, tls_session_, strm_read_sec, strm_read_usec, write_timeout_sec_, 18004write_timeout_usec_)); 18005return true; 18006} 18007#else 18008( void )error; 18009( void )ssl_error; 18010( void )ssl_backend_error; 18011( void )hs_sec; 18012( void )hs_usec; 18013#endif 18014strm = std:: unique_ptr < Stream > ( 18015new detail:: SocketStream (sock_, strm_read_sec, strm_read_usec, 18016write_timeout_sec_, write_timeout_usec_)); 18017return true; 18018} 18019 18020void WebSocketClient :: prepare_default_headers ( Request & req) { 18021#ifdef CPPHTTPLIB_SSL_ENABLED 18022auto is_ssl = is_ssl_; 18023#else 18024auto is_ssl = false; 18025#endif 18026 18027if (!req. has_header ( "Host" )) { 18028req. headers . emplace ( "Host" , detail:: make_default_host_header_value ( 18029host_, port_, is_ssl, address_family_)); 18030} 18031 18032detail:: add_default_user_agent_header (req); 18033} 18034 18035Result WebSocketClient :: connect () { 18036if (!is_valid_) { return Result { Error ::Connection, -1 , Headers {}}; } 18037shutdown_and_close (); 18038 18039// Check is custom IP or hostname specified for host_ 18040std:: string connect_host; 18041std:: string ip; 18042detail:: apply_addr_map (addr_map_, host_, connect_host, ip); 18043 18044auto error = Error ::Success; 18045sock_ = detail:: create_client_socket ( 18046connect_host, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_, 18047socket_options_, connection_timeout_sec_, connection_timeout_usec_, 18048read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, 18049write_timeout_usec_, interface_, error); 18050 18051if (sock_ == INVALID_SOCKET ) { 18052if (error == Error ::Success) { error = Error ::Connection; } 18053return Result {error, -1 , Headers {}}; 18054} 18055 18056std:: unique_ptr < Stream > strm; 18057auto stream_error = Error ::SSLConnection; 18058int ssl_error = 0 ; 18059uint64_t ssl_backend_error = 0 ; 18060if (! create_stream (strm, stream_error, ssl_error, ssl_backend_error)) { 18061shutdown_and_close (); 18062#ifdef CPPHTTPLIB_SSL_ENABLED 18063return Result {stream_error, -1 , Headers {}, ssl_error, ssl_backend_error}; 18064#else 18065return Result {stream_error, -1 , Headers {}}; 18066#endif 18067} 18068 18069Request req; 18070req. method = "GET" ; 18071req. path = path_; 18072req. headers = headers_; 18073prepare_default_headers (req); 18074 18075detail:: WebSocketUpgradeResponse upgrade; 18076if (!detail:: perform_websocket_handshake ( * strm, req, upgrade)) { 18077shutdown_and_close (); 18078return Result {upgrade. error , upgrade. status , std:: move (upgrade. headers )}; 18079} 18080subprotocol_ = std:: move (upgrade. selected_subprotocol ); 18081 18082ws_ = std:: unique_ptr < WebSocket > ( new WebSocket (std:: move (strm), req, false, 18083websocket_ping_interval_sec_, 18084websocket_max_missed_pongs_)); 18085// The stream was created with the timeout already; tell the WebSocket 18086// whether it came from the caller, so read() knows to report it as Timeout. 18087ws_ -> read_timeout_set_ = read_timeout_set_; 18088return Result { Error ::Success, upgrade. status , std:: move (upgrade. headers )}; 18089} 18090 18091ReadResult WebSocketClient :: read (std:: string & msg) { 18092if (!ws_) { return Fail; } 18093return ws_ -> read (msg); 18094} 18095 18096bool WebSocketClient :: send ( const std:: string & data) { 18097if (!ws_) { return false; } 18098return ws_ -> send (data); 18099} 18100 18101bool WebSocketClient :: send ( const char * data, size_t len) { 18102if (!ws_) { return false; } 18103return ws_ -> send (data, len); 18104} 18105 18106void WebSocketClient :: close ( CloseStatus status, 18107const std:: string & reason) { 18108if (ws_) { ws_ -> close (status, reason); } 18109} 18110 18111bool WebSocketClient :: is_open () const { return ws_ && ws_ -> is_open (); } 18112 18113const std:: string & WebSocketClient :: subprotocol () const { 18114return subprotocol_; 18115} 18116 18117void WebSocketClient :: set_read_timeout ( time_t sec, time_t usec) { 18118read_timeout_sec_ = sec; 18119read_timeout_usec_ = usec; 18120read_timeout_set_ = true; 18121// The members above only seed the next connect(); read() consults the 18122// stream, so an already-open connection has to be told directly. 18123if (ws_) { ws_ -> set_read_timeout (sec, usec); } 18124} 18125 18126void WebSocketClient :: set_write_timeout ( time_t sec, time_t usec) { 18127write_timeout_sec_ = sec; 18128write_timeout_usec_ = usec; 18129} 18130 18131void WebSocketClient :: set_websocket_ping_interval ( time_t sec) { 18132websocket_ping_interval_sec_ = sec; 18133} 18134 18135void WebSocketClient :: set_websocket_max_missed_pongs ( int count) { 18136websocket_max_missed_pongs_ = count; 18137} 18138 18139void WebSocketClient :: set_tcp_nodelay ( bool on) { tcp_nodelay_ = on; } 18140 18141void WebSocketClient :: set_address_family ( int family) { 18142address_family_ = family; 18143} 18144 18145void WebSocketClient :: set_ipv6_v6only ( bool on) { ipv6_v6only_ = on; } 18146 18147void WebSocketClient :: set_socket_options ( SocketOptions socket_options) { 18148socket_options_ = std:: move (socket_options); 18149} 18150 18151void WebSocketClient :: set_connection_timeout ( time_t sec, time_t usec) { 18152connection_timeout_sec_ = sec; 18153connection_timeout_usec_ = usec; 18154} 18155 18156void WebSocketClient :: set_interface ( const std:: string & intf) { 18157interface_ = intf; 18158} 18159 18160void WebSocketClient :: set_hostname_addr_map ( 18161std:: map < std:: string , std:: string > addr_map) { 18162addr_map_ = std:: move (addr_map); 18163} 18164 18165#ifdef CPPHTTPLIB_SSL_ENABLED 18166 18167void 18168WebSocketClient :: set_ca_cert_path ( const std:: string & ca_cert_file_path, 18169const std:: string & ca_cert_dir_path) { 18170ca_cert_file_path_ = ca_cert_file_path; 18171ca_cert_dir_path_ = ca_cert_dir_path; 18172} 18173 18174void WebSocketClient :: set_ca_cert_store (tls:: ca_store_t store) { 18175if (store && tls_ctx_) { 18176// set_ca_store takes ownership of store 18177tls:: set_ca_store (tls_ctx_, store); 18178custom_ca_loaded_ = true; 18179} else if (store) { 18180tls:: free_ca_store (store); 18181} 18182} 18183 18184void WebSocketClient :: load_ca_cert_store ( const char * ca_cert, 18185std:: size_t size) { 18186if (tls_ctx_ && ca_cert && size > 0 ) { 18187tls:: load_ca_pem (tls_ctx_, ca_cert, size); 18188custom_ca_loaded_ = true; 18189} 18190} 18191 18192void 18193WebSocketClient :: enable_server_certificate_verification ( bool enabled) { 18194server_certificate_verification_ = enabled; 18195} 18196 18197void WebSocketClient :: enable_server_hostname_verification ( bool enabled) { 18198server_hostname_verification_ = enabled; 18199} 18200 18201void WebSocketClient :: enable_system_ca ( bool enabled) { 18202system_ca_mode_ = enabled ? SystemCAMode ::Enabled : SystemCAMode ::Disabled; 18203} 18204 18205#endif // CPPHTTPLIB_SSL_ENABLED 18206 18207} // namespace ws 18208 18209} // namespace httplib