Merge pull request #3263 from lightpanda-io/usp-invalid-escapes

webapi: URLSearchParams passes invalid percent-escapes through
This commit is contained in:
Karl Seguin authored and GitHub committed 2026-08-26 06:33:49 +08:00
commit 22e02f5070
2 files changed
+25 -8

No files matched your search

@@ -635,3 +635,20 @@
testing.expectEqual('param1=1', usp.toString());
}
</script>
<script id=invalidPercentEscapes>
{
// An invalid %XX escape is not an error; the bytes pass through literally.
testing.expectEqual('%', new URLSearchParams('id=0&value=%').get('value'));
testing.expectEqual('%2sf*', new URLSearchParams('b=%2sf%2a').get('b'));
testing.expectEqual('%2*f*', new URLSearchParams('b=%2%2af%2a').get('b'));
testing.expectEqual('%*', new URLSearchParams('b=%%2a').get('b'));
testing.expectEqual('a', new URLSearchParams('%=a').get('%'));
testing.expectEqual('a', new URLSearchParams('%a=a').get('%a'));
// ... and they re-serialize with the '%' escaped.
testing.expectEqual('id=0&value=%25', new URLSearchParams('id=0&value=%').toString());
testing.expectEqual('b=%252sf*', new URLSearchParams('b=%2sf%2a').toString());
testing.expectEqual('b=%25*', new URLSearchParams('b=%%2a').toString());
}
</script>
+8 -8
View File
@@ -315,16 +315,11 @@ fn unescape(arena: Allocator, value: []const u8, buf: []u8) !String {
var in_i: usize = 0;
while (in_i < value.len) {
const b = value[in_i];
if (b == '%') {
if (in_i + 2 >= value.len or !std.ascii.isHex(value[in_i + 1]) or !std.ascii.isHex(value[in_i + 2])) {
return error.InvalidEscapeSequence;
}
if (b == '%' and isEscapeTriplet(value, in_i)) {
in_i += 3;
unescaped_len -= 2;
} else if (b == '+') {
has_plus = true;
in_i += 1;
} else {
has_plus = has_plus or b == '+';
in_i += 1;
}
}
@@ -344,7 +339,7 @@ fn unescape(arena: Allocator, value: []const u8, buf: []u8) !String {
in_i = 0;
for (0..unescaped_len) |i| {
const b = value[in_i];
if (b == '%') {
if (b == '%' and isEscapeTriplet(value, in_i)) {
out[i] = decodeHex(value[in_i + 1]) << 4 | decodeHex(value[in_i + 2]);
in_i += 3;
} else if (b == '+') {
@@ -398,6 +393,11 @@ pub const Iterator = struct {
}
};
// True when value[i] starts a valid %XX escape
fn isEscapeTriplet(value: []const u8, i: usize) bool {
return i + 2 < value.len and std.ascii.isHex(value[i + 1]) and std.ascii.isHex(value[i + 2]);
}
const GenericIterator = @import("../collections/iterator.zig").Entry;
pub const KeyIterator = GenericIterator(Iterator, "0");
pub const ValueIterator = GenericIterator(Iterator, "1");