@ -70,24 +70,34 @@ abstract class SafeValueImpl implements SafeValue {
|
||||
}
|
||||
|
||||
class SafeHtmlImpl extends SafeValueImpl implements SafeHtml {
|
||||
getTypeName() { return BypassType.Html; }
|
||||
getTypeName() {
|
||||
return BypassType.Html;
|
||||
}
|
||||
}
|
||||
class SafeStyleImpl extends SafeValueImpl implements SafeStyle {
|
||||
getTypeName() { return BypassType.Style; }
|
||||
getTypeName() {
|
||||
return BypassType.Style;
|
||||
}
|
||||
}
|
||||
class SafeScriptImpl extends SafeValueImpl implements SafeScript {
|
||||
getTypeName() { return BypassType.Script; }
|
||||
getTypeName() {
|
||||
return BypassType.Script;
|
||||
}
|
||||
}
|
||||
class SafeUrlImpl extends SafeValueImpl implements SafeUrl {
|
||||
getTypeName() { return BypassType.Url; }
|
||||
getTypeName() {
|
||||
return BypassType.Url;
|
||||
}
|
||||
}
|
||||
class SafeResourceUrlImpl extends SafeValueImpl implements SafeResourceUrl {
|
||||
getTypeName() { return BypassType.ResourceUrl; }
|
||||
getTypeName() {
|
||||
return BypassType.ResourceUrl;
|
||||
}
|
||||
}
|
||||
|
||||
export function unwrapSafeValue(value: SafeValue): string;
|
||||
export function unwrapSafeValue<T>(value: T): T;
|
||||
export function unwrapSafeValue<T>(value: T | SafeValue): T {
|
||||
export function unwrapSafeValue<T>(value: T|SafeValue): T {
|
||||
return value instanceof SafeValueImpl ? value.changingThisBreaksApplicationSecurity as any as T :
|
||||
value as any as T;
|
||||
}
|
||||
|
@ -114,19 +114,19 @@ class SanitizingHtmlSerializer {
|
||||
// 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 = el.firstChild !;
|
||||
let current: Node = el.firstChild!;
|
||||
let traverseContent = true;
|
||||
while (current) {
|
||||
if (current.nodeType === Node.ELEMENT_NODE) {
|
||||
traverseContent = this.startElement(current as Element);
|
||||
} else if (current.nodeType === Node.TEXT_NODE) {
|
||||
this.chars(current.nodeValue !);
|
||||
this.chars(current.nodeValue!);
|
||||
} else {
|
||||
// Strip non-element, non-text nodes.
|
||||
this.sanitizedSomething = true;
|
||||
}
|
||||
if (traverseContent && current.firstChild) {
|
||||
current = current.firstChild !;
|
||||
current = current.firstChild!;
|
||||
continue;
|
||||
}
|
||||
while (current) {
|
||||
@ -135,14 +135,14 @@ class SanitizingHtmlSerializer {
|
||||
this.endElement(current as Element);
|
||||
}
|
||||
|
||||
let next = this.checkClobberedElement(current, current.nextSibling !);
|
||||
let next = this.checkClobberedElement(current, current.nextSibling!);
|
||||
|
||||
if (next) {
|
||||
current = next;
|
||||
break;
|
||||
}
|
||||
|
||||
current = this.checkClobberedElement(current, current.parentNode !);
|
||||
current = this.checkClobberedElement(current, current.parentNode!);
|
||||
}
|
||||
}
|
||||
return this.buf.join('');
|
||||
@ -167,13 +167,13 @@ class SanitizingHtmlSerializer {
|
||||
const elAttrs = element.attributes;
|
||||
for (let i = 0; i < elAttrs.length; i++) {
|
||||
const elAttr = elAttrs.item(i);
|
||||
const attrName = elAttr !.name;
|
||||
const attrName = elAttr!.name;
|
||||
const lower = attrName.toLowerCase();
|
||||
if (!VALID_ATTRS.hasOwnProperty(lower)) {
|
||||
this.sanitizedSomething = true;
|
||||
continue;
|
||||
}
|
||||
let value = elAttr !.value;
|
||||
let value = elAttr!.value;
|
||||
// TODO(martinprobst): Special case image URIs for data:image/...
|
||||
if (URI_ATTRS[lower]) value = _sanitizeUrl(value);
|
||||
if (SRCSET_ATTRS[lower]) value = sanitizeSrcset(value);
|
||||
@ -192,14 +192,16 @@ class SanitizingHtmlSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
private chars(chars: string) { this.buf.push(encodeEntities(chars)); }
|
||||
private chars(chars: string) {
|
||||
this.buf.push(encodeEntities(chars));
|
||||
}
|
||||
|
||||
checkClobberedElement(node: Node, nextNode: Node): Node {
|
||||
if (nextNode &&
|
||||
(node.compareDocumentPosition(nextNode) &
|
||||
Node.DOCUMENT_POSITION_CONTAINED_BY) === Node.DOCUMENT_POSITION_CONTAINED_BY) {
|
||||
throw new Error(
|
||||
`Failed to sanitize html because the element is clobbered: ${(node as Element).outerHTML}`);
|
||||
throw new Error(`Failed to sanitize html because the element is clobbered: ${
|
||||
(node as Element).outerHTML}`);
|
||||
}
|
||||
return nextNode;
|
||||
}
|
||||
@ -227,7 +229,9 @@ function encodeEntities(value: string) {
|
||||
})
|
||||
.replace(
|
||||
NON_ALPHANUMERIC_REGEXP,
|
||||
function(match: string) { return '&#' + match.charCodeAt(0) + ';'; })
|
||||
function(match: string) {
|
||||
return '&#' + match.charCodeAt(0) + ';';
|
||||
})
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
@ -258,13 +262,13 @@ export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string
|
||||
mXSSAttempts--;
|
||||
|
||||
unsafeHtml = parsedHtml;
|
||||
parsedHtml = inertBodyElement !.innerHTML;
|
||||
parsedHtml = inertBodyElement!.innerHTML;
|
||||
inertBodyElement = inertBodyHelper.getInertBodyElement(unsafeHtml);
|
||||
} while (unsafeHtml !== parsedHtml);
|
||||
|
||||
const sanitizer = new SanitizingHtmlSerializer();
|
||||
const safeHtml = sanitizer.sanitizeChildren(
|
||||
getTemplateContent(inertBodyElement !) as Element || inertBodyElement);
|
||||
getTemplateContent(inertBodyElement!) as Element || inertBodyElement);
|
||||
if (isDevMode() && sanitizer.sanitizedSomething) {
|
||||
console.warn(
|
||||
'WARNING: sanitizing HTML stripped some content, see http://g.co/ng/security#xss');
|
||||
|
@ -80,7 +80,7 @@ export class InertBodyHelper {
|
||||
xhr.open('GET', 'data:text/html;charset=utf-8,' + html, false);
|
||||
xhr.send(undefined);
|
||||
const body: HTMLBodyElement = xhr.response.body;
|
||||
body.removeChild(body.firstChild !);
|
||||
body.removeChild(body.firstChild!);
|
||||
return body;
|
||||
}
|
||||
|
||||
@ -95,11 +95,9 @@ export class InertBodyHelper {
|
||||
// `<head>` tag.
|
||||
html = '<body><remove></remove>' + html + '</body>';
|
||||
try {
|
||||
const body = new (window as any)
|
||||
.DOMParser()
|
||||
.parseFromString(html, 'text/html')
|
||||
.body as HTMLBodyElement;
|
||||
body.removeChild(body.firstChild !);
|
||||
const body = new (window as any).DOMParser().parseFromString(html, 'text/html').body as
|
||||
HTMLBodyElement;
|
||||
body.removeChild(body.firstChild!);
|
||||
return body;
|
||||
} catch {
|
||||
return null;
|
||||
@ -152,7 +150,7 @@ export class InertBodyHelper {
|
||||
// loop backwards so that we can support removals.
|
||||
for (let i = elAttrs.length - 1; 0 < i; i--) {
|
||||
const attrib = elAttrs.item(i);
|
||||
const attrName = attrib !.name;
|
||||
const attrName = attrib!.name;
|
||||
if (attrName === 'xmlns:ns1' || attrName.indexOf('ns1:') === 0) {
|
||||
el.removeAttribute(attrName);
|
||||
}
|
||||
|
@ -11,11 +11,11 @@ import {SANITIZER} from '../render3/interfaces/view';
|
||||
import {getLView} from '../render3/state';
|
||||
import {renderStringify} from '../render3/util/misc_utils';
|
||||
|
||||
import {BypassType, allowSanitizationBypassAndThrow, unwrapSafeValue} from './bypass';
|
||||
import {allowSanitizationBypassAndThrow, BypassType, unwrapSafeValue} from './bypass';
|
||||
import {_sanitizeHtml as _sanitizeHtml} from './html_sanitizer';
|
||||
import {Sanitizer} from './sanitizer';
|
||||
import {SecurityContext} from './security';
|
||||
import {StyleSanitizeFn, StyleSanitizeMode, _sanitizeStyle} from './style_sanitizer';
|
||||
import {_sanitizeStyle, StyleSanitizeFn, StyleSanitizeMode} from './style_sanitizer';
|
||||
import {_sanitizeUrl as _sanitizeUrl} from './url_sanitizer';
|
||||
|
||||
|
||||
@ -152,8 +152,9 @@ export function ɵɵsanitizeScript(unsafeScript: any): string {
|
||||
* If tag and prop names don't match Resource URL schema, use URL sanitizer.
|
||||
*/
|
||||
export function getUrlSanitizer(tag: string, prop: string) {
|
||||
if ((prop === 'src' && (tag === 'embed' || tag === 'frame' || tag === 'iframe' ||
|
||||
tag === 'media' || tag === 'script')) ||
|
||||
if ((prop === 'src' &&
|
||||
(tag === 'embed' || tag === 'frame' || tag === 'iframe' || tag === 'media' ||
|
||||
tag === 'script')) ||
|
||||
(prop === 'href' && (tag === 'base' || tag === 'link'))) {
|
||||
return ɵɵsanitizeResourceUrl;
|
||||
}
|
||||
@ -186,7 +187,7 @@ export function ɵɵsanitizeUrlOrResourceUrl(unsafeUrl: any, tag: string, prop:
|
||||
* @publicApi
|
||||
*/
|
||||
export const ɵɵdefaultStyleSanitizer =
|
||||
(function(prop: string, value: string|null, mode?: StyleSanitizeMode): string | boolean | null {
|
||||
(function(prop: string, value: string|null, mode?: StyleSanitizeMode): string|boolean|null {
|
||||
if (value === undefined && mode === undefined) {
|
||||
// This is a workaround for the fact that `StyleSanitizeFn` should not exist once PR#34480
|
||||
// lands. For now the `StyleSanitizeFn` and should act like `(value: any) => string` as a
|
||||
|
Reference in New Issue
Block a user