Zero-pad percent-encoding for code points below 0x10 in UriTemplate (#323012)

fix: zero-pad percent-encoding for code points below 0x10 in UriTemplate

pctEncode emitted '%' + chr.toString(16) without padding, so a code point < 0x10 produced a single-digit escape (e.g. tab -> %9, newline -> %0A becomes %A, CR -> %D) instead of the RFC 3986/6570 two-digit form. The result is a malformed URI that decodeURIComponent rejects with "URI malformed". UriTemplate is used to resolve MCP and agent resource URIs, where a variable value containing a control character would yield a broken URL.

Zero-pad to two hex digits with padStart(2, '0'); this is a no-op for code points >= 0x10, so existing escapes (%20, %21, ...) are unchanged. Adds a regression test.
This commit is contained in:
Syed Osama Ali Shah
2026-07-03 15:42:14 +03:00
committed by GitHub
parent 629ea48932
commit 2ba2224839
2 changed files with 9 additions and 1 deletions

View File

@@ -298,7 +298,7 @@ function pctEncode(str: string): string {
) {
out += str[i];
} else {
out += '%' + chr.toString(16).toUpperCase();
out += '%' + chr.toString(16).toUpperCase().padStart(2, '0');
}
}
return out;

View File

@@ -103,6 +103,14 @@ suite('UriTemplate', () => {
testResolution('{hello}', variables, 'Hello%20World%21');
});
test('control characters are percent-encoded with two hex digits', () => {
// Code points below 0x10 must be zero-padded (e.g. %09, not %9) so the
// output is a valid percent-encoding that decodeURIComponent accepts.
testResolution('{x}', { x: 'a\tb' }, 'a%09b');
testResolution('{x}', { x: '\n' }, '%0A');
testResolution('{x}', { x: '\r' }, '%0D');
});
test('Level 2 - Reserved expansion', () => {
// Test cases from RFC 6570 Section 1.2
const variables = {