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, '
'))
+ expect(sanitizeHtml(defaultDoc, ''))
.toEqual('');
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, 'ab c')).toEqual('abc');
+ expect(sanitizeHtml(defaultDoc, 'ab c')).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!${tag}>`)).toEqual('evil!');
+ expect(sanitizeHtml(defaultDoc, `<${tag}>evil!${tag}>`)).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!${tag}>`)).toEqual('');
+ expect(sanitizeHtml(defaultDoc, `<${tag}>evil!${tag}>`)).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, 'bar')).toEqual('foobar');
- expect(_sanitizeHtml(defaultDoc, 'foobar')).toEqual('foobar');
+ expect(sanitizeHtml(defaultDoc, 'bar')).toEqual('foobar');
+ expect(sanitizeHtml(defaultDoc, 'foobar')).toEqual('foobar');
});
it('should not enter an infinite loop on clobbered elements', () => {
@@ -200,18 +204,17 @@ import {isDOMParserAvailable} from '../../src/sanitization/inert_body';
// Anyway what we want to test is that browsers do not enter an infinite loop which would
// result in a timeout error for the test.
try {
- _sanitizeHtml(defaultDoc, '');
+ sanitizeHtml(defaultDoc, '');
} catch (e) {
// depending on the browser, we might ge an exception
}
try {
- _sanitizeHtml(defaultDoc, '');
+ sanitizeHtml(defaultDoc, '');
} catch (e) {
// depending on the browser, we might ge an exception
}
try {
- _sanitizeHtml(
- defaultDoc, '');
+ sanitizeHtml(defaultDoc, '');
} catch (e) {
// depending on the browser, we might ge an exception
}
@@ -220,7 +223,7 @@ import {isDOMParserAvailable} from '../../src/sanitization/inert_body';
// See
// https://github.com/cure53/DOMPurify/blob/a992d3a75031cb8bb032e5ea8399ba972bdf9a65/src/purify.js#L439-L449
it('should not allow JavaScript execution when creating inert document', () => {
- const output = _sanitizeHtml(defaultDoc, ' ');
+ const output = sanitizeHtml(defaultDoc, ' ');
const window = defaultDoc.defaultView;
if (window) {
expect(window.xxx).toBe(undefined);
@@ -232,7 +235,7 @@ import {isDOMParserAvailable} from '../../src/sanitization/inert_body';
// See https://github.com/cure53/DOMPurify/releases/tag/0.6.7
it('should not allow JavaScript hidden in badly formed HTML to get through sanitization (Firefox bug)',
() => {
- expect(_sanitizeHtml(
+ expect(sanitizeHtml(
defaultDoc, ' '))
.toEqual(
isDOMParserAvailable() ?
@@ -245,7 +248,7 @@ import {isDOMParserAvailable} from '../../src/sanitization/inert_body';
if (browserDetection.isWebkit) {
it('should prevent mXSS attacks', function() {
// In Chrome Canary 62, the ideographic space character is kept as a stringified HTML entity
- expect(_sanitizeHtml(defaultDoc, 'CLICKME '))
+ expect(sanitizeHtml(defaultDoc, 'CLICKME '))
.toMatch(/CLICKME<\/a>/);
});
}
diff --git a/packages/core/test/sanitization/sanitization_spec.ts b/packages/core/test/sanitization/sanitization_spec.ts
index 389e28b52a..e144427a6d 100644
--- a/packages/core/test/sanitization/sanitization_spec.ts
+++ b/packages/core/test/sanitization/sanitization_spec.ts
@@ -29,11 +29,11 @@ describe('sanitization', () => {
}
}
it('should sanitize html', () => {
- expect(ɵɵsanitizeHtml('
')).toEqual('
');
- expect(ɵɵsanitizeHtml(new Wrap('
'))).toEqual('
');
- expect(ɵɵsanitizeHtml(' '))
+ expect(ɵɵsanitizeHtml('
').toString()).toEqual('
');
+ expect(ɵɵsanitizeHtml(new Wrap('
')).toString()).toEqual('
');
+ expect(ɵɵsanitizeHtml(' ').toString())
.toEqual(' ');
- expect(ɵɵsanitizeHtml(new Wrap(' ')))
+ expect(ɵɵsanitizeHtml(new Wrap(' ')).toString())
.toEqual(' ');
expect(() => ɵɵsanitizeHtml(bypassSanitizationTrustUrl(' ')))
.toThrowError(/Required a safe HTML, got a URL/);
diff --git a/packages/platform-browser/src/security/dom_sanitization_service.ts b/packages/platform-browser/src/security/dom_sanitization_service.ts
index 69c08cb201..1521a93f39 100644
--- a/packages/platform-browser/src/security/dom_sanitization_service.ts
+++ b/packages/platform-browser/src/security/dom_sanitization_service.ts
@@ -162,7 +162,7 @@ export class DomSanitizerImpl extends DomSanitizer {
if (allowSanitizationBypassOrThrow(value, BypassType.Html)) {
return unwrapSafeValue(value);
}
- return _sanitizeHtml(this._doc, String(value));
+ return _sanitizeHtml(this._doc, String(value)).toString();
case SecurityContext.STYLE:
if (allowSanitizationBypassOrThrow(value, BypassType.Style)) {
return unwrapSafeValue(value);