From 8ebac24b48cb4ed86c3606dc0dee2c6646ef2244 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Mon, 14 Dec 2020 16:03:31 +0000 Subject: [PATCH] fix(core): ensure sanitizer works if DOMParser return null body (#40107) In some browsers, notably a mobile version of webkit on iPad, the result of calling `DOMParser.parseFromString()` returns a document whose `body` property is null until the next tick of the browser. Since this is of no use to us for sanitization, we now fall back to the "inert document" strategy for this case. Fixes #39834 PR Close #40107 --- packages/core/src/sanitization/inert_body.ts | 11 ++++++++++- .../core/test/sanitization/html_sanitizer_spec.ts | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/core/src/sanitization/inert_body.ts b/packages/core/src/sanitization/inert_body.ts index c460c5dc65..4086956ac6 100644 --- a/packages/core/src/sanitization/inert_body.ts +++ b/packages/core/src/sanitization/inert_body.ts @@ -16,7 +16,8 @@ import {trustedHTMLFromString} from '../util/security/trusted_types'; * Fallback: InertDocument strategy */ export function getInertBodyHelper(defaultDoc: Document): InertBodyHelper { - return isDOMParserAvailable() ? new DOMParserHelper() : new InertDocumentHelper(defaultDoc); + const inertDocumentHelper = new InertDocumentHelper(defaultDoc); + return isDOMParserAvailable() ? new DOMParserHelper(inertDocumentHelper) : inertDocumentHelper; } export interface InertBodyHelper { @@ -31,6 +32,8 @@ export interface InertBodyHelper { * This is the default strategy used in browsers that support it. */ class DOMParserHelper implements InertBodyHelper { + constructor(private inertDocumentHelper: InertBodyHelper) {} + getInertBodyElement(html: string): HTMLElement|null { // We add these extra elements to ensure that the rest of the content is parsed as expected // e.g. leading whitespace is maintained and tags like `` do not get hoisted to the @@ -41,6 +44,12 @@ class DOMParserHelper implements InertBodyHelper { const body = new window.DOMParser() .parseFromString(trustedHTMLFromString(html) as string, 'text/html') .body as HTMLBodyElement; + if (body === null) { + // In some browsers (e.g. Mozilla/5.0 iPad AppleWebKit Mobile) the `body` property only + // becomes available in the following tick of the JS engine. In that case we fall back to + // the `inertDocumentHelper` instead. + return this.inertDocumentHelper.getInertBodyElement(html); + } body.removeChild(body.firstChild!); return body; } catch { diff --git a/packages/core/test/sanitization/html_sanitizer_spec.ts b/packages/core/test/sanitization/html_sanitizer_spec.ts index aae4a18421..01d805c9be 100644 --- a/packages/core/test/sanitization/html_sanitizer_spec.ts +++ b/packages/core/test/sanitization/html_sanitizer_spec.ts @@ -252,5 +252,14 @@ function sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string { .toMatch(/CLICKME<\/a>/); }); } + + if (isDOMParserAvailable()) { + it('should work even if DOMParser returns a null body', () => { + // Simulate `DOMParser.parseFromString()` returning a null body. + // See https://github.com/angular/angular/issues/39834 + spyOn(window.DOMParser.prototype, 'parseFromString').and.returnValue({body: null} as any); + expect(sanitizeHtml(defaultDoc, 'Hello, World')).toEqual('Hello, World'); + }); + } }); }