angular-cn/packages/core/test/sanitization/sanatization_spec.ts
Miško Hevery 2e4d17f3a9 perf(core): make sanitization tree-shakable in Ivy mode (#31934)
In VE the `Sanitizer` is always available in `BrowserModule` because the VE retrieves it using injection.

In Ivy the injection is optional and we have instructions instead of component definition arrays. The implication of this is that in Ivy the instructions can pull in the sanitizer only when they are working with a property which is known to be unsafe. Because the Injection is optional this works even if no Sanitizer is present. So in Ivy we first use the sanitizer which is pulled in by the instruction, unless one is available through the `Injector` then we use that one instead.

This PR does few things:
1) It makes `Sanitizer` optional in Ivy.
2) It makes `DomSanitizer` tree shakable.
3) It aligns the semantics of Ivy `Sanitizer` with that of the Ivy sanitization rules.
4) It refactors `DomSanitizer` to use same functions as Ivy sanitization for consistency.

PR Close #31934
2019-08-15 10:30:12 -07:00

135 lines
6.7 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {SECURITY_SCHEMA} from '@angular/compiler/src/schema/dom_security_schema';
import {HEADER_OFFSET, LView} from '@angular/core/src/render3/interfaces/view';
import {setTNodeAndViewData} from '@angular/core/src/render3/state';
import {bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl} from '../../src/sanitization/bypass';
import {getUrlSanitizer, ɵɵsanitizeHtml, ɵɵsanitizeResourceUrl, ɵɵsanitizeScript, ɵɵsanitizeStyle, ɵɵsanitizeUrl, ɵɵsanitizeUrlOrResourceUrl} from '../../src/sanitization/sanitization';
import {SecurityContext} from '../../src/sanitization/security';
function fakeLView(): LView {
return [null, {}] as LView;
}
describe('sanitization', () => {
beforeEach(() => setTNodeAndViewData(null !, fakeLView()));
afterEach(() => setTNodeAndViewData(null !, null !));
class Wrap {
constructor(private value: string) {}
toString() { return this.value; }
}
it('should sanitize html', () => {
expect(ɵɵsanitizeHtml('<div></div>')).toEqual('<div></div>');
expect(ɵɵsanitizeHtml(new Wrap('<div></div>'))).toEqual('<div></div>');
expect(ɵɵsanitizeHtml('<img src="javascript:true">'))
.toEqual('<img src="unsafe:javascript:true">');
expect(ɵɵsanitizeHtml(new Wrap('<img src="javascript:true">')))
.toEqual('<img src="unsafe:javascript:true">');
expect(() => ɵɵsanitizeHtml(bypassSanitizationTrustUrl('<img src="javascript:true">')))
.toThrowError(/Required a safe HTML, got a URL/);
expect(ɵɵsanitizeHtml(bypassSanitizationTrustHtml('<img src="javascript:true">')))
.toEqual('<img src="javascript:true">');
});
it('should sanitize url', () => {
expect(ɵɵsanitizeUrl('http://server')).toEqual('http://server');
expect(ɵɵsanitizeUrl(new Wrap('http://server'))).toEqual('http://server');
expect(ɵɵsanitizeUrl('javascript:true')).toEqual('unsafe:javascript:true');
expect(ɵɵsanitizeUrl(new Wrap('javascript:true'))).toEqual('unsafe:javascript:true');
expect(() => ɵɵsanitizeUrl(bypassSanitizationTrustHtml('javascript:true')))
.toThrowError(/Required a safe URL, got a HTML/);
expect(ɵɵsanitizeUrl(bypassSanitizationTrustUrl('javascript:true'))).toEqual('javascript:true');
});
it('should sanitize resourceUrl', () => {
const ERROR = 'unsafe value used in a resource URL context (see http://g.co/ng/security#xss)';
expect(() => ɵɵsanitizeResourceUrl('http://server')).toThrowError(ERROR);
expect(() => ɵɵsanitizeResourceUrl('javascript:true')).toThrowError(ERROR);
expect(() => ɵɵsanitizeResourceUrl(bypassSanitizationTrustHtml('javascript:true')))
.toThrowError(/Required a safe ResourceURL, got a HTML/);
expect(ɵɵsanitizeResourceUrl(bypassSanitizationTrustResourceUrl('javascript:true')))
.toEqual('javascript:true');
});
it('should sanitize style', () => {
expect(ɵɵsanitizeStyle('red')).toEqual('red');
expect(ɵɵsanitizeStyle(new Wrap('red'))).toEqual('red');
expect(ɵɵsanitizeStyle('url("http://server")')).toEqual('unsafe');
expect(ɵɵsanitizeStyle(new Wrap('url("http://server")'))).toEqual('unsafe');
expect(() => ɵɵsanitizeStyle(bypassSanitizationTrustHtml('url("http://server")')))
.toThrowError(/Required a safe Style, got a HTML/);
expect(ɵɵsanitizeStyle(bypassSanitizationTrustStyle('url("http://server")')))
.toEqual('url("http://server")');
});
it('should sanitize script', () => {
const ERROR = 'unsafe value used in a script context';
expect(() => ɵɵsanitizeScript('true')).toThrowError(ERROR);
expect(() => ɵɵsanitizeScript('true')).toThrowError(ERROR);
expect(() => ɵɵsanitizeScript(bypassSanitizationTrustHtml('true')))
.toThrowError(/Required a safe Script, got a HTML/);
expect(ɵɵsanitizeScript(bypassSanitizationTrustScript('true'))).toEqual('true');
});
it('should select correct sanitizer for URL props', () => {
// making sure security schema we have on compiler side is in sync with the `getUrlSanitizer`
// runtime function definition
const schema = SECURITY_SCHEMA();
const contextsByProp: Map<string, Set<number>> = new Map();
const sanitizerNameByContext: Map<number, Function> = new Map([
[SecurityContext.URL, ɵɵsanitizeUrl], [SecurityContext.RESOURCE_URL, ɵɵsanitizeResourceUrl]
]);
Object.keys(schema).forEach(key => {
const context = schema[key];
if (context === SecurityContext.URL || SecurityContext.RESOURCE_URL) {
const [tag, prop] = key.split('|');
const contexts = contextsByProp.get(prop) || new Set<number>();
contexts.add(context);
contextsByProp.set(prop, contexts);
// check only in case a prop can be a part of both URL contexts
if (contexts.size === 2) {
expect(getUrlSanitizer(tag, prop)).toEqual(sanitizerNameByContext.get(context) !);
}
}
});
});
it('should sanitize resourceUrls via sanitizeUrlOrResourceUrl', () => {
const ERROR = 'unsafe value used in a resource URL context (see http://g.co/ng/security#xss)';
expect(() => ɵɵsanitizeUrlOrResourceUrl('http://server', 'iframe', 'src')).toThrowError(ERROR);
expect(() => ɵɵsanitizeUrlOrResourceUrl('javascript:true', 'iframe', 'src'))
.toThrowError(ERROR);
expect(
() => ɵɵsanitizeUrlOrResourceUrl(
bypassSanitizationTrustHtml('javascript:true'), 'iframe', 'src'))
.toThrowError(/Required a safe ResourceURL, got a HTML/);
expect(ɵɵsanitizeUrlOrResourceUrl(
bypassSanitizationTrustResourceUrl('javascript:true'), 'iframe', 'src'))
.toEqual('javascript:true');
});
it('should sanitize urls via sanitizeUrlOrResourceUrl', () => {
expect(ɵɵsanitizeUrlOrResourceUrl('http://server', 'a', 'href')).toEqual('http://server');
expect(ɵɵsanitizeUrlOrResourceUrl(new Wrap('http://server'), 'a', 'href'))
.toEqual('http://server');
expect(ɵɵsanitizeUrlOrResourceUrl('javascript:true', 'a', 'href'))
.toEqual('unsafe:javascript:true');
expect(ɵɵsanitizeUrlOrResourceUrl(new Wrap('javascript:true'), 'a', 'href'))
.toEqual('unsafe:javascript:true');
expect(
() =>
ɵɵsanitizeUrlOrResourceUrl(bypassSanitizationTrustHtml('javascript:true'), 'a', 'href'))
.toThrowError(/Required a safe URL, got a HTML/);
expect(ɵɵsanitizeUrlOrResourceUrl(bypassSanitizationTrustUrl('javascript:true'), 'a', 'href'))
.toEqual('javascript:true');
});
});