diff --git a/packages/core/src/sanitization/html_sanitizer.ts b/packages/core/src/sanitization/html_sanitizer.ts index ab7bbbe836..b0b30311a0 100644 --- a/packages/core/src/sanitization/html_sanitizer.ts +++ b/packages/core/src/sanitization/html_sanitizer.ts @@ -7,6 +7,8 @@ */ import {isDevMode} from '../util/is_dev_mode'; +import {TrustedHTML} from '../util/security/trusted_type_defs'; +import {trustedHTMLFromString} from '../util/security/trusted_types'; import {getInertBodyHelper, InertBodyHelper} from './inert_body'; import {_sanitizeUrl, sanitizeSrcset} from './url_sanitizer'; @@ -242,7 +244,7 @@ let inertBodyHelper: InertBodyHelper; * Sanitizes the given unsafe, untrusted HTML fragment, and returns HTML text that is safe to add to * the DOM in a browser environment. */ -export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string { +export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): TrustedHTML|string { let inertBodyElement: HTMLElement|null = null; try { inertBodyHelper = inertBodyHelper || getInertBodyHelper(defaultDoc); @@ -274,7 +276,7 @@ export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string 'WARNING: sanitizing HTML stripped some content, see http://g.co/ng/security#xss'); } - return safeHtml; + return trustedHTMLFromString(safeHtml); } finally { // In case anything goes wrong, clear out inertElement to reset the entire DOM structure. if (inertBodyElement) { diff --git a/packages/core/test/sanitization/html_sanitizer_spec.ts b/packages/core/test/sanitization/html_sanitizer_spec.ts index d577ce2c4d..aae4a18421 100644 --- a/packages/core/test/sanitization/html_sanitizer_spec.ts +++ b/packages/core/test/sanitization/html_sanitizer_spec.ts @@ -11,6 +11,10 @@ import {browserDetection} from '@angular/platform-browser/testing/src/browser_ut import {_sanitizeHtml} from '../../src/sanitization/html_sanitizer'; import {isDOMParserAvailable} from '../../src/sanitization/inert_body'; +function sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string { + return _sanitizeHtml(defaultDoc, unsafeHtmlInput).toString(); +} + { describe('HTML sanitizer', () => { let defaultDoc: any; @@ -29,73 +33,73 @@ import {isDOMParserAvailable} from '../../src/sanitization/inert_body'; }); it('serializes nested structures', () => { - expect(_sanitizeHtml(defaultDoc, '

a

bcde
')) + expect(sanitizeHtml(defaultDoc, '

a

bcde
')) .toEqual('

a

bcde
'); expect(logMsgs).toEqual([]); }); it('serializes self closing elements', () => { - expect(_sanitizeHtml(defaultDoc, '

Hello
World

')) + expect(sanitizeHtml(defaultDoc, '

Hello
World

')) .toEqual('

Hello
World

'); }); it('supports namespaced elements', () => { - expect(_sanitizeHtml(defaultDoc, 'abc')).toEqual('abc'); + expect(sanitizeHtml(defaultDoc, 'abc')).toEqual('abc'); }); it('supports namespaced attributes', () => { - expect(_sanitizeHtml(defaultDoc, 't')) + expect(sanitizeHtml(defaultDoc, 't')) .toEqual('t'); - expect(_sanitizeHtml(defaultDoc, 't')).toEqual('t'); - expect(_sanitizeHtml(defaultDoc, 't')) + expect(sanitizeHtml(defaultDoc, 't')).toEqual('t'); + expect(sanitizeHtml(defaultDoc, 't')) .toEqual('t'); }); it('supports HTML5 elements', () => { - expect(_sanitizeHtml(defaultDoc, '
Works
')) + expect(sanitizeHtml(defaultDoc, '
Works
')) .toEqual('
Works
'); }); it('supports ARIA attributes', () => { - expect(_sanitizeHtml(defaultDoc, '

Test

')) + expect(sanitizeHtml(defaultDoc, '

Test

')) .toEqual('

Test

'); - expect(_sanitizeHtml(defaultDoc, 'Info')) + expect(sanitizeHtml(defaultDoc, 'Info')) .toEqual('Info'); - expect(_sanitizeHtml(defaultDoc, '')) + expect(sanitizeHtml(defaultDoc, '')) .toEqual(''); }); it('sanitizes srcset attributes', () => { - expect(_sanitizeHtml(defaultDoc, '')) + expect(sanitizeHtml(defaultDoc, '')) .toEqual(''); }); it('supports sanitizing plain text', () => { - expect(_sanitizeHtml(defaultDoc, 'Hello, World')).toEqual('Hello, World'); + expect(sanitizeHtml(defaultDoc, 'Hello, World')).toEqual('Hello, World'); }); it('ignores non-element, non-attribute nodes', () => { - expect(_sanitizeHtml(defaultDoc, 'no.')).toEqual('no.'); - expect(_sanitizeHtml(defaultDoc, 'no.')).toEqual('no.'); + expect(sanitizeHtml(defaultDoc, 'no.')).toEqual('no.'); + expect(sanitizeHtml(defaultDoc, 'no.')).toEqual('no.'); expect(logMsgs.join('\n')).toMatch(/sanitizing HTML stripped some content/); }); it('supports sanitizing escaped entities', () => { - expect(_sanitizeHtml(defaultDoc, '🚀')).toEqual('🚀'); + expect(sanitizeHtml(defaultDoc, '🚀')).toEqual('🚀'); expect(logMsgs).toEqual([]); }); it('does not warn when just re-encoding text', () => { - expect(_sanitizeHtml(defaultDoc, '

Hellö Wörld

')) + expect(sanitizeHtml(defaultDoc, '

Hellö Wörld

')) .toEqual('

Hellö Wörld

'); expect(logMsgs).toEqual([]); }); it('escapes entities', () => { - expect(_sanitizeHtml(defaultDoc, '

Hello < World

')) + expect(sanitizeHtml(defaultDoc, '

Hello < World

')) .toEqual('

Hello < World

'); - expect(_sanitizeHtml(defaultDoc, '

Hello < World

')).toEqual('

Hello < World

'); - expect(_sanitizeHtml(defaultDoc, '

Hello

')) + expect(sanitizeHtml(defaultDoc, '

Hello < World

')).toEqual('

Hello < World

'); + expect(sanitizeHtml(defaultDoc, '

Hello

')) .toEqual('

Hello

'); // NB: quote encoded as ASCII ". }); @@ -110,7 +114,7 @@ import {isDOMParserAvailable} from '../../src/sanitization/inert_body'; ]; for (const tag of dangerousTags) { it(tag, () => { - expect(_sanitizeHtml(defaultDoc, `<${tag}>evil!`)).toEqual('evil!'); + expect(sanitizeHtml(defaultDoc, `<${tag}>evil!`)).toEqual('evil!'); }); } @@ -125,7 +129,7 @@ import {isDOMParserAvailable} from '../../src/sanitization/inert_body'; ]; for (const tag of dangerousSelfClosingTags) { it(tag, () => { - expect(_sanitizeHtml(defaultDoc, `before<${tag}>After`)).toEqual('beforeAfter'); + expect(sanitizeHtml(defaultDoc, `before<${tag}>After`)).toEqual('beforeAfter'); }); } @@ -136,7 +140,7 @@ import {isDOMParserAvailable} from '../../src/sanitization/inert_body'; ]; for (const tag of dangerousSkipContentTags) { it(tag, () => { - expect(_sanitizeHtml(defaultDoc, `<${tag}>evil!`)).toEqual(''); + expect(sanitizeHtml(defaultDoc, `<${tag}>evil!`)).toEqual(''); }); } @@ -144,7 +148,7 @@ import {isDOMParserAvailable} from '../../src/sanitization/inert_body'; // `` is special, because different browsers treat it differently (e.g. remove it // altogether). // We just verify that (one way or another), there is no `` element // after sanitization. - expect(_sanitizeHtml(defaultDoc, `evil!`)).not.toContain(''); + expect(sanitizeHtml(defaultDoc, `evil!`)).not.toContain(''); }); }); @@ -153,45 +157,45 @@ import {isDOMParserAvailable} from '../../src/sanitization/inert_body'; for (const attr of dangerousAttrs) { it(`${attr}`, () => { - expect(_sanitizeHtml(defaultDoc, `evil!`)).toEqual('evil!'); + expect(sanitizeHtml(defaultDoc, `evil!`)).toEqual('evil!'); }); } }); it('ignores content of script elements', () => { - expect(_sanitizeHtml(defaultDoc, '')).toEqual(''); - expect(_sanitizeHtml(defaultDoc, '
hi
')) + expect(sanitizeHtml(defaultDoc, '')).toEqual(''); + expect(sanitizeHtml(defaultDoc, '
hi
')) .toEqual('
hi
'); - expect(_sanitizeHtml(defaultDoc, '')).toEqual(''); + expect(sanitizeHtml(defaultDoc, '')).toEqual(''); }); it('ignores content of style elements', () => { - expect(_sanitizeHtml(defaultDoc, '
hi
')) + expect(sanitizeHtml(defaultDoc, '
hi
')) .toEqual('
hi
'); - expect(_sanitizeHtml(defaultDoc, '')).toEqual(''); - expect(_sanitizeHtml(defaultDoc, '')).toEqual(''); + expect(sanitizeHtml(defaultDoc, '')).toEqual(''); + expect(sanitizeHtml(defaultDoc, '')).toEqual(''); expect(logMsgs.join('\n')).toMatch(/sanitizing HTML stripped some content/); }); it('should strip unclosed iframe tag', () => { - expect(_sanitizeHtml(defaultDoc, '