refactor: move angular source to /packages rather than modules/@angular
This commit is contained in:
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @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 {SecurityContext} from '@angular/core';
|
||||
import * as t from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {DomSanitizerImpl} from '../../src/security/dom_sanitization_service';
|
||||
|
||||
export function main() {
|
||||
t.describe('DOM Sanitization Service', () => {
|
||||
t.it('accepts resource URL values for resource contexts', () => {
|
||||
const svc = new DomSanitizerImpl(null);
|
||||
const resourceUrl = svc.bypassSecurityTrustResourceUrl('http://hello/world');
|
||||
t.expect(svc.sanitize(SecurityContext.URL, resourceUrl)).toBe('http://hello/world');
|
||||
});
|
||||
});
|
||||
}
|
113
packages/platform-browser/test/security/html_sanitizer_spec.ts
Normal file
113
packages/platform-browser/test/security/html_sanitizer_spec.ts
Normal file
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* @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 * as t from '@angular/core/testing/testing_internal';
|
||||
import {browserDetection} from '@angular/platform-browser/testing/browser_util';
|
||||
|
||||
import {getDOM} from '../../src/dom/dom_adapter';
|
||||
import {sanitizeHtml} from '../../src/security/html_sanitizer';
|
||||
|
||||
export function main() {
|
||||
t.describe('HTML sanitizer', () => {
|
||||
let defaultDoc: any;
|
||||
let originalLog: (msg: any) => any = null;
|
||||
let logMsgs: string[];
|
||||
|
||||
t.beforeEach(() => {
|
||||
defaultDoc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
|
||||
logMsgs = [];
|
||||
originalLog = getDOM().log; // Monkey patch DOM.log.
|
||||
getDOM().log = (msg) => logMsgs.push(msg);
|
||||
});
|
||||
t.afterEach(() => { getDOM().log = originalLog; });
|
||||
|
||||
t.it('serializes nested structures', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<div alt="x"><p>a</p>b<b>c<a alt="more">d</a></b>e</div>'))
|
||||
.toEqual('<div alt="x"><p>a</p>b<b>c<a alt="more">d</a></b>e</div>');
|
||||
t.expect(logMsgs).toEqual([]);
|
||||
});
|
||||
t.it('serializes self closing elements', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<p>Hello <br> World</p>'))
|
||||
.toEqual('<p>Hello <br> World</p>');
|
||||
});
|
||||
t.it('supports namespaced elements', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, 'a<my:hr/><my:div>b</my:div>c')).toEqual('abc');
|
||||
});
|
||||
t.it('supports namespaced attributes', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<a xlink:href="something">t</a>'))
|
||||
.toEqual('<a xlink:href="something">t</a>');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<a xlink:evil="something">t</a>')).toEqual('<a>t</a>');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<a xlink:href="javascript:foo()">t</a>'))
|
||||
.toEqual('<a xlink:href="unsafe:javascript:foo()">t</a>');
|
||||
});
|
||||
t.it('supports HTML5 elements', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<main><summary>Works</summary></main>'))
|
||||
.toEqual('<main><summary>Works</summary></main>');
|
||||
});
|
||||
t.it('sanitizes srcset attributes', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<img srcset="/foo.png 400px, javascript:evil() 23px">'))
|
||||
.toEqual('<img srcset="/foo.png 400px, unsafe:javascript:evil() 23px">');
|
||||
});
|
||||
|
||||
t.it('supports sanitizing plain text', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, 'Hello, World')).toEqual('Hello, World');
|
||||
});
|
||||
t.it('ignores non-element, non-attribute nodes', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<!-- comments? -->no.')).toEqual('no.');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<?pi nodes?>no.')).toEqual('no.');
|
||||
t.expect(logMsgs.join('\n')).toMatch(/sanitizing HTML stripped some content/);
|
||||
});
|
||||
t.it('supports sanitizing escaped entities', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '🚀')).toEqual('🚀');
|
||||
t.expect(logMsgs).toEqual([]);
|
||||
});
|
||||
t.it('does not warn when just re-encoding text', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<p>Hellö Wörld</p>'))
|
||||
.toEqual('<p>Hellö Wörld</p>');
|
||||
t.expect(logMsgs).toEqual([]);
|
||||
});
|
||||
t.it('escapes entities', () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<p>Hello < World</p>'))
|
||||
.toEqual('<p>Hello < World</p>');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<p>Hello < World</p>')).toEqual('<p>Hello < World</p>');
|
||||
t.expect(sanitizeHtml(defaultDoc, '<p alt="% & " !">Hello</p>'))
|
||||
.toEqual('<p alt="% & " !">Hello</p>'); // NB: quote encoded as ASCII ".
|
||||
});
|
||||
t.describe('should strip dangerous elements', () => {
|
||||
const dangerousTags = [
|
||||
'frameset', 'form', 'param', 'object', 'embed', 'textarea', 'input', 'button', 'option',
|
||||
'select', 'script', 'style', 'link', 'base', 'basefont'
|
||||
];
|
||||
|
||||
for (const tag of dangerousTags) {
|
||||
t.it(`${tag}`, () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, `<${tag}>evil!</${tag}>`)).toEqual('evil!');
|
||||
});
|
||||
}
|
||||
t.it(`swallows frame entirely`, () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, `<frame>evil!</frame>`)).not.toContain('<frame>');
|
||||
});
|
||||
});
|
||||
t.describe('should strip dangerous attributes', () => {
|
||||
const dangerousAttrs = ['id', 'name', 'style'];
|
||||
|
||||
for (const attr of dangerousAttrs) {
|
||||
t.it(`${attr}`, () => {
|
||||
t.expect(sanitizeHtml(defaultDoc, `<a ${attr}="x">evil!</a>`)).toEqual('<a>evil!</a>');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (browserDetection.isWebkit) {
|
||||
t.it('should prevent mXSS attacks', function() {
|
||||
t.expect(sanitizeHtml(defaultDoc, '<a href=" javascript:alert(1)">CLICKME</a>'))
|
||||
.toEqual('<a href="unsafe:javascript:alert(1)">CLICKME</a>');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @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 * as t from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {getDOM} from '../../src/dom/dom_adapter';
|
||||
import {sanitizeStyle} from '../../src/security/style_sanitizer';
|
||||
|
||||
export function main() {
|
||||
t.describe('Style sanitizer', () => {
|
||||
let logMsgs: string[];
|
||||
let originalLog: (msg: any) => any;
|
||||
|
||||
t.beforeEach(() => {
|
||||
logMsgs = [];
|
||||
originalLog = getDOM().log; // Monkey patch DOM.log.
|
||||
getDOM().log = (msg) => logMsgs.push(msg);
|
||||
});
|
||||
t.afterEach(() => { getDOM().log = originalLog; });
|
||||
|
||||
function expectSanitize(v: string) { return t.expect(sanitizeStyle(v)); }
|
||||
|
||||
t.it('sanitizes values', () => {
|
||||
expectSanitize('').toEqual('');
|
||||
expectSanitize('abc').toEqual('abc');
|
||||
expectSanitize('50px').toEqual('50px');
|
||||
expectSanitize('rgb(255, 0, 0)').toEqual('rgb(255, 0, 0)');
|
||||
expectSanitize('expression(haha)').toEqual('unsafe');
|
||||
});
|
||||
t.it('rejects unblanaced quotes', () => { expectSanitize('"value" "').toEqual('unsafe'); });
|
||||
t.it('accepts transform functions', () => {
|
||||
expectSanitize('rotate(90deg)').toEqual('rotate(90deg)');
|
||||
expectSanitize('rotate(javascript:evil())').toEqual('unsafe');
|
||||
expectSanitize('translateX(12px, -5px)').toEqual('translateX(12px, -5px)');
|
||||
expectSanitize('scale3d(1, 1, 2)').toEqual('scale3d(1, 1, 2)');
|
||||
});
|
||||
t.it('accepts gradients', () => {
|
||||
expectSanitize('linear-gradient(to bottom, #fg34a1, #bada55)')
|
||||
.toEqual('linear-gradient(to bottom, #fg34a1, #bada55)');
|
||||
expectSanitize('repeating-radial-gradient(ellipse cover, black, red, black, red)')
|
||||
.toEqual('repeating-radial-gradient(ellipse cover, black, red, black, red)');
|
||||
});
|
||||
t.it('accepts calc', () => { expectSanitize('calc(90%-123px)').toEqual('calc(90%-123px)'); });
|
||||
t.it('accepts attr', () => {
|
||||
expectSanitize('attr(value string)').toEqual('attr(value string)');
|
||||
});
|
||||
t.it('sanitizes URLs', () => {
|
||||
expectSanitize('url(foo/bar.png)').toEqual('url(foo/bar.png)');
|
||||
expectSanitize('url( foo/bar.png\n )').toEqual('url( foo/bar.png\n )');
|
||||
expectSanitize('url(javascript:evil())').toEqual('unsafe');
|
||||
expectSanitize('url(strangeprotocol:evil)').toEqual('unsafe');
|
||||
});
|
||||
t.it('accepts quoted URLs', () => {
|
||||
expectSanitize('url("foo/bar.png")').toEqual('url("foo/bar.png")');
|
||||
expectSanitize(`url('foo/bar.png')`).toEqual(`url('foo/bar.png')`);
|
||||
expectSanitize(`url( 'foo/bar.png'\n )`).toEqual(`url( 'foo/bar.png'\n )`);
|
||||
expectSanitize('url("javascript:evil()")').toEqual('unsafe');
|
||||
expectSanitize('url( " javascript:evil() " )').toEqual('unsafe');
|
||||
});
|
||||
});
|
||||
}
|
118
packages/platform-browser/test/security/url_sanitizer_spec.ts
Normal file
118
packages/platform-browser/test/security/url_sanitizer_spec.ts
Normal file
@ -0,0 +1,118 @@
|
||||
/**
|
||||
* @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 * as t from '@angular/core/testing/testing_internal';
|
||||
|
||||
import {getDOM} from '../../src/dom/dom_adapter';
|
||||
import {sanitizeSrcset, sanitizeUrl} from '../../src/security/url_sanitizer';
|
||||
|
||||
export function main() {
|
||||
t.describe('URL sanitizer', () => {
|
||||
let logMsgs: string[];
|
||||
let originalLog: (msg: any) => any;
|
||||
|
||||
t.beforeEach(() => {
|
||||
logMsgs = [];
|
||||
originalLog = getDOM().log; // Monkey patch DOM.log.
|
||||
getDOM().log = (msg) => logMsgs.push(msg);
|
||||
});
|
||||
t.afterEach(() => { getDOM().log = originalLog; });
|
||||
|
||||
t.it('reports unsafe URLs', () => {
|
||||
t.expect(sanitizeUrl('javascript:evil()')).toBe('unsafe:javascript:evil()');
|
||||
t.expect(logMsgs.join('\n')).toMatch(/sanitizing unsafe URL value/);
|
||||
});
|
||||
|
||||
t.describe('valid URLs', () => {
|
||||
const validUrls = [
|
||||
'',
|
||||
'http://abc',
|
||||
'HTTP://abc',
|
||||
'https://abc',
|
||||
'HTTPS://abc',
|
||||
'ftp://abc',
|
||||
'FTP://abc',
|
||||
'mailto:me@example.com',
|
||||
'MAILTO:me@example.com',
|
||||
'tel:123-123-1234',
|
||||
'TEL:123-123-1234',
|
||||
'#anchor',
|
||||
'/page1.md',
|
||||
'http://JavaScript/my.js',
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/', // Truncated.
|
||||
'data:video/webm;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'data:audio/opus;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
];
|
||||
for (const url of validUrls) {
|
||||
t.it(`valid ${url}`, () => t.expect(sanitizeUrl(url)).toEqual(url));
|
||||
}
|
||||
});
|
||||
|
||||
t.describe('invalid URLs', () => {
|
||||
const invalidUrls = [
|
||||
'javascript:evil()',
|
||||
'JavaScript:abc',
|
||||
'evilNewProtocol:abc',
|
||||
' \n Java\n Script:abc',
|
||||
'javascript:',
|
||||
'javascript:',
|
||||
'j avascript:',
|
||||
'javascript:',
|
||||
'javascript:',
|
||||
'jav	ascript:alert();',
|
||||
'jav\u0000ascript:alert();',
|
||||
'data:;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'data:,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'data:iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'data:text/javascript;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
'data:application/x-msdownload;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||||
];
|
||||
for (const url of invalidUrls) {
|
||||
t.it(`valid ${url}`, () => t.expect(sanitizeUrl(url)).toMatch(/^unsafe:/));
|
||||
}
|
||||
});
|
||||
|
||||
t.describe('valid srcsets', () => {
|
||||
const validSrcsets = [
|
||||
'',
|
||||
'http://angular.io/images/test.png',
|
||||
'http://angular.io/images/test.png, http://angular.io/images/test.png',
|
||||
'http://angular.io/images/test.png, http://angular.io/images/test.png, http://angular.io/images/test.png',
|
||||
'http://angular.io/images/test.png 2x',
|
||||
'http://angular.io/images/test.png 2x, http://angular.io/images/test.png 3x',
|
||||
'http://angular.io/images/test.png 1.5x',
|
||||
'http://angular.io/images/test.png 1.25x',
|
||||
'http://angular.io/images/test.png 200w, http://angular.io/images/test.png 300w',
|
||||
'https://angular.io/images/test.png, http://angular.io/images/test.png',
|
||||
'http://angular.io:80/images/test.png, http://angular.io:8080/images/test.png',
|
||||
'http://www.angular.io:80/images/test.png, http://www.angular.io:8080/images/test.png',
|
||||
'https://angular.io/images/test.png, https://angular.io/images/test.png',
|
||||
'//angular.io/images/test.png, //angular.io/images/test.png',
|
||||
'/images/test.png, /images/test.png',
|
||||
'images/test.png, images/test.png',
|
||||
'http://angular.io/images/test.png?12345, http://angular.io/images/test.png?12345',
|
||||
'http://angular.io/images/test.png?maxage, http://angular.io/images/test.png?maxage',
|
||||
'http://angular.io/images/test.png?maxage=234, http://angular.io/images/test.png?maxage=234',
|
||||
];
|
||||
for (const srcset of validSrcsets) {
|
||||
t.it(`valid ${srcset}`, () => t.expect(sanitizeSrcset(srcset)).toEqual(srcset));
|
||||
}
|
||||
});
|
||||
|
||||
t.describe('invalid srcsets', () => {
|
||||
const invalidSrcsets = [
|
||||
'ht:tp://angular.io/images/test.png',
|
||||
'http://angular.io/images/test.png, ht:tp://angular.io/images/test.png',
|
||||
];
|
||||
for (const srcset of invalidSrcsets) {
|
||||
t.it(`valid ${srcset}`, () => t.expect(sanitizeSrcset(srcset)).toMatch(/unsafe:/));
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user