feat($sce): handle URL sanitization through the $sce service
Thanks to @rjamet for the original work on this feature.
This is a large patch to handle URLs with the $sce service, similarly to HTML context.
Where we previously sanitized URL attributes when setting attribute value inside the
`$compile` service, we now only apply an `$sce` context requirement and leave the
`$interpolate` service to deal with sanitization.
This commit introduces a new `$sce` context called `MEDIA_URL`, which represents
a URL used as a source for a media element that is not expected to execute code, such as
image, video, audio, etc.
The context hierarchy is setup so that a value trusted as `URL` is also trusted in the
`MEDIA_URL` context, in the same way that the a value trusted as `RESOURCE_URL` is also
trusted in the `URL` context (and transitively also the `MEDIA_URL` context).
The `$sce` service will now automatically attempt to sanitize non-trusted values that
require the `URL` or `MEDIA_URL` context:
* When calling `getTrustedMediaUrl()` a value that is not already a trusted `MEDIA_URL`
will be sanitized using the `imgSrcSanitizationWhitelist`.
* When calling `getTrustedUrl()` a value that is not already a trusted `URL` will be
sanitized using the `aHrefSanitizationWhitelist`.
This results in behaviour that closely matches the previous sanitization behaviour.
To keep rough compatibility with existing apps, we need to allow concatenation of values
that may contain trusted contexts. The following approach is taken for situations that
require a `URL` or `MEDIA_URL` secure context:
* A single trusted value is trusted, e.g. `"{{trustedUrl}}"` and will not be sanitized.
* A single non-trusted value, e.g. `"{{ 'javascript:foo' }}"`, will be handled by
`getTrustedMediaUrl` or `getTrustedUrl)` and sanitized.
* Any concatenation of values (which may or may not be trusted) results in a
non-trusted type that will be handled by `getTrustedMediaUrl` or `getTrustedUrl` once the
concatenation is complete.
E.g. `"javascript:{{safeType}}"` is a concatenation of a non-trusted and a trusted value,
which will be sanitized as a whole after unwrapping the `safeType` value.
* An interpolation containing no expressions will still be handled by `getTrustedMediaUrl` or
`getTrustedUrl`, whereas before this would have been short-circuited in the `$interpolate`
service. E.g. `"some/hard/coded/url"`. This ensures that `ngHref` and similar directives
still securely, even if the URL is hard-coded into a template or index.html (perhaps by
server-side rendering).
BREAKING CHANGES:
If you use `attrs.$set` for URL attributes (a[href] and img[src]) there will no
longer be any automated sanitization of the value. This is in line with other
programmatic operations, such as writing to the innerHTML of an element.
If you are programmatically writing URL values to attributes from untrusted
input then you must sanitize it yourself. You could write your own sanitizer or copy
the private `$$sanitizeUri` service.
Note that values that have been passed through the `$interpolate` service within the
`URL` or `MEDIA_URL` will have already been sanitized, so you would not need to sanitize
these values again.
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
@ngdoc error
|
||||
@name $compile:srcset
|
||||
@fullName Invalid value passed to `attr.$set('srcset', value)`
|
||||
@description
|
||||
|
||||
This error occurs if you try to programmatically set the `srcset` attribute with a non-string value.
|
||||
|
||||
This can be the case if you tried to avoid the automatic sanitization of the `srcset` value by
|
||||
passing a "trusted" value provided by calls to `$sce.trustAsMediaUrl(value)`.
|
||||
|
||||
If you want to programmatically set explicitly trusted unsafe URLs, you should use `$sce.trustAsHtml`
|
||||
on the whole `img` tag and inject it into the DOM using the `ng-bind-html` directive.
|
||||
+31
-17
@@ -1528,9 +1528,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
this.$get = [
|
||||
'$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
|
||||
'$controller', '$rootScope', '$sce', '$animate', '$$sanitizeUri',
|
||||
'$controller', '$rootScope', '$sce', '$animate',
|
||||
function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse,
|
||||
$controller, $rootScope, $sce, $animate, $$sanitizeUri) {
|
||||
$controller, $rootScope, $sce, $animate) {
|
||||
|
||||
var SIMPLE_ATTR_NAME = /^\w/;
|
||||
var specialAttrHolder = window.document.createElement('div');
|
||||
@@ -1679,8 +1679,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
*/
|
||||
$set: function(key, value, writeAttr, attrName) {
|
||||
// TODO: decide whether or not to throw an error if "class"
|
||||
//is set through this function since it may cause $updateClass to
|
||||
//become unstable.
|
||||
// is set through this function since it may cause $updateClass to
|
||||
// become unstable.
|
||||
|
||||
var node = this.$$element[0],
|
||||
booleanKey = getBooleanAttrName(node, key),
|
||||
@@ -1710,13 +1710,20 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
|
||||
nodeName = nodeName_(this.$$element);
|
||||
|
||||
if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) ||
|
||||
(nodeName === 'img' && key === 'src') ||
|
||||
(nodeName === 'image' && key === 'xlinkHref')) {
|
||||
// sanitize a[href] and img[src] values
|
||||
this[key] = value = $$sanitizeUri(value, nodeName === 'img' || nodeName === 'image');
|
||||
} else if (nodeName === 'img' && key === 'srcset' && isDefined(value)) {
|
||||
// sanitize img[srcset] values
|
||||
// Sanitize img[srcset] values.
|
||||
if (nodeName === 'img' && key === 'srcset' && value) {
|
||||
if (!isString(value)) {
|
||||
throw $compileMinErr('srcset', 'Can\'t pass trusted values to `$set(\'srcset\', value)`: "{0}"', value.toString());
|
||||
}
|
||||
|
||||
// Such values are a bit too complex to handle automatically inside $sce.
|
||||
// Instead, we sanitize each of the URIs individually, which works, even dynamically.
|
||||
|
||||
// It's not possible to work around this using `$sce.trustAsMediaUrl`.
|
||||
// If you want to programmatically set explicitly trusted unsafe URLs, you should use
|
||||
// `$sce.trustAsHtml` on the whole `img` tag and inject it into the DOM using the
|
||||
// `ng-bind-html` directive.
|
||||
|
||||
var result = '';
|
||||
|
||||
// first check if there are spaces because it's not the same pattern
|
||||
@@ -1733,16 +1740,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
for (var i = 0; i < nbrUrisWith2parts; i++) {
|
||||
var innerIdx = i * 2;
|
||||
// sanitize the uri
|
||||
result += $$sanitizeUri(trim(rawUris[innerIdx]), true);
|
||||
result += $sce.getTrustedMediaUrl(trim(rawUris[innerIdx]));
|
||||
// add the descriptor
|
||||
result += (' ' + trim(rawUris[innerIdx + 1]));
|
||||
result += ' ' + trim(rawUris[innerIdx + 1]);
|
||||
}
|
||||
|
||||
// split the last item into uri and descriptor
|
||||
var lastTuple = trim(rawUris[i * 2]).split(/\s/);
|
||||
|
||||
// sanitize the last uri
|
||||
result += $$sanitizeUri(trim(lastTuple[0]), true);
|
||||
result += $sce.getTrustedMediaUrl(trim(lastTuple[0]));
|
||||
|
||||
// and add the last descriptor if any
|
||||
if (lastTuple.length === 2) {
|
||||
@@ -3268,14 +3275,18 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}
|
||||
var tag = nodeName_(node);
|
||||
// All tags with src attributes require a RESOURCE_URL value, except for
|
||||
// img and various html5 media tags.
|
||||
// img and various html5 media tags, which require the MEDIA_URL context.
|
||||
if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') {
|
||||
if (['img', 'video', 'audio', 'source', 'track'].indexOf(tag) === -1) {
|
||||
return $sce.RESOURCE_URL;
|
||||
}
|
||||
return $sce.MEDIA_URL;
|
||||
} else if (attrNormalizedName === 'xlinkHref') {
|
||||
// Some xlink:href are okay, most aren't
|
||||
if (tag === 'image') return $sce.MEDIA_URL;
|
||||
if (tag === 'a') return $sce.URL;
|
||||
return $sce.RESOURCE_URL;
|
||||
} else if (
|
||||
// Some xlink:href are okay, most aren't
|
||||
(attrNormalizedName === 'xlinkHref' && (tag !== 'image' && tag !== 'a')) ||
|
||||
// Formaction
|
||||
(tag === 'form' && attrNormalizedName === 'action') ||
|
||||
// If relative URLs can go where they are not expected to, then
|
||||
@@ -3285,6 +3296,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
(tag === 'link' && attrNormalizedName === 'href')
|
||||
) {
|
||||
return $sce.RESOURCE_URL;
|
||||
} else if (tag === 'a' && (attrNormalizedName === 'href' ||
|
||||
attrNormalizedName === 'ngHref')) {
|
||||
return $sce.URL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -436,7 +436,7 @@ forEach(['src', 'srcset', 'href'], function(attrName) {
|
||||
// On IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
|
||||
// then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
|
||||
// to set the property as well to achieve the desired effect.
|
||||
// We use attr[attrName] value since $set can sanitize the url.
|
||||
// We use attr[attrName] value since $set might have sanitized the url.
|
||||
if (msie && propName) element.prop(propName, attr[name]);
|
||||
});
|
||||
}
|
||||
|
||||
+50
-25
@@ -238,16 +238,21 @@ function $InterpolateProvider() {
|
||||
* - `context`: evaluation context for all expressions embedded in the interpolated text
|
||||
*/
|
||||
function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
|
||||
var contextAllowsConcatenation = trustedContext === $sce.URL || trustedContext === $sce.MEDIA_URL;
|
||||
|
||||
// Provide a quick exit and simplified result function for text with no interpolation
|
||||
if (!text.length || text.indexOf(startSymbol) === -1) {
|
||||
var constantInterp;
|
||||
if (!mustHaveExpression) {
|
||||
var unescapedText = unescapeText(text);
|
||||
constantInterp = valueFn(unescapedText);
|
||||
constantInterp.exp = text;
|
||||
constantInterp.expressions = [];
|
||||
constantInterp.$$watchDelegate = constantWatchDelegate;
|
||||
if (mustHaveExpression && !contextAllowsConcatenation) return;
|
||||
|
||||
var unescapedText = unescapeText(text);
|
||||
if (contextAllowsConcatenation) {
|
||||
unescapedText = $sce.getTrusted(trustedContext, unescapedText);
|
||||
}
|
||||
var constantInterp = valueFn(unescapedText);
|
||||
constantInterp.exp = text;
|
||||
constantInterp.expressions = [];
|
||||
constantInterp.$$watchDelegate = constantWatchDelegate;
|
||||
|
||||
return constantInterp;
|
||||
}
|
||||
|
||||
@@ -256,11 +261,13 @@ function $InterpolateProvider() {
|
||||
endIndex,
|
||||
index = 0,
|
||||
expressions = [],
|
||||
parseFns = [],
|
||||
parseFns,
|
||||
textLength = text.length,
|
||||
exp,
|
||||
concat = [],
|
||||
expressionPositions = [];
|
||||
expressionPositions = [],
|
||||
singleExpression;
|
||||
|
||||
|
||||
while (index < textLength) {
|
||||
if (((startIndex = text.indexOf(startSymbol, index)) !== -1) &&
|
||||
@@ -270,10 +277,9 @@ function $InterpolateProvider() {
|
||||
}
|
||||
exp = text.substring(startIndex + startSymbolLength, endIndex);
|
||||
expressions.push(exp);
|
||||
parseFns.push($parse(exp, parseStringifyInterceptor));
|
||||
index = endIndex + endSymbolLength;
|
||||
expressionPositions.push(concat.length);
|
||||
concat.push('');
|
||||
concat.push(''); // Placeholder that will get replaced with the evaluated expression.
|
||||
} else {
|
||||
// we did not find an interpolation, so we have to add the remainder to the separators array
|
||||
if (index !== textLength) {
|
||||
@@ -283,15 +289,25 @@ function $InterpolateProvider() {
|
||||
}
|
||||
}
|
||||
|
||||
singleExpression = concat.length === 1 && expressionPositions.length === 1;
|
||||
// Intercept expression if we need to stringify concatenated inputs, which may be SCE trusted
|
||||
// objects rather than simple strings
|
||||
// (we don't modify the expression if the input consists of only a single trusted input)
|
||||
var interceptor = contextAllowsConcatenation && singleExpression ? undefined : parseStringifyInterceptor;
|
||||
parseFns = expressions.map(function(exp) { return $parse(exp, interceptor); });
|
||||
|
||||
// Concatenating expressions makes it hard to reason about whether some combination of
|
||||
// concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
|
||||
// single expression be used for iframe[src], object[src], etc., we ensure that the value
|
||||
// that's used is assigned or constructed by some JS code somewhere that is more testable or
|
||||
// make it obvious that you bound the value to some user controlled value. This helps reduce
|
||||
// the load when auditing for XSS issues.
|
||||
if (trustedContext && concat.length > 1) {
|
||||
$interpolateMinErr.throwNoconcat(text);
|
||||
}
|
||||
// single expression be used for some $sce-managed secure contexts (RESOURCE_URLs mostly),
|
||||
// we ensure that the value that's used is assigned or constructed by some JS code somewhere
|
||||
// that is more testable or make it obvious that you bound the value to some user controlled
|
||||
// value. This helps reduce the load when auditing for XSS issues.
|
||||
|
||||
// Note that URL and MEDIA_URL $sce contexts do not need this, since `$sce` can sanitize the values
|
||||
// passed to it. In that case, `$sce.getTrusted` will be called on either the single expression
|
||||
// or on the overall concatenated string (losing trusted types used in the mix, by design).
|
||||
// Both these methods will sanitize plain strings. Also, HTML could be included, but since it's
|
||||
// only used in srcdoc attributes, this would not be very useful.
|
||||
|
||||
if (!mustHaveExpression || expressions.length) {
|
||||
var compute = function(values) {
|
||||
@@ -299,13 +315,16 @@ function $InterpolateProvider() {
|
||||
if (allOrNothing && isUndefined(values[i])) return;
|
||||
concat[expressionPositions[i]] = values[i];
|
||||
}
|
||||
return concat.join('');
|
||||
};
|
||||
|
||||
var getValue = function(value) {
|
||||
return trustedContext ?
|
||||
$sce.getTrusted(trustedContext, value) :
|
||||
$sce.valueOf(value);
|
||||
if (contextAllowsConcatenation) {
|
||||
// If `singleExpression` then `concat[0]` might be a "trusted" value or `null`, rather than a string
|
||||
return $sce.getTrusted(trustedContext, singleExpression ? concat[0] : concat.join(''));
|
||||
} else if (trustedContext && concat.length > 1) {
|
||||
// This context does not allow more than one part, e.g. expr + string or exp + exp.
|
||||
$interpolateMinErr.throwNoconcat(text);
|
||||
}
|
||||
// In an unprivileged context or only one part: just concatenate and return.
|
||||
return concat.join('');
|
||||
};
|
||||
|
||||
return extend(function interpolationFn(context) {
|
||||
@@ -340,7 +359,13 @@ function $InterpolateProvider() {
|
||||
|
||||
function parseStringifyInterceptor(value) {
|
||||
try {
|
||||
value = getValue(value);
|
||||
// In concatenable contexts, getTrusted comes at the end, to avoid sanitizing individual
|
||||
// parts of a full URL. We don't care about losing the trustedness here.
|
||||
// In non-concatenable contexts, where there is only one expression, this interceptor is
|
||||
// not applied to the expression.
|
||||
value = (trustedContext && !contextAllowsConcatenation) ?
|
||||
$sce.getTrusted(trustedContext, value) :
|
||||
$sce.valueOf(value);
|
||||
return allOrNothing && !isDefined(value) ? value : stringify(value);
|
||||
} catch (err) {
|
||||
$exceptionHandler($interpolateMinErr.interr(text, err));
|
||||
|
||||
+24
-14
@@ -6,6 +6,7 @@
|
||||
* Private service to sanitize uris for links and images. Used by $compile and $sanitize.
|
||||
*/
|
||||
function $$SanitizeUriProvider() {
|
||||
|
||||
var aHrefSanitizationWhitelist = /^\s*(https?|s?ftp|mailto|tel|file):/,
|
||||
imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/;
|
||||
|
||||
@@ -14,12 +15,16 @@ function $$SanitizeUriProvider() {
|
||||
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
|
||||
* urls during a[href] sanitization.
|
||||
*
|
||||
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
|
||||
* The sanitization is a security measure aimed at prevent XSS attacks via HTML anchor links.
|
||||
*
|
||||
* Any url about to be assigned to a[href] via data-binding is first normalized and turned into
|
||||
* an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
|
||||
* regular expression. If a match is found, the original url is written into the dom. Otherwise,
|
||||
* the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
|
||||
* Any url due to be assigned to an `a[href]` attribute via interpolation is marked as requiring
|
||||
* the $sce.URL security context. When interpolation occurs a call is made to `$sce.trustAsUrl(url)`
|
||||
* which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize the potentially malicious URL.
|
||||
*
|
||||
* If the URL matches the `aHrefSanitizationWhitelist` regular expression, it is returned unchanged.
|
||||
*
|
||||
* If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written
|
||||
* to the DOM it is inactive and potentially malicious code will not be executed.
|
||||
*
|
||||
* @param {RegExp=} regexp New regexp to whitelist urls with.
|
||||
* @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
|
||||
@@ -39,12 +44,17 @@ function $$SanitizeUriProvider() {
|
||||
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
|
||||
* urls during img[src] sanitization.
|
||||
*
|
||||
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
|
||||
* The sanitization is a security measure aimed at prevent XSS attacks via HTML image src links.
|
||||
*
|
||||
* Any url about to be assigned to img[src] via data-binding is first normalized and turned into
|
||||
* an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist`
|
||||
* regular expression. If a match is found, the original url is written into the dom. Otherwise,
|
||||
* the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
|
||||
* Any URL due to be assigned to an `img[src]` attribute via interpolation is marked as requiring
|
||||
* the $sce.MEDIA_URL security context. When interpolation occurs a call is made to
|
||||
* `$sce.trustAsMediaUrl(url)` which in turn may call `$$sanitizeUri(url, isMedia)` to sanitize
|
||||
* the potentially malicious URL.
|
||||
*
|
||||
* If the URL matches the `aImgSanitizationWhitelist` regular expression, it is returned unchanged.
|
||||
*
|
||||
* If there is no match the URL is returned prefixed with `'unsafe:'` to ensure that when it is written
|
||||
* to the DOM it is inactive and potentially malicious code will not be executed.
|
||||
*
|
||||
* @param {RegExp=} regexp New regexp to whitelist urls with.
|
||||
* @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
|
||||
@@ -59,10 +69,10 @@ function $$SanitizeUriProvider() {
|
||||
};
|
||||
|
||||
this.$get = function() {
|
||||
return function sanitizeUri(uri, isImage) {
|
||||
var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
|
||||
var normalizedVal;
|
||||
normalizedVal = urlResolve(uri && uri.trim()).href;
|
||||
return function sanitizeUri(uri, isMediaUrl) {
|
||||
// if (!uri) return uri;
|
||||
var regex = isMediaUrl ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist;
|
||||
var normalizedVal = urlResolve(uri && uri.trim()).href;
|
||||
if (normalizedVal !== '' && !normalizedVal.match(regex)) {
|
||||
return 'unsafe:' + normalizedVal;
|
||||
}
|
||||
|
||||
+66
-31
@@ -22,12 +22,17 @@ var SCE_CONTEXTS = {
|
||||
// Style statements or stylesheets. Currently unused in AngularJS.
|
||||
CSS: 'css',
|
||||
|
||||
// An URL used in a context where it does not refer to a resource that loads code. Currently
|
||||
// unused in AngularJS.
|
||||
// An URL used in a context where it refers to the source of media, which are not expected to be run
|
||||
// as scripts, such as an image, audio, video, etc.
|
||||
MEDIA_URL: 'mediaUrl',
|
||||
|
||||
// An URL used in a context where it does not refer to a resource that loads code.
|
||||
// A value that can be trusted as a URL can also trusted as a MEDIA_URL.
|
||||
URL: 'url',
|
||||
|
||||
// RESOURCE_URL is a subtype of URL used where the referred-to resource could be interpreted as
|
||||
// code. (e.g. ng-include, script src binding, templateUrl)
|
||||
// A value that can be trusted as a RESOURCE_URL, can also trusted as a URL and a MEDIA_URL.
|
||||
RESOURCE_URL: 'resourceUrl',
|
||||
|
||||
// Script. Currently unused in AngularJS.
|
||||
@@ -242,7 +247,7 @@ function $SceDelegateProvider() {
|
||||
return resourceUrlBlacklist;
|
||||
};
|
||||
|
||||
this.$get = ['$injector', function($injector) {
|
||||
this.$get = ['$injector', '$$sanitizeUri', function($injector, $$sanitizeUri) {
|
||||
|
||||
var htmlSanitizer = function htmlSanitizer(html) {
|
||||
throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
|
||||
@@ -307,7 +312,8 @@ function $SceDelegateProvider() {
|
||||
|
||||
byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase);
|
||||
byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase);
|
||||
byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase);
|
||||
byType[SCE_CONTEXTS.MEDIA_URL] = generateHolderType(trustedValueHolderBase);
|
||||
byType[SCE_CONTEXTS.URL] = generateHolderType(byType[SCE_CONTEXTS.MEDIA_URL]);
|
||||
byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase);
|
||||
byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]);
|
||||
|
||||
@@ -386,15 +392,27 @@ function $SceDelegateProvider() {
|
||||
* @name $sceDelegate#getTrusted
|
||||
*
|
||||
* @description
|
||||
* Takes any input, and either returns a value that's safe to use in the specified context, or
|
||||
* throws an exception.
|
||||
* Given an object and a security context in which to assign it, returns a value that's safe to
|
||||
* use in this context, which was represented by the parameter. To do so, this function either
|
||||
* unwraps the safe type it has been given (for instance, a {@link ng.$sceDelegate#trustAs
|
||||
* `$sceDelegate.trustAs`} result), or it might try to sanitize the value given, depending on
|
||||
* the context and sanitizer availablility.
|
||||
*
|
||||
* In practice, there are several cases. When given a string, this function runs checks
|
||||
* and sanitization to make it safe without prior assumptions. When given the result of a {@link
|
||||
* ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call, it returns the originally supplied
|
||||
* value if that value's context is valid for this call's context. Finally, this function can
|
||||
* also throw when there is no way to turn `maybeTrusted` in a safe value (e.g., no sanitization
|
||||
* is available or possible.)
|
||||
* The contexts that can be sanitized are $sce.MEDIA_URL, $sce.URL and $sce.HTML. The first two are available
|
||||
* by default, and the third one relies on the `$sanitize` service (which may be loaded through
|
||||
* the `ngSanitize` module). Furthermore, for $sce.RESOURCE_URL context, a plain string may be
|
||||
* accepted if the resource url policy defined by {@link ng.$sceDelegateProvider#resourceUrlWhitelist
|
||||
* `$sceDelegateProvider.resourceUrlWhitelist`} and {@link ng.$sceDelegateProvider#resourceUrlBlacklist
|
||||
* `$sceDelegateProvider.resourceUrlBlacklist`} accepts that resource.
|
||||
*
|
||||
* This function will throw if the safe type isn't appropriate for this context, or if the
|
||||
* value given cannot be accepted in the context (which might be caused by sanitization not
|
||||
* being available, or the value not being recognized as safe).
|
||||
*
|
||||
* <div class="alert alert-danger">
|
||||
* Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting
|
||||
* (XSS) vulnerability in your application.
|
||||
* </div>
|
||||
*
|
||||
* @param {string} type The context in which this value is to be used (such as `$sce.HTML`).
|
||||
* @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs
|
||||
@@ -412,12 +430,18 @@ function $SceDelegateProvider() {
|
||||
if (constructor && maybeTrusted instanceof constructor) {
|
||||
return maybeTrusted.$$unwrapTrustedValue();
|
||||
}
|
||||
// Otherwise, if we get here, then we may either make it safe, or throw an exception. This
|
||||
// depends on the context: some are sanitizatible (HTML), some use whitelists (RESOURCE_URL),
|
||||
// some are impossible to do (JS). This step isn't implemented for CSS and URL, as AngularJS
|
||||
// has no corresponding sinks.
|
||||
if (type === SCE_CONTEXTS.RESOURCE_URL) {
|
||||
// RESOURCE_URL uses a whitelist.
|
||||
|
||||
// If maybeTrusted is a trusted class instance but not of the correct trusted type
|
||||
// then unwrap it and allow it to pass through to the rest of the checks
|
||||
if (isFunction(maybeTrusted.$$unwrapTrustedValue)) {
|
||||
maybeTrusted = maybeTrusted.$$unwrapTrustedValue();
|
||||
}
|
||||
|
||||
// If we get here, then we will either sanitize the value or throw an exception.
|
||||
if (type === SCE_CONTEXTS.MEDIA_URL || type === SCE_CONTEXTS.URL) {
|
||||
// we attempt to sanitize non-resource URLs
|
||||
return $$sanitizeUri(maybeTrusted, type === SCE_CONTEXTS.MEDIA_URL);
|
||||
} else if (type === SCE_CONTEXTS.RESOURCE_URL) {
|
||||
if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
|
||||
return maybeTrusted;
|
||||
} else {
|
||||
@@ -572,9 +596,10 @@ function $SceDelegateProvider() {
|
||||
*
|
||||
* If your expressions are constant literals, they're automatically trusted and you don't need to
|
||||
* call `$sce.trustAs` on them (e.g.
|
||||
* `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. The `$sceDelegate` will
|
||||
* also use the `$sanitize` service if it is available when binding untrusted values to
|
||||
* `$sce.HTML` context. AngularJS provides an implementation in `angular-sanitize.js`, and if you
|
||||
* `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works (remember to include the
|
||||
* `ngSanitize` module). The `$sceDelegate` will also use the `$sanitize` service if it is available
|
||||
* when binding untrusted values to `$sce.HTML` context.
|
||||
* AngularJS provides an implementation in `angular-sanitize.js`, and if you
|
||||
* wish to use it, you will also need to depend on the {@link ngSanitize `ngSanitize`} module in
|
||||
* your application.
|
||||
*
|
||||
@@ -594,17 +619,27 @@ function $SceDelegateProvider() {
|
||||
*
|
||||
* | Context | Notes |
|
||||
* |---------------------|----------------|
|
||||
* | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered, and the {@link ngSanitize.$sanitize $sanitize} service is available (implemented by the {@link ngSanitize ngSanitize} module) this will sanitize the value instead of throwing an error. |
|
||||
* | `$sce.CSS` | For CSS that's safe to source into the application. Currently, no bindings require this context. Feel free to use it in your own directives. |
|
||||
* | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=`, `<img src=`, and some others sanitize their urls and don't constitute an SCE context.) |
|
||||
* | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG`, `VIDEO`, `AUDIO`, `SOURCE`, and `TRACK` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does (it's not just the URL that matters, but also what is at the end of it), and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. |
|
||||
* | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently, no bindings require this context. Feel free to use it in your own directives. |
|
||||
* | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. |
|
||||
* | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. |
|
||||
* | `$sce.MEDIA_URL` | For URLs that are safe to render as media. Is automatically converted from string by sanitizing when needed. |
|
||||
* | `$sce.URL` | For URLs that are safe to follow as links. Is automatically converted from string by sanitizing when needed. Note that `$sce.URL` makes a stronger statement about the URL than `$sce.MEDIA_URL` does and therefore contexts requiring values trusted for `$sce.URL` can be used anywhere that values trusted for `$sce.MEDIA_URL` are required.|
|
||||
* | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` or `$sce.MEDIA_URL` do and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` or `$sce.MEDIA_URL` are required. |
|
||||
* | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. |
|
||||
*
|
||||
*
|
||||
* Be aware that `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
|
||||
* through {@link ng.$sce#getTrusted $sce.getTrusted}. There's no CSS-, URL-, or JS-context bindings
|
||||
* in AngularJS currently, so their corresponding `$sce.trustAs` functions aren't useful yet. This
|
||||
* might evolve.
|
||||
* <div class="alert alert-warning">
|
||||
* Be aware that, before AngularJS 1.7.0, `a[href]` and `img[src]` used to sanitize their
|
||||
* interpolated values directly rather than rely upon {@link ng.$sce#getTrusted `$sce.getTrusted`}.
|
||||
*
|
||||
* **As of 1.7.0, this is no longer the case.**
|
||||
*
|
||||
* Now such interpolations are marked as requiring `$sce.URL` (for `a[href]`) or `$sce.MEDIA_URL`
|
||||
* (for `img[src]`), so that the sanitization happens (via `$sce.getTrusted...`) when the `$interpolate`
|
||||
* service evaluates the expressions.
|
||||
* </div>
|
||||
*
|
||||
* There are no CSS or JS context bindings in AngularJS currently, so their corresponding `$sce.trustAs`
|
||||
* functions aren't useful yet. This might evolve.
|
||||
*
|
||||
* ### Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a>
|
||||
*
|
||||
@@ -778,7 +813,7 @@ function $SceProvider() {
|
||||
* such a value.
|
||||
*
|
||||
* - getTrusted(contextEnum, value)
|
||||
* This function should return the a value that is safe to use in the context specified by
|
||||
* This function should return the value that is safe to use in the context specified by
|
||||
* contextEnum or throw and exception otherwise.
|
||||
*
|
||||
* NOTE: This contract deliberately does NOT state that values returned by trustAs() must be
|
||||
|
||||
@@ -41,12 +41,11 @@ var htmlSanitizeWriter;
|
||||
* Sanitizes an html string by stripping all potentially dangerous tokens.
|
||||
*
|
||||
* The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are
|
||||
* then serialized back to properly escaped html string. This means that no unsafe input can make
|
||||
* then serialized back to a properly escaped HTML string. This means that no unsafe input can make
|
||||
* it into the returned string.
|
||||
*
|
||||
* The whitelist for URL sanitization of attribute values is configured using the functions
|
||||
* `aHrefSanitizationWhitelist` and `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider
|
||||
* `$compileProvider`}.
|
||||
* `aHrefSanitizationWhitelist` and `imgSrcSanitizationWhitelist` of {@link $compileProvider}.
|
||||
*
|
||||
* The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}.
|
||||
*
|
||||
|
||||
+300
-150
@@ -151,13 +151,34 @@ describe('$compile', function() {
|
||||
|
||||
describe('configuration', function() {
|
||||
|
||||
it('should allow aHrefSanitizationWhitelist to be configured', function() {
|
||||
module(function($compileProvider) {
|
||||
expect($compileProvider.aHrefSanitizationWhitelist()).toEqual(/^\s*(https?|s?ftp|mailto|tel|file):/); // the default
|
||||
$compileProvider.aHrefSanitizationWhitelist(/other/);
|
||||
expect($compileProvider.aHrefSanitizationWhitelist()).toEqual(/other/);
|
||||
it('should use $$sanitizeUriProvider for reconfiguration of the `aHrefSanitizationWhitelist`', function() {
|
||||
module(function($compileProvider, $$sanitizeUriProvider) {
|
||||
var newRe = /safe:/, returnVal;
|
||||
|
||||
expect($compileProvider.aHrefSanitizationWhitelist()).toBe($$sanitizeUriProvider.aHrefSanitizationWhitelist());
|
||||
returnVal = $compileProvider.aHrefSanitizationWhitelist(newRe);
|
||||
expect(returnVal).toBe($compileProvider);
|
||||
expect($$sanitizeUriProvider.aHrefSanitizationWhitelist()).toBe(newRe);
|
||||
expect($compileProvider.aHrefSanitizationWhitelist()).toBe(newRe);
|
||||
});
|
||||
inject(function() {
|
||||
// needed to the module definition above is run...
|
||||
});
|
||||
});
|
||||
|
||||
it('should use $$sanitizeUriProvider for reconfiguration of the `imgSrcSanitizationWhitelist`', function() {
|
||||
module(function($compileProvider, $$sanitizeUriProvider) {
|
||||
var newRe = /safe:/, returnVal;
|
||||
|
||||
expect($compileProvider.imgSrcSanitizationWhitelist()).toBe($$sanitizeUriProvider.imgSrcSanitizationWhitelist());
|
||||
returnVal = $compileProvider.imgSrcSanitizationWhitelist(newRe);
|
||||
expect(returnVal).toBe($compileProvider);
|
||||
expect($$sanitizeUriProvider.imgSrcSanitizationWhitelist()).toBe(newRe);
|
||||
expect($compileProvider.imgSrcSanitizationWhitelist()).toBe(newRe);
|
||||
});
|
||||
inject(function() {
|
||||
// needed to the module definition above is run...
|
||||
});
|
||||
inject();
|
||||
});
|
||||
|
||||
it('should allow debugInfoEnabled to be configured', function() {
|
||||
@@ -3393,6 +3414,15 @@ describe('$compile', function() {
|
||||
})
|
||||
);
|
||||
|
||||
it('should interpolate a multi-part expression for regular attributes', inject(function($compile, $rootScope) {
|
||||
element = $compile('<div foo="some/{{id}}"></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('foo')).toBe('some/');
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = 1;
|
||||
});
|
||||
expect(element.attr('foo')).toEqual('some/1');
|
||||
}));
|
||||
|
||||
it('should process attribute interpolation in pre-linking phase at priority 100', function() {
|
||||
module(function() {
|
||||
@@ -4135,12 +4165,15 @@ describe('$compile', function() {
|
||||
var attr;
|
||||
beforeEach(function() {
|
||||
module(function() {
|
||||
directive('input', valueFn({
|
||||
restrict: 'ECA',
|
||||
link: function(scope, element, attr) {
|
||||
scope.attr = attr;
|
||||
}
|
||||
}));
|
||||
// Create directives that capture the `attr` object
|
||||
['input', 'a', 'img'].forEach(function(tag) {
|
||||
directive(tag, valueFn({
|
||||
restrict: 'ECA',
|
||||
link: function(scope, element, attr) {
|
||||
scope.attr = attr;
|
||||
}
|
||||
}));
|
||||
});
|
||||
});
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile('<input></input>')($rootScope);
|
||||
@@ -4187,6 +4220,37 @@ describe('$compile', function() {
|
||||
expect(element.attr('test')).toBeUndefined();
|
||||
expect(attr.test).toBe('value');
|
||||
});
|
||||
|
||||
it('should not automatically sanitize a[href]', inject(function($compile, $rootScope) {
|
||||
// Breaking change in https://github.com/angular/angular.js/pull/16378
|
||||
element = $compile('<a></a>')($rootScope);
|
||||
$rootScope.attr.$set('href', 'evil:foo()');
|
||||
expect(element.attr('href')).toEqual('evil:foo()');
|
||||
expect($rootScope.attr.href).toEqual('evil:foo()');
|
||||
}));
|
||||
|
||||
it('should not automatically sanitize img[src]', inject(function($compile, $rootScope) {
|
||||
// Breaking change in https://github.com/angular/angular.js/pull/16378
|
||||
element = $compile('<img></img>')($rootScope);
|
||||
$rootScope.attr.$set('img', 'evil:foo()');
|
||||
expect(element.attr('img')).toEqual('evil:foo()');
|
||||
expect($rootScope.attr.img).toEqual('evil:foo()');
|
||||
}));
|
||||
|
||||
it('should automatically sanitize img[srcset]', inject(function($compile, $rootScope) {
|
||||
element = $compile('<img></img>')($rootScope);
|
||||
$rootScope.attr.$set('srcset', 'evil:foo()');
|
||||
expect(element.attr('srcset')).toEqual('unsafe:evil:foo()');
|
||||
expect($rootScope.attr.srcset).toEqual('unsafe:evil:foo()');
|
||||
}));
|
||||
|
||||
it('should not accept trusted values for img[srcset]', inject(function($compile, $rootScope, $sce) {
|
||||
var trusted = $sce.trustAsMediaUrl('trustme:foo()');
|
||||
element = $compile('<img></img>')($rootScope);
|
||||
expect(function() {
|
||||
$rootScope.attr.$set('srcset', trusted);
|
||||
}).toThrowMinErr('$compile', 'srcset', 'Can\'t pass trusted values to `$set(\'srcset\', value)`: "trustme:foo()"');
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11071,91 +11135,114 @@ describe('$compile', function() {
|
||||
);
|
||||
});
|
||||
|
||||
describe('*[src] context requirement', function() {
|
||||
|
||||
it('should NOT require trusted values for img src', inject(function($rootScope, $compile, $sce) {
|
||||
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
|
||||
$rootScope.testUrl = 'http://example.com/image.png';
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toEqual('http://example.com/image.png');
|
||||
// But it should accept trusted values anyway.
|
||||
$rootScope.testUrl = $sce.trustAsUrl('http://example.com/image2.png');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toEqual('http://example.com/image2.png');
|
||||
}));
|
||||
|
||||
['img', 'audio', 'video'].forEach(function(tag) {
|
||||
// Support: IE 9 only
|
||||
// IE9 rejects the video / audio tag with "Error: Not implemented" and the source tag with
|
||||
// "Unable to get value of the property 'childNodes': object is null or undefined"
|
||||
if (msie !== 9) {
|
||||
they('should NOT require trusted values for $prop src', ['video', 'audio'],
|
||||
function(tag) {
|
||||
inject(function($rootScope, $compile, $sce) {
|
||||
// IE9 rejects the `video` / `audio` tags with "Error: Not implemented"
|
||||
if (msie !== 9 || tag === 'img') {
|
||||
describe(tag + '[src] context requirement', function() {
|
||||
it('should NOT require trusted values for whitelisted URIs', inject(function($rootScope, $compile) {
|
||||
element = $compile('<' + tag + ' src="{{testUrl}}"></' + tag + '>')($rootScope);
|
||||
$rootScope.testUrl = 'http://example.com/image.mp4';
|
||||
$rootScope.testUrl = 'http://example.com/image.mp4'; // `http` is whitelisted
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toEqual('http://example.com/image.mp4');
|
||||
}));
|
||||
|
||||
// But it should accept trusted values anyway.
|
||||
$rootScope.testUrl = $sce.trustAsUrl('http://example.com/image2.mp4');
|
||||
it('should accept trusted values', inject(function($rootScope, $compile, $sce) {
|
||||
// As a MEDIA_URL URL
|
||||
element = $compile('<' + tag + ' src="{{testUrl}}"></' + tag + '>')($rootScope);
|
||||
// Some browsers complain if you try to write `javascript:` into an `img[src]`
|
||||
// So for the test use something different
|
||||
$rootScope.testUrl = $sce.trustAsMediaUrl('untrusted:foo()');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toEqual('http://example.com/image2.mp4');
|
||||
expect(element.attr('src')).toEqual('untrusted:foo()');
|
||||
|
||||
// and trustedResourceUrls for retrocompatibility
|
||||
$rootScope.testUrl = $sce.trustAsResourceUrl('http://example.com/image3.mp4');
|
||||
// As a URL
|
||||
element = $compile('<' + tag + ' src="{{testUrl}}"></' + tag + '>')($rootScope);
|
||||
$rootScope.testUrl = $sce.trustAsUrl('untrusted:foo()');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toEqual('http://example.com/image3.mp4');
|
||||
});
|
||||
});
|
||||
expect(element.attr('src')).toEqual('untrusted:foo()');
|
||||
|
||||
they('should NOT require trusted values for $prop src', ['source', 'track'],
|
||||
function(tag) {
|
||||
inject(function($rootScope, $compile, $sce) {
|
||||
element = $compile('<video><' + tag + ' src="{{testUrl}}"></' + tag + '></video>')($rootScope);
|
||||
$rootScope.testUrl = 'http://example.com/image.mp4';
|
||||
// As a RESOURCE URL
|
||||
element = $compile('<' + tag + ' src="{{testUrl}}"></' + tag + '>')($rootScope);
|
||||
$rootScope.testUrl = $sce.trustAsResourceUrl('untrusted:foo()');
|
||||
$rootScope.$digest();
|
||||
expect(element.find(tag).attr('src')).toEqual('http://example.com/image.mp4');
|
||||
|
||||
// But it should accept trusted values anyway.
|
||||
$rootScope.testUrl = $sce.trustAsUrl('http://example.com/image2.mp4');
|
||||
$rootScope.$digest();
|
||||
expect(element.find(tag).attr('src')).toEqual('http://example.com/image2.mp4');
|
||||
|
||||
// and trustedResourceUrls for retrocompatibility
|
||||
$rootScope.testUrl = $sce.trustAsResourceUrl('http://example.com/image3.mp4');
|
||||
$rootScope.$digest();
|
||||
expect(element.find(tag).attr('src')).toEqual('http://example.com/image3.mp4');
|
||||
});
|
||||
expect(element.attr('src')).toEqual('untrusted:foo()');
|
||||
}));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Support: IE 9 only
|
||||
// IE 9 rejects the `source` / `track` tags with
|
||||
// "Unable to get value of the property 'childNodes': object is null or undefined"
|
||||
if (msie !== 9) {
|
||||
['source', 'track'].forEach(function(tag) {
|
||||
describe(tag + '[src]', function() {
|
||||
it('should NOT require trusted values for whitelisted URIs', inject(function($rootScope, $compile) {
|
||||
element = $compile('<video><' + tag + ' src="{{testUrl}}"></' + tag + '></video>')($rootScope);
|
||||
$rootScope.testUrl = 'http://example.com/image.mp4'; // `http` is whitelisted
|
||||
$rootScope.$digest();
|
||||
expect(element.find(tag).attr('src')).toEqual('http://example.com/image.mp4');
|
||||
}));
|
||||
|
||||
it('should accept trusted values', inject(function($rootScope, $compile, $sce) {
|
||||
// As a MEDIA_URL URL
|
||||
element = $compile('<video><' + tag + ' src="{{testUrl}}"></' + tag + '></video>')($rootScope);
|
||||
$rootScope.testUrl = $sce.trustAsMediaUrl('javascript:foo()');
|
||||
$rootScope.$digest();
|
||||
expect(element.find(tag).attr('src')).toEqual('javascript:foo()');
|
||||
|
||||
// As a URL
|
||||
element = $compile('<video><' + tag + ' src="{{testUrl}}"></' + tag + '></video>')($rootScope);
|
||||
$rootScope.testUrl = $sce.trustAsUrl('javascript:foo()');
|
||||
$rootScope.$digest();
|
||||
expect(element.find(tag).attr('src')).toEqual('javascript:foo()');
|
||||
|
||||
// As a RESOURCE URL
|
||||
element = $compile('<video><' + tag + ' src="{{testUrl}}"></' + tag + '></video>')($rootScope);
|
||||
$rootScope.testUrl = $sce.trustAsResourceUrl('javascript:foo()');
|
||||
$rootScope.$digest();
|
||||
expect(element.find(tag).attr('src')).toEqual('javascript:foo()');
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('img[src] sanitization', function() {
|
||||
|
||||
it('should accept trusted values', inject(function($rootScope, $compile, $sce) {
|
||||
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
|
||||
// Some browsers complain if you try to write `javascript:` into an `img[src]`
|
||||
// So for the test use something different
|
||||
$rootScope.testUrl = $sce.trustAsMediaUrl('someUntrustedThing:foo();');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toEqual('someUntrustedThing:foo();');
|
||||
}));
|
||||
|
||||
it('should sanitize concatenated values even if they are trusted', inject(function($rootScope, $compile, $sce) {
|
||||
element = $compile('<img src="{{testUrl}}ponies"></img>')($rootScope);
|
||||
$rootScope.testUrl = $sce.trustAsUrl('untrusted:foo();');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toEqual('unsafe:untrusted:foo();ponies');
|
||||
|
||||
element = $compile('<img src="http://{{testUrl2}}"></img>')($rootScope);
|
||||
$rootScope.testUrl2 = $sce.trustAsUrl('xyz;');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toEqual('http://xyz;');
|
||||
|
||||
element = $compile('<img src="{{testUrl3}}{{testUrl3}}"></img>')($rootScope);
|
||||
$rootScope.testUrl3 = $sce.trustAsUrl('untrusted:foo();');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toEqual('unsafe:untrusted:foo();untrusted:foo();');
|
||||
}));
|
||||
|
||||
it('should not sanitize attributes other than src', inject(function($compile, $rootScope) {
|
||||
element = $compile('<img title="{{testUrl}}"></img>')($rootScope);
|
||||
$rootScope.testUrl = 'javascript:doEvilStuff()';
|
||||
$rootScope.$apply();
|
||||
|
||||
expect(element.attr('title')).toBe('javascript:doEvilStuff()');
|
||||
}));
|
||||
|
||||
it('should use $$sanitizeUriProvider for reconfiguration of the src whitelist', function() {
|
||||
module(function($compileProvider, $$sanitizeUriProvider) {
|
||||
var newRe = /javascript:/,
|
||||
returnVal;
|
||||
expect($compileProvider.imgSrcSanitizationWhitelist()).toBe($$sanitizeUriProvider.imgSrcSanitizationWhitelist());
|
||||
|
||||
returnVal = $compileProvider.imgSrcSanitizationWhitelist(newRe);
|
||||
expect(returnVal).toBe($compileProvider);
|
||||
expect($$sanitizeUriProvider.imgSrcSanitizationWhitelist()).toBe(newRe);
|
||||
expect($compileProvider.imgSrcSanitizationWhitelist()).toBe(newRe);
|
||||
});
|
||||
inject(function() {
|
||||
// needed to the module definition above is run...
|
||||
});
|
||||
});
|
||||
|
||||
it('should use $$sanitizeUri', function() {
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
|
||||
module(function($provide) {
|
||||
@@ -11171,55 +11258,113 @@ describe('$compile', function() {
|
||||
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should use $$sanitizeUri on concatenated trusted values', function() {
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri').and.returnValue('someSanitizedUrl');
|
||||
module(function($provide) {
|
||||
$provide.value('$$sanitizeUri', $$sanitizeUri);
|
||||
});
|
||||
inject(function($compile, $rootScope, $sce) {
|
||||
element = $compile('<img src="{{testUrl}}ponies"></img>')($rootScope);
|
||||
$rootScope.testUrl = $sce.trustAsUrl('javascript:foo();');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toEqual('someSanitizedUrl');
|
||||
|
||||
element = $compile('<img src="http://{{testUrl}}"></img>')($rootScope);
|
||||
$rootScope.testUrl = $sce.trustAsUrl('xyz');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toEqual('someSanitizedUrl');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not use $$sanitizeUri with trusted values', function() {
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri').and.throwError('Should not have been called');
|
||||
module(function($provide) {
|
||||
$provide.value('$$sanitizeUri', $$sanitizeUri);
|
||||
});
|
||||
inject(function($compile, $rootScope, $sce) {
|
||||
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
|
||||
// Assigning javascript:foo to src makes at least IE9-11 complain, so use another
|
||||
// protocol name.
|
||||
$rootScope.testUrl = $sce.trustAsMediaUrl('untrusted:foo();');
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('src')).toEqual('untrusted:foo();');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('img[srcset] sanitization', function() {
|
||||
|
||||
it('should not error if undefined', function() {
|
||||
it('should not error if srcset is undefined', function() {
|
||||
var linked = false;
|
||||
module(function() {
|
||||
directive('setter', valueFn(function(scope, elem, attrs) {
|
||||
// Set srcset to a value
|
||||
attrs.$set('srcset', 'http://example.com/');
|
||||
expect(attrs.srcset).toBe('http://example.com/');
|
||||
|
||||
// Now set it to undefined
|
||||
attrs.$set('srcset', undefined);
|
||||
expect(attrs.srcset).toBeUndefined();
|
||||
|
||||
linked = true;
|
||||
}));
|
||||
});
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile('<img setter></img>')($rootScope);
|
||||
|
||||
expect(linked).toBe(true);
|
||||
expect(element.attr('srcset')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT require trusted values for img srcset', inject(function($rootScope, $compile, $sce) {
|
||||
it('should NOT require trusted values for whitelisted values', inject(function($rootScope, $compile, $sce) {
|
||||
element = $compile('<img srcset="{{testUrl}}"></img>')($rootScope);
|
||||
$rootScope.testUrl = 'http://example.com/image.png';
|
||||
$rootScope.testUrl = 'http://example.com/image.png'; // `http` is whitelisted
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('srcset')).toEqual('http://example.com/image.png');
|
||||
// But it should accept trusted values anyway.
|
||||
$rootScope.testUrl = $sce.trustAsUrl('http://example.com/image2.png');
|
||||
}));
|
||||
|
||||
it('should accept trusted values, if they are also whitelisted', inject(function($rootScope, $compile, $sce) {
|
||||
element = $compile('<img srcset="{{testUrl}}"></img>')($rootScope);
|
||||
$rootScope.testUrl = $sce.trustAsUrl('http://example.com');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('srcset')).toEqual('http://example.com/image2.png');
|
||||
expect(element.attr('srcset')).toEqual('http://example.com');
|
||||
}));
|
||||
|
||||
it('does not work with trusted values', inject(function($rootScope, $compile, $sce) {
|
||||
// A limitation of the approach used for srcset is that you cannot use `trustAsUrl`.
|
||||
// Use trustAsHtml and ng-bind-html to work around this.
|
||||
element = $compile('<img srcset="{{testUrl}}"></img>')($rootScope);
|
||||
$rootScope.testUrl = $sce.trustAsUrl('javascript:something');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('srcset')).toEqual('unsafe:javascript:something');
|
||||
|
||||
element = $compile('<img srcset="{{testUrl}},{{testUrl}}"></img>')($rootScope);
|
||||
$rootScope.testUrl = $sce.trustAsUrl('javascript:something');
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('srcset')).toEqual(
|
||||
'unsafe:javascript:something ,unsafe:javascript:something');
|
||||
}));
|
||||
|
||||
it('should use $$sanitizeUri', function() {
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri').and.returnValue('someSanitizedUrl');
|
||||
module(function($provide) {
|
||||
$provide.value('$$sanitizeUri', $$sanitizeUri);
|
||||
});
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile('<img srcset="{{testUrl}}"></img>')($rootScope);
|
||||
$rootScope.testUrl = 'someUrl';
|
||||
|
||||
$$sanitizeUri.and.returnValue('someSanitizedUrl');
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('srcset')).toBe('someSanitizedUrl');
|
||||
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, true);
|
||||
|
||||
element = $compile('<img srcset="{{testUrl}}, {{testUrl}}"></img>')($rootScope);
|
||||
$rootScope.testUrl = 'javascript:yay';
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('srcset')).toEqual('someSanitizedUrl ,someSanitizedUrl');
|
||||
|
||||
element = $compile('<img srcset="java{{testUrl}}"></img>')($rootScope);
|
||||
$rootScope.testUrl = 'script:yay, javascript:nay';
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('srcset')).toEqual('someSanitizedUrl ,someSanitizedUrl');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11263,6 +11408,38 @@ describe('$compile', function() {
|
||||
});
|
||||
|
||||
describe('a[href] sanitization', function() {
|
||||
it('should NOT require trusted values for whitelisted values', inject(function($rootScope, $compile) {
|
||||
$rootScope.testUrl = 'http://example.com/image.png'; // `http` is whitelisted
|
||||
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual('http://example.com/image.png');
|
||||
|
||||
element = $compile('<a ng-href="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('ng-href')).toEqual('http://example.com/image.png');
|
||||
}));
|
||||
|
||||
it('should accept trusted values for non-whitelisted values', inject(function($rootScope, $compile, $sce) {
|
||||
$rootScope.testUrl = $sce.trustAsUrl('javascript:foo()'); // `javascript` is not whitelisted
|
||||
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual('javascript:foo()');
|
||||
|
||||
element = $compile('<a ng-href="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('ng-href')).toEqual('javascript:foo()');
|
||||
}));
|
||||
|
||||
it('should sanitize non-whitelisted values', inject(function($rootScope, $compile) {
|
||||
$rootScope.testUrl = 'javascript:foo()'; // `javascript` is not whitelisted
|
||||
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual('unsafe:javascript:foo()');
|
||||
|
||||
element = $compile('<a ng-href="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual('unsafe:javascript:foo()');
|
||||
}));
|
||||
|
||||
it('should not sanitize href on elements other than anchor', inject(function($compile, $rootScope) {
|
||||
element = $compile('<div href="{{testUrl}}"></div>')($rootScope);
|
||||
@@ -11272,7 +11449,7 @@ describe('$compile', function() {
|
||||
expect(element.attr('href')).toBe('javascript:doEvilStuff()');
|
||||
}));
|
||||
|
||||
it('should not sanitize attributes other than href', inject(function($compile, $rootScope) {
|
||||
it('should not sanitize attributes other than href/ng-href', inject(function($compile, $rootScope) {
|
||||
element = $compile('<a title="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.testUrl = 'javascript:doEvilStuff()';
|
||||
$rootScope.$apply();
|
||||
@@ -11280,48 +11457,21 @@ describe('$compile', function() {
|
||||
expect(element.attr('title')).toBe('javascript:doEvilStuff()');
|
||||
}));
|
||||
|
||||
it('should use $$sanitizeUriProvider for reconfiguration of the href whitelist', function() {
|
||||
module(function($compileProvider, $$sanitizeUriProvider) {
|
||||
var newRe = /javascript:/,
|
||||
returnVal;
|
||||
expect($compileProvider.aHrefSanitizationWhitelist()).toBe($$sanitizeUriProvider.aHrefSanitizationWhitelist());
|
||||
|
||||
returnVal = $compileProvider.aHrefSanitizationWhitelist(newRe);
|
||||
expect(returnVal).toBe($compileProvider);
|
||||
expect($$sanitizeUriProvider.aHrefSanitizationWhitelist()).toBe(newRe);
|
||||
expect($compileProvider.aHrefSanitizationWhitelist()).toBe(newRe);
|
||||
});
|
||||
inject(function() {
|
||||
// needed to the module definition above is run...
|
||||
});
|
||||
});
|
||||
|
||||
it('should use $$sanitizeUri', function() {
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri').and.returnValue('someSanitizedUrl');
|
||||
module(function($provide) {
|
||||
$provide.value('$$sanitizeUri', $$sanitizeUri);
|
||||
});
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile('<a href="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.testUrl = 'someUrl';
|
||||
|
||||
$$sanitizeUri.and.returnValue('someSanitizedUrl');
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('someSanitizedUrl');
|
||||
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should use $$sanitizeUri when declared via ng-href', function() {
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
|
||||
module(function($provide) {
|
||||
$provide.value('$$sanitizeUri', $$sanitizeUri);
|
||||
});
|
||||
inject(function($compile, $rootScope) {
|
||||
$$sanitizeUri.calls.reset();
|
||||
|
||||
element = $compile('<a ng-href="{{testUrl}}"></a>')($rootScope);
|
||||
$rootScope.testUrl = 'someUrl';
|
||||
|
||||
$$sanitizeUri.and.returnValue('someSanitizedUrl');
|
||||
$rootScope.$apply();
|
||||
expect(element.attr('href')).toBe('someSanitizedUrl');
|
||||
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, false);
|
||||
@@ -11329,72 +11479,72 @@ describe('$compile', function() {
|
||||
});
|
||||
|
||||
it('should use $$sanitizeUri when working with svg and xlink:href', function() {
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri').and.returnValue('https://clean.example.org');
|
||||
module(function($provide) {
|
||||
$provide.value('$$sanitizeUri', $$sanitizeUri);
|
||||
});
|
||||
inject(function($compile, $rootScope) {
|
||||
var elementA = $compile('<svg><a xlink:href="{{ testUrl + \'aTag\' }}"></a></svg>')($rootScope);
|
||||
var elementImage = $compile('<svg><image xlink:href="{{ testUrl + \'imageTag\' }}"></image></svg>')($rootScope);
|
||||
|
||||
//both of these fail the RESOURCE_URL test, that shouldn't be run
|
||||
// This URL would fail the RESOURCE_URL whitelist, but that test shouldn't be run
|
||||
// because these interpolations will be resolved against the URL context instead
|
||||
$rootScope.testUrl = 'https://bad.example.org';
|
||||
$$sanitizeUri.and.returnValue('https://clean.example.org');
|
||||
|
||||
var elementA = $compile('<svg><a xlink:href="{{ testUrl + \'aTag\' }}"></a></svg>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(elementA.find('a').attr('xlink:href')).toBe('https://clean.example.org');
|
||||
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl + 'aTag', false);
|
||||
|
||||
var elementImage = $compile('<svg><image xlink:href="{{ testUrl + \'imageTag\' }}"></image></svg>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(elementImage.find('image').attr('xlink:href')).toBe('https://clean.example.org');
|
||||
// <a> is navigational, so the second argument should be false to reach the aHref whitelist
|
||||
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl + 'aTag' , false);
|
||||
// <image> is media inclusion, it should use the imgSrc whitelist
|
||||
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl + 'imageTag', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should use $$sanitizeUri when working with svg and xlink:href through ng-href', function() {
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri').and.returnValue('https://clean.example.org');
|
||||
module(function($provide) {
|
||||
$provide.value('$$sanitizeUri', $$sanitizeUri);
|
||||
});
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile('<svg><a xlink:href="" ng-href="{{ testUrl }}"></a></svg>')($rootScope);
|
||||
//both of these fail the RESOURCE_URL test, that shouldn't be run
|
||||
// This URL would fail the RESOURCE_URL whitelist, but that test shouldn't be run
|
||||
// because these interpolations will be resolved against the URL context instead
|
||||
$rootScope.testUrl = 'https://bad.example.org';
|
||||
$$sanitizeUri.and.returnValue('https://clean.example.org');
|
||||
|
||||
element = $compile('<svg><a xlink:href="" ng-href="{{ testUrl }}"></a></svg>')($rootScope);
|
||||
$rootScope.$apply();
|
||||
expect(element.find('a').prop('href').baseVal).toBe('https://clean.example.org');
|
||||
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should use $$sanitizeUri when working with svg and xlink:href through ng-href', function() {
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
|
||||
module(function($provide) {
|
||||
$provide.value('$$sanitizeUri', $$sanitizeUri);
|
||||
});
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile('<svg><a xlink:href="" ng-href="{{ testUrl }}"></a></svg>')($rootScope);
|
||||
$rootScope.testUrl = 'evilUrl';
|
||||
|
||||
$$sanitizeUri.and.returnValue('someSanitizedUrl');
|
||||
$rootScope.$apply();
|
||||
expect(element.find('a').prop('href').baseVal).toBe('someSanitizedUrl');
|
||||
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have a RESOURCE_URL context for xlink:href by default', function() {
|
||||
it('should require a RESOURCE_URL context for xlink:href by if not on an anchor or image', function() {
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile('<svg><whatever xlink:href="{{ testUrl }}"></whatever></svg>')($rootScope);
|
||||
$rootScope.testUrl = 'https://bad.example.org';
|
||||
|
||||
expect(function() {
|
||||
$rootScope.$apply();
|
||||
}).toThrowError(/\$sce:insecurl/);
|
||||
}).toThrowMinErr('$interpolate', 'interr', 'Can\'t interpolate: {{ testUrl }}\n' +
|
||||
'Error: [$sce:insecurl] Blocked loading resource from url not allowed by $sceDelegate policy. ' +
|
||||
'URL: https://bad.example.org');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not have endless digests when given arrays in concatenable context', inject(function($compile, $rootScope) {
|
||||
element = $compile('<foo href="{{testUrl}}"></foo><foo href="{{::testUrl}}"></foo>' +
|
||||
'<foo href="http://example.com/{{testUrl}}"></foo><foo href="http://example.com/{{::testUrl}}"></foo>')($rootScope);
|
||||
$rootScope.testUrl = [1];
|
||||
$rootScope.$digest();
|
||||
|
||||
$rootScope.testUrl = [];
|
||||
$rootScope.$digest();
|
||||
|
||||
$rootScope.testUrl = {a:'b'};
|
||||
$rootScope.$digest();
|
||||
|
||||
$rootScope.testUrl = {};
|
||||
$rootScope.$digest();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('interpolation on HTML DOM event handler attributes onclick, onXYZ, formaction', function() {
|
||||
|
||||
@@ -118,211 +118,3 @@ describe('boolean attr directives', function() {
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('ngSrc', function() {
|
||||
it('should interpolate the expression and bind to src with raw same-domain value',
|
||||
inject(function($compile, $rootScope) {
|
||||
var element = $compile('<div ng-src="{{id}}"></div>')($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toBeUndefined();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = '/somewhere/here';
|
||||
});
|
||||
expect(element.attr('src')).toEqual('/somewhere/here');
|
||||
|
||||
dealoc(element);
|
||||
}));
|
||||
|
||||
|
||||
it('should interpolate the expression and bind to src with a trusted value', inject(function($compile, $rootScope, $sce) {
|
||||
var element = $compile('<div ng-src="{{id}}"></div>')($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toBeUndefined();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = $sce.trustAsResourceUrl('http://somewhere');
|
||||
});
|
||||
expect(element.attr('src')).toEqual('http://somewhere');
|
||||
|
||||
dealoc(element);
|
||||
}));
|
||||
|
||||
|
||||
it('should NOT interpolate a multi-part expression for non-img src attribute', inject(function($compile, $rootScope) {
|
||||
expect(function() {
|
||||
var element = $compile('<div ng-src="some/{{id}}"></div>')($rootScope);
|
||||
dealoc(element);
|
||||
}).toThrowMinErr(
|
||||
'$interpolate', 'noconcat', 'Error while interpolating: some/{{id}}\nStrict ' +
|
||||
'Contextual Escaping disallows interpolations that concatenate multiple expressions ' +
|
||||
'when a trusted value is required. See http://docs.angularjs.org/api/ng.$sce');
|
||||
}));
|
||||
|
||||
|
||||
it('should interpolate a multi-part expression for regular attributes', inject(function($compile, $rootScope) {
|
||||
var element = $compile('<div foo="some/{{id}}"></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('foo')).toBe('some/');
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = 1;
|
||||
});
|
||||
expect(element.attr('foo')).toEqual('some/1');
|
||||
}));
|
||||
|
||||
|
||||
it('should NOT interpolate a wrongly typed expression', inject(function($compile, $rootScope, $sce) {
|
||||
expect(function() {
|
||||
var element = $compile('<div ng-src="{{id}}"></div>')($rootScope);
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = $sce.trustAsUrl('http://somewhere');
|
||||
});
|
||||
element.attr('src');
|
||||
}).toThrowMinErr(
|
||||
'$interpolate', 'interr', 'Can\'t interpolate: {{id}}\nError: [$sce:insecurl] Blocked ' +
|
||||
'loading resource from url not allowed by $sceDelegate policy. URL: http://somewhere');
|
||||
}));
|
||||
|
||||
|
||||
// Support: IE 9-11 only
|
||||
if (msie) {
|
||||
it('should update the element property as well as the attribute', inject(
|
||||
function($compile, $rootScope, $sce) {
|
||||
// on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
|
||||
// then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
|
||||
// to set the property as well to achieve the desired effect
|
||||
|
||||
var element = $compile('<div ng-src="{{id}}"></div>')($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element.prop('src')).toBeUndefined();
|
||||
dealoc(element);
|
||||
|
||||
element = $compile('<div ng-src="some/"></div>')($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element.prop('src')).toEqual('some/');
|
||||
dealoc(element);
|
||||
|
||||
element = $compile('<div ng-src="{{id}}"></div>')($rootScope);
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = $sce.trustAsResourceUrl('http://somewhere');
|
||||
});
|
||||
expect(element.prop('src')).toEqual('http://somewhere');
|
||||
|
||||
dealoc(element);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
describe('ngSrcset', function() {
|
||||
it('should interpolate the expression and bind to srcset', inject(function($compile, $rootScope) {
|
||||
var element = $compile('<div ng-srcset="some/{{id}} 2x"></div>')($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('srcset')).toBeUndefined();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = 1;
|
||||
});
|
||||
expect(element.attr('srcset')).toEqual('some/1 2x');
|
||||
|
||||
dealoc(element);
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
describe('ngHref', function() {
|
||||
var element;
|
||||
|
||||
afterEach(function() {
|
||||
dealoc(element);
|
||||
});
|
||||
|
||||
|
||||
it('should interpolate the expression and bind to href', inject(function($compile, $rootScope) {
|
||||
element = $compile('<div ng-href="some/{{id}}"></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual('some/');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = 1;
|
||||
});
|
||||
expect(element.attr('href')).toEqual('some/1');
|
||||
}));
|
||||
|
||||
|
||||
it('should bind href and merge with other attrs', inject(function($rootScope, $compile) {
|
||||
element = $compile('<a ng-href="{{url}}" rel="{{rel}}"></a>')($rootScope);
|
||||
$rootScope.url = 'http://server';
|
||||
$rootScope.rel = 'REL';
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual('http://server');
|
||||
expect(element.attr('rel')).toEqual('REL');
|
||||
}));
|
||||
|
||||
|
||||
it('should bind href even if no interpolation', inject(function($rootScope, $compile) {
|
||||
element = $compile('<a ng-href="http://server"></a>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual('http://server');
|
||||
}));
|
||||
|
||||
it('should not set the href if ng-href is empty', inject(function($rootScope, $compile) {
|
||||
$rootScope.url = null;
|
||||
element = $compile('<a ng-href="{{url}}">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual(undefined);
|
||||
}));
|
||||
|
||||
it('should remove the href if ng-href changes to empty', inject(function($rootScope, $compile) {
|
||||
$rootScope.url = 'http://www.google.com/';
|
||||
element = $compile('<a ng-href="{{url}}">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
$rootScope.url = null;
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual(undefined);
|
||||
}));
|
||||
|
||||
// Support: IE 9-11 only, Edge 12-15+
|
||||
if (msie || /\bEdge\/[\d.]+\b/.test(window.navigator.userAgent)) {
|
||||
// IE/Edge fail when setting a href to a URL containing a % that isn't a valid escape sequence
|
||||
// See https://github.com/angular/angular.js/issues/13388
|
||||
it('should throw error if ng-href contains a non-escaped percent symbol', inject(function($rootScope, $compile) {
|
||||
element = $compile('<a ng-href="http://www.google.com/{{\'a%link\'}}">')($rootScope);
|
||||
|
||||
expect(function() {
|
||||
$rootScope.$digest();
|
||||
}).toThrow();
|
||||
}));
|
||||
}
|
||||
|
||||
if (isDefined(window.SVGElement)) {
|
||||
describe('SVGAElement', function() {
|
||||
it('should interpolate the expression and bind to xlink:href', inject(function($compile, $rootScope) {
|
||||
element = $compile('<svg><a ng-href="some/{{id}}"></a></svg>')($rootScope);
|
||||
var child = element.children('a');
|
||||
$rootScope.$digest();
|
||||
expect(child.attr('xlink:href')).toEqual('some/');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = 1;
|
||||
});
|
||||
expect(child.attr('xlink:href')).toEqual('some/1');
|
||||
}));
|
||||
|
||||
|
||||
it('should bind xlink:href even if no interpolation', inject(function($rootScope, $compile) {
|
||||
element = $compile('<svg><a ng-href="http://server"></a></svg>')($rootScope);
|
||||
var child = element.children('a');
|
||||
$rootScope.$digest();
|
||||
expect(child.attr('xlink:href')).toEqual('http://server');
|
||||
}));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
'use strict';
|
||||
|
||||
describe('ngHref', function() {
|
||||
var element;
|
||||
|
||||
afterEach(function() {
|
||||
dealoc(element);
|
||||
});
|
||||
|
||||
|
||||
it('should interpolate the expression and bind to href', inject(function($compile, $rootScope) {
|
||||
element = $compile('<a ng-href="some/{{id}}"></div>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual('some/');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = 1;
|
||||
});
|
||||
expect(element.attr('href')).toEqual('some/1');
|
||||
}));
|
||||
|
||||
|
||||
it('should bind href and merge with other attrs', inject(function($rootScope, $compile) {
|
||||
element = $compile('<a ng-href="{{url}}" rel="{{rel}}"></a>')($rootScope);
|
||||
$rootScope.url = 'http://server';
|
||||
$rootScope.rel = 'REL';
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual('http://server');
|
||||
expect(element.attr('rel')).toEqual('REL');
|
||||
}));
|
||||
|
||||
|
||||
it('should bind href even if no interpolation', inject(function($rootScope, $compile) {
|
||||
element = $compile('<a ng-href="http://server"></a>')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual('http://server');
|
||||
}));
|
||||
|
||||
it('should not set the href if ng-href is empty', inject(function($rootScope, $compile) {
|
||||
$rootScope.url = null;
|
||||
element = $compile('<a ng-href="{{url}}">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual(undefined);
|
||||
}));
|
||||
|
||||
it('should remove the href if ng-href changes to empty', inject(function($rootScope, $compile) {
|
||||
$rootScope.url = 'http://www.google.com/';
|
||||
element = $compile('<a ng-href="{{url}}">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
|
||||
$rootScope.url = null;
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toEqual(undefined);
|
||||
}));
|
||||
|
||||
it('should sanitize interpolated url', inject(function($rootScope, $compile) {
|
||||
/* eslint no-script-url: "off" */
|
||||
$rootScope.imageUrl = 'javascript:alert(1);';
|
||||
element = $compile('<a ng-href="{{imageUrl}}">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toBe('unsafe:javascript:alert(1);');
|
||||
}));
|
||||
|
||||
it('should sanitize non-interpolated url', inject(function($rootScope, $compile) {
|
||||
element = $compile('<a ng-href="javascript:alert(1);">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('href')).toBe('unsafe:javascript:alert(1);');
|
||||
}));
|
||||
|
||||
|
||||
// Support: IE 9-11 only, Edge 12-15+
|
||||
if (msie || /\bEdge\/[\d.]+\b/.test(window.navigator.userAgent)) {
|
||||
// IE/Edge fail when setting a href to a URL containing a % that isn't a valid escape sequence
|
||||
// See https://github.com/angular/angular.js/issues/13388
|
||||
it('should throw error if ng-href contains a non-escaped percent symbol', inject(function($rootScope, $compile) {
|
||||
expect(function() {
|
||||
element = $compile('<a ng-href="http://www.google.com/{{\'a%link\'}}">')($rootScope);
|
||||
}).toThrow();
|
||||
}));
|
||||
}
|
||||
|
||||
if (isDefined(window.SVGElement)) {
|
||||
describe('SVGAElement', function() {
|
||||
it('should interpolate the expression and bind to xlink:href', inject(function($compile, $rootScope) {
|
||||
element = $compile('<svg><a ng-href="some/{{id}}"></a></svg>')($rootScope);
|
||||
var child = element.children('a');
|
||||
$rootScope.$digest();
|
||||
expect(child.attr('xlink:href')).toEqual('some/');
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = 1;
|
||||
});
|
||||
expect(child.attr('xlink:href')).toEqual('some/1');
|
||||
}));
|
||||
|
||||
|
||||
it('should bind xlink:href even if no interpolation', inject(function($rootScope, $compile) {
|
||||
element = $compile('<svg><a ng-href="http://server"></a></svg>')($rootScope);
|
||||
var child = element.children('a');
|
||||
$rootScope.$digest();
|
||||
expect(child.attr('xlink:href')).toEqual('http://server');
|
||||
}));
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -18,12 +18,66 @@ describe('ngSrc', function() {
|
||||
expect(element.attr('src')).toBeUndefined();
|
||||
}));
|
||||
|
||||
it('should sanitize url', inject(function($rootScope, $compile) {
|
||||
it('should sanitize interpolated url', inject(function($rootScope, $compile) {
|
||||
$rootScope.imageUrl = 'javascript:alert(1);';
|
||||
element = $compile('<img ng-src="{{imageUrl}}">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toBe('unsafe:javascript:alert(1);');
|
||||
}));
|
||||
|
||||
it('should sanitize non-interpolated url', inject(function($rootScope, $compile) {
|
||||
element = $compile('<img ng-src="javascript:alert(1);">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toBe('unsafe:javascript:alert(1);');
|
||||
}));
|
||||
|
||||
it('should interpolate the expression and bind to src with raw same-domain value', inject(function($compile, $rootScope) {
|
||||
element = $compile('<img ng-src="{{id}}"></img>')($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toBeUndefined();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = '/somewhere/here';
|
||||
});
|
||||
expect(element.attr('src')).toEqual('/somewhere/here');
|
||||
}));
|
||||
|
||||
it('should interpolate a multi-part expression for img src attribute (which requires the MEDIA_URL context)', inject(function($compile, $rootScope) {
|
||||
element = $compile('<img ng-src="some/{{id}}"></img>')($rootScope);
|
||||
expect(element.attr('src')).toBe(undefined); // URL concatenations are all-or-nothing
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = 1;
|
||||
});
|
||||
expect(element.attr('src')).toEqual('some/1');
|
||||
}));
|
||||
|
||||
// Support: IE 9-11 only
|
||||
if (msie) {
|
||||
it('should update the element property as well as the attribute', inject(function($compile, $rootScope, $sce) {
|
||||
// on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
|
||||
// then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
|
||||
// to set the property as well to achieve the desired effect
|
||||
|
||||
element = $compile('<img ng-src="{{id}}"></img>')($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element.prop('src')).toBe('');
|
||||
dealoc(element);
|
||||
|
||||
element = $compile('<img ng-src="some/"></img>')($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element.prop('src')).toMatch('/some/$');
|
||||
dealoc(element);
|
||||
|
||||
element = $compile('<img ng-src="{{id}}"></img>')($rootScope);
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = $sce.trustAsResourceUrl('http://somewhere/abc');
|
||||
});
|
||||
expect(element.prop('src')).toEqual('http://somewhere/abc');
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
describe('iframe[ng-src]', function() {
|
||||
@@ -68,5 +122,43 @@ describe('ngSrc', function() {
|
||||
|
||||
expect(element.attr('src')).toEqual('javascript:doTrustedStuff()');
|
||||
}));
|
||||
|
||||
it('should interpolate the expression and bind to src with a trusted value', inject(function($compile, $rootScope, $sce) {
|
||||
element = $compile('<iframe ng-src="{{id}}"></iframe>')($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('src')).toBeUndefined();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = $sce.trustAsResourceUrl('http://somewhere');
|
||||
});
|
||||
expect(element.attr('src')).toEqual('http://somewhere');
|
||||
}));
|
||||
|
||||
|
||||
it('should NOT interpolate a multi-part expression in a `src` attribute that requires a non-MEDIA_URL context', inject(function($compile, $rootScope) {
|
||||
expect(function() {
|
||||
element = $compile('<iframe ng-src="some/{{id}}"></iframe>')($rootScope);
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = 1;
|
||||
});
|
||||
}).toThrowMinErr(
|
||||
'$interpolate', 'noconcat', 'Error while interpolating: some/{{id}}\nStrict ' +
|
||||
'Contextual Escaping disallows interpolations that concatenate multiple expressions ' +
|
||||
'when a trusted value is required. See http://docs.angularjs.org/api/ng.$sce');
|
||||
}));
|
||||
|
||||
|
||||
it('should NOT interpolate a wrongly typed expression', inject(function($compile, $rootScope, $sce) {
|
||||
expect(function() {
|
||||
element = $compile('<iframe ng-src="{{id}}"></iframe>')($rootScope);
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = $sce.trustAsUrl('http://somewhere');
|
||||
});
|
||||
element.attr('src');
|
||||
}).toThrowMinErr(
|
||||
'$interpolate', 'interr', 'Can\'t interpolate: {{id}}\nError: [$sce:insecurl] Blocked ' +
|
||||
'loading resource from url not allowed by $sceDelegate policy. URL: http://somewhere');
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,5 +34,18 @@ describe('ngSrcset', function() {
|
||||
element = $compile('<img ng-attr-srcset="{{undefined}}">')($rootScope);
|
||||
$rootScope.$digest();
|
||||
}));
|
||||
});
|
||||
|
||||
it('should interpolate the expression and bind to srcset', inject(function($compile, $rootScope) {
|
||||
var element = $compile('<img ng-srcset="some/{{id}} 2x"></div>')($rootScope);
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element.attr('srcset')).toBeUndefined();
|
||||
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.id = 1;
|
||||
});
|
||||
expect(element.attr('srcset')).toEqual('some/1 2x');
|
||||
|
||||
dealoc(element);
|
||||
}));
|
||||
});
|
||||
|
||||
+49
-21
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable no-script-url */
|
||||
|
||||
describe('$interpolate', function() {
|
||||
|
||||
it('should return the interpolation object when there are no bindings and textOnly is undefined',
|
||||
@@ -267,7 +269,9 @@ describe('$interpolate', function() {
|
||||
|
||||
expect(function() {
|
||||
$interpolate('{{foo}}', true, sce.CSS)(scope);
|
||||
}).toThrowMinErr('$interpolate', 'interr');
|
||||
}).toThrowMinErr(
|
||||
'$interpolate', 'interr', 'Can\'t interpolate: {{foo}}\nError: [$sce:unsafe] ' +
|
||||
'Attempting to use an unsafe value in a safe context.');
|
||||
}));
|
||||
|
||||
it('should NOT interpolate mistyped expressions', inject(function($interpolate, $rootScope) {
|
||||
@@ -276,7 +280,9 @@ describe('$interpolate', function() {
|
||||
|
||||
expect(function() {
|
||||
$interpolate('{{foo}}', true, sce.HTML)(scope);
|
||||
}).toThrowMinErr('$interpolate', 'interr');
|
||||
}).toThrowMinErr(
|
||||
'$interpolate', 'interr', 'Can\'t interpolate: {{foo}}\nError: [$sce:unsafe] ' +
|
||||
'Attempting to use an unsafe value in a safe context.');
|
||||
}));
|
||||
|
||||
it('should interpolate trusted expressions in a regular context', inject(function($interpolate) {
|
||||
@@ -291,17 +297,16 @@ describe('$interpolate', function() {
|
||||
|
||||
// The concatenation of trusted values does not necessarily result in a trusted value. (For
|
||||
// instance, you can construct evil JS code by putting together pieces of JS strings that are by
|
||||
// themselves safe to execute in isolation.)
|
||||
// themselves safe to execute in isolation). Therefore, some contexts disable it, such as CSS.
|
||||
it('should NOT interpolate trusted expressions with multiple parts', inject(function($interpolate) {
|
||||
var foo = sce.trustAsCss('foo');
|
||||
var bar = sce.trustAsCss('bar');
|
||||
expect(function() {
|
||||
return $interpolate('{{foo}}{{bar}}', true, sce.CSS)({foo: foo, bar: bar});
|
||||
}).toThrowMinErr(
|
||||
'$interpolate', 'noconcat', 'Error while interpolating: {{foo}}{{bar}}\n' +
|
||||
'$interpolate', 'interr', 'Error while interpolating: {{foo}}{{bar}}\n' +
|
||||
'Strict Contextual Escaping disallows interpolations that concatenate multiple ' +
|
||||
'expressions when a trusted value is required. See ' +
|
||||
'http://docs.angularjs.org/api/ng.$sce');
|
||||
'expressions when a trusted value is required. See http://docs.angularjs.org/api/ng.$sce');
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -380,26 +385,32 @@ describe('$interpolate', function() {
|
||||
|
||||
|
||||
describe('isTrustedContext', function() {
|
||||
it('should NOT interpolate a multi-part expression when isTrustedContext is true', inject(function($interpolate) {
|
||||
var isTrustedContext = true;
|
||||
it('should NOT interpolate a multi-part expression when isTrustedContext is RESOURCE_URL', inject(function($sce, $interpolate) {
|
||||
var isTrustedContext = $sce.RESOURCE_URL;
|
||||
expect(function() {
|
||||
$interpolate('constant/{{var}}', true, isTrustedContext);
|
||||
$interpolate('constant/{{var}}', true, isTrustedContext)('val');
|
||||
}).toThrowMinErr(
|
||||
'$interpolate', 'noconcat', 'Error while interpolating: constant/{{var}}\nStrict ' +
|
||||
'Contextual Escaping disallows interpolations that concatenate multiple expressions ' +
|
||||
'when a trusted value is required. See http://docs.angularjs.org/api/ng.$sce');
|
||||
'$interpolate', 'interr',
|
||||
'Can\'t interpolate: constant/{{var}}\nError: [$interpolate:noconcat] Error while ' +
|
||||
'interpolating: constant/{{var}}\nStrict Contextual Escaping disallows interpolations ' +
|
||||
'that concatenate multiple expressions when a trusted value is required. ' +
|
||||
'See http://docs.angularjs.org/api/ng.$sce');
|
||||
expect(function() {
|
||||
$interpolate('{{var}}/constant', true, isTrustedContext);
|
||||
$interpolate('{{var}}/constant', true, isTrustedContext)('val');
|
||||
}).toThrowMinErr(
|
||||
'$interpolate', 'noconcat', 'Error while interpolating: {{var}}/constant\nStrict ' +
|
||||
'Contextual Escaping disallows interpolations that concatenate multiple expressions ' +
|
||||
'when a trusted value is required. See http://docs.angularjs.org/api/ng.$sce');
|
||||
expect(function() {
|
||||
$interpolate('{{foo}}{{bar}}', true, isTrustedContext);
|
||||
'$interpolate', 'interr',
|
||||
'Can\'t interpolate: {{var}}/constant\nError: [$interpolate:noconcat] Error while ' +
|
||||
'interpolating: {{var}}/constant\nStrict Contextual Escaping disallows interpolations ' +
|
||||
'that concatenate multiple expressions when a trusted value is required. ' +
|
||||
'See http://docs.angularjs.org/api/ng.$sce');
|
||||
expect(function() {
|
||||
$interpolate('{{foo}}{{bar}}', true, isTrustedContext)('val');
|
||||
}).toThrowMinErr(
|
||||
'$interpolate', 'noconcat', 'Error while interpolating: {{foo}}{{bar}}\nStrict ' +
|
||||
'Contextual Escaping disallows interpolations that concatenate multiple expressions ' +
|
||||
'when a trusted value is required. See http://docs.angularjs.org/api/ng.$sce');
|
||||
'$interpolate', 'interr',
|
||||
'Can\'t interpolate: {{foo}}{{bar}}\nError: [$interpolate:noconcat] Error while ' +
|
||||
'interpolating: {{foo}}{{bar}}\nStrict Contextual Escaping disallows interpolations ' +
|
||||
'that concatenate multiple expressions when a trusted value is required. ' +
|
||||
'See http://docs.angularjs.org/api/ng.$sce');
|
||||
}));
|
||||
|
||||
it('should interpolate a multi-part expression when isTrustedContext is false', inject(function($interpolate) {
|
||||
@@ -407,6 +418,23 @@ describe('$interpolate', function() {
|
||||
expect($interpolate('some/{{id}}')({id: 1})).toEqual('some/1');
|
||||
expect($interpolate('{{foo}}{{bar}}')({foo: 1, bar: 2})).toEqual('12');
|
||||
}));
|
||||
|
||||
|
||||
it('should interpolate a multi-part expression when isTrustedContext is URL', inject(function($sce, $interpolate) {
|
||||
expect($interpolate('some/{{id}}', true, $sce.URL)({})).toEqual('some/');
|
||||
expect($interpolate('some/{{id}}', true, $sce.URL)({id: 1})).toEqual('some/1');
|
||||
expect($interpolate('{{foo}}{{bar}}', true, $sce.URL)({foo: 1, bar: 2})).toEqual('12');
|
||||
}));
|
||||
|
||||
|
||||
it('should interpolate and sanitize a multi-part expression when isTrustedContext is URL', inject(function($sce, $interpolate) {
|
||||
expect($interpolate('some/{{id}}', true, $sce.URL)({})).toEqual('some/');
|
||||
expect($interpolate('some/{{id}}', true, $sce.URL)({id: 'javascript:'})).toEqual('some/javascript:');
|
||||
expect($interpolate('{{foo}}{{bar}}', true, $sce.URL)({foo: 'javascript:', bar: 'javascript:'})).toEqual('unsafe:javascript:javascript:');
|
||||
}));
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
+80
-1
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable no-script-url */
|
||||
|
||||
describe('SCE', function() {
|
||||
|
||||
describe('when disabled', function() {
|
||||
@@ -211,7 +213,7 @@ describe('SCE', function() {
|
||||
expect($sce.parseAsJs('"string"')()).toBe('string');
|
||||
}));
|
||||
|
||||
it('should be possible to do one-time binding', function() {
|
||||
it('should be possible to do one-time binding on a non-concatenable context', function() {
|
||||
module(provideLog);
|
||||
inject(function($sce, $rootScope, log) {
|
||||
$rootScope.$watch($sce.parseAsHtml('::foo'), function(value) {
|
||||
@@ -236,6 +238,31 @@ describe('SCE', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should be possible to do one-time binding on a concatenable context', function() {
|
||||
module(provideLog);
|
||||
inject(function($sce, $rootScope, log) {
|
||||
$rootScope.$watch($sce.parseAsUrl('::foo'), function(value) {
|
||||
log(value + '');
|
||||
});
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual('undefined'); // initial listener call
|
||||
log.reset();
|
||||
|
||||
$rootScope.foo = $sce.trustAs($sce.URL, 'trustedValue');
|
||||
expect($rootScope.$$watchers.length).toBe(1);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect($rootScope.$$watchers.length).toBe(0);
|
||||
expect(log).toEqual('trustedValue');
|
||||
log.reset();
|
||||
|
||||
$rootScope.foo = $sce.trustAs($sce.URL, 'anotherTrustedValue');
|
||||
$rootScope.$digest();
|
||||
expect(log).toEqual(''); // watcher no longer active
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT parse constant non-literals', inject(function($sce) {
|
||||
// Until there's a real world use case for this, we're disallowing
|
||||
// constant non-literals. See $SceParseProvider.
|
||||
@@ -525,6 +552,44 @@ describe('SCE', function() {
|
||||
));
|
||||
});
|
||||
|
||||
describe('URL-context sanitization', function() {
|
||||
it('should sanitize values that are not whitelisted', inject(function($sce) {
|
||||
expect($sce.getTrustedMediaUrl('javascript:foo')).toEqual('unsafe:javascript:foo');
|
||||
expect($sce.getTrustedUrl('javascript:foo')).toEqual('unsafe:javascript:foo');
|
||||
}));
|
||||
|
||||
it('should not sanitize values that are whitelisted', inject(function($sce) {
|
||||
expect($sce.getTrustedMediaUrl('http://example.com')).toEqual('http://example.com');
|
||||
expect($sce.getTrustedUrl('http://example.com')).toEqual('http://example.com');
|
||||
}));
|
||||
|
||||
it('should not sanitize trusted values', inject(function($sce) {
|
||||
expect($sce.getTrustedMediaUrl($sce.trustAsMediaUrl('javascript:foo'))).toEqual('javascript:foo');
|
||||
expect($sce.getTrustedMediaUrl($sce.trustAsUrl('javascript:foo'))).toEqual('javascript:foo');
|
||||
expect($sce.getTrustedMediaUrl($sce.trustAsResourceUrl('javascript:foo'))).toEqual('javascript:foo');
|
||||
|
||||
expect($sce.getTrustedUrl($sce.trustAsMediaUrl('javascript:foo'))).toEqual('unsafe:javascript:foo');
|
||||
expect($sce.getTrustedUrl($sce.trustAsUrl('javascript:foo'))).toEqual('javascript:foo');
|
||||
expect($sce.getTrustedUrl($sce.trustAsResourceUrl('javascript:foo'))).toEqual('javascript:foo');
|
||||
}));
|
||||
|
||||
it('should use the $$sanitizeUri', function() {
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri').and.returnValue('someSanitizedUrl');
|
||||
module(function($provide) {
|
||||
$provide.value('$$sanitizeUri', $$sanitizeUri);
|
||||
});
|
||||
inject(function($sce) {
|
||||
expect($sce.getTrustedMediaUrl('someUrl')).toEqual('someSanitizedUrl');
|
||||
expect($$sanitizeUri).toHaveBeenCalledOnceWith('someUrl', true);
|
||||
|
||||
$$sanitizeUri.calls.reset();
|
||||
|
||||
expect($sce.getTrustedUrl('someUrl')).toEqual('someSanitizedUrl');
|
||||
expect($$sanitizeUri).toHaveBeenCalledOnceWith('someUrl', false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sanitizing html', function() {
|
||||
describe('when $sanitize is NOT available', function() {
|
||||
it('should throw an exception for getTrusted(string) values', inject(function($sce) {
|
||||
@@ -535,9 +600,23 @@ describe('SCE', function() {
|
||||
|
||||
describe('when $sanitize is available', function() {
|
||||
beforeEach(function() { module('ngSanitize'); });
|
||||
|
||||
it('should sanitize html using $sanitize', inject(function($sce) {
|
||||
expect($sce.getTrustedHtml('a<xxx><B>b</B></xxx>c')).toBe('a<b>b</b>c');
|
||||
}));
|
||||
|
||||
// Note: that test only passes if HTML is added to the concatenable contexts list.
|
||||
// See isConcatenableSecureContext in interpolate.js for that.
|
||||
//
|
||||
// if (!msie || msie >= 11) {
|
||||
// it('can set dynamic srcdocs with concatenations and sanitize the result',
|
||||
// inject(function($compile, $rootScope) {
|
||||
// var element = $compile('<iframe srcdoc="<b><script>{{html}}"></iframe>')($rootScope);
|
||||
// $rootScope.html = 'no</script>yes</b>';
|
||||
// $rootScope.$digest();
|
||||
// expect(angular.lowercase(element.attr('srcdoc'))).toEqual('<b>yes</b>');
|
||||
// }));
|
||||
// }
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -495,7 +495,7 @@ describe('HTML', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should use $$sanitizeUri for links', function() {
|
||||
it('should use $$sanitizeUri for a[href] links', function() {
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
|
||||
module(function($provide) {
|
||||
$provide.value('$$sanitizeUri', $$sanitizeUri);
|
||||
@@ -511,7 +511,7 @@ describe('HTML', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should use $$sanitizeUri for links', function() {
|
||||
it('should use $$sanitizeUri for img[src] links', function() {
|
||||
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
|
||||
module(function($provide) {
|
||||
$provide.value('$$sanitizeUri', $$sanitizeUri);
|
||||
|
||||
Reference in New Issue
Block a user