From 2ba222483967d97b8a8e1ea4fb3a3ffbdba09dd1 Mon Sep 17 00:00:00 2001 From: Syed Osama Ali Shah <86572800+Osamaali313@users.noreply.github.com> Date: Fri, 3 Jul 2026 15:42:14 +0300 Subject: [PATCH] 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. --- src/vs/base/common/uriTemplate.ts | 2 +- src/vs/base/test/common/uriTemplate.test.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/base/common/uriTemplate.ts b/src/vs/base/common/uriTemplate.ts index 4734dc0d9f1..f425f810a28 100644 --- a/src/vs/base/common/uriTemplate.ts +++ b/src/vs/base/common/uriTemplate.ts @@ -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; diff --git a/src/vs/base/test/common/uriTemplate.test.ts b/src/vs/base/test/common/uriTemplate.test.ts index 0165f0aa4a4..fbe0ad8d1d7 100644 --- a/src/vs/base/test/common/uriTemplate.test.ts +++ b/src/vs/base/test/common/uriTemplate.test.ts @@ -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 = {