mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-02 13:32:54 +07:00
Better from_chars implementation (Fix #2475)
This commit is contained in:
+104
@@ -638,6 +638,75 @@ TEST(TrimTests, TrimStringTests) {
|
||||
EXPECT_TRUE(detail::trim_copy("").empty());
|
||||
}
|
||||
|
||||
TEST(FromCharsTest, Double) {
|
||||
// detail::from_chars(double) recognizes exactly the HTTP quality-value
|
||||
// grammar (RFC 9110 12.4.2): a non-negative decimal "1*DIGIT [ '.' *DIGIT ]"
|
||||
// with no sign, exponent, or "inf"/"nan", parsed locale-independently.
|
||||
auto parse = [](const std::string &s, double &v) {
|
||||
return detail::from_chars(s.data(), s.data() + s.size(), v);
|
||||
};
|
||||
|
||||
double v = -1.0;
|
||||
|
||||
// Representative quality values.
|
||||
EXPECT_EQ(parse("0", v).ec, std::errc{});
|
||||
EXPECT_DOUBLE_EQ(v, 0.0);
|
||||
EXPECT_EQ(parse("1", v).ec, std::errc{});
|
||||
EXPECT_DOUBLE_EQ(v, 1.0);
|
||||
EXPECT_EQ(parse("0.8", v).ec, std::errc{});
|
||||
EXPECT_DOUBLE_EQ(v, 0.8);
|
||||
EXPECT_EQ(parse("0.001", v).ec, std::errc{});
|
||||
EXPECT_DOUBLE_EQ(v, 0.001);
|
||||
EXPECT_EQ(parse("1.000", v).ec, std::errc{});
|
||||
EXPECT_DOUBLE_EQ(v, 1.0);
|
||||
|
||||
// A missing integer or fractional part is tolerated.
|
||||
EXPECT_EQ(parse(".5", v).ec, std::errc{});
|
||||
EXPECT_DOUBLE_EQ(v, 0.5);
|
||||
EXPECT_EQ(parse("5.", v).ec, std::errc{});
|
||||
EXPECT_DOUBLE_EQ(v, 5.0);
|
||||
|
||||
// Values outside [0, 1] still parse; the caller range-checks them.
|
||||
EXPECT_EQ(parse("123.456", v).ec, std::errc{});
|
||||
EXPECT_DOUBLE_EQ(v, 123.456);
|
||||
|
||||
// Stops at the first byte that is not part of the number and reports where.
|
||||
std::string trailing = "0.9, text/html";
|
||||
auto r = parse(trailing, v);
|
||||
EXPECT_EQ(r.ec, std::errc{});
|
||||
EXPECT_DOUBLE_EQ(v, 0.9);
|
||||
EXPECT_EQ(*r.ptr, ',');
|
||||
|
||||
// Sign and exponent are NOT part of the grammar: '+'/'-' are rejected
|
||||
// outright, and an 'e'/'E' simply ends the number.
|
||||
EXPECT_EQ(parse("-3.25", v).ec, std::errc::invalid_argument);
|
||||
EXPECT_EQ(parse("+2.5", v).ec, std::errc::invalid_argument);
|
||||
std::string exp_input = "1.5e3";
|
||||
r = parse(exp_input, v);
|
||||
EXPECT_EQ(r.ec, std::errc{});
|
||||
EXPECT_DOUBLE_EQ(v, 1.5);
|
||||
EXPECT_EQ(*r.ptr, 'e');
|
||||
|
||||
// Invalid inputs.
|
||||
EXPECT_EQ(parse("", v).ec, std::errc::invalid_argument);
|
||||
EXPECT_EQ(parse(".", v).ec, std::errc::invalid_argument);
|
||||
EXPECT_EQ(parse("abc", v).ec, std::errc::invalid_argument);
|
||||
EXPECT_EQ(parse("nan", v).ec, std::errc::invalid_argument);
|
||||
EXPECT_EQ(parse("inf", v).ec, std::errc::invalid_argument);
|
||||
|
||||
// Pathological but well-formed inputs must stay bounded (no overflow, no
|
||||
// out-of-bounds table access) and stay within [0, 1] for the caller.
|
||||
EXPECT_EQ(parse(std::string("0.") + std::string(500, '0'), v).ec,
|
||||
std::errc{});
|
||||
EXPECT_DOUBLE_EQ(v, 0.0);
|
||||
EXPECT_EQ(parse(std::string("0.") + std::string(500, '9'), v).ec,
|
||||
std::errc{});
|
||||
EXPECT_GE(v, 0.0);
|
||||
EXPECT_LE(v, 1.0);
|
||||
EXPECT_EQ(parse(std::string(500, '9'), v).ec, std::errc{});
|
||||
EXPECT_GT(v, 1.0); // huge integer: finite, > 1, so the caller rejects it
|
||||
}
|
||||
|
||||
TEST(ParseAcceptHeaderTest, BasicAcceptParsing) {
|
||||
// Simple case without quality values
|
||||
std::vector<std::string> result1;
|
||||
@@ -745,6 +814,41 @@ TEST(ParseAcceptHeaderTest, SpecialCases) {
|
||||
EXPECT_EQ(no_space_result[2], "text/plain");
|
||||
}
|
||||
|
||||
TEST(ParseAcceptHeaderTest, QualityValueLocaleIndependence) {
|
||||
// Quality values always use '.' as the decimal separator, so parsing must
|
||||
// not depend on the process locale. An embedding application may have
|
||||
// switched to a locale that uses ',' via setlocale(LC_ALL, "").
|
||||
const char *cur = std::setlocale(LC_NUMERIC, nullptr);
|
||||
std::string saved = cur ? cur : "C";
|
||||
|
||||
const char *comma_locales[] = {"de_DE.UTF-8", "de_DE.utf8", "nl_NL.UTF-8",
|
||||
"fr_FR.UTF-8"};
|
||||
bool switched = false;
|
||||
for (const auto loc : comma_locales) {
|
||||
if (std::setlocale(LC_NUMERIC, loc) != nullptr &&
|
||||
std::localeconv()->decimal_point[0] == ',') {
|
||||
switched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!switched) {
|
||||
std::setlocale(LC_NUMERIC, saved.c_str());
|
||||
GTEST_SKIP() << "no comma-decimal locale available on this host";
|
||||
}
|
||||
|
||||
// The higher-weighted type appears later in the list, so a correct parse
|
||||
// must reorder it ahead of the earlier, lower-weighted one. A locale-
|
||||
// sensitive parse reads both weights as 0 and leaves the original order.
|
||||
std::vector<std::string> result;
|
||||
EXPECT_TRUE(detail::parse_accept_header(
|
||||
"application/json;q=0.1,text/html;q=0.9", result));
|
||||
ASSERT_EQ(result.size(), 2U);
|
||||
EXPECT_EQ(result[0], "text/html");
|
||||
EXPECT_EQ(result[1], "application/json");
|
||||
|
||||
std::setlocale(LC_NUMERIC, saved.c_str());
|
||||
}
|
||||
|
||||
TEST(ParseAcceptHeaderTest, InvalidCases) {
|
||||
std::vector<std::string> result;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user