From 538f1d980f62a2f01ddc87c56c4ddb63a5c4298f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mis=CC=8Cko=20Hevery?= Date: Thu, 1 Mar 2018 13:16:13 -0800 Subject: [PATCH] refactor(core): move sanitization into core (#22540) This is in preparation of having Ivy have sanitization inline. PR Close #22540 --- packages/core/src/core.ts | 2 +- packages/core/src/core_private_export.ts | 3 + .../core/src/core_render3_private_export.ts | 4 ++ .../src/sanitization}/html_sanitizer.ts | 64 +++++++++++-------- .../src/sanitization}/inert_body.ts | 48 +++++++------- packages/core/src/sanitization/readme.md | 10 +++ .../core/src/{ => sanitization}/security.ts | 0 .../src/sanitization}/style_sanitizer.ts | 4 +- .../src/sanitization}/url_sanitizer.ts | 4 +- packages/core/src/view/element.ts | 2 +- packages/core/src/view/services.ts | 2 +- packages/core/src/view/types.ts | 2 +- .../test/sanitization}/html_sanitizer_spec.ts | 11 ++-- .../sanitization}/style_sanitizer_spec.ts | 10 +-- .../test/sanitization}/url_sanitizer_spec.ts | 10 +-- packages/core/testing/src/render3.ts | 7 ++ .../src/security/dom_sanitization_service.ts | 6 +- 17 files changed, 108 insertions(+), 81 deletions(-) rename packages/{platform-browser/src/security => core/src/sanitization}/html_sanitizer.ts (83%) rename packages/{platform-browser/src/security => core/src/sanitization}/inert_body.ts (79%) create mode 100644 packages/core/src/sanitization/readme.md rename packages/core/src/{ => sanitization}/security.ts (100%) rename packages/{platform-browser/src/security => core/src/sanitization}/style_sanitizer.ts (98%) rename packages/{platform-browser/src/security => core/src/sanitization}/url_sanitizer.ts (95%) rename packages/{platform-browser/test/security => core/test/sanitization}/html_sanitizer_spec.ts (94%) rename packages/{platform-browser/test/security => core/test/sanitization}/style_sanitizer_spec.ts (90%) rename packages/{platform-browser/test/security => core/test/sanitization}/url_sanitizer_spec.ts (93%) diff --git a/packages/core/src/core.ts b/packages/core/src/core.ts index 713c99fe06..eae4219f21 100644 --- a/packages/core/src/core.ts +++ b/packages/core/src/core.ts @@ -33,7 +33,7 @@ export {EventEmitter} from './event_emitter'; export {ErrorHandler} from './error_handler'; export * from './core_private_export'; export * from './core_render3_private_export'; -export {Sanitizer, SecurityContext} from './security'; +export {Sanitizer, SecurityContext} from './sanitization/security'; export * from './codegen_private_exports'; export * from './animation/animation_metadata_wrapped'; import {AnimationTriggerMetadata} from './animation/animation_metadata_wrapped'; diff --git a/packages/core/src/core_private_export.ts b/packages/core/src/core_private_export.ts index 26e714858f..14166b5dfb 100644 --- a/packages/core/src/core_private_export.ts +++ b/packages/core/src/core_private_export.ts @@ -18,6 +18,9 @@ export {CodegenComponentFactoryResolver as ɵCodegenComponentFactoryResolver} fr export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/reflection_capabilities'; export {GetterFn as ɵGetterFn, MethodFn as ɵMethodFn, SetterFn as ɵSetterFn} from './reflection/types'; export {DirectRenderer as ɵDirectRenderer, RenderDebugInfo as ɵRenderDebugInfo} from './render/api'; +export {sanitizeHtml as ɵsanitizeHtml} from './sanitization/html_sanitizer'; +export {sanitizeStyle as ɵsanitizeStyle} from './sanitization/style_sanitizer'; +export {sanitizeUrl as ɵsanitizeUrl} from './sanitization/url_sanitizer'; export {global as ɵglobal, looseIdentical as ɵlooseIdentical, stringify as ɵstringify} from './util'; export {makeDecorator as ɵmakeDecorator} from './util/decorators'; export {isObservable as ɵisObservable, isPromise as ɵisPromise} from './util/lang'; diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 41a4a91bce..d13d4b212c 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -72,3 +72,7 @@ export { Pp as ɵPp, } from './render3/index'; // clang-format on + +export {htmlSanitizer as ɵhtmlSanitizer} from './sanitization/html_sanitizer'; +export {styleSanitizer as ɵstyleSanitizer} from './sanitization/style_sanitizer'; +export {urlSanitizer as ɵurlSanitizer, resourceUrlSanitizer as ɵresourceUrlSanitizer} from './sanitization/url_sanitizer'; diff --git a/packages/platform-browser/src/security/html_sanitizer.ts b/packages/core/src/sanitization/html_sanitizer.ts similarity index 83% rename from packages/platform-browser/src/security/html_sanitizer.ts rename to packages/core/src/sanitization/html_sanitizer.ts index fd49067b42..1f60b9e01f 100644 --- a/packages/platform-browser/src/security/html_sanitizer.ts +++ b/packages/core/src/sanitization/html_sanitizer.ts @@ -8,8 +8,6 @@ import {isDevMode} from '@angular/core'; -import {DomAdapter, getDOM} from '../dom/dom_adapter'; - import {InertBodyHelper} from './inert_body'; import {sanitizeSrcset, sanitizeUrl} from './url_sanitizer'; @@ -95,58 +93,61 @@ class SanitizingHtmlSerializer { // because characters were re-encoded. public sanitizedSomething = false; private buf: string[] = []; - private DOM = getDOM(); sanitizeChildren(el: Element): string { // This cannot use a TreeWalker, as it has to run on Angular's various DOM adapters. // However this code never accesses properties off of `document` before deleting its contents // again, so it shouldn't be vulnerable to DOM clobbering. - let current: Node = this.DOM.firstChild(el) !; + let current: Node = el.firstChild !; while (current) { - if (this.DOM.isElementNode(current)) { + if (current.nodeType === Node.ELEMENT_NODE) { this.startElement(current as Element); - } else if (this.DOM.isTextNode(current)) { - this.chars(this.DOM.nodeValue(current) !); + } else if (current.nodeType === Node.TEXT_NODE) { + this.chars(current.nodeValue !); } else { // Strip non-element, non-text nodes. this.sanitizedSomething = true; } - if (this.DOM.firstChild(current)) { - current = this.DOM.firstChild(current) !; + if (current.firstChild) { + current = current.firstChild !; continue; } while (current) { // Leaving the element. Walk up and to the right, closing tags as we go. - if (this.DOM.isElementNode(current)) { + if (current.nodeType === Node.ELEMENT_NODE) { this.endElement(current as Element); } - let next = this.checkClobberedElement(current, this.DOM.nextSibling(current) !); + let next = this.checkClobberedElement(current, current.nextSibling !); if (next) { current = next; break; } - current = this.checkClobberedElement(current, this.DOM.parentElement(current) !); + current = this.checkClobberedElement(current, current.parentNode !); } } return this.buf.join(''); } private startElement(element: Element) { - const tagName = this.DOM.nodeName(element).toLowerCase(); + const tagName = element.nodeName.toLowerCase(); if (!VALID_ELEMENTS.hasOwnProperty(tagName)) { this.sanitizedSomething = true; return; } this.buf.push('<'); this.buf.push(tagName); - this.DOM.attributeMap(element).forEach((value: string, attrName: string) => { + const elAttrs = element.attributes; + for (let i = 0; i < elAttrs.length; i++) { + const elAttr = elAttrs.item(i); + const attrName = elAttr.name; + let value = elAttr.value; const lower = attrName.toLowerCase(); if (!VALID_ATTRS.hasOwnProperty(lower)) { this.sanitizedSomething = true; - return; + continue; } // TODO(martinprobst): Special case image URIs for data:image/... if (URI_ATTRS[lower]) value = sanitizeUrl(value); @@ -156,12 +157,12 @@ class SanitizingHtmlSerializer { this.buf.push('="'); this.buf.push(encodeEntities(value)); this.buf.push('"'); - }); + }; this.buf.push('>'); } private endElement(current: Element) { - const tagName = this.DOM.nodeName(current).toLowerCase(); + const tagName = current.nodeName.toLowerCase(); if (VALID_ELEMENTS.hasOwnProperty(tagName) && !VOID_ELEMENTS.hasOwnProperty(tagName)) { this.buf.push('el).content : null; +} +function isTemplateElement(el: Node): boolean { + return el.nodeType === Node.ELEMENT_NODE && el.nodeName === 'TEMPLATE'; +} diff --git a/packages/platform-browser/src/security/inert_body.ts b/packages/core/src/sanitization/inert_body.ts similarity index 79% rename from packages/platform-browser/src/security/inert_body.ts rename to packages/core/src/sanitization/inert_body.ts index d4cb58fb45..85463c7c9d 100644 --- a/packages/platform-browser/src/security/inert_body.ts +++ b/packages/core/src/sanitization/inert_body.ts @@ -6,8 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {DomAdapter, getDOM} from '../dom/dom_adapter'; - /** * This helper class is used to get hold of an inert tree of DOM elements containing dirty HTML * that needs sanitizing. @@ -18,22 +16,22 @@ import {DomAdapter, getDOM} from '../dom/dom_adapter'; */ export class InertBodyHelper { private inertBodyElement: HTMLElement; + private inertDocument: Document; - constructor(private defaultDoc: any, private DOM: DomAdapter) { - const inertDocument = this.DOM.createHtmlDocument(); - this.inertBodyElement = inertDocument.body; + constructor(private defaultDoc: Document) { + this.inertDocument = this.defaultDoc.implementation.createHTMLDocument('sanitization-inert'); + this.inertBodyElement = this.inertDocument.body; if (this.inertBodyElement == null) { // usually there should be only one body element in the document, but IE doesn't have any, so // we need to create one. - const inertHtml = this.DOM.createElement('html', inertDocument); - this.inertBodyElement = this.DOM.createElement('body', inertDocument); - this.DOM.appendChild(inertHtml, this.inertBodyElement); - this.DOM.appendChild(inertDocument, inertHtml); + const inertHtml = this.inertDocument.createElement('html'); + this.inertDocument.appendChild(inertHtml); + this.inertBodyElement = this.inertDocument.createElement('body'); + inertHtml.appendChild(this.inertBodyElement); } - this.DOM.setInnerHTML( - this.inertBodyElement, ''); + this.inertBodyElement.innerHTML = ''; if (this.inertBodyElement.querySelector && !this.inertBodyElement.querySelector('svg')) { // We just hit the Safari 10.1 bug - which allows JS to run inside the SVG G element // so use the XHR strategy. @@ -41,8 +39,8 @@ export class InertBodyHelper { return; } - this.DOM.setInnerHTML( - this.inertBodyElement, '

'); + this.inertBodyElement.innerHTML = + '

'; if (this.inertBodyElement.querySelector && this.inertBodyElement.querySelector('svg img')) { // We just hit the Firefox bug - which prevents the inner img JS from being sanitized // so use the DOMParser strategy, if it is available. @@ -118,17 +116,17 @@ export class InertBodyHelper { */ private getInertBodyElement_InertDocument(html: string) { // Prefer using