@@ -117,6 +117,8 @@ time_t parse_http_date(const std::string &date_str) {
117117
118118#ifdef _WIN32
119119 return _mkgmtime (&tm_buf);
120+ #elif defined _AIX
121+ return mktime (&tm_buf);
120122#else
121123 return timegm (&tm_buf);
122124#endif
@@ -1376,7 +1378,7 @@ int getaddrinfo_with_timeout(const char *node, const char *service,
13761378
13771379 // Allocate on the heap, so the resolver thread can keep using the data.
13781380 auto state = std::make_shared<GetAddrInfoState>();
1379- state->node = node;
1381+ if (node) { state->node = node; }
13801382 state->service = service;
13811383 state->hints = *hints;
13821384
@@ -2896,10 +2898,20 @@ bool parse_range_header(const std::string &s, Ranges &ranges) try {
28962898 return ;
28972899 }
28982900
2899- const auto first =
2900- static_cast <ssize_t >(lhs.empty () ? -1 : std::stoll (lhs));
2901- const auto last =
2902- static_cast <ssize_t >(rhs.empty () ? -1 : std::stoll (rhs));
2901+ ssize_t first = -1 ;
2902+ if (!lhs.empty ()) {
2903+ ssize_t v;
2904+ auto res = detail::from_chars (lhs.data (), lhs.data () + lhs.size (), v);
2905+ if (res.ec == std::errc{}) { first = v; }
2906+ }
2907+
2908+ ssize_t last = -1 ;
2909+ if (!rhs.empty ()) {
2910+ ssize_t v;
2911+ auto res = detail::from_chars (rhs.data (), rhs.data () + rhs.size (), v);
2912+ if (res.ec == std::errc{}) { last = v; }
2913+ }
2914+
29032915 if ((first == -1 && last == -1 ) ||
29042916 (first != -1 && last != -1 && first > last)) {
29052917 all_valid_ranges = false ;
@@ -2974,25 +2986,17 @@ bool parse_accept_header(const std::string &s,
29742986 return ;
29752987 }
29762988
2977- #ifdef CPPHTTPLIB_NO_EXCEPTIONS
29782989 {
2979- std::istringstream iss (quality_str);
2980- iss >> accept_entry.quality ;
2981-
2982- // Check if conversion was successful and entire string was consumed
2983- if (iss.fail () || !iss.eof ()) {
2990+ double v = 0.0 ;
2991+ auto res = detail::from_chars (
2992+ quality_str.data (), quality_str.data () + quality_str.size (), v);
2993+ if (res.ec == std::errc{}) {
2994+ accept_entry.quality = v;
2995+ } else {
29842996 has_invalid_entry = true ;
29852997 return ;
29862998 }
29872999 }
2988- #else
2989- try {
2990- accept_entry.quality = std::stod (quality_str);
2991- } catch (...) {
2992- has_invalid_entry = true ;
2993- return ;
2994- }
2995- #endif
29963000 // Check if quality is in valid range [0.0, 1.0]
29973001 if (accept_entry.quality < 0.0 || accept_entry.quality > 1.0 ) {
29983002 has_invalid_entry = true ;
@@ -5570,13 +5574,26 @@ bool Server::read_content(Stream &strm, Request &req, Response &res) {
55705574 strm, req, res,
55715575 // Regular
55725576 [&](const char *buf, size_t n) {
5577+ // Prevent arithmetic overflow when checking sizes.
5578+ // Avoid computing (req.body.size() + n) directly because
5579+ // adding two unsigned `size_t` values can wrap around and
5580+ // produce a small result instead of indicating overflow.
5581+ // Instead, check using subtraction: ensure `n` does not
5582+ // exceed the remaining capacity `max_size() - size()`.
5583+ if (req.body .size () >= req.body .max_size () ||
5584+ n > req.body .max_size () - req.body .size ()) {
5585+ return false ;
5586+ }
5587+
55735588 // Limit decompressed body size to payload_max_length_ to protect
55745589 // against "zip bomb" attacks where a small compressed payload
55755590 // decompresses to a massive size.
5576- if (req.body .size () + n > payload_max_length_ ||
5577- req.body .size () + n > req.body .max_size ()) {
5591+ if (payload_max_length_ > 0 &&
5592+ (req.body .size () >= payload_max_length_ ||
5593+ n > payload_max_length_ - req.body .size ())) {
55785594 return false ;
55795595 }
5596+
55805597 req.body .append (buf, n);
55815598 return true ;
55825599 },
@@ -5666,22 +5683,29 @@ bool Server::read_content_core(
56665683 // oversized request and fail early (causing connection close). For SSL
56675684 // builds we cannot reliably peek the decrypted application bytes, so keep
56685685 // the original behaviour.
5669- #if !defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(_WIN32)
5686+ #if !defined(CPPHTTPLIB_OPENSSL_SUPPORT)
56705687 if (!req.has_header (" Content-Length" ) &&
56715688 !detail::is_chunked_transfer_encoding (req.headers )) {
5672- socket_t s = strm.socket ();
5673- if (s != INVALID_SOCKET) {
5674- // Peek up to payload_max_length_ + 1 bytes. If more than
5675- // payload_max_length_ bytes are pending, reject the request.
5676- size_t to_peek =
5677- (payload_max_length_ > 0 )
5678- ? (std::min)(payload_max_length_ + 1 , static_cast <size_t >(4096 ))
5679- : 1 ;
5680- std::vector<char > peekbuf (to_peek);
5681- ssize_t n = ::recv (s, peekbuf.data (), to_peek, MSG_PEEK);
5682- if (n > 0 && static_cast <size_t >(n) > payload_max_length_) {
5683- // Indicate failure so connection will be closed.
5684- return false ;
5689+ // Only peek if payload_max_length is set to a finite value
5690+ if (payload_max_length_ > 0 &&
5691+ payload_max_length_ < (std::numeric_limits<size_t >::max)()) {
5692+ socket_t s = strm.socket ();
5693+ if (s != INVALID_SOCKET) {
5694+ // Peek to check if there is any pending data
5695+ char peekbuf[1 ];
5696+ ssize_t n = ::recv (s, peekbuf, 1 , MSG_PEEK);
5697+ if (n > 0 ) {
5698+ // There is data, so read it with payload limit enforcement
5699+ auto result = detail::read_content_without_length (
5700+ strm, payload_max_length_, out);
5701+ if (result == detail::ReadContentResult::PayloadTooLarge) {
5702+ res.status = StatusCode::PayloadTooLarge_413;
5703+ return false ;
5704+ } else if (result != detail::ReadContentResult::Success) {
5705+ return false ;
5706+ }
5707+ return true ;
5708+ }
56855709 }
56865710 }
56875711 return true ;
@@ -6656,7 +6680,8 @@ void ClientImpl::close_socket(Socket &socket) {
66566680}
66576681
66586682bool ClientImpl::read_response_line (Stream &strm, const Request &req,
6659- Response &res) const {
6683+ Response &res,
6684+ bool skip_100_continue) const {
66606685 std::array<char , 2048 > buf{};
66616686
66626687 detail::stream_line_reader line_reader (strm, buf.data (), buf.size ());
@@ -6677,8 +6702,8 @@ bool ClientImpl::read_response_line(Stream &strm, const Request &req,
66776702 res.status = std::stoi (std::string (m[2 ]));
66786703 res.reason = std::string (m[3 ]);
66796704
6680- // Ignore '100 Continue'
6681- while (res.status == StatusCode::Continue_100) {
6705+ // Ignore '100 Continue' (only when not using Expect: 100-continue explicitly)
6706+ while (skip_100_continue && res.status == StatusCode::Continue_100) {
66826707 if (!line_reader.getline ()) { return false ; } // CRLF
66836708 if (!line_reader.getline ()) { return false ; } // next response line
66846709
@@ -7463,7 +7488,8 @@ bool ClientImpl::write_content_with_provider(Stream &strm,
74637488}
74647489
74657490bool ClientImpl::write_request (Stream &strm, Request &req,
7466- bool close_connection, Error &error) {
7491+ bool close_connection, Error &error,
7492+ bool skip_body) {
74677493 // Prepare additional headers
74687494 if (close_connection) {
74697495 if (!req.has_header (" Connection" )) {
@@ -7582,7 +7608,59 @@ bool ClientImpl::write_request(Stream &strm, Request &req,
75827608 }
75837609 }
75847610
7611+ // After sending request line and headers, wait briefly for an early server
7612+ // response (e.g. 4xx) and avoid sending a potentially large request body
7613+ // unnecessarily. This workaround is only enabled on Windows because Unix
7614+ // platforms surface write errors (EPIPE) earlier; on Windows kernel send
7615+ // buffering can accept large writes even when the peer already responded.
7616+ // Check the stream first (which covers SSL via `is_readable()`), then
7617+ // fall back to select on the socket. Only perform the wait for very large
7618+ // request bodies to avoid interfering with normal small requests and
7619+ // reduce side-effects. Poll briefly (up to 50ms as default) for an early
7620+ // response. Skip this check when using Expect: 100-continue, as the protocol
7621+ // handles early responses properly.
7622+ #if defined(_WIN32)
7623+ if (!skip_body &&
7624+ req.body .size () > CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD &&
7625+ req.path .size () > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) {
7626+ auto start = std::chrono::high_resolution_clock::now ();
7627+
7628+ for (;;) {
7629+ // Prefer socket-level readiness to avoid SSL_pending() false-positives
7630+ // from SSL internals. If the underlying socket is readable, assume an
7631+ // early response may be present.
7632+ auto sock = strm.socket ();
7633+ if (sock != INVALID_SOCKET && detail::select_read (sock, 0 , 0 ) > 0 ) {
7634+ return false ;
7635+ }
7636+
7637+ // Fallback to stream-level check for non-socket streams or when the
7638+ // socket isn't reporting readable. Avoid using `is_readable()` for
7639+ // SSL, since `SSL_pending()` may report buffered records that do not
7640+ // indicate a complete application-level response yet.
7641+ if (!is_ssl () && strm.is_readable ()) { return false ; }
7642+
7643+ auto now = std::chrono::high_resolution_clock::now ();
7644+ auto elapsed =
7645+ std::chrono::duration_cast<std::chrono::milliseconds>(now - start)
7646+ .count ();
7647+ if (elapsed >= CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND) {
7648+ break ;
7649+ }
7650+
7651+ std::this_thread::sleep_for (std::chrono::milliseconds (1 ));
7652+ }
7653+ }
7654+ #endif
7655+
75857656 // Body
7657+ if (skip_body) { return true ; }
7658+
7659+ return write_request_body (strm, req, error);
7660+ }
7661+
7662+ bool ClientImpl::write_request_body (Stream &strm, Request &req,
7663+ Error &error) {
75867664 if (req.body .empty ()) {
75877665 return write_content_with_provider (strm, req, error);
75887666 }
@@ -7758,8 +7836,20 @@ void ClientImpl::output_error_log(const Error &err,
77587836bool ClientImpl::process_request (Stream &strm, Request &req,
77597837 Response &res, bool close_connection,
77607838 Error &error) {
7761- // Send request
7762- if (!write_request (strm, req, close_connection, error)) { return false ; }
7839+ // Auto-add Expect: 100-continue for large bodies
7840+ if (CPPHTTPLIB_EXPECT_100_THRESHOLD > 0 && !req.has_header (" Expect" )) {
7841+ auto body_size = req.body .empty () ? req.content_length_ : req.body .size ();
7842+ if (body_size >= CPPHTTPLIB_EXPECT_100_THRESHOLD) {
7843+ req.set_header (" Expect" , " 100-continue" );
7844+ }
7845+ }
7846+
7847+ // Check for Expect: 100-continue
7848+ auto expect_100_continue = req.get_header_value (" Expect" ) == " 100-continue" ;
7849+
7850+ // Send request (skip body if using Expect: 100-continue)
7851+ auto write_request_success =
7852+ write_request (strm, req, close_connection, error, expect_100_continue);
77637853
77647854#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
77657855 if (is_ssl ()) {
@@ -7774,14 +7864,48 @@ bool ClientImpl::process_request(Stream &strm, Request &req,
77747864 }
77757865#endif
77767866
7867+ // Handle Expect: 100-continue with timeout
7868+ if (expect_100_continue && CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND > 0 ) {
7869+ time_t sec = CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND / 1000 ;
7870+ time_t usec = (CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND % 1000 ) * 1000 ;
7871+ auto ret = detail::select_read (strm.socket (), sec, usec);
7872+ if (ret <= 0 ) {
7873+ // Timeout or error: send body anyway (server didn't respond in time)
7874+ if (!write_request_body (strm, req, error)) { return false ; }
7875+ expect_100_continue = false ; // Switch to normal response handling
7876+ }
7877+ }
7878+
77777879 // Receive response and headers
7778- if (!read_response_line (strm, req, res) ||
7880+ // When using Expect: 100-continue, don't auto-skip `100 Continue` response
7881+ if (!read_response_line (strm, req, res, !expect_100_continue) ||
77797882 !detail::read_headers (strm, res.headers )) {
7780- error = Error::Read;
7883+ if (write_request_success) { error = Error::Read; }
77817884 output_error_log (error, &req);
77827885 return false ;
77837886 }
77847887
7888+ if (!write_request_success) { return false ; }
7889+
7890+ // Handle Expect: 100-continue response
7891+ if (expect_100_continue) {
7892+ if (res.status == StatusCode::Continue_100) {
7893+ // Server accepted, send the body
7894+ if (!write_request_body (strm, req, error)) { return false ; }
7895+
7896+ // Read the actual response
7897+ res.headers .clear ();
7898+ res.body .clear ();
7899+ if (!read_response_line (strm, req, res) ||
7900+ !detail::read_headers (strm, res.headers )) {
7901+ error = Error::Read;
7902+ output_error_log (error, &req);
7903+ return false ;
7904+ }
7905+ }
7906+ // If not 100 Continue, server returned an error; proceed with that response
7907+ }
7908+
77857909 // Body
77867910 if ((res.status != StatusCode::NoContent_204) && req.method != " HEAD" &&
77877911 req.method != " CONNECT" ) {
@@ -9543,7 +9667,7 @@ bool SSLClient::load_certs() {
95439667 last_openssl_error_ = ERR_get_error ();
95449668 ret = false ;
95459669 }
9546- } else {
9670+ } else if (!ca_cert_store_) {
95479671 auto loaded = false ;
95489672#ifdef _WIN32
95499673 loaded =
@@ -9790,7 +9914,11 @@ bool SSLClient::verify_host_with_common_name(X509 *server_cert) const {
97909914
97919915bool SSLClient::check_host_name (const char *pattern,
97929916 size_t pattern_len) const {
9793- if (host_.size () == pattern_len && host_ == pattern) { return true ; }
9917+ // Exact match (case-insensitive)
9918+ if (host_.size () == pattern_len &&
9919+ detail::case_ignore::equal (host_, std::string (pattern, pattern_len))) {
9920+ return true ;
9921+ }
97949922
97959923 // Wildcard match
97969924 // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484
@@ -9805,9 +9933,23 @@ bool SSLClient::check_host_name(const char *pattern,
98059933 auto itr = pattern_components.begin ();
98069934 for (const auto &h : host_components_) {
98079935 auto &p = *itr;
9808- if (p != h && p != " *" ) {
9809- auto partial_match = (p.size () > 0 && p[p.size () - 1 ] == ' *' &&
9810- !p.compare (0 , p.size () - 1 , h));
9936+ if (!httplib::detail::case_ignore::equal (p, h) && p != " *" ) {
9937+ bool partial_match = false ;
9938+ if (!p.empty () && p[p.size () - 1 ] == ' *' ) {
9939+ const auto prefix_length = p.size () - 1 ;
9940+ if (prefix_length == 0 ) {
9941+ partial_match = true ;
9942+ } else if (h.size () >= prefix_length) {
9943+ partial_match =
9944+ std::equal (p.begin (),
9945+ p.begin () + static_cast <std::string::difference_type>(
9946+ prefix_length),
9947+ h.begin (), [](const char ca, const char cb) {
9948+ return httplib::detail::case_ignore::to_lower (ca) ==
9949+ httplib::detail::case_ignore::to_lower (cb);
9950+ });
9951+ }
9952+ }
98119953 if (!partial_match) { return false ; }
98129954 }
98139955 ++itr;
0 commit comments