diff --git a/aio/src/app/app.module.ts b/aio/src/app/app.module.ts
index c81ccecec43..0c5f2d10f1d 100644
--- a/aio/src/app/app.module.ts
+++ b/aio/src/app/app.module.ts
@@ -12,6 +12,8 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatToolbarModule } from '@angular/material/toolbar';
+import { svg } from 'app/shared/security';
+
import { AppComponent } from 'app/app.component';
import { CustomIconRegistry, SVG_ICONS } from 'app/shared/custom-icon-registry';
import { Deployment } from 'app/shared/deployment.service';
@@ -53,11 +55,10 @@ export const svgIconProviders = [
provide: SVG_ICONS,
useValue: {
name: 'close',
- svgSource:
- '',
+ svgSource: svg``,
},
multi: true,
},
@@ -65,11 +66,10 @@ export const svgIconProviders = [
provide: SVG_ICONS,
useValue: {
name: 'insert_comment',
- svgSource:
- '',
+ svgSource: svg``,
},
multi: true,
},
@@ -77,10 +77,9 @@ export const svgIconProviders = [
provide: SVG_ICONS,
useValue: {
name: 'keyboard_arrow_right',
- svgSource:
- '',
+ svgSource: svg``,
},
multi: true,
},
@@ -88,10 +87,9 @@ export const svgIconProviders = [
provide: SVG_ICONS,
useValue: {
name: 'menu',
- svgSource:
- '',
+ svgSource: svg``,
},
multi: true,
},
@@ -103,14 +101,14 @@ export const svgIconProviders = [
namespace: 'logos',
name: 'github',
svgSource:
- '',
+ svg``,
},
multi: true,
},
@@ -119,14 +117,13 @@ export const svgIconProviders = [
useValue: {
namespace: 'logos',
name: 'twitter',
- svgSource:
- '',
+ svgSource: svg``,
},
multi: true,
},
@@ -135,12 +132,11 @@ export const svgIconProviders = [
useValue: {
namespace: 'logos',
name: 'youtube',
- svgSource:
- '',
+ svgSource: svg``,
},
multi: true,
},
diff --git a/aio/src/app/shared/custom-icon-registry.spec.ts b/aio/src/app/shared/custom-icon-registry.spec.ts
index 9c017859a6c..fe1466560b2 100644
--- a/aio/src/app/shared/custom-icon-registry.spec.ts
+++ b/aio/src/app/shared/custom-icon-registry.spec.ts
@@ -2,7 +2,10 @@ import { HttpClient } from '@angular/common/http';
import { ErrorHandler } from '@angular/core';
import { MatIconRegistry } from '@angular/material/icon';
import { DomSanitizer } from '@angular/platform-browser';
+import { unwrapHtmlForSink } from 'safevalues';
+import { concatHtmls } from 'safevalues/builders/html_builders';
import { CustomIconRegistry, SvgIconInfo } from './custom-icon-registry';
+import { svg } from './security';
describe('CustomIconRegistry', () => {
const fakeHttpClient: HttpClient = {} as any;
@@ -11,8 +14,9 @@ describe('CustomIconRegistry', () => {
const fakeErrorHandler: ErrorHandler = {handleError: err => console.error(err)};
it('should get the SVG element for a preloaded icon from the cache', () => {
- const svgSrc = '';
+ const svgSrc = concatHtmls([
+ svg``]);
const svgIcons: SvgIconInfo[] = [
{ name: 'test_icon', svgSource: svgSrc }
];
@@ -26,9 +30,9 @@ describe('CustomIconRegistry', () => {
});
it('should support caching icons with a namespace', () => {
- const svgSrc1 = '';
- const svgSrc2 = '';
- const svgSrc3 = '';
+ const svgSrc1 = svg``;
+ const svgSrc2 = svg``;
+ const svgSrc3 = svg``;
const svgIcons: SvgIconInfo[] = [
{ name: 'test_icon', svgSource: svgSrc1 },
{ namespace: 'foo', name: 'test_icon', svgSource: svgSrc2 },
@@ -44,8 +48,9 @@ describe('CustomIconRegistry', () => {
});
it('should call through to the MdIconRegistry if the icon name is not in the preloaded cache', () => {
- const svgSrc = '';
+ const svgSrc = concatHtmls([
+ svg``]);
const svgIcons: SvgIconInfo[] = [
{ name: 'test_icon', svgSource: svgSrc }
];
@@ -62,8 +67,8 @@ describe('CustomIconRegistry', () => {
});
});
-function createSvg(svgSrc: string): SVGElement {
+function createSvg(svgSrc: TrustedHTML): SVGElement {
const div = document.createElement('div');
- div.innerHTML = svgSrc;
+ div.innerHTML = unwrapHtmlForSink(svgSrc);
return div.querySelector('svg') as SVGElement;
}
diff --git a/aio/src/app/shared/custom-icon-registry.ts b/aio/src/app/shared/custom-icon-registry.ts
index 4cc0f048622..d28af196b29 100644
--- a/aio/src/app/shared/custom-icon-registry.ts
+++ b/aio/src/app/shared/custom-icon-registry.ts
@@ -4,6 +4,7 @@ import { of } from 'rxjs';
import { MatIconRegistry } from '@angular/material/icon';
import { HttpClient } from '@angular/common/http';
import { DomSanitizer } from '@angular/platform-browser';
+import { unwrapHtmlForSink } from 'safevalues';
/**
* Use SVG_ICONS (and SvgIconInfo) as "multi" providers to provide the SVG source
@@ -22,7 +23,7 @@ export const SVG_ICONS = new InjectionToken>('SvgIcons');
export interface SvgIconInfo {
namespace?: string;
name: string;
- svgSource: string;
+ svgSource: TrustedHTML;
}
interface SvgIconMap {
@@ -75,7 +76,7 @@ export class CustomIconRegistry extends MatIconRegistry {
const div = document.createElement('DIV');
// SECURITY: the source for the SVG icons is provided in code by trusted developers
- div.innerHTML = svgIcon.svgSource;
+ div.innerHTML = unwrapHtmlForSink(svgIcon.svgSource);
const svgElement = div.querySelector('svg') as SVGElement;
nsIconMap[svgIcon.name] = svgElement;
diff --git a/aio/src/app/shared/security.ts b/aio/src/app/shared/security.ts
index 70bb11c3ba8..fcf8def83d7 100644
--- a/aio/src/app/shared/security.ts
+++ b/aio/src/app/shared/security.ts
@@ -9,3 +9,9 @@ export function fromOuterHTML(el: Element): TrustedHTML {
// SECURITY: Existing outerHTML content is already trusted.
return htmlFromStringKnownToSatisfyTypeContract(el.outerHTML, '^');
}
+
+export function svg(constantSvg: TemplateStringsArray): TrustedHTML {
+ // SECURITY: Template literal argument with no interpolation is constant, and
+ // hence trusted.
+ return htmlFromStringKnownToSatisfyTypeContract(constantSvg[0], '^');
+}