Skip to content

Commit

Permalink
Create safe setter for SVGUseElement.href
Browse files Browse the repository at this point in the history
The attribute contains a URL that points to an SVG fragment to be loaded and
presented inside the element. The URL can additionally contain a URL fragment
representing the ID of a particular element to fetch from within that fragment.
See https://developer.mozilla.org/en-US/docs/Web/SVG/Content_type#iri for
details.

The <use> element only supports loading same-origin resources, but data: and javascript: URLs could cause XSS (e.g. w3c/trusted-types#357) and are thus sanitized.

PiperOrigin-RevId: 480854265
  • Loading branch information
bjarkler authored and copybara-github committed Oct 13, 2022
1 parent a2d61dc commit 67e52bf
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 1 deletion.
8 changes: 7 additions & 1 deletion src/builders/url_sanitizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@

import '../environment/dev';

function extractScheme(url: string): string|undefined {
/**
* Extracts the scheme from the given URL. If the URL is relative, https: is
* assumed.
* @param url The URL to extract the scheme from.
* @return the URL scheme.
*/
export function extractScheme(url: string): string|undefined {
let parsedUrl;
try {
parsedUrl = new URL(url);
Expand Down
25 changes: 25 additions & 0 deletions src/dom/elements/svg_use.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/

import '../../environment/dev';

import {extractScheme} from '../../builders/url_sanitizer';

/**
* Sets the Href attribute from the given TrustedResourceUrl.
*/
export function setHref(useEl: SVGUseElement, url: string) {
const scheme = extractScheme(url);
if (scheme === 'javascript:' || scheme === 'data:') {
if (process.env.NODE_ENV !== 'production') {
const msg = `A URL with content '${url}' was sanitized away.`;
console.error(msg);
}
return;
}

// Note that the href property is read-only, so setAttribute must be used.
useEl.setAttribute('href', url);
}
1 change: 1 addition & 0 deletions src/dom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export * as safeLinkEl from './elements/link';
export * as safeObjectEl from './elements/object';
export * as safeScriptEl from './elements/script';
export * as safeStyleEl from './elements/style';
export * as safeSvgUseEl from './elements/svg_use';
export * as safeDocument from './globals/document';
export * as safeDomParser from './globals/dom_parser';
export * as safeGlobal from './globals/global';
Expand Down
42 changes: 42 additions & 0 deletions test/dom/elements/svg_use_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/

import * as svgUseEl from '../../../src/dom/elements/svg_use';

describe('svgUseEl', () => {
let element: SVGUseElement;

beforeEach(() => {
element = document.createElementNS('http://www.w3.org/2000/svg', 'use');
element.setAttribute('href', 'unchanged');
});

describe('setHref', () => {
it('can set inline resource identifiers', () => {
svgUseEl.setHref(element, '#MyElement');
expect(element.href.baseVal).toEqual('#MyElement');
});

it('can set relative URLs', () => {
svgUseEl.setHref(element, 'image.svg');
expect(element.href.baseVal).toEqual('image.svg');
});

it('can set URLs with safe scheme', () => {
svgUseEl.setHref(element, 'https://google.com/image.svg');
expect(element.href.baseVal).toEqual('https://google.com/image.svg');
});

it('can not set URLs with data: scheme', () => {
svgUseEl.setHref(element, 'data:image/svg+xml,<svg></svg>');
expect(element.href.baseVal).toEqual('unchanged');
});

it('can not set URLs with javascript: scheme', () => {
svgUseEl.setHref(element, 'javascript:alert(1)');
expect(element.href.baseVal).toEqual('unchanged');
});
});
});

0 comments on commit 67e52bf

Please sign in to comment.