mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-19 11:19:36 +00:00
Honor quoted-pairs in parameter values
split_unquoted ended a quoted-string at an escaped quote, so name="a\"; filename=evil.php" came apart into a name and a filename that a parser in front following RFC 9110 would not see. Skip quoted-pairs when splitting, and unescape \" and \\ in Content-Disposition values, leaving other backslashes, such as those of a Windows path, as they are.
This commit is contained in:
1 parent
d5851d7e89
commit
64bebd3b2b
2 files changed
+29
-3
No files matched your search
@@ -6196,8 +6196,12 @@ inline void split_unquoted(const char *b, const char *e, char d, size_t m,
|
|||||||
size_t count = 1;
|
size_t count = 1;
|
||||||
auto in_quotes = false;
|
auto in_quotes = false;
|
||||||
|
|
||||||
while (e ? (b + i < e) : (b[i] != '\0')) {
|
auto has = [&](size_t j) { return e ? (b + j < e) : (b[j] != '\0'); };
|
||||||
if (b[i] == '"') {
|
while (has(i)) {
|
||||||
|
if (in_quotes && b[i] == '\\' && has(i + 1)) {
|
||||||
|
// A quoted-pair, whose second character does not end the string.
|
||||||
|
i++;
|
||||||
|
} else if (b[i] == '"') {
|
||||||
in_quotes = !in_quotes;
|
in_quotes = !in_quotes;
|
||||||
} else if (b[i] == d && !in_quotes && count < m) {
|
} else if (b[i] == d && !in_quotes && count < m) {
|
||||||
auto r = trim(b, e, beg, i);
|
auto r = trim(b, e, beg, i);
|
||||||
@@ -9241,6 +9245,22 @@ inline bool parse_multipart_boundary(const std::string &content_type,
|
|||||||
return !boundary.empty() && boundary.size() <= 70;
|
return !boundary.empty() && boundary.size() <= 70;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Takes the quotes off a parameter value. Only \" and \\ are quoted-pairs
|
||||||
|
// here, as browsers send any other backslash, such as one in a Windows path,
|
||||||
|
// as it is.
|
||||||
|
inline std::string unquote_disposition_param(const std::string &s) {
|
||||||
|
if (s.size() < 2 || s.front() != '"' || s.back() != '"') { return s; }
|
||||||
|
std::string out;
|
||||||
|
for (size_t i = 1; i + 1 < s.size(); i++) {
|
||||||
|
if (s[i] == '\\' && i + 2 < s.size() &&
|
||||||
|
(s[i + 1] == '"' || s[i + 1] == '\\')) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
out += s[i];
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
inline void parse_disposition_params(const std::string &s, Params ¶ms) {
|
inline void parse_disposition_params(const std::string &s, Params ¶ms) {
|
||||||
std::set<std::string> cache;
|
std::set<std::string> cache;
|
||||||
split_unquoted(s.data(), s.data() + s.size(), ';',
|
split_unquoted(s.data(), s.data() + s.size(), ';',
|
||||||
@@ -9255,7 +9275,7 @@ inline void parse_disposition_params(const std::string &s, Params ¶ms) {
|
|||||||
|
|
||||||
if (!key.empty()) {
|
if (!key.empty()) {
|
||||||
params.emplace(trim_double_quotes_copy(key),
|
params.emplace(trim_double_quotes_copy(key),
|
||||||
trim_double_quotes_copy(val));
|
unquote_disposition_param(val));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1857,6 +1857,12 @@ TEST(ParseDispositionParamsTest, QuotedValues) {
|
|||||||
// A parameter with no key is dropped rather than turned into one whose
|
// A parameter with no key is dropped rather than turned into one whose
|
||||||
// key is the value
|
// key is the value
|
||||||
{"=nokey; name=\"file5\"", {{"name", "file5"}}},
|
{"=nokey; name=\"file5\"", {{"name", "file5"}}},
|
||||||
|
// An escaped quote does not end the value
|
||||||
|
{"name=\"a\\\"; filename=evil.php\"",
|
||||||
|
{{"name", "a\"; filename=evil.php"}}},
|
||||||
|
// Other backslashes, as in a Windows path, are kept
|
||||||
|
{"name=\"f\"; filename=\"C:\\dir\\a.txt\"",
|
||||||
|
{{"name", "f"}, {"filename", "C:\\dir\\a.txt"}}},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto &c : cases) {
|
for (const auto &c : cases) {
|
||||||
|
|||||||
Reference in new issue
Block a user