From 2b267209a1da63152157a1b5ca8f158ddbc8fba7 Mon Sep 17 00:00:00 2001 From: Bjarki Date: Tue, 6 Jul 2021 02:25:29 +0000 Subject: [PATCH] fix(docs-infra): make custom icon registry compatible with Trusted Types (#42800) Change SvgIconInfo.svgSource to be a TrustedHTML and update all its users accordingly. Also introduce the svg template tag function for building TrustedHTML from constant SVG sources. PR Close #42800 --- aio/src/app/app.module.ts | 76 +++++++++---------- .../app/shared/custom-icon-registry.spec.ts | 23 +++--- aio/src/app/shared/custom-icon-registry.ts | 5 +- aio/src/app/shared/security.ts | 6 ++ 4 files changed, 59 insertions(+), 51 deletions(-) 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], '^'); +}