From 6e50443a3930adb14a483899b87589ccf42e7596 Mon Sep 17 00:00:00 2001 From: Ruben Verborgh Date: Thu, 28 Jan 2021 22:12:00 +0100 Subject: [PATCH] fix: Preserve query string in transformations. --- src/util/PathUtil.ts | 20 ++++++++++++++------ test/unit/util/PathUtil.test.ts | 24 ++++++++++++++++++++---- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/util/PathUtil.ts b/src/util/PathUtil.ts index 61acecbaa..22f25cb6e 100644 --- a/src/util/PathUtil.ts +++ b/src/util/PathUtil.ts @@ -89,28 +89,36 @@ export function getExtension(path: string): string { return extension ? extension[1] : ''; } +/** + * Performs a transformation on the path components of a URI. + */ +function transformPathComponents(path: string, transform: (part: string) => string): string { + const [ , base, queryString ] = /^([^?]*)(.*)$/u.exec(path)!; + const transformed = base.split('/').map(transform).join('/'); + return !queryString ? transformed : `${transformed}${queryString}`; +} + /** * Converts a URI path to the canonical version by splitting on slashes, - * decoding any percent-based encodings, - * and then encoding any special characters. + * decoding any percent-based encodings, and then encoding any special characters. */ export function toCanonicalUriPath(path: string): string { - return path.split('/').map((part): string => - encodeURIComponent(decodeURIComponent(part))).join('/'); + return transformPathComponents(path, (part): string => + encodeURIComponent(decodeURIComponent(part))); } /** * Decodes all components of a URI path. */ export function decodeUriPathComponents(path: string): string { - return path.split('/').map(decodeURIComponent).join('/'); + return transformPathComponents(path, decodeURIComponent); } /** * Encodes all (non-slash) special characters in a URI path. */ export function encodeUriPathComponents(path: string): string { - return path.split('/').map(encodeURIComponent).join('/'); + return transformPathComponents(path, encodeURIComponent); } /** diff --git a/test/unit/util/PathUtil.test.ts b/test/unit/util/PathUtil.test.ts index 8d0a76839..c3fecb63c 100644 --- a/test/unit/util/PathUtil.test.ts +++ b/test/unit/util/PathUtil.test.ts @@ -52,17 +52,33 @@ describe('PathUtil', (): void => { }); }); - describe('UriPath functions', (): void => { - it('makes sure only the necessary parts are encoded with toCanonicalUriPath.', async(): Promise => { + describe('#toCanonicalUriPath', (): void => { + it('encodes only the necessary parts.', async(): Promise => { expect(toCanonicalUriPath('/a%20path&/name')).toEqual('/a%20path%26/name'); }); - it('decodes all parts of a path with decodeUriPathComponents.', async(): Promise => { + it('leaves the query string untouched.', async(): Promise => { + expect(toCanonicalUriPath('/a%20path&/name?abc=def&xyz')).toEqual('/a%20path%26/name?abc=def&xyz'); + }); + }); + + describe('#decodeUriPathComponents', (): void => { + it('decodes all parts of a path.', async(): Promise => { expect(decodeUriPathComponents('/a%20path&/name')).toEqual('/a path&/name'); }); - it('encodes all parts of a path with encodeUriPathComponents.', async(): Promise => { + it('leaves the query string untouched.', async(): Promise => { + expect(decodeUriPathComponents('/a%20path&/name?abc=def&xyz')).toEqual('/a path&/name?abc=def&xyz'); + }); + }); + + describe('#encodeUriPathComponents', (): void => { + it('encodes all parts of a path.', async(): Promise => { expect(encodeUriPathComponents('/a%20path&/name')).toEqual('/a%2520path%26/name'); }); + + it('leaves the query string untouched.', async(): Promise => { + expect(encodeUriPathComponents('/a%20path&/name?abc=def&xyz')).toEqual('/a%2520path%26/name?abc=def&xyz'); + }); }); });