Keep a %u escape that is not a character

decode_path_component dropped a %u escape of a surrogate, joining what
was on either side of it, so /ad%uD800min became /admin after a filter
had seen something else. Keep such an escape as it is.
This commit is contained in:
yhirose committed 2026-10-16 16:39:00 -04:00
1 parent 3420ba8fca
commit 455166314b
2 files changed
+12 -4

No files matched your search

+8 -2
View File
@@ -11612,8 +11612,14 @@ inline std::string decode_path_component(const std::string &component) {
// so to_utf8 writes at most 3 bytes. buff[4] is safe. // so to_utf8 writes at most 3 bytes. buff[4] is safe.
char buff[4]; char buff[4];
size_t len = detail::to_utf8(val, buff); size_t len = detail::to_utf8(val, buff);
if (len > 0) { result.append(buff, len); } if (len > 0) {
i += 5; // 'u0000' result.append(buff, len);
i += 5; // 'u0000'
} else {
// A surrogate is no character. Dropping it would join what is on
// either side of it, so the escape is kept as it is.
result += component[i];
}
} else { } else {
result += component[i]; result += component[i];
} }
+4 -2
View File
@@ -459,8 +459,10 @@ TEST(DecodePathTest, UnicodeEncoding) {
EXPECT_EQ(U8("あ"), decode_path_component("%u3042")); EXPECT_EQ(U8("あ"), decode_path_component("%u3042"));
// %uFFFF = max 4-digit hex (3-byte UTF-8, must not overflow buff[4]) // %uFFFF = max 4-digit hex (3-byte UTF-8, must not overflow buff[4])
EXPECT_FALSE(decode_path_component("%uFFFF").empty()); EXPECT_FALSE(decode_path_component("%uFFFF").empty());
// %uD800 = surrogate (invalid, silently dropped) // %uD800 = surrogate (invalid, kept as it is rather than joining its
EXPECT_EQ("", decode_path_component("%uD800")); // neighbours)
EXPECT_EQ("%uD800", decode_path_component("%uD800"));
EXPECT_EQ("/ad%uD800min", decode_path_component("/ad%uD800min"));
} }
TEST(DecodeQueryTest, RejectsNonHexEscapes) { TEST(DecodeQueryTest, RejectsNonHexEscapes) {