fix: Preserve query string in transformations.

This commit is contained in:
Ruben Verborgh
2021-01-28 22:12:00 +01:00
committed by Joachim Van Herwegen
parent 03e631ff17
commit 6e50443a39
2 changed files with 34 additions and 10 deletions

View File

@@ -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);
}
/**

View File

@@ -52,17 +52,33 @@ describe('PathUtil', (): void => {
});
});
describe('UriPath functions', (): void => {
it('makes sure only the necessary parts are encoded with toCanonicalUriPath.', async(): Promise<void> => {
describe('#toCanonicalUriPath', (): void => {
it('encodes only the necessary parts.', async(): Promise<void> => {
expect(toCanonicalUriPath('/a%20path&/name')).toEqual('/a%20path%26/name');
});
it('decodes all parts of a path with decodeUriPathComponents.', async(): Promise<void> => {
it('leaves the query string untouched.', async(): Promise<void> => {
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<void> => {
expect(decodeUriPathComponents('/a%20path&/name')).toEqual('/a path&/name');
});
it('encodes all parts of a path with encodeUriPathComponents.', async(): Promise<void> => {
it('leaves the query string untouched.', async(): Promise<void> => {
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<void> => {
expect(encodeUriPathComponents('/a%20path&/name')).toEqual('/a%2520path%26/name');
});
it('leaves the query string untouched.', async(): Promise<void> => {
expect(encodeUriPathComponents('/a%20path&/name?abc=def&xyz')).toEqual('/a%2520path%26/name?abc=def&xyz');
});
});
});