refactor: disable sanitization for [style] and [style.prop] bindings (#35621)
This patch is the first of many commits to disable sanitization for [stlye.prop] and [style] bindings in Angular. Historically, style-based sanitization has only been required for old IE browsers (IE6 and IE7). Since Angular does not support these old browsers at all, there is no reason for the framework to support style-based sanitization. PR Close #35621
This commit is contained in:
parent
3c6c00d1d4
commit
420b9be1c1
|
@ -494,6 +494,7 @@ The following APIs have been removed starting with version 10.0.0*:
|
|||
| ---------------- | -------------- | ----------- | ----- |
|
||||
| `@angular/core` | Undecorated base classes that use Angular features | Add Angular decorator | See [migration guide](guide/migration-undecorated-classes) for more info |
|
||||
| `@angular/core` | `ModuleWithProviders` without a generic | `ModuleWithProviders` with a generic | See [migration guide](guide/migration-module-with-providers) for more info |
|
||||
| `@angular/core` | Style Sanitization | no action needed | See [style sanitization API removal](#style-sanitization) for more info
|
||||
|
||||
*To see APIs removed in version 9, check out this guide on the [version 9 docs site](https://v9.angular.io/guide/deprecations#removed).
|
||||
|
||||
|
@ -545,3 +546,93 @@ In practical terms, the `package.json` of all `@angular` packages has changed in
|
|||
```
|
||||
|
||||
For more information about the npm package format, see the [Angular Package Format spec](https://goo.gl/jB3GVv).
|
||||
|
||||
{@a ie-9-10}
|
||||
### IE 9 and 10 support
|
||||
|
||||
Support for IE 9 and 10 has been deprecated and will be removed in a future version.
|
||||
Supporting outdated browsers like these increases bundle size, code complexity, and test load, and also requires time and effort that could be spent on improvements to the framework.
|
||||
For example, fixing issues can be more difficult, as a straightforward fix for modern browsers could break old ones that have quirks due to not receiving updates from vendors.
|
||||
|
||||
The final decision was made on three key points:
|
||||
* __Vendor support__: Microsoft dropped support of IE 9 and 10 on 1/12/16, meaning they no longer provide security updates or technical support.
|
||||
* __Usage statistics__: We looked at usage trends for IE 9 and 10 from various sources and all indicated that usage percentages were extremely small (fractions of 1%).
|
||||
* __Feedback from partners__: We also reached out to some of our Angular customers and none expressed concern about dropping IE 9 and 10 support.
|
||||
|
||||
{@a removed}
|
||||
## Removed APIs
|
||||
|
||||
The following APIs have been removed starting with version 10.0.0*:
|
||||
|
||||
| Package | API | Replacement | Notes |
|
||||
| ---------------- | -------------- | ----------- | ----- |
|
||||
| `@angular/core` | Undecorated base classes that use Angular features | Add Angular decorator | See [migration guide](guide/migration-undecorated-classes) for more info |
|
||||
| `@angular/core` | `ModuleWithProviders` without a generic | `ModuleWithProviders` with a generic | See [migration guide](guide/migration-module-with-providers) for more info |
|
||||
|
||||
*To see APIs removed in version 9, check out this guide on the [version 9 docs site](https://v9.angular.io/guide/deprecations#removed).
|
||||
|
||||
|
||||
<!-- The following anchor is used by redirects from the removed API pages. Do not change or remove. -->
|
||||
{@a http}
|
||||
### @angular/http
|
||||
|
||||
<!--
|
||||
Deprecation announced in version 5
|
||||
https://blog.angular.io/version-5-0-0-of-angular-now-available-37e414935ced)
|
||||
-->
|
||||
|
||||
|
||||
The entire [`@angular/http`](http://v7.angular.io/api/http) package has been removed. Use [`@angular/common/http`](api/common/http) instead.
|
||||
|
||||
The new API is a smaller, easier, and more powerful way to make HTTP requests in Angular.
|
||||
The new API simplifies the default ergonomics: There is no need to map by invoking the `.json()` method.
|
||||
It also supports typed return values and interceptors.
|
||||
|
||||
To update your apps:
|
||||
* Replace `HttpModule` with [`HttpClientModule`](api/common/http/HttpClientModule) (from [`@angular/common/http`](api/common/http)) in each of your modules.
|
||||
* Replace the `Http` service with the [`HttpClient`](api/common/http/HttpClient) service.
|
||||
* Remove any `map(res => res.json())` calls. They are no longer needed.
|
||||
|
||||
For more information about using `@angular/common/http`, see the [HttpClient guide](guide/http "HTTP Client guide").
|
||||
|
||||
|
||||
| `@angular/http` | Closest replacement in `@angular/common/http` |
|
||||
| ------------- | ------------------------------------------- |
|
||||
| `BaseRequestOptions` | [`HttpRequest`](/api/common/http/HttpRequest) |
|
||||
| `BaseResponseOptions` | [`HttpResponse`](/api/common/http/HttpResponse) |
|
||||
| `BrowserXhr` | |
|
||||
| `Connection` | [`HttpBackend`](/api/common/http/HttpBackend) |
|
||||
| `ConnectionBackend` | [`HttpBackend`](/api/common/http/HttpBackend) |
|
||||
| `CookieXSRFStrategy` | [`HttpClientXsrfModule`](/api/common/http/HttpClientXsrfModule) |
|
||||
| `Headers` | [`HttpHeaders`](/api/common/http/HttpHeaders) |
|
||||
| `Http` | [`HttpClient`](/api/common/http/HttpClient) |
|
||||
| `HttpModule` | [`HttpClientModule`](/api/common/http/HttpClientModule) |
|
||||
| `Jsonp` | [`HttpClient`](/api/common/http/HttpClient) |
|
||||
| `JSONPBackend` | [`JsonpClientBackend`](/api/common/http/JsonpClientBackend) |
|
||||
| `JSONPConnection` | [`JsonpClientBackend`](/api/common/http/JsonpClientBackend) |
|
||||
| `JsonpModule` | [`HttpClientJsonpModule`](/api/common/http/HttpClientJsonpModule) |
|
||||
| `QueryEncoder` | [`HttpUrlEncodingCodec`](/api/common/http/HttpUrlEncodingCodec) |
|
||||
| `ReadyState` | [`HttpBackend`](/api/common/http/HttpBackend) |
|
||||
| `Request` | [`HttpRequest`](/api/common/http/HttpRequest) |
|
||||
| `RequestMethod` | [`HttpClient`](/api/common/http/HttpClient) |
|
||||
| `RequestOptions` | [`HttpRequest`](/api/common/http/HttpRequest) |
|
||||
| `RequestOptionsArgs` | [`HttpRequest`](/api/common/http/HttpRequest) |
|
||||
| `Response` | [`HttpResponse`](/api/common/http/HttpResponse) |
|
||||
| `ResponseContentType` | [`HttpClient`](/api/common/http/HttpClient) |
|
||||
| `ResponseOptions` | [`HttpResponse`](/api/common/http/HttpResponse) |
|
||||
| `ResponseOptionsArgs` | [`HttpResponse`](/api/common/http/HttpResponse) |
|
||||
| `ResponseType` | [`HttpClient`](/api/common/http/HttpClient) |
|
||||
| `URLSearchParams` | [`HttpParams`](/api/common/http/HttpParams) |
|
||||
| `XHRBackend` | [`HttpXhrBackend`](/api/common/http/HttpXhrBackend) |
|
||||
| `XHRConnection` | [`HttpXhrBackend`](/api/common/http/HttpXhrBackend) |
|
||||
| `XSRFStrategy` | [`HttpClientXsrfModule`](/api/common/http/HttpClientXsrfModule) |
|
||||
|
||||
|
||||
| `@angular/http/testing` | Closest replacement in `@angular/common/http/testing` |
|
||||
| --------------------- | ------------------------------------------- |
|
||||
| `MockBackend` | [`HttpTestingController`](/api/common/http/testing/HttpTestingController) |
|
||||
| `MockConnection` | [`HttpTestingController`](/api/common/http/testing/HttpTestingController) |
|
||||
|
||||
{@a style-sanitization}
|
||||
### Style Sanitization for `[style]` and `[style.prop]` bindings
|
||||
Angular used to sanitize `[style]` and `[style.prop]` bindings to prevent malicious code from being inserted through `javascript:` expressions in CSS `url()` entries. However, most modern browsers no longer support the usage of these expressions, so sanitization was only maintained for the sake of IE 6 and 7. Given that Angular does not support either IE 6 or 7 and sanitization has a performance cost, we will no longer sanitize style bindings as of version 10 of Angular.
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 2987,
|
||||
"main-es2015": 454047,
|
||||
"polyfills-es2015": 52628
|
||||
"main-es2015": 453518,
|
||||
"polyfills-es2015": 52195
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -21,8 +21,8 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 3097,
|
||||
"main-es2015": 430383,
|
||||
"polyfills-es2015": 52628
|
||||
"main-es2015": 429742,
|
||||
"polyfills-es2015": 52195
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 2289,
|
||||
"main-es2015": 248671,
|
||||
"main-es2015": 247761,
|
||||
"polyfills-es2015": 36657,
|
||||
"5-es2015": 751
|
||||
}
|
||||
|
@ -60,7 +60,7 @@
|
|||
"uncompressed": {
|
||||
"bundle": "TODO(i): temporarily increase the payload size limit from 105779 - this is due to a closure issue related to ESM reexports that still needs to be investigated",
|
||||
"bundle": "TODO(i): we should define ngDevMode to false in Closure, but --define only works in the global scope.",
|
||||
"bundle": 170618
|
||||
"bundle": 169991
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ export {ReflectionCapabilities as ɵReflectionCapabilities} from './reflection/r
|
|||
export {GetterFn as ɵGetterFn, MethodFn as ɵMethodFn, SetterFn as ɵSetterFn} from './reflection/types';
|
||||
export {allowSanitizationBypassAndThrow as ɵallowSanitizationBypassAndThrow, BypassType as ɵBypassType, getSanitizationBypassType as ɵgetSanitizationBypassType, SafeHtml as ɵSafeHtml, SafeResourceUrl as ɵSafeResourceUrl, SafeScript as ɵSafeScript, SafeStyle as ɵSafeStyle, SafeUrl as ɵSafeUrl, SafeValue as ɵSafeValue, unwrapSafeValue as ɵunwrapSafeValue} from './sanitization/bypass';
|
||||
export {_sanitizeHtml as ɵ_sanitizeHtml} from './sanitization/html_sanitizer';
|
||||
export {_sanitizeStyle as ɵ_sanitizeStyle} from './sanitization/style_sanitizer';
|
||||
export {_sanitizeUrl as ɵ_sanitizeUrl} from './sanitization/url_sanitizer';
|
||||
export {looseIdentical as ɵlooseIdentical,} from './util/comparison';
|
||||
export {makeDecorator as ɵmakeDecorator} from './util/decorators';
|
||||
|
|
|
@ -15,7 +15,7 @@ import {allowSanitizationBypassAndThrow, BypassType, unwrapSafeValue} from './by
|
|||
import {_sanitizeHtml as _sanitizeHtml} from './html_sanitizer';
|
||||
import {Sanitizer} from './sanitizer';
|
||||
import {SecurityContext} from './security';
|
||||
import {_sanitizeStyle, StyleSanitizeFn, StyleSanitizeMode} from './style_sanitizer';
|
||||
import {StyleSanitizeFn, StyleSanitizeMode} from './style_sanitizer';
|
||||
import {_sanitizeUrl as _sanitizeUrl} from './url_sanitizer';
|
||||
|
||||
|
||||
|
@ -50,14 +50,10 @@ export function ɵɵsanitizeHtml(unsafeHtml: any): string {
|
|||
* A `style` sanitizer which converts untrusted `style` **string** into trusted string by removing
|
||||
* dangerous content.
|
||||
*
|
||||
* This method parses the `style` and locates potentially dangerous content (such as urls and
|
||||
* javascript) and removes it.
|
||||
*
|
||||
* It is possible to mark a string as trusted by calling {@link bypassSanitizationTrustStyle}.
|
||||
*
|
||||
* @param unsafeStyle untrusted `style`, typically from the user.
|
||||
* @returns `style` string which is safe to bind to the `style` properties, because all of the
|
||||
* dangerous javascript and urls have been removed.
|
||||
* @returns `style` string which is safe to bind to the `style` properties.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
|
@ -69,7 +65,7 @@ export function ɵɵsanitizeStyle(unsafeStyle: any): string {
|
|||
if (allowSanitizationBypassAndThrow(unsafeStyle, BypassType.Style)) {
|
||||
return unwrapSafeValue(unsafeStyle);
|
||||
}
|
||||
return _sanitizeStyle(renderStringify(unsafeStyle));
|
||||
return renderStringify(unsafeStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -181,8 +177,14 @@ export function ɵɵsanitizeUrlOrResourceUrl(unsafeUrl: any, tag: string, prop:
|
|||
}
|
||||
|
||||
/**
|
||||
* The default style sanitizer will handle sanitization for style properties by
|
||||
* sanitizing any CSS property that can include a `url` value (usually image-based properties)
|
||||
* The default style sanitizer will handle sanitization for style properties.
|
||||
*
|
||||
* Style sanitization is no longer apart of Angular because modern browsers no
|
||||
* longer support javascript expressions. Therefore, the reason why this API
|
||||
* exists is exclusively for unwrapping any style value expressions that were
|
||||
* marked as `SafeValue` values.
|
||||
*
|
||||
* This API will be removed in a future release of Angular.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
|
|
|
@ -5,104 +5,22 @@
|
|||
* 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 {isDevMode} from '../util/is_dev_mode';
|
||||
import {SafeValue} from './bypass';
|
||||
import {_sanitizeUrl} from './url_sanitizer';
|
||||
|
||||
|
||||
/**
|
||||
* Regular expression for safe style values.
|
||||
/*
|
||||
* ========== WARNING ==========
|
||||
*
|
||||
* Quotes (" and ') are allowed, but a check must be done elsewhere to ensure they're balanced.
|
||||
* Style sanitization in Angular (for `[style.prop]` and `[style]` bindings)
|
||||
* is no longer required and has been removed. The reason why this feature
|
||||
* has been removed is because style-based sanitization is no longer
|
||||
* required with modern browsers.
|
||||
*
|
||||
* ',' allows multiple values to be assigned to the same property (e.g. background-attachment or
|
||||
* font-family) and hence could allow multiple values to get injected, but that should pose no risk
|
||||
* of XSS.
|
||||
* The contents of this file are still in flux. Various APIs and symbols will
|
||||
* be removed in a future version of Angular. Please hold off from modifying this
|
||||
* file for the time being.
|
||||
*
|
||||
* The function expression checks only for XSS safety, not for CSS validity.
|
||||
*
|
||||
* This regular expression was taken from the Closure sanitization library, and augmented for
|
||||
* transformation values.
|
||||
* =============================
|
||||
*/
|
||||
const VALUES = '[-,."\'%_!# a-zA-Z0-9]+';
|
||||
const TRANSFORMATION_FNS = '(?:matrix|translate|scale|rotate|skew|perspective)(?:X|Y|Z|3d)?';
|
||||
const COLOR_FNS = '(?:rgb|hsl)a?';
|
||||
const GRADIENTS = '(?:repeating-)?(?:linear|radial)-gradient';
|
||||
const CSS3_FNS = '(?:attr|calc|var)';
|
||||
const FN_ARGS = '\\([-0-9.%, #a-zA-Z]+\\)';
|
||||
const SAFE_STYLE_VALUE = new RegExp(
|
||||
`^(${VALUES}|` +
|
||||
`(?:${TRANSFORMATION_FNS}|${COLOR_FNS}|${GRADIENTS}|${CSS3_FNS})` +
|
||||
`${FN_ARGS})$`,
|
||||
'g');
|
||||
|
||||
/**
|
||||
* Matches a `url(...)` value with an arbitrary argument as long as it does
|
||||
* not contain parentheses.
|
||||
*
|
||||
* The URL value still needs to be sanitized separately.
|
||||
*
|
||||
* `url(...)` values are a very common use case, e.g. for `background-image`. With carefully crafted
|
||||
* CSS style rules, it is possible to construct an information leak with `url` values in CSS, e.g.
|
||||
* by observing whether scroll bars are displayed, or character ranges used by a font face
|
||||
* definition.
|
||||
*
|
||||
* Angular only allows binding CSS values (as opposed to entire CSS rules), so it is unlikely that
|
||||
* binding a URL value without further cooperation from the page will cause an information leak, and
|
||||
* if so, it is just a leak, not a full blown XSS vulnerability.
|
||||
*
|
||||
* Given the common use case, low likelihood of attack vector, and low impact of an attack, this
|
||||
* code is permissive and allows URLs that sanitize otherwise.
|
||||
*/
|
||||
const URL_RE = /^url\(([^)]+)\)$/;
|
||||
|
||||
/**
|
||||
* Checks that quotes (" and ') are properly balanced inside a string. Assumes
|
||||
* that neither escape (\) nor any other character that could result in
|
||||
* breaking out of a string parsing context are allowed;
|
||||
* see http://www.w3.org/TR/css3-syntax/#string-token-diagram.
|
||||
*
|
||||
* This code was taken from the Closure sanitization library.
|
||||
*/
|
||||
function hasBalancedQuotes(value: string) {
|
||||
let outsideSingle = true;
|
||||
let outsideDouble = true;
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const c = value.charAt(i);
|
||||
if (c === '\'' && outsideDouble) {
|
||||
outsideSingle = !outsideSingle;
|
||||
} else if (c === '"' && outsideSingle) {
|
||||
outsideDouble = !outsideDouble;
|
||||
}
|
||||
}
|
||||
return outsideSingle && outsideDouble;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the given untrusted CSS style property value (i.e. not an entire object, just a single
|
||||
* value) and returns a value that is safe to use in a browser environment.
|
||||
*/
|
||||
export function _sanitizeStyle(value: string): string {
|
||||
value = String(value).trim(); // Make sure it's actually a string.
|
||||
if (!value) return '';
|
||||
|
||||
// Single url(...) values are supported, but only for URLs that sanitize cleanly. See above for
|
||||
// reasoning behind this.
|
||||
const urlMatch = value.match(URL_RE);
|
||||
if ((urlMatch && _sanitizeUrl(urlMatch[1]) === urlMatch[1]) ||
|
||||
value.match(SAFE_STYLE_VALUE) && hasBalancedQuotes(value)) {
|
||||
return value; // Safe style values.
|
||||
}
|
||||
|
||||
if (isDevMode()) {
|
||||
console.warn(
|
||||
`WARNING: sanitizing unsafe style value ${value} (see http://g.co/ng/security#xss).`);
|
||||
}
|
||||
|
||||
return 'unsafe';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A series of flags to instruct a style sanitizer to either validate
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('styling', () => {
|
|||
|
||||
it('should perform prop bindings', () => {
|
||||
@Component({
|
||||
template: `<div [class.dynamic]="true"
|
||||
template: `<div [class.dynamic]="true"
|
||||
[style.color]="'blue'"
|
||||
[style.width.px]="100"></div>`
|
||||
})
|
||||
|
@ -52,7 +52,7 @@ describe('styling', () => {
|
|||
|
||||
onlyInIvy('style merging is ivy only feature').it('should perform map bindings', () => {
|
||||
@Component({
|
||||
template: `<div [class]="{dynamic: true}"
|
||||
template: `<div [class]="{dynamic: true}"
|
||||
[style]="{color: 'blue', width: '100px'}"></div>`
|
||||
})
|
||||
class Cmp {
|
||||
|
@ -1883,9 +1883,9 @@ describe('styling', () => {
|
|||
.it('should sanitize style values before writing them', () => {
|
||||
@Component({
|
||||
template: `
|
||||
<div [style.width]="widthExp"
|
||||
[style.background-image]="bgImageExp"></div>
|
||||
`
|
||||
<div [style.width]="widthExp"
|
||||
[style.background-image]="bgImageExp"></div>
|
||||
`
|
||||
})
|
||||
class Cmp {
|
||||
widthExp = '';
|
||||
|
@ -1902,10 +1902,7 @@ describe('styling', () => {
|
|||
|
||||
comp.bgImageExp = 'url("javascript:img")';
|
||||
fixture.detectChanges();
|
||||
// for some reasons `background-image: unsafe` is suppressed
|
||||
expect(getSortedStyle(div)).toEqual('');
|
||||
fixture.detectChanges();
|
||||
expect(getSortedStyle(div)).not.toContain('javascript');
|
||||
expect(getSortedStyle(div)).toContain('javascript:img');
|
||||
|
||||
// Prove that bindings work.
|
||||
comp.widthExp = '789px';
|
||||
|
@ -1938,11 +1935,6 @@ describe('styling', () => {
|
|||
|
||||
comp.styleMapExp['background-image'] = 'url("javascript:img")';
|
||||
fixture.detectChanges();
|
||||
// for some reasons `background-image: unsafe` is suppressed
|
||||
expect(getSortedStyle(div)).toEqual('');
|
||||
|
||||
// for some reasons `border-image: unsafe` is NOT suppressed
|
||||
fixture.detectChanges();
|
||||
expect(getSortedStyle(div)).not.toContain('javascript');
|
||||
|
||||
// Prove that bindings work.
|
||||
|
@ -2943,7 +2935,7 @@ describe('styling', () => {
|
|||
() => {
|
||||
@Component({
|
||||
template: `
|
||||
<div
|
||||
<div
|
||||
dir-that-sets-styles
|
||||
[style]="{'font-size': '300px'}"
|
||||
[attr.title]="'my-title'"
|
||||
|
|
|
@ -238,26 +238,6 @@ function declareTests(config?: {useJit: boolean}) {
|
|||
checkEscapeOfHrefProperty(fixture, true);
|
||||
});
|
||||
|
||||
it('should escape unsafe style values', () => {
|
||||
const template = `<div [style.background]="ctxProp">Text</div>`;
|
||||
TestBed.overrideComponent(SecuredComponent, {set: {template}});
|
||||
const fixture = TestBed.createComponent(SecuredComponent);
|
||||
|
||||
const e = fixture.debugElement.children[0].nativeElement;
|
||||
const ci = fixture.componentInstance;
|
||||
// Make sure binding harmless values works.
|
||||
ci.ctxProp = 'red';
|
||||
fixture.detectChanges();
|
||||
// In some browsers, this will contain the full background specification, not just
|
||||
// the color.
|
||||
expect(e.style['background']).toMatch(/red.*/);
|
||||
|
||||
ci.ctxProp = 'url(javascript:evil())';
|
||||
fixture.detectChanges();
|
||||
// Updated value gets rejected, no value change.
|
||||
expect(e.style['background']).not.toContain('javascript');
|
||||
});
|
||||
|
||||
modifiedInIvy('Unknown property error thrown during update mode, not creation mode')
|
||||
.it('should escape unsafe SVG attributes', () => {
|
||||
const template = `<svg:circle [xlink:href]="ctxProp">Text</svg:circle>`;
|
||||
|
|
|
@ -242,32 +242,6 @@ describe('styling', () => {
|
|||
expectStyle(div).toEqual({width: '200px'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sanitization', () => {
|
||||
it('should sanitize property', () => {
|
||||
ɵɵstyleSanitizer(ɵɵsanitizeStyle);
|
||||
ɵɵstyleProp('background', 'url("javascript:/unsafe")');
|
||||
expect(div.style.getPropertyValue('background')).not.toContain('javascript');
|
||||
|
||||
clearFirstUpdatePass();
|
||||
|
||||
rewindBindingIndex();
|
||||
ɵɵstyleProp('background', bypassSanitizationTrustStyle('url("javascript:/trusted")'));
|
||||
expect(div.style.getPropertyValue('background')).toContain('url("javascript:/trusted")');
|
||||
});
|
||||
|
||||
it('should sanitize map', () => {
|
||||
ɵɵstyleSanitizer(ɵɵsanitizeStyle);
|
||||
ɵɵstyleMap('background: url("javascript:/unsafe")');
|
||||
expect(div.style.getPropertyValue('background')).not.toContain('javascript');
|
||||
|
||||
clearFirstUpdatePass();
|
||||
|
||||
rewindBindingIndex();
|
||||
ɵɵstyleMap({'background': bypassSanitizationTrustStyle('url("javascript:/trusted")')});
|
||||
expect(div.style.getPropertyValue('background')).toContain('url("javascript:/trusted")');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('static', () => {
|
||||
|
@ -578,4 +552,4 @@ function expectTStylingKeys(styling: 'style'|'class') {
|
|||
(isClassBased ? tNode.residualClasses : tNode.residualStyles) as null | string[]);
|
||||
|
||||
return expect(tStylingKeys);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -163,7 +163,7 @@ describe('instructions', () => {
|
|||
});
|
||||
|
||||
describe('styleProp', () => {
|
||||
it('should automatically sanitize unless a bypass operation is applied', () => {
|
||||
it('should allow values even if a bypass operation is applied', () => {
|
||||
let backgroundImage: string|SafeValue = 'url("http://server")';
|
||||
const t = new TemplateFixture(
|
||||
() => {
|
||||
|
@ -176,7 +176,7 @@ describe('instructions', () => {
|
|||
2, 2);
|
||||
// nothing is set because sanitizer suppresses it.
|
||||
expect((t.hostElement.firstChild as HTMLElement).style.getPropertyValue('background-image'))
|
||||
.toEqual('');
|
||||
.toEqual('url("http://server")');
|
||||
|
||||
backgroundImage = bypassSanitizationTrustStyle('url("http://server2")');
|
||||
t.update();
|
||||
|
|
|
@ -64,8 +64,8 @@ describe('sanitization', () => {
|
|||
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('url("http://server")')).toEqual('url("http://server")');
|
||||
expect(ɵɵsanitizeStyle(new Wrap('url("http://server")'))).toEqual('url("http://server")');
|
||||
expect(() => ɵɵsanitizeStyle(bypassSanitizationTrustHtml('url("http://server")')))
|
||||
.toThrowError(/Required a safe Style, got a HTML/);
|
||||
expect(ɵɵsanitizeStyle(bypassSanitizationTrustStyle('url("http://server")')))
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
/**
|
||||
* @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 {_sanitizeStyle} from '../../src/sanitization/style_sanitizer';
|
||||
|
||||
|
||||
describe('Style sanitizer', () => {
|
||||
let logMsgs: string[];
|
||||
let originalLog: (msg: any) => any;
|
||||
|
||||
beforeEach(() => {
|
||||
logMsgs = [];
|
||||
originalLog = console.warn; // Monkey patch DOM.log.
|
||||
console.warn = (msg: any) => logMsgs.push(msg);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
console.warn = originalLog;
|
||||
});
|
||||
|
||||
function expectSanitize(v: string) {
|
||||
return expect(_sanitizeStyle(v));
|
||||
}
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
it('rejects unblanaced quotes', () => {
|
||||
expectSanitize('"value" "').toEqual('unsafe');
|
||||
});
|
||||
|
||||
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)');
|
||||
});
|
||||
|
||||
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)');
|
||||
});
|
||||
|
||||
it('accepts attr', () => {
|
||||
expectSanitize('attr(value string)').toEqual('attr(value string)');
|
||||
});
|
||||
|
||||
it('accepts calc', () => {
|
||||
expectSanitize('calc(90%-123px)').toEqual('calc(90%-123px)');
|
||||
});
|
||||
|
||||
it('accepts var', () => {
|
||||
expectSanitize('var(--my-custom-var)').toEqual('var(--my-custom-var)');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {DOCUMENT} from '@angular/common';
|
||||
import {forwardRef, Inject, Injectable, Injector, Sanitizer, SecurityContext, ɵ_sanitizeHtml as _sanitizeHtml, ɵ_sanitizeStyle as _sanitizeStyle, ɵ_sanitizeUrl as _sanitizeUrl, ɵallowSanitizationBypassAndThrow as allowSanitizationBypassOrThrow, ɵbypassSanitizationTrustHtml as bypassSanitizationTrustHtml, ɵbypassSanitizationTrustResourceUrl as bypassSanitizationTrustResourceUrl, ɵbypassSanitizationTrustScript as bypassSanitizationTrustScript, ɵbypassSanitizationTrustStyle as bypassSanitizationTrustStyle, ɵbypassSanitizationTrustUrl as bypassSanitizationTrustUrl, ɵBypassType as BypassType, ɵgetSanitizationBypassType as getSanitizationBypassType, ɵunwrapSafeValue as unwrapSafeValue} from '@angular/core';
|
||||
import {forwardRef, Inject, Injectable, Injector, Sanitizer, SecurityContext, ɵ_sanitizeHtml as _sanitizeHtml, ɵ_sanitizeUrl as _sanitizeUrl, ɵallowSanitizationBypassAndThrow as allowSanitizationBypassOrThrow, ɵbypassSanitizationTrustHtml as bypassSanitizationTrustHtml, ɵbypassSanitizationTrustResourceUrl as bypassSanitizationTrustResourceUrl, ɵbypassSanitizationTrustScript as bypassSanitizationTrustScript, ɵbypassSanitizationTrustStyle as bypassSanitizationTrustStyle, ɵbypassSanitizationTrustUrl as bypassSanitizationTrustUrl, ɵBypassType as BypassType, ɵgetSanitizationBypassType as getSanitizationBypassType, ɵunwrapSafeValue as unwrapSafeValue} from '@angular/core';
|
||||
|
||||
export {SecurityContext};
|
||||
|
||||
|
@ -167,7 +167,7 @@ export class DomSanitizerImpl extends DomSanitizer {
|
|||
if (allowSanitizationBypassOrThrow(value, BypassType.Style)) {
|
||||
return unwrapSafeValue(value);
|
||||
}
|
||||
return _sanitizeStyle(value as string);
|
||||
return value as string;
|
||||
case SecurityContext.SCRIPT:
|
||||
if (allowSanitizationBypassOrThrow(value, BypassType.Script)) {
|
||||
return unwrapSafeValue(value);
|
||||
|
|
Loading…
Reference in New Issue