2018-03-01 20:14:01 -05:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
|
2018-04-14 14:52:53 -04:00
|
|
|
import {NgForOfContext} from '@angular/common';
|
2019-05-28 13:31:01 -04:00
|
|
|
|
2019-05-17 21:49:21 -04:00
|
|
|
import {ɵɵdefineComponent} from '../../src/render3/definition';
|
2019-09-09 16:14:26 -04:00
|
|
|
import {RenderFlags, ɵɵattribute, ɵɵclassMap, ɵɵelement, ɵɵelementEnd, ɵɵelementStart, ɵɵproperty, ɵɵselect, ɵɵstyleMap, ɵɵstyleProp, ɵɵstyleSanitizer, ɵɵtemplate, ɵɵtext, ɵɵtextInterpolate1} from '../../src/render3/index';
|
2018-10-12 21:49:00 -04:00
|
|
|
import {AttributeMarker} from '../../src/render3/interfaces/node';
|
2019-07-31 16:15:50 -04:00
|
|
|
import {bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, getSanitizationBypassType, unwrapSafeValue} from '../../src/sanitization/bypass';
|
2019-05-17 21:49:21 -04:00
|
|
|
import {ɵɵdefaultStyleSanitizer, ɵɵsanitizeHtml, ɵɵsanitizeResourceUrl, ɵɵsanitizeScript, ɵɵsanitizeStyle, ɵɵsanitizeUrl} from '../../src/sanitization/sanitization';
|
2019-07-31 16:15:50 -04:00
|
|
|
import {Sanitizer} from '../../src/sanitization/sanitizer';
|
|
|
|
import {SecurityContext} from '../../src/sanitization/security';
|
2019-04-04 14:41:52 -04:00
|
|
|
|
2018-04-14 14:52:53 -04:00
|
|
|
import {NgForOf} from './common_with_def';
|
2018-06-06 16:38:19 -04:00
|
|
|
import {ComponentFixture, TemplateFixture} from './render_util';
|
2018-03-01 20:14:01 -05:00
|
|
|
|
|
|
|
describe('instructions', () => {
|
2019-09-09 16:14:26 -04:00
|
|
|
function createAnchor() { ɵɵelement(0, 'a'); }
|
2018-05-09 18:30:16 -04:00
|
|
|
|
2019-09-23 14:08:51 -04:00
|
|
|
function createDiv() { ɵɵelement(0, 'div', 0); }
|
2018-03-01 20:14:01 -05:00
|
|
|
|
2019-05-17 21:49:21 -04:00
|
|
|
function createScript() { ɵɵelement(0, 'script'); }
|
2018-05-09 18:30:16 -04:00
|
|
|
|
2019-05-17 21:49:21 -04:00
|
|
|
describe('ɵɵselect', () => {
|
2019-05-06 15:45:09 -04:00
|
|
|
it('should error in DevMode if index is out of range', () => {
|
|
|
|
// Only one constant added, meaning only index `0` is valid.
|
|
|
|
const t = new TemplateFixture(createDiv, () => {}, 1, 0);
|
2019-05-17 21:49:21 -04:00
|
|
|
expect(() => { t.update(() => { ɵɵselect(-1); }); }).toThrow();
|
|
|
|
expect(() => { t.update(() => { ɵɵselect(1); }); }).toThrow();
|
2019-05-06 15:45:09 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2018-05-13 15:01:37 -04:00
|
|
|
describe('bind', () => {
|
2019-05-06 15:45:09 -04:00
|
|
|
it('should update bindings when value changes with the correct perf counters', () => {
|
2018-08-21 03:03:21 -04:00
|
|
|
const t = new TemplateFixture(createAnchor, () => {}, 1, 1);
|
2018-05-13 15:01:37 -04:00
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵproperty('title', 'Hello'); });
|
2018-05-13 15:01:37 -04:00
|
|
|
expect(t.html).toEqual('<a title="Hello"></a>');
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵproperty('title', 'World'); });
|
2018-05-13 15:01:37 -04:00
|
|
|
expect(t.html).toEqual('<a title="World"></a>');
|
|
|
|
expect(ngDevMode).toHaveProperties({
|
|
|
|
firstTemplatePass: 1,
|
|
|
|
tNode: 2, // 1 for hostElement + 1 for the template under test
|
2018-09-13 19:07:23 -04:00
|
|
|
tView: 2, // 1 for rootView + 1 for the template view
|
2018-05-13 15:01:37 -04:00
|
|
|
rendererCreateElement: 1,
|
|
|
|
rendererSetProperty: 2
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-05-06 15:45:09 -04:00
|
|
|
it('should not update bindings when value does not change, with the correct perf counters',
|
|
|
|
() => {
|
2019-05-28 13:31:01 -04:00
|
|
|
const idempotentUpdate = () => { ɵɵproperty('title', 'Hello'); };
|
2019-05-06 15:45:09 -04:00
|
|
|
const t = new TemplateFixture(createAnchor, idempotentUpdate, 1, 1);
|
2018-05-13 15:01:37 -04:00
|
|
|
|
2019-05-06 15:45:09 -04:00
|
|
|
t.update();
|
|
|
|
expect(t.html).toEqual('<a title="Hello"></a>');
|
2018-05-13 15:01:37 -04:00
|
|
|
|
2019-05-06 15:45:09 -04:00
|
|
|
t.update();
|
|
|
|
expect(t.html).toEqual('<a title="Hello"></a>');
|
|
|
|
expect(ngDevMode).toHaveProperties({
|
|
|
|
firstTemplatePass: 1,
|
|
|
|
tNode: 2, // 1 for hostElement + 1 for the template under test
|
|
|
|
tView: 2, // 1 for rootView + 1 for the template view
|
|
|
|
rendererCreateElement: 1,
|
|
|
|
rendererSetProperty: 1
|
|
|
|
});
|
|
|
|
});
|
2018-05-13 15:01:37 -04:00
|
|
|
});
|
|
|
|
|
2018-06-08 13:48:27 -04:00
|
|
|
describe('element', () => {
|
2019-05-06 15:45:09 -04:00
|
|
|
it('should create an element with the correct perf counters', () => {
|
2018-08-16 21:53:21 -04:00
|
|
|
const t = new TemplateFixture(() => {
|
2019-09-23 14:08:51 -04:00
|
|
|
ɵɵelement(0, 'div', 0);
|
|
|
|
}, () => {}, 1, 0, null, null, null, undefined, [['id', 'test', 'title', 'Hello']]);
|
2018-06-08 13:48:27 -04:00
|
|
|
|
2018-10-12 18:02:54 -04:00
|
|
|
const div = (t.hostElement as HTMLElement).querySelector('div') !;
|
2018-06-08 13:48:27 -04:00
|
|
|
expect(div.id).toEqual('test');
|
|
|
|
expect(div.title).toEqual('Hello');
|
2018-06-08 18:25:39 -04:00
|
|
|
expect(ngDevMode).toHaveProperties({
|
|
|
|
firstTemplatePass: 1,
|
|
|
|
tNode: 2, // 1 for div, 1 for host element
|
2018-09-13 19:07:23 -04:00
|
|
|
tView: 2, // 1 for rootView + 1 for the template view
|
2018-06-08 18:25:39 -04:00
|
|
|
rendererCreateElement: 1,
|
|
|
|
});
|
|
|
|
});
|
2018-06-08 13:48:27 -04:00
|
|
|
});
|
|
|
|
|
2019-05-31 13:39:14 -04:00
|
|
|
describe('attribute', () => {
|
2018-03-01 20:14:01 -05:00
|
|
|
it('should use sanitizer function', () => {
|
2019-05-31 13:39:14 -04:00
|
|
|
const t = new TemplateFixture(createDiv, () => {}, 1, 1);
|
2018-03-01 20:14:01 -05:00
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵattribute('title', 'javascript:true', ɵɵsanitizeUrl); });
|
2018-03-01 20:14:01 -05:00
|
|
|
expect(t.html).toEqual('<div title="unsafe:javascript:true"></div>');
|
|
|
|
|
2019-05-31 13:39:14 -04:00
|
|
|
t.update(() => {
|
|
|
|
ɵɵattribute('title', bypassSanitizationTrustUrl('javascript:true'), ɵɵsanitizeUrl);
|
|
|
|
});
|
2018-03-01 20:14:01 -05:00
|
|
|
expect(t.html).toEqual('<div title="javascript:true"></div>');
|
2018-04-14 14:52:53 -04:00
|
|
|
expect(ngDevMode).toHaveProperties({
|
|
|
|
firstTemplatePass: 1,
|
2018-05-16 08:56:01 -04:00
|
|
|
tNode: 2, // 1 for div, 1 for host element
|
2018-09-13 19:07:23 -04:00
|
|
|
tView: 2, // 1 for rootView + 1 for the template view
|
2018-04-14 14:52:53 -04:00
|
|
|
rendererCreateElement: 1,
|
|
|
|
rendererSetAttribute: 2
|
|
|
|
});
|
2018-03-01 20:14:01 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-03-26 17:57:36 -04:00
|
|
|
describe('property', () => {
|
2019-05-06 15:45:09 -04:00
|
|
|
/**
|
|
|
|
* TODO: We need to replace this with an acceptance test, but for right now,
|
|
|
|
* this is the only test that ensures chaining works, since code generation
|
|
|
|
* is not producing chained instructions yet.
|
|
|
|
*/
|
2019-03-26 17:57:36 -04:00
|
|
|
it('should chain', () => {
|
|
|
|
// <div [title]="title" [accesskey]="key"></div>
|
|
|
|
const t = new TemplateFixture(createDiv, () => {}, 1, 2);
|
2019-06-03 13:29:14 -04:00
|
|
|
t.update(() => { ɵɵproperty('title', 'one')('accessKey', 'A'); });
|
2019-03-26 17:57:36 -04:00
|
|
|
expect(t.html).toEqual('<div accesskey="A" title="one"></div>');
|
2019-06-03 13:29:14 -04:00
|
|
|
t.update(() => { ɵɵproperty('title', 'two')('accessKey', 'B'); });
|
2019-03-26 17:57:36 -04:00
|
|
|
expect(t.html).toEqual('<div accesskey="B" title="two"></div>');
|
|
|
|
expect(ngDevMode).toHaveProperties({
|
|
|
|
firstTemplatePass: 1,
|
|
|
|
tNode: 2, // 1 for div, 1 for host element
|
|
|
|
tView: 2, // 1 for rootView + 1 for the template view
|
|
|
|
rendererCreateElement: 1,
|
|
|
|
rendererSetProperty: 4,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-05-08 14:26:40 -04:00
|
|
|
describe('styleProp', () => {
|
2018-07-11 13:58:18 -04:00
|
|
|
it('should automatically sanitize unless a bypass operation is applied', () => {
|
2019-05-28 13:31:01 -04:00
|
|
|
const t = new TemplateFixture(() => { return createDiv(); }, () => {}, 1);
|
2018-06-19 15:45:00 -04:00
|
|
|
t.update(() => {
|
2019-05-28 13:31:01 -04:00
|
|
|
ɵɵstyleSanitizer(ɵɵdefaultStyleSanitizer);
|
|
|
|
ɵɵstyleProp('background-image', 'url("http://server")');
|
2018-06-19 15:45:00 -04:00
|
|
|
});
|
2018-03-01 20:14:01 -05:00
|
|
|
// nothing is set because sanitizer suppresses it.
|
|
|
|
expect(t.html).toEqual('<div></div>');
|
|
|
|
|
2018-06-19 15:45:00 -04:00
|
|
|
t.update(() => {
|
2019-07-31 16:15:50 -04:00
|
|
|
ɵɵstyleSanitizer(ɵɵdefaultStyleSanitizer);
|
2019-05-28 13:31:01 -04:00
|
|
|
ɵɵstyleProp('background-image', bypassSanitizationTrustStyle('url("http://server2")'));
|
2018-06-19 15:45:00 -04:00
|
|
|
});
|
2018-03-01 20:14:01 -05:00
|
|
|
expect((t.hostElement.firstChild as HTMLElement).style.getPropertyValue('background-image'))
|
2018-07-11 13:58:18 -04:00
|
|
|
.toEqual('url("http://server2")');
|
|
|
|
});
|
2018-03-01 20:14:01 -05:00
|
|
|
});
|
2018-03-08 16:57:56 -05:00
|
|
|
|
2019-05-08 14:26:40 -04:00
|
|
|
describe('styleMap', () => {
|
2019-09-23 14:08:51 -04:00
|
|
|
const attrs = [[AttributeMarker.Styles, 'height', '10px']];
|
|
|
|
|
|
|
|
function createDivWithStyle() { ɵɵelement(0, 'div', 0); }
|
2018-03-08 16:57:56 -05:00
|
|
|
|
|
|
|
it('should add style', () => {
|
2019-09-23 14:08:51 -04:00
|
|
|
const fixture = new TemplateFixture(
|
|
|
|
createDivWithStyle, () => {}, 1, 0, null, null, null, undefined, attrs);
|
2019-09-09 16:14:26 -04:00
|
|
|
fixture.update(() => { ɵɵstyleMap({'background-color': 'red'}); });
|
2018-08-22 12:17:59 -04:00
|
|
|
expect(fixture.html).toEqual('<div style="background-color: red; height: 10px;"></div>');
|
2018-03-08 16:57:56 -05:00
|
|
|
});
|
2018-07-11 13:58:18 -04:00
|
|
|
|
|
|
|
it('should sanitize new styles that may contain `url` properties', () => {
|
|
|
|
const detectedValues: string[] = [];
|
|
|
|
const sanitizerInterceptor =
|
|
|
|
new MockSanitizerInterceptor(value => { detectedValues.push(value); });
|
2019-05-28 13:31:01 -04:00
|
|
|
const fixture =
|
|
|
|
createTemplateFixtureWithSanitizer(() => createDiv(), 1, sanitizerInterceptor);
|
2018-07-11 13:58:18 -04:00
|
|
|
|
|
|
|
fixture.update(() => {
|
2019-05-28 13:31:01 -04:00
|
|
|
ɵɵstyleSanitizer(sanitizerInterceptor.getStyleSanitizer());
|
2019-05-17 21:49:21 -04:00
|
|
|
ɵɵstyleMap({
|
2018-07-11 13:58:18 -04:00
|
|
|
'background-image': 'background-image',
|
|
|
|
'background': 'background',
|
|
|
|
'border-image': 'border-image',
|
|
|
|
'list-style': 'list-style',
|
|
|
|
'list-style-image': 'list-style-image',
|
|
|
|
'filter': 'filter',
|
|
|
|
'width': 'width'
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
const props = detectedValues.sort();
|
|
|
|
expect(props).toEqual([
|
|
|
|
'background', 'background-image', 'border-image', 'filter', 'list-style', 'list-style-image'
|
|
|
|
]);
|
|
|
|
});
|
2018-03-08 16:57:56 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('elementClass', () => {
|
2019-09-09 16:14:26 -04:00
|
|
|
function createDivWithStyling() { ɵɵelement(0, 'div'); }
|
2018-03-08 16:57:56 -05:00
|
|
|
|
|
|
|
it('should add class', () => {
|
2018-08-16 21:53:21 -04:00
|
|
|
const fixture = new TemplateFixture(createDivWithStyling, () => {}, 1);
|
2019-09-09 16:14:26 -04:00
|
|
|
fixture.update(() => { ɵɵclassMap('multiple classes'); });
|
2019-05-28 13:31:01 -04:00
|
|
|
expect(fixture.html).toEqual('<div class="classes multiple"></div>');
|
2018-03-08 16:57:56 -05:00
|
|
|
});
|
|
|
|
});
|
2018-04-14 14:52:53 -04:00
|
|
|
|
|
|
|
describe('performance counters', () => {
|
|
|
|
it('should create tViews only once for each nested level', () => {
|
2018-08-16 21:53:21 -04:00
|
|
|
function ToDoAppComponent_NgForOf_Template_0(rf: RenderFlags, ctx0: NgForOfContext<any>) {
|
|
|
|
if (rf & RenderFlags.Create) {
|
2019-05-17 21:49:21 -04:00
|
|
|
ɵɵelementStart(0, 'ul');
|
2019-09-23 14:08:51 -04:00
|
|
|
ɵɵtemplate(1, ToDoAppComponent_NgForOf_NgForOf_Template_1, 2, 1, 'li', 0);
|
2019-05-17 21:49:21 -04:00
|
|
|
ɵɵelementEnd();
|
2018-08-16 21:53:21 -04:00
|
|
|
}
|
|
|
|
if (rf & RenderFlags.Update) {
|
|
|
|
const row_r2 = ctx0.$implicit;
|
2019-05-24 17:20:41 -04:00
|
|
|
ɵɵselect(1);
|
|
|
|
ɵɵproperty('ngForOf', row_r2);
|
2018-08-16 21:53:21 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function ToDoAppComponent_NgForOf_NgForOf_Template_1(
|
|
|
|
rf: RenderFlags, ctx1: NgForOfContext<any>) {
|
|
|
|
if (rf & RenderFlags.Create) {
|
2019-05-17 21:49:21 -04:00
|
|
|
ɵɵelementStart(0, 'li');
|
|
|
|
ɵɵtext(1);
|
|
|
|
ɵɵelementEnd();
|
2018-08-16 21:53:21 -04:00
|
|
|
}
|
|
|
|
if (rf & RenderFlags.Update) {
|
|
|
|
const col_r3 = ctx1.$implicit;
|
2019-05-31 17:41:07 -04:00
|
|
|
ɵɵselect(1);
|
|
|
|
ɵɵtextInterpolate1('', col_r3, '');
|
2018-08-16 21:53:21 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-14 14:52:53 -04:00
|
|
|
/**
|
|
|
|
* <ul *ngFor="let row of rows">
|
|
|
|
* <li *ngFor="let col of row.cols">{{col}}</li>
|
|
|
|
* </ul>
|
|
|
|
*/
|
|
|
|
class NestedLoops {
|
|
|
|
rows = [['a', 'b'], ['A', 'B'], ['a', 'b'], ['A', 'B']];
|
|
|
|
|
2019-10-11 17:18:45 -04:00
|
|
|
static ɵfac = function ToDoAppComponent_Factory() { return new NestedLoops(); };
|
2019-10-10 17:57:15 -04:00
|
|
|
static ɵcmp = ɵɵdefineComponent({
|
2018-04-14 14:52:53 -04:00
|
|
|
type: NestedLoops,
|
2018-04-26 13:44:49 -04:00
|
|
|
selectors: [['nested-loops']],
|
2019-09-23 14:08:51 -04:00
|
|
|
decls: 1,
|
2018-08-18 14:14:50 -04:00
|
|
|
vars: 1,
|
2019-09-23 14:08:51 -04:00
|
|
|
consts: [[AttributeMarker.Template, 'ngFor', 'ngForOf']],
|
2018-04-14 14:52:53 -04:00
|
|
|
template: function ToDoAppComponent_Template(rf: RenderFlags, ctx: NestedLoops) {
|
2018-04-26 13:44:49 -04:00
|
|
|
if (rf & RenderFlags.Create) {
|
2019-09-23 14:08:51 -04:00
|
|
|
ɵɵtemplate(0, ToDoAppComponent_NgForOf_Template_0, 2, 1, 'ul', 0);
|
2018-04-14 14:52:53 -04:00
|
|
|
}
|
2018-04-26 13:44:49 -04:00
|
|
|
if (rf & RenderFlags.Update) {
|
2019-05-24 17:20:41 -04:00
|
|
|
ɵɵproperty('ngForOf', ctx.rows);
|
2018-04-14 14:52:53 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
directives: [NgForOf]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
const fixture = new ComponentFixture(NestedLoops);
|
|
|
|
expect(ngDevMode).toHaveProperties({
|
2018-04-26 13:44:49 -04:00
|
|
|
// Expect: fixture view/Host view + component + ngForRow + ngForCol
|
|
|
|
tView: 4, // should be: 4,
|
2018-04-14 14:52:53 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
});
|
2018-05-09 18:30:16 -04:00
|
|
|
|
|
|
|
describe('sanitization injection compatibility', () => {
|
|
|
|
it('should work for url sanitization', () => {
|
|
|
|
const s = new LocalMockSanitizer(value => `${value}-sanitized`);
|
2019-05-31 13:39:14 -04:00
|
|
|
const t = new TemplateFixture(createAnchor, undefined, 1, 1, null, null, s);
|
2018-05-09 18:30:16 -04:00
|
|
|
const inputValue = 'http://foo';
|
|
|
|
const outputValue = 'http://foo-sanitized';
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵattribute('href', inputValue, ɵɵsanitizeUrl); });
|
2018-05-09 18:30:16 -04:00
|
|
|
expect(t.html).toEqual(`<a href="${outputValue}"></a>`);
|
|
|
|
expect(s.lastSanitizedValue).toEqual(outputValue);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should bypass url sanitization if marked by the service', () => {
|
|
|
|
const s = new LocalMockSanitizer(value => '');
|
2019-05-31 13:39:14 -04:00
|
|
|
const t = new TemplateFixture(createAnchor, undefined, 1, 1, null, null, s);
|
2018-05-09 18:30:16 -04:00
|
|
|
const inputValue = s.bypassSecurityTrustUrl('http://foo');
|
|
|
|
const outputValue = 'http://foo';
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵattribute('href', inputValue, ɵɵsanitizeUrl); });
|
2018-05-09 18:30:16 -04:00
|
|
|
expect(t.html).toEqual(`<a href="${outputValue}"></a>`);
|
|
|
|
expect(s.lastSanitizedValue).toBeFalsy();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should bypass ivy-level url sanitization if a custom sanitizer is used', () => {
|
|
|
|
const s = new LocalMockSanitizer(value => '');
|
2019-05-31 13:39:14 -04:00
|
|
|
const t = new TemplateFixture(createAnchor, undefined, 1, 1, null, null, s);
|
2018-05-09 18:30:16 -04:00
|
|
|
const inputValue = bypassSanitizationTrustUrl('http://foo');
|
|
|
|
const outputValue = 'http://foo-ivy';
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵattribute('href', inputValue, ɵɵsanitizeUrl); });
|
2018-05-09 18:30:16 -04:00
|
|
|
expect(t.html).toEqual(`<a href="${outputValue}"></a>`);
|
|
|
|
expect(s.lastSanitizedValue).toBeFalsy();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should work for style sanitization', () => {
|
|
|
|
const s = new LocalMockSanitizer(value => `color:blue`);
|
2019-05-31 13:39:14 -04:00
|
|
|
const t = new TemplateFixture(createDiv, undefined, 1, 1, null, null, s);
|
2018-05-09 18:30:16 -04:00
|
|
|
const inputValue = 'color:red';
|
|
|
|
const outputValue = 'color:blue';
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵattribute('style', inputValue, ɵɵsanitizeStyle); });
|
2018-05-09 18:30:16 -04:00
|
|
|
expect(stripStyleWsCharacters(t.html)).toEqual(`<div style="${outputValue}"></div>`);
|
|
|
|
expect(s.lastSanitizedValue).toEqual(outputValue);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should bypass style sanitization if marked by the service', () => {
|
|
|
|
const s = new LocalMockSanitizer(value => '');
|
2019-05-31 13:39:14 -04:00
|
|
|
const t = new TemplateFixture(createDiv, undefined, 1, 1, null, null, s);
|
2018-05-09 18:30:16 -04:00
|
|
|
const inputValue = s.bypassSecurityTrustStyle('color:maroon');
|
|
|
|
const outputValue = 'color:maroon';
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵattribute('style', inputValue, ɵɵsanitizeStyle); });
|
2018-05-09 18:30:16 -04:00
|
|
|
expect(stripStyleWsCharacters(t.html)).toEqual(`<div style="${outputValue}"></div>`);
|
|
|
|
expect(s.lastSanitizedValue).toBeFalsy();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should bypass ivy-level style sanitization if a custom sanitizer is used', () => {
|
|
|
|
const s = new LocalMockSanitizer(value => '');
|
2019-05-31 13:39:14 -04:00
|
|
|
const t = new TemplateFixture(createDiv, undefined, 1, 1, null, null, s);
|
2018-05-09 18:30:16 -04:00
|
|
|
const inputValue = bypassSanitizationTrustStyle('font-family:foo');
|
|
|
|
const outputValue = 'font-family:foo-ivy';
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵattribute('style', inputValue, ɵɵsanitizeStyle); });
|
2018-05-09 18:30:16 -04:00
|
|
|
expect(stripStyleWsCharacters(t.html)).toEqual(`<div style="${outputValue}"></div>`);
|
|
|
|
expect(s.lastSanitizedValue).toBeFalsy();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should work for resourceUrl sanitization', () => {
|
|
|
|
const s = new LocalMockSanitizer(value => `${value}-sanitized`);
|
2019-05-31 13:39:14 -04:00
|
|
|
const t = new TemplateFixture(createScript, undefined, 1, 1, null, null, s);
|
2018-05-09 18:30:16 -04:00
|
|
|
const inputValue = 'http://resource';
|
|
|
|
const outputValue = 'http://resource-sanitized';
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵattribute('src', inputValue, ɵɵsanitizeResourceUrl); });
|
2018-05-09 18:30:16 -04:00
|
|
|
expect(t.html).toEqual(`<script src="${outputValue}"></script>`);
|
|
|
|
expect(s.lastSanitizedValue).toEqual(outputValue);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should bypass resourceUrl sanitization if marked by the service', () => {
|
|
|
|
const s = new LocalMockSanitizer(value => '');
|
2019-05-31 13:39:14 -04:00
|
|
|
const t = new TemplateFixture(createScript, undefined, 1, 1, null, null, s);
|
2018-05-09 18:30:16 -04:00
|
|
|
const inputValue = s.bypassSecurityTrustResourceUrl('file://all-my-secrets.pdf');
|
|
|
|
const outputValue = 'file://all-my-secrets.pdf';
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵattribute('src', inputValue, ɵɵsanitizeResourceUrl); });
|
2018-05-09 18:30:16 -04:00
|
|
|
expect(t.html).toEqual(`<script src="${outputValue}"></script>`);
|
|
|
|
expect(s.lastSanitizedValue).toBeFalsy();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should bypass ivy-level resourceUrl sanitization if a custom sanitizer is used', () => {
|
|
|
|
const s = new LocalMockSanitizer(value => '');
|
2019-05-31 13:39:14 -04:00
|
|
|
const t = new TemplateFixture(createScript, undefined, 1, 1, null, null, s);
|
2018-05-09 18:30:16 -04:00
|
|
|
const inputValue = bypassSanitizationTrustResourceUrl('file://all-my-secrets.pdf');
|
|
|
|
const outputValue = 'file://all-my-secrets.pdf-ivy';
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵattribute('src', inputValue, ɵɵsanitizeResourceUrl); });
|
2018-05-09 18:30:16 -04:00
|
|
|
expect(t.html).toEqual(`<script src="${outputValue}"></script>`);
|
|
|
|
expect(s.lastSanitizedValue).toBeFalsy();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should work for script sanitization', () => {
|
|
|
|
const s = new LocalMockSanitizer(value => `${value} //sanitized`);
|
2019-05-24 17:20:41 -04:00
|
|
|
const t = new TemplateFixture(createScript, undefined, 1, 1, null, null, s);
|
2018-05-09 18:30:16 -04:00
|
|
|
const inputValue = 'fn();';
|
|
|
|
const outputValue = 'fn(); //sanitized';
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵproperty('innerHTML', inputValue, ɵɵsanitizeScript); });
|
2018-05-09 18:30:16 -04:00
|
|
|
expect(t.html).toEqual(`<script>${outputValue}</script>`);
|
|
|
|
expect(s.lastSanitizedValue).toEqual(outputValue);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should bypass script sanitization if marked by the service', () => {
|
|
|
|
const s = new LocalMockSanitizer(value => '');
|
2019-05-24 17:20:41 -04:00
|
|
|
const t = new TemplateFixture(createScript, undefined, 1, 1, null, null, s);
|
2018-05-09 18:30:16 -04:00
|
|
|
const inputValue = s.bypassSecurityTrustScript('alert("bar")');
|
|
|
|
const outputValue = 'alert("bar")';
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵproperty('innerHTML', inputValue, ɵɵsanitizeScript); });
|
2018-05-09 18:30:16 -04:00
|
|
|
expect(t.html).toEqual(`<script>${outputValue}</script>`);
|
|
|
|
expect(s.lastSanitizedValue).toBeFalsy();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should bypass ivy-level script sanitization if a custom sanitizer is used', () => {
|
|
|
|
const s = new LocalMockSanitizer(value => '');
|
2019-05-24 17:20:41 -04:00
|
|
|
const t = new TemplateFixture(createScript, undefined, 1, 1, null, null, s);
|
2018-05-09 18:30:16 -04:00
|
|
|
const inputValue = bypassSanitizationTrustScript('alert("bar")');
|
|
|
|
const outputValue = 'alert("bar")-ivy';
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵproperty('innerHTML', inputValue, ɵɵsanitizeScript); });
|
2018-05-09 18:30:16 -04:00
|
|
|
expect(t.html).toEqual(`<script>${outputValue}</script>`);
|
|
|
|
expect(s.lastSanitizedValue).toBeFalsy();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should work for html sanitization', () => {
|
|
|
|
const s = new LocalMockSanitizer(value => `${value} <!--sanitized-->`);
|
2019-05-24 17:20:41 -04:00
|
|
|
const t = new TemplateFixture(createDiv, undefined, 1, 1, null, null, s);
|
2018-05-09 18:30:16 -04:00
|
|
|
const inputValue = '<header></header>';
|
|
|
|
const outputValue = '<header></header> <!--sanitized-->';
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵproperty('innerHTML', inputValue, ɵɵsanitizeHtml); });
|
2018-05-09 18:30:16 -04:00
|
|
|
expect(t.html).toEqual(`<div>${outputValue}</div>`);
|
|
|
|
expect(s.lastSanitizedValue).toEqual(outputValue);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should bypass html sanitization if marked by the service', () => {
|
|
|
|
const s = new LocalMockSanitizer(value => '');
|
2019-05-24 17:20:41 -04:00
|
|
|
const t = new TemplateFixture(createDiv, undefined, 1, 1, null, null, s);
|
2018-05-09 18:30:16 -04:00
|
|
|
const inputValue = s.bypassSecurityTrustHtml('<div onclick="alert(123)"></div>');
|
|
|
|
const outputValue = '<div onclick="alert(123)"></div>';
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵproperty('innerHTML', inputValue, ɵɵsanitizeHtml); });
|
2018-05-09 18:30:16 -04:00
|
|
|
expect(t.html).toEqual(`<div>${outputValue}</div>`);
|
|
|
|
expect(s.lastSanitizedValue).toBeFalsy();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should bypass ivy-level script sanitization if a custom sanitizer is used', () => {
|
|
|
|
const s = new LocalMockSanitizer(value => '');
|
2019-05-24 17:20:41 -04:00
|
|
|
const t = new TemplateFixture(createDiv, undefined, 1, 1, null, null, s);
|
2018-05-09 18:30:16 -04:00
|
|
|
const inputValue = bypassSanitizationTrustHtml('<div onclick="alert(123)"></div>');
|
|
|
|
const outputValue = '<div onclick="alert(123)"></div>-ivy';
|
|
|
|
|
2019-05-28 13:31:01 -04:00
|
|
|
t.update(() => { ɵɵproperty('innerHTML', inputValue, ɵɵsanitizeHtml); });
|
2018-05-09 18:30:16 -04:00
|
|
|
expect(t.html).toEqual(`<div>${outputValue}</div>`);
|
|
|
|
expect(s.lastSanitizedValue).toBeFalsy();
|
|
|
|
});
|
|
|
|
});
|
2018-03-01 20:14:01 -05:00
|
|
|
});
|
2018-05-09 18:30:16 -04:00
|
|
|
|
|
|
|
class LocalSanitizedValue {
|
|
|
|
constructor(public value: any) {}
|
|
|
|
|
|
|
|
toString() { return this.value; }
|
|
|
|
}
|
|
|
|
|
|
|
|
class LocalMockSanitizer implements Sanitizer {
|
2018-06-18 19:38:33 -04:00
|
|
|
// TODO(issue/24571): remove '!'.
|
|
|
|
public lastSanitizedValue !: string | null;
|
2018-05-09 18:30:16 -04:00
|
|
|
|
|
|
|
constructor(private _interceptor: (value: string|null|any) => string) {}
|
|
|
|
|
|
|
|
sanitize(context: SecurityContext, value: LocalSanitizedValue|string|null|any): string|null {
|
2019-07-31 16:15:50 -04:00
|
|
|
if (getSanitizationBypassType(value) != null) {
|
|
|
|
return unwrapSafeValue(value) + '-ivy';
|
2018-05-09 18:30:16 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (value instanceof LocalSanitizedValue) {
|
|
|
|
return value.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.lastSanitizedValue = this._interceptor(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
bypassSecurityTrustHtml(value: string) { return new LocalSanitizedValue(value); }
|
|
|
|
|
|
|
|
bypassSecurityTrustStyle(value: string) { return new LocalSanitizedValue(value); }
|
|
|
|
|
|
|
|
bypassSecurityTrustScript(value: string) { return new LocalSanitizedValue(value); }
|
|
|
|
|
|
|
|
bypassSecurityTrustUrl(value: string) { return new LocalSanitizedValue(value); }
|
|
|
|
|
|
|
|
bypassSecurityTrustResourceUrl(value: string) { return new LocalSanitizedValue(value); }
|
|
|
|
}
|
|
|
|
|
2018-07-11 13:58:18 -04:00
|
|
|
class MockSanitizerInterceptor {
|
|
|
|
public lastValue: string|null = null;
|
|
|
|
constructor(private _interceptorFn?: ((value: any) => any)|null) {}
|
2019-05-17 21:49:21 -04:00
|
|
|
getStyleSanitizer() { return ɵɵdefaultStyleSanitizer; }
|
2018-07-11 13:58:18 -04:00
|
|
|
sanitize(context: SecurityContext, value: LocalSanitizedValue|string|null|any): string|null {
|
|
|
|
if (this._interceptorFn) {
|
|
|
|
this._interceptorFn(value);
|
|
|
|
}
|
|
|
|
return this.lastValue = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-09 18:30:16 -04:00
|
|
|
function stripStyleWsCharacters(value: string): string {
|
|
|
|
// color: blue; => color:blue
|
|
|
|
return value.replace(/;/g, '').replace(/:\s+/g, ':');
|
|
|
|
}
|
2018-07-11 13:58:18 -04:00
|
|
|
|
2018-08-16 21:53:21 -04:00
|
|
|
function createTemplateFixtureWithSanitizer(
|
2019-09-23 14:08:51 -04:00
|
|
|
buildFn: () => any, decls: number, sanitizer: Sanitizer) {
|
|
|
|
return new TemplateFixture(buildFn, () => {}, decls, 0, null, null, sanitizer);
|
2018-07-11 13:58:18 -04:00
|
|
|
}
|