From 5746386aff1e8fd8b310efd130660e094f352bde Mon Sep 17 00:00:00 2001 From: yhirose Date: Sat, 31 Jan 2026 16:40:41 -0500 Subject: [PATCH] Fix problems with CPPHTTPLIB_NO_EXCEPTIONS --- httplib.h | 117 +++++++++++++++++++++++++++++++++++++++------------ test/test.cc | 14 ++---- 2 files changed, 93 insertions(+), 38 deletions(-) diff --git a/httplib.h b/httplib.h index 5f3094a..24f9e47 100644 --- a/httplib.h +++ b/httplib.h @@ -305,6 +305,7 @@ using socket_t = int; #include #include #include +#include #include #include #include @@ -322,6 +323,7 @@ using socket_t = int; #include #include #include +#include #include #include #include @@ -550,6 +552,69 @@ private: bool execute_on_destruction; }; +// Simple from_chars implementation for integer and double types (C++17 +// substitute) +template struct from_chars_result { + const char *ptr; + std::errc ec; +}; + +template +inline from_chars_result from_chars(const char *first, const char *last, + T &value, int base = 10) { + value = 0; + const char *p = first; + bool negative = false; + + if (p != last && *p == '-') { + negative = true; + ++p; + } + if (p == last) { return {first, std::errc::invalid_argument}; } + + T result = 0; + for (; p != last; ++p) { + char c = *p; + int digit = -1; + if ('0' <= c && c <= '9') { + digit = c - '0'; + } else if ('a' <= c && c <= 'z') { + digit = c - 'a' + 10; + } else if ('A' <= c && c <= 'Z') { + digit = c - 'A' + 10; + } else { + break; + } + + if (digit < 0 || digit >= base) { break; } + if (result > ((std::numeric_limits::max)() - digit) / base) { + return {p, std::errc::result_out_of_range}; + } + result = result * base + digit; + } + + if (p == first || (negative && p == first + 1)) { + return {first, std::errc::invalid_argument}; + } + + value = negative ? -result : result; + return {p, std::errc{}}; +} + +// from_chars for double (simple wrapper for strtod) +inline from_chars_result from_chars(const char *first, const char *last, + double &value) { + std::string s(first, last); + char *endptr = nullptr; + errno = 0; + value = std::strtod(s.c_str(), &endptr); + if (endptr == s.c_str()) { return {first, std::errc::invalid_argument}; } + if (errno == ERANGE) { + return {first + (endptr - s.c_str()), std::errc::result_out_of_range}; + } + return {first + (endptr - s.c_str()), std::errc{}}; +} + } // namespace detail enum SSLVerifierResponse { @@ -3363,18 +3428,12 @@ private: msg.id = value; } else if (field == "retry") { // Parse retry interval in milliseconds -#ifdef CPPHTTPLIB_NO_EXCEPTIONS { - std::istringstream iss(value); - iss >> retry_ms; + int v = 0; + auto res = + detail::from_chars(value.data(), value.data() + value.size(), v); + if (res.ec == std::errc{}) { retry_ms = v; } } -#else - try { - retry_ms = std::stoi(value); - } catch (...) { - // Invalid retry value, ignore - } -#endif } // Unknown fields are ignored per SSE spec @@ -6529,10 +6588,20 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) try { return; } - const auto first = - static_cast(lhs.empty() ? -1 : std::stoll(lhs)); - const auto last = - static_cast(rhs.empty() ? -1 : std::stoll(rhs)); + ssize_t first = -1; + if (!lhs.empty()) { + ssize_t v; + auto res = detail::from_chars(lhs.data(), lhs.data() + lhs.size(), v); + if (res.ec == std::errc{}) { first = v; } + } + + ssize_t last = -1; + if (!rhs.empty()) { + ssize_t v; + auto res = detail::from_chars(rhs.data(), rhs.data() + rhs.size(), v); + if (res.ec == std::errc{}) { last = v; } + } + if ((first == -1 && last == -1) || (first != -1 && last != -1 && first > last)) { all_valid_ranges = false; @@ -6607,25 +6676,17 @@ inline bool parse_accept_header(const std::string &s, return; } -#ifdef CPPHTTPLIB_NO_EXCEPTIONS { - std::istringstream iss(quality_str); - iss >> accept_entry.quality; - - // Check if conversion was successful and entire string was consumed - if (iss.fail() || !iss.eof()) { + double v = 0.0; + auto res = detail::from_chars( + quality_str.data(), quality_str.data() + quality_str.size(), v); + if (res.ec == std::errc{}) { + accept_entry.quality = v; + } else { has_invalid_entry = true; return; } } -#else - try { - accept_entry.quality = std::stod(quality_str); - } catch (...) { - has_invalid_entry = true; - return; - } -#endif // Check if quality is in valid range [0.0, 1.0] if (accept_entry.quality < 0.0 || accept_entry.quality > 1.0) { has_invalid_entry = true; diff --git a/test/test.cc b/test/test.cc index 02c38c6..7e8a89f 100644 --- a/test/test.cc +++ b/test/test.cc @@ -14159,18 +14159,12 @@ protected: msg.id = value; } else if (field == "retry") { // Parse retry interval in milliseconds -#ifdef CPPHTTPLIB_NO_EXCEPTIONS { - std::istringstream iss(value); - iss >> retry_ms; + int v = 0; + auto res = + detail::from_chars(value.data(), value.data() + value.size(), v); + if (res.ec == std::errc{}) { retry_ms = v; } } -#else - try { - retry_ms = std::stoi(value); - } catch (...) { - // Invalid retry value, ignore - } -#endif } // Unknown fields are ignored per SSE spec