/** * @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 {LView} from '@angular/core/src/render3/interfaces/view'; import {enterView, leaveView} 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(() => enterView(fakeLView(), null)); afterEach(() => leaveView()); class Wrap { constructor(private value: string) {} toString() { return this.value; } } it('should sanitize html', () => { expect(ɵɵsanitizeHtml('
')).toEqual('
'); expect(ɵɵsanitizeHtml(new Wrap('
'))).toEqual('
'); expect(ɵɵsanitizeHtml('')) .toEqual(''); expect(ɵɵsanitizeHtml(new Wrap(''))) .toEqual(''); expect(() => ɵɵsanitizeHtml(bypassSanitizationTrustUrl(''))) .toThrowError(/Required a safe HTML, got a URL/); expect(ɵɵsanitizeHtml(bypassSanitizationTrustHtml(''))) .toEqual(''); }); 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> = new Map(); const sanitizerNameByContext: Map = 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(); 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'); }); });