Skip to content

Commit b769d28

Browse files
deepview-autofixclaudeChALkeR
authored
querystring: validate surrogate pairs in encodeStr
The surrogate pair path in `encodeStr` only checked that a second code unit existed; it did not verify that the leading unit was a high surrogate (0xD800-0xDBFF) or that the trailing unit was a low surrogate (0xDC00-0xDFFF). A lone high surrogate followed by a non-surrogate (or a lone low surrogate) was silently combined and emitted as a syntactically valid four-byte UTF-8 sequence representing an entirely different code point, producing malformed output that does not roundtrip through `querystring.parse`. Match `encodeURIComponent` semantics by throwing `ERR_INVALID_URI` when either half of the pair is not a valid surrogate. Co-Authored-By: Claude <[email protected]> Co-Authored-By: DeepView Autofix <[email protected]> Co-Authored-By: Nikita Skovoroda <[email protected]> Signed-off-by: Nikita Skovoroda <[email protected]>
1 parent 14e16db commit b769d28

2 files changed

Lines changed: 24 additions & 13 deletions

File tree

lib/internal/querystring.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,15 @@ function encodeStr(str, noEscapeTable, hexTable) {
9393
// This branch should never happen because all URLSearchParams entries
9494
// should already be converted to USVString. But, included for
9595
// completion's sake anyway.
96-
if (i >= len)
96+
if (i >= len || c >= 0xDC00)
9797
throw new ERR_INVALID_URI();
9898

99-
const c2 = StringPrototypeCharCodeAt(str, i) & 0x3FF;
99+
const c2 = StringPrototypeCharCodeAt(str, i);
100+
if (c2 < 0xDC00 || c2 >= 0xE000)
101+
throw new ERR_INVALID_URI();
100102

101103
lastPos = i + 1;
102-
c = 0x10000 + (((c & 0x3FF) << 10) | c2);
104+
c = 0x10000 + (((c & 0x3FF) << 10) | (c2 & 0x3FF));
103105
out += hexTable[0xF0 | (c >> 18)] +
104106
hexTable[0x80 | ((c >> 12) & 0x3F)] +
105107
hexTable[0x80 | ((c >> 6) & 0x3F)] +

test/parallel/test-querystring-escape.js

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,26 @@ assert.strictEqual(qs.escape({}), '%5Bobject%20Object%5D');
1010
assert.strictEqual(qs.escape([5, 10]), '5%2C10');
1111
assert.strictEqual(qs.escape('Ŋōđĕ'), '%C5%8A%C5%8D%C4%91%C4%95');
1212
assert.strictEqual(qs.escape('testŊōđĕ'), 'test%C5%8A%C5%8D%C4%91%C4%95');
13-
assert.strictEqual(qs.escape(`${String.fromCharCode(0xD800 + 1)}test`),
14-
'%F0%90%91%B4est');
13+
assert.strictEqual(
14+
qs.escape(`${String.fromCharCode(0xD800 + 1)}${String.fromCharCode(0xDC00)}`),
15+
'%F0%90%90%80');
1516

16-
assert.throws(
17-
() => qs.escape(String.fromCharCode(0xD800 + 1)),
18-
{
19-
code: 'ERR_INVALID_URI',
20-
name: 'URIError',
21-
message: 'URI malformed'
22-
}
23-
);
17+
for (const invalid of [
18+
String.fromCharCode(0xD800 + 1),
19+
`${String.fromCharCode(0xD800 + 1)}test`,
20+
`${String.fromCharCode(0xDC00)}test`,
21+
`${String.fromCharCode(0xD800 + 1)}${String.fromCharCode(0x41)}`,
22+
`${String.fromCharCode(0xD800 + 1)}${String.fromCharCode(0xD800)}`,
23+
]) {
24+
assert.throws(
25+
() => qs.escape(invalid),
26+
{
27+
code: 'ERR_INVALID_URI',
28+
name: 'URIError',
29+
message: 'URI malformed',
30+
},
31+
);
32+
}
2433

2534
// Using toString for objects
2635
assert.strictEqual(

0 commit comments

Comments
 (0)