@@ -638,6 +638,75 @@ TEST(TrimTests, TrimStringTests) {
638638 EXPECT_TRUE(detail::trim_copy("").empty());
639639}
640640
641+ TEST(FromCharsTest, Double) {
642+ // detail::from_chars(double) recognizes exactly the HTTP quality-value
643+ // grammar (RFC 9110 12.4.2): a non-negative decimal "1*DIGIT [ '.' *DIGIT ]"
644+ // with no sign, exponent, or "inf"/"nan", parsed locale-independently.
645+ auto parse = [](const std::string &s, double &v) {
646+ return detail::from_chars(s.data(), s.data() + s.size(), v);
647+ };
648+
649+ double v = -1.0;
650+
651+ // Representative quality values.
652+ EXPECT_EQ(parse("0", v).ec, std::errc{});
653+ EXPECT_DOUBLE_EQ(v, 0.0);
654+ EXPECT_EQ(parse("1", v).ec, std::errc{});
655+ EXPECT_DOUBLE_EQ(v, 1.0);
656+ EXPECT_EQ(parse("0.8", v).ec, std::errc{});
657+ EXPECT_DOUBLE_EQ(v, 0.8);
658+ EXPECT_EQ(parse("0.001", v).ec, std::errc{});
659+ EXPECT_DOUBLE_EQ(v, 0.001);
660+ EXPECT_EQ(parse("1.000", v).ec, std::errc{});
661+ EXPECT_DOUBLE_EQ(v, 1.0);
662+
663+ // A missing integer or fractional part is tolerated.
664+ EXPECT_EQ(parse(".5", v).ec, std::errc{});
665+ EXPECT_DOUBLE_EQ(v, 0.5);
666+ EXPECT_EQ(parse("5.", v).ec, std::errc{});
667+ EXPECT_DOUBLE_EQ(v, 5.0);
668+
669+ // Values outside [0, 1] still parse; the caller range-checks them.
670+ EXPECT_EQ(parse("123.456", v).ec, std::errc{});
671+ EXPECT_DOUBLE_EQ(v, 123.456);
672+
673+ // Stops at the first byte that is not part of the number and reports where.
674+ std::string trailing = "0.9, text/html";
675+ auto r = parse(trailing, v);
676+ EXPECT_EQ(r.ec, std::errc{});
677+ EXPECT_DOUBLE_EQ(v, 0.9);
678+ EXPECT_EQ(*r.ptr, ',');
679+
680+ // Sign and exponent are NOT part of the grammar: '+'/'-' are rejected
681+ // outright, and an 'e'/'E' simply ends the number.
682+ EXPECT_EQ(parse("-3.25", v).ec, std::errc::invalid_argument);
683+ EXPECT_EQ(parse("+2.5", v).ec, std::errc::invalid_argument);
684+ std::string exp_input = "1.5e3";
685+ r = parse(exp_input, v);
686+ EXPECT_EQ(r.ec, std::errc{});
687+ EXPECT_DOUBLE_EQ(v, 1.5);
688+ EXPECT_EQ(*r.ptr, 'e');
689+
690+ // Invalid inputs.
691+ EXPECT_EQ(parse("", v).ec, std::errc::invalid_argument);
692+ EXPECT_EQ(parse(".", v).ec, std::errc::invalid_argument);
693+ EXPECT_EQ(parse("abc", v).ec, std::errc::invalid_argument);
694+ EXPECT_EQ(parse("nan", v).ec, std::errc::invalid_argument);
695+ EXPECT_EQ(parse("inf", v).ec, std::errc::invalid_argument);
696+
697+ // Pathological but well-formed inputs must stay bounded (no overflow, no
698+ // out-of-bounds table access) and stay within [0, 1] for the caller.
699+ EXPECT_EQ(parse(std::string("0.") + std::string(500, '0'), v).ec,
700+ std::errc{});
701+ EXPECT_DOUBLE_EQ(v, 0.0);
702+ EXPECT_EQ(parse(std::string("0.") + std::string(500, '9'), v).ec,
703+ std::errc{});
704+ EXPECT_GE(v, 0.0);
705+ EXPECT_LE(v, 1.0);
706+ EXPECT_EQ(parse(std::string(500, '9'), v).ec, std::errc{});
707+ EXPECT_GT(v, 1.0); // huge integer: finite, > 1, so the caller rejects it
708+ }
709+
641710TEST(ParseAcceptHeaderTest, BasicAcceptParsing) {
642711 // Simple case without quality values
643712 std::vector<std::string> result1;
@@ -745,6 +814,41 @@ TEST(ParseAcceptHeaderTest, SpecialCases) {
745814 EXPECT_EQ(no_space_result[2], "text/plain");
746815}
747816
817+ TEST(ParseAcceptHeaderTest, QualityValueLocaleIndependence) {
818+ // Quality values always use '.' as the decimal separator, so parsing must
819+ // not depend on the process locale. An embedding application may have
820+ // switched to a locale that uses ',' via setlocale(LC_ALL, "").
821+ const char *cur = std::setlocale(LC_NUMERIC, nullptr);
822+ std::string saved = cur ? cur : "C";
823+
824+ const char *comma_locales[] = {"de_DE.UTF-8", "de_DE.utf8", "nl_NL.UTF-8",
825+ "fr_FR.UTF-8"};
826+ bool switched = false;
827+ for (const auto loc : comma_locales) {
828+ if (std::setlocale(LC_NUMERIC, loc) != nullptr &&
829+ std::localeconv()->decimal_point[0] == ',') {
830+ switched = true;
831+ break;
832+ }
833+ }
834+ if (!switched) {
835+ std::setlocale(LC_NUMERIC, saved.c_str());
836+ GTEST_SKIP() << "no comma-decimal locale available on this host";
837+ }
838+
839+ // The higher-weighted type appears later in the list, so a correct parse
840+ // must reorder it ahead of the earlier, lower-weighted one. A locale-
841+ // sensitive parse reads both weights as 0 and leaves the original order.
842+ std::vector<std::string> result;
843+ EXPECT_TRUE(detail::parse_accept_header(
844+ "application/json;q=0.1,text/html;q=0.9", result));
845+ ASSERT_EQ(result.size(), 2U);
846+ EXPECT_EQ(result[0], "text/html");
847+ EXPECT_EQ(result[1], "application/json");
848+
849+ std::setlocale(LC_NUMERIC, saved.c_str());
850+ }
851+
748852TEST(ParseAcceptHeaderTest, InvalidCases) {
749853 std::vector<std::string> result;
750854
0 commit comments