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
This commit is contained in:
parent
8dbd2204c3
commit
8ebac24b48
|
@ -16,7 +16,8 @@ import {trustedHTMLFromString} from '../util/security/trusted_types';
|
||||||
* Fallback: InertDocument strategy
|
* Fallback: InertDocument strategy
|
||||||
*/
|
*/
|
||||||
export function getInertBodyHelper(defaultDoc: Document): InertBodyHelper {
|
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 {
|
export interface InertBodyHelper {
|
||||||
|
@ -31,6 +32,8 @@ export interface InertBodyHelper {
|
||||||
* This is the default strategy used in browsers that support it.
|
* This is the default strategy used in browsers that support it.
|
||||||
*/
|
*/
|
||||||
class DOMParserHelper implements InertBodyHelper {
|
class DOMParserHelper implements InertBodyHelper {
|
||||||
|
constructor(private inertDocumentHelper: InertBodyHelper) {}
|
||||||
|
|
||||||
getInertBodyElement(html: string): HTMLElement|null {
|
getInertBodyElement(html: string): HTMLElement|null {
|
||||||
// We add these extra elements to ensure that the rest of the content is parsed as expected
|
// 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 `<meta>` do not get hoisted to the
|
// e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the
|
||||||
|
@ -41,6 +44,12 @@ class DOMParserHelper implements InertBodyHelper {
|
||||||
const body = new window.DOMParser()
|
const body = new window.DOMParser()
|
||||||
.parseFromString(trustedHTMLFromString(html) as string, 'text/html')
|
.parseFromString(trustedHTMLFromString(html) as string, 'text/html')
|
||||||
.body as HTMLBodyElement;
|
.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!);
|
body.removeChild(body.firstChild!);
|
||||||
return body;
|
return body;
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -252,5 +252,14 @@ function sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string {
|
||||||
.toMatch(/<a href="unsafe:( )?javascript:alert\(1\)">CLICKME<\/a>/);
|
.toMatch(/<a href="unsafe:( )?javascript:alert\(1\)">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');
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue