From 3497d855b9a89370c596ccd1062ce85fa5c40015 Mon Sep 17 00:00:00 2001 From: Mohak Gupta Date: Sat, 22 Aug 2026 07:10:19 +0530 Subject: [PATCH] Fix live preview breaking on # in file or folder names PathUtil.EscapePathParts used encodeURI on each path segment, which deliberately leaves # unescaped (it's meant for encoding a full URI, where # is a structural delimiter, not an individual path segment). A folder or file name containing # therefore produced a preview URL where everything from # onward is treated by the browser as a fragment and never reaches the server, showing the parent directory listing instead of the file. Switched EscapePathParts/UnescapePathParts to encodeURIComponent/decodeURIComponent, which escape all reserved characters in a path segment rather than only some of them. Fixes #750 --- src/test/suite/pathUtil.test.ts | 18 ++++++++++++++++++ src/utils/pathUtil.ts | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/test/suite/pathUtil.test.ts b/src/test/suite/pathUtil.test.ts index dad4fcfc..c6b64713 100644 --- a/src/test/suite/pathUtil.test.ts +++ b/src/test/suite/pathUtil.test.ts @@ -122,6 +122,24 @@ describe('GetWorkspaceFromRelativePath / GetWorkspaceFromAbsolutePath', () => { }); }); +describe('EscapePathParts / UnescapePathParts', () => { + it('escapes # in a path segment so it is not treated as a URL fragment delimiter', () => { + const actual = PathUtil.EscapePathParts('my#folder/index.html'); + assert.strictEqual(actual, 'my%23folder/index.html'); + }); + + it('round-trips a path segment containing #', () => { + const escaped = PathUtil.EscapePathParts('my#folder/index.html'); + const actual = PathUtil.UnescapePathParts(escaped); + assert.strictEqual(actual, 'my#folder/index.html'); + }); + + it('escapes a leading # the same as one in the middle of a segment', () => { + const actual = PathUtil.EscapePathParts('#folder/index.html'); + assert.strictEqual(actual, '%23folder/index.html'); + }); +}); + describe('getEndpointParent', () => { it('returns the correct endpoint parent for full paths', async () => { const endpoint1 = PathUtil.GetEndpointParent('c:/Users/TestUser/workspace1/'); diff --git a/src/utils/pathUtil.ts b/src/utils/pathUtil.ts index 5f01fdd9..c08be01a 100644 --- a/src/utils/pathUtil.ts +++ b/src/utils/pathUtil.ts @@ -26,7 +26,7 @@ export class PathUtil { const newParts = parts .filter((part) => part.length > 0) - .map((filterdPart) => encodeURI(filterdPart)); + .map((filterdPart) => encodeURIComponent(filterdPart)); return newParts.join('/'); } @@ -39,7 +39,7 @@ export class PathUtil { const parts = file.split('/'); const newParts = parts .filter((part) => part.length > 0) - .map((filterdPart) => decodeURI(filterdPart)); + .map((filterdPart) => decodeURIComponent(filterdPart)); return newParts.join('/'); }