mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-13 10:24:47 +00:00
Ignore quoted-strings when parsing an Accept header
parse_accept_header() split on every ',' and then on every ';', with no idea that a parameter value can be a quoted-string (RFC 9110 §5.6.6). A value such as text/html;profile="a,b" was cut in the middle, and the request was answered with 400. The parser never looks at a parameter value, so remove each quoted-string (§5.6.4) before parsing rather than teach every split about quotes. A header without a '"' takes the same path as before. A quoted-string is removed wherever it appears, so a malformed value can now be read without it instead of being rejected, and an unterminated quote hides the rest of the value.
This commit is contained in:
@@ -9247,8 +9247,30 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
|
|||||||
} catch (...) { return false; }
|
} catch (...) { return false; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Removes each quoted-string, honoring a quoted-pair (RFC 9110 Section 5.6.4)
|
||||||
|
inline std::string remove_quoted_strings(const std::string &s) {
|
||||||
|
std::string out;
|
||||||
|
auto in_quotes = false;
|
||||||
|
for (size_t i = 0; i < s.size(); i++) {
|
||||||
|
if (s[i] == '"') {
|
||||||
|
in_quotes = !in_quotes;
|
||||||
|
} else if (!in_quotes) {
|
||||||
|
out += s[i];
|
||||||
|
} else if (s[i] == '\\') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
inline bool parse_accept_header(const std::string &s,
|
inline bool parse_accept_header(const std::string &s,
|
||||||
std::vector<std::string> &content_types) {
|
std::vector<std::string> &content_types) {
|
||||||
|
// Parameter values are not used here, so a quoted-string is dropped rather
|
||||||
|
// than parsed: a ',' or ';' inside one must not be read as a delimiter.
|
||||||
|
if (s.find('"') != std::string::npos) {
|
||||||
|
return parse_accept_header(remove_quoted_strings(s), content_types);
|
||||||
|
}
|
||||||
|
|
||||||
content_types.clear();
|
content_types.clear();
|
||||||
|
|
||||||
// Empty string is considered valid (no preference)
|
// Empty string is considered valid (no preference)
|
||||||
|
|||||||
@@ -1119,6 +1119,39 @@ TEST(ParseAcceptHeaderTest, EmptyListElementsAreIgnored) {
|
|||||||
EXPECT_FALSE(detail::parse_accept_header(",invalidtype,", result));
|
EXPECT_FALSE(detail::parse_accept_header(",invalidtype,", result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RFC 9110 Section 5.6.6: a parameter value may be a quoted-string, and a ','
|
||||||
|
// or ';' inside one does not end the list element or the parameter. Section
|
||||||
|
// 5.6.4: nor does a '"' that a backslash escapes end the quoted-string.
|
||||||
|
TEST(ParseAcceptHeaderTest, DelimitersInQuotedParameterValues) {
|
||||||
|
struct {
|
||||||
|
const char *value;
|
||||||
|
std::vector<std::string> expected;
|
||||||
|
} cases[] = {
|
||||||
|
{"text/html;profile=\"a,b\",application/json",
|
||||||
|
{"text/html", "application/json"}},
|
||||||
|
// A "q=" inside a quoted value is not the weight
|
||||||
|
{"text/html;profile=\"a;q=0.1\",application/json;q=0.5",
|
||||||
|
{"text/html", "application/json"}},
|
||||||
|
// The weight after a quoted value still applies
|
||||||
|
{"text/html;profile=\"a,b;q=0.1\";q=0.8,application/json;q=0.9",
|
||||||
|
{"application/json", "text/html"}},
|
||||||
|
{"text/html;profile=\"a\\\",b\";q=0.8,application/json;q=0.9",
|
||||||
|
{"application/json", "text/html"}},
|
||||||
|
{"text/html;profile=\"a\\\"b\",application/json",
|
||||||
|
{"text/html", "application/json"}},
|
||||||
|
// An escaped backslash does not escape the '"' after it
|
||||||
|
{"text/html;profile=\"a\\\\\",application/json",
|
||||||
|
{"text/html", "application/json"}},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &c : cases) {
|
||||||
|
std::vector<std::string> result;
|
||||||
|
EXPECT_TRUE(detail::parse_accept_header(c.value, result))
|
||||||
|
<< "value: " << c.value;
|
||||||
|
EXPECT_EQ(c.expected, result) << "value: " << c.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST(ParseAcceptHeaderTest, ContentTypesPopulatedAndInvalidHeaderHandling) {
|
TEST(ParseAcceptHeaderTest, ContentTypesPopulatedAndInvalidHeaderHandling) {
|
||||||
Server svr;
|
Server svr;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user