Skip to content

Commit

Permalink
Allows for relative paths in trustedResourceUrl template builder.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 574067601
  • Loading branch information
neuracr authored and copybara-github committed Oct 19, 2023
1 parent 0040879 commit 8d5551c
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 8 deletions.
20 changes: 19 additions & 1 deletion src/builders/resource_url_builders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,21 @@ function isValidPathStart(base: string): boolean {
throw new Error('The path start in the url is invalid.');
}

/**
* Check whether the base url contains a valid relative path start at its
* beginning.
*
* A valid relative path start is a non empty string that has no ':', '/' nor
* '\', and that is followed by a '/'.
*
* @param base The base url.
*/
function isValidRelativePathStart(base: string): boolean {
// Using the RegExp syntax as the native JS RegExp syntax is not well handled
// by some downstream bundlers with this regex.
return new RegExp('^[^:\\\\/]+/').test(base);
}

/**
* Builds TrustedResourceUrl from a template literal.
*
Expand All @@ -105,6 +120,7 @@ function isValidPathStart(base: string): boolean {
* - `https://<origin>/`
* - `//<origin>/`
* - `/<pathStart>`
* - `<relativePathStart>/`
* - `about:blank`
* - `data:`
*
Expand All @@ -121,6 +137,8 @@ function isValidPathStart(base: string): boolean {
* In other words, `/<pathStart>` is either a '/' or a
* '/' followed by at least one character that is not '/' or '\'.
*
* `<relativePathStart> is a non empty string that has no ':', '/' nor '\'.
*
* `data:` (data URL) does not allow embedded expressions in the template
* literal input.
*
Expand Down Expand Up @@ -152,7 +170,7 @@ export function trustedResourceUrl(
}

if (!hasValidOrigin(base) && !isValidPathStart(base) &&
!isValidAboutUrl(base)) {
!isValidRelativePathStart(base) && !isValidAboutUrl(base)) {
throw new Error(
'Trying to interpolate expressions in an unsupported url format.');
}
Expand Down
35 changes: 28 additions & 7 deletions test/builders/resource_url_builders_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ describe('resource_url_builders', () => {
expect(trustedResourceUrl`/path/${foo}`.toString()).toBe('/path/foo');
expect(trustedResourceUrl`/path#${foo}`.toString()).toBe('/path#foo');
expect(trustedResourceUrl`/path?${foo}`.toString()).toBe('/path?foo');
// Path-relative.
expect(trustedResourceUrl`path/${foo}`.toString()).toBe('path/foo');
expect(trustedResourceUrl`path/to/${foo}`.toString()).toBe('path/to/foo');
// Mixed case.
expect(trustedResourceUrl`httpS://www.google.cOm/pAth/${foo}`.toString())
.toBe('httpS://www.google.cOm/pAth/foo');
Expand Down Expand Up @@ -107,23 +110,41 @@ describe('resource_url_builders', () => {
expect(() => {
return trustedResourceUrl`//127.0.0.1:1337/${foo}`;
}).toThrowError(/The top-level domain must start with a letter./);
// Constant origin bypass attempt
const bypassAttempt = '/evil.com';
expect(() => trustedResourceUrl`https:/${bypassAttempt}`)
.toThrowError(
/Trying to interpolate expressions in an unsupported url format./);
// Odd cases.
expect(() => {
return trustedResourceUrl`//./${foo}`;
}).toThrowError(/The top-level domain must start with a letter./);
expect(() => trustedResourceUrl`something:/${foo}`)
.toThrowError(
/Trying to interpolate expressions in an unsupported url format./);
expect(() => trustedResourceUrl`something:${foo}`)
.toThrowError(
/Trying to interpolate expressions in an unsupported url format./);
// Relative URL with a backslash in the first segment
expect(() => trustedResourceUrl`abc\\def/${foo}`)
.toThrowError(
/Trying to interpolate expressions in an unsupported url format./);
// Two slashes. IE allowed (allows?) '\' instead of '/'.
expect(() => {
return trustedResourceUrl`/\\${foo}`;
}).toThrowError(/The path start in the url is invalid./);
// Relative path.
expect(() => {
return trustedResourceUrl`abc${foo}`;
})
// Relative path with interpolation in the first segment
expect(() => trustedResourceUrl`abc${foo}`)
.toThrowError(
/Trying to interpolate expressions in an unsupported url format./);
expect(() => {
return trustedResourceUrl`about:blankX${foo}`;
}).toThrowError(/The about url is invalid./);
expect(() => trustedResourceUrl`${foo}bar`)
.toThrowError(
/Trying to interpolate expressions in an unsupported url format./);
expect(() => trustedResourceUrl`${foo}bar/`)
.toThrowError(
/Trying to interpolate expressions in an unsupported url format./);
expect(() => trustedResourceUrl`about:blankX${foo}`)
.toThrowError(/The about url is invalid./);
});

it('calls encodeURIComponent on interpolated values', () => {
Expand Down

0 comments on commit 8d5551c

Please sign in to comment.