diff --git a/src/ng/compile.js b/src/ng/compile.js index 47fc44c2e053..07c3fb51a835 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1270,14 +1270,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { /** * @ngdoc method - * @name $compileProvider#aHrefSanitizationWhitelist + * @name $compileProvider#uriSanitizationWhitelist * @kind function * * @description * Retrieves or overrides the default regular expression that is used for whitelisting of safe - * urls during a[href] sanitization. + * urls during URL-context sanitization. * - * The sanitization is a security measure aimed at preventing XSS attacks via html links. + * The sanitization is a security measure aimed at preventing XSS attacks. * * 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` @@ -1288,45 +1288,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ - this.aHrefSanitizationWhitelist = function(regexp) { + this.uriSanitizationWhitelist = function(regexp) { if (isDefined(regexp)) { - $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); + $$sanitizeUriProvider.uriSanitizationWhitelist(regexp); return this; } else { - return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); + return $$sanitizeUriProvider.uriSanitizationWhitelist(); } }; - /** - * @ngdoc method - * @name $compileProvider#imgSrcSanitizationWhitelist - * @kind function - * - * @description - * 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. - * - * 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. - * - * @param {RegExp=} regexp New regexp to whitelist urls with. - * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for - * chaining otherwise. - */ - this.imgSrcSanitizationWhitelist = function(regexp) { - if (isDefined(regexp)) { - $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); - return this; - } else { - return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); - } - }; - /** * @ngdoc method * @name $compileProvider#debugInfoEnabled @@ -1447,9 +1418,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'); @@ -1629,11 +1600,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { nodeName = nodeName_(this.$$element); - if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) || - (nodeName === 'img' && key === 'src')) { - // sanitize a[href] and img[src] values - this[key] = value = $$sanitizeUri(value, key === 'src'); - } else if (nodeName === 'img' && key === 'srcset' && isDefined(value)) { + // img[srcset] is a bit too weird of a beast to handle through $sce. + // Instead, for now at least, sanitize each of the URIs individually. + // That works even dynamically, but it's not bypassable through the $sce. + // Instead, if you want several unsafe URLs as-is, you should probably + // use trustAsHtml on the whole tag. + if (nodeName === 'img' && key === 'srcset' && value) { + // sanitize img[srcset] values var result = ''; @@ -1651,7 +1624,7 @@ 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.getTrustedUrl(trim(rawUris[innerIdx])); // add the descriptor result += (' ' + trim(rawUris[innerIdx + 1])); } @@ -1660,7 +1633,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var lastTuple = trim(rawUris[i * 2]).split(/\s/); // sanitize the last uri - result += $$sanitizeUri(trim(lastTuple[0]), true); + result += $sce.getTrustedUrl(trim(lastTuple[0])); // and add the last descriptor if any if (lastTuple.length === 2) { @@ -3186,6 +3159,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (attrNormalizedName === 'src' || attrNormalizedName === 'ngSrc') { if (['img', 'video', 'audio', 'source', 'track'].indexOf(tag) === -1) { return $sce.RESOURCE_URL; + } else { + return $sce.URL; } // maction[xlink:href] can source SVG. It's not limited to . } else if (attrNormalizedName === 'xlinkHref' || @@ -3194,6 +3169,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { (tag === 'link' && attrNormalizedName === 'href') ) { return $sce.RESOURCE_URL; + } else if (tag === 'a' && (attrNormalizedName === 'href' || + attrNormalizedName === 'xlinkHref' || + attrNormalizedName === 'ngHref')) { + return $sce.URL; } } diff --git a/src/ng/directive/attrs.js b/src/ng/directive/attrs.js index adc398425fd9..384b443010bd 100644 --- a/src/ng/directive/attrs.js +++ b/src/ng/directive/attrs.js @@ -429,7 +429,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 reuse the value put in attr[name] since $set might have sanitized the url. if (msie && propName) element.prop(propName, attr[name]); }); } diff --git a/src/ng/interpolate.js b/src/ng/interpolate.js index ebfdb72c5f26..334f1976e72f 100644 --- a/src/ng/interpolate.js +++ b/src/ng/interpolate.js @@ -121,6 +121,10 @@ function $InterpolateProvider() { return unwatch; } + function isConcatenationAllowed(context) { + return context === $sce.URL; + } + /** * @ngdoc service * @name $interpolate @@ -262,7 +266,9 @@ function $InterpolateProvider() { textLength = text.length, exp, concat = [], - expressionPositions = []; + expressionPositions = [], + singleExpression = false, + contextAllowsConcatenation = isConcatenationAllowed(trustedContext); while (index < textLength) { if (((startIndex = text.indexOf(startSymbol, index)) !== -1) && @@ -275,7 +281,7 @@ function $InterpolateProvider() { 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) { @@ -285,15 +291,21 @@ function $InterpolateProvider() { } } + if (concat.length === 1 && expressionPositions.length === 1) { + singleExpression = true; + } + // 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-context is dispensed of this, since its getTrusted method can sanitize. + // In that context, .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) { @@ -301,11 +313,32 @@ function $InterpolateProvider() { if (allOrNothing && isUndefined(values[i])) return; concat[expressionPositions[i]] = values[i]; } - return concat.join(''); + + if (contextAllowsConcatenation) { + if (singleExpression) { + // The raw value was left as-is by parseStringifyInterceptor + return $sce.getTrusted(trustedContext, concat[0]); + } else { + return $sce.getTrusted(trustedContext, concat.join('')); + } + } else if (trustedContext) { + if (concat.length > 1) { + // there's at least two parts, so expr + string or exp + exp, and this context + // doesn't allow that. + $interpolateMinErr.throwNoconcat(text); + } else { + return concat.join(''); + } + } else { // In an unprivileged context, just concatenate and return. + return concat.join(''); + } }; var getValue = function(value) { - return trustedContext ? + // 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, that's handled in + // parseStringifyInterceptor below. + return (trustedContext && !contextAllowsConcatenation) ? $sce.getTrusted(trustedContext, value) : $sce.valueOf(value); }; @@ -344,8 +377,13 @@ function $InterpolateProvider() { function parseStringifyInterceptor(value) { try { - value = getValue(value); - return allOrNothing && !isDefined(value) ? value : stringify(value); + if (contextAllowsConcatenation && singleExpression) { + // No stringification in this case, to keep the trusted value until unwrapping. + return value; + } else { + value = getValue(value); + return allOrNothing && !isDefined(value) ? value : stringify(value); + } } catch (err) { $exceptionHandler($interpolateMinErr.interr(text, err)); } diff --git a/src/ng/sanitizeUri.js b/src/ng/sanitizeUri.js index aa09d0b4864d..b73681e96e8a 100644 --- a/src/ng/sanitizeUri.js +++ b/src/ng/sanitizeUri.js @@ -3,67 +3,41 @@ /** * @this * @description - * Private service to sanitize uris for links and images. Used by $compile and $sanitize. + * Private service to sanitize uris for $sce.URL context. Used by $compile, $sce and $sanitize. */ function $$SanitizeUriProvider() { - var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, - imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; - - /** - * @description - * 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. - * - * 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. - * - * @param {RegExp=} regexp New regexp to whitelist urls with. - * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for - * chaining otherwise. - */ - this.aHrefSanitizationWhitelist = function(regexp) { - if (isDefined(regexp)) { - aHrefSanitizationWhitelist = regexp; - return this; - } - return aHrefSanitizationWhitelist; - }; + var uriSanitizationWhitelist = /^\s*((https?|ftp|file|blob|tel|mailto):|data:image\/)/i; /** * @description * Retrieves or overrides the default regular expression that is used for whitelisting of safe - * urls during img[src] sanitization. + * urls. * - * 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. * - * 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` + * Any url about to be assigned to URL context via data-binding is first normalized and turned into + * an absolute url. Afterwards, the url is matched against the `urlSanitizationWhitelist` * 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. + * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM, + * making it inactive. * * @param {RegExp=} regexp New regexp to whitelist urls with. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ - this.imgSrcSanitizationWhitelist = function(regexp) { + this.uriSanitizationWhitelist = function(regexp) { if (isDefined(regexp)) { - imgSrcSanitizationWhitelist = regexp; + uriSanitizationWhitelist = regexp; return this; } - return imgSrcSanitizationWhitelist; + return uriSanitizationWhitelist; }; this.$get = function() { - return function sanitizeUri(uri, isImage) { - var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; - var normalizedVal; - normalizedVal = urlResolve(uri).href; - if (normalizedVal !== '' && !normalizedVal.match(regex)) { + return function sanitizeUri(uri) { + var normalizedVal = urlResolve(uri).href; + if (normalizedVal !== '' && !normalizedVal.match(uriSanitizationWhitelist)) { return 'unsafe:' + normalizedVal; } return uri; diff --git a/src/ng/sce.js b/src/ng/sce.js index 78d21981619b..e3c0627fa01b 100644 --- a/src/ng/sce.js +++ b/src/ng/sce.js @@ -207,7 +207,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.'); @@ -344,9 +344,22 @@ function $SceDelegateProvider() { * @name $sceDelegate#getTrusted * * @description - * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and - * returns the originally supplied value if the queried context type is a supertype of the - * created type. If this condition isn't satisfied, 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. + * + * The two contexts that can be sanitized are $sce.URL and $sce.HTML. The first one is available + * by default, and the second 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 being unsafe). * *
* Disabling auto-escaping is extremely dangerous, it usually creates a Cross Site Scripting @@ -370,7 +383,10 @@ function $SceDelegateProvider() { // If we get here, then we may only take one of two actions. // 1. sanitize the value for the requested type, or // 2. throw an exception. - if (type === SCE_CONTEXTS.RESOURCE_URL) { + + if (type === SCE_CONTEXTS.URL) { + return $$sanitizeUri(maybeTrusted); + } else if (type === SCE_CONTEXTS.RESOURCE_URL) { if (isResourceUrlAllowedByPolicy(maybeTrusted)) { return maybeTrusted; } else { @@ -515,9 +531,6 @@ function $SceDelegateProvider() { * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. * `
`) just works. * - * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them - * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. - * * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load * templates in `ng-include` from your application's domain without having to even know about SCE. * It blocks loading templates from other domains or loading templates over http from an https @@ -536,7 +549,7 @@ function $SceDelegateProvider() { * |---------------------|----------------| * | `$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.URL` | For URLs that are safe to follow as links. Currently unused (`
Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does 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 unused. Feel free to use it in your own directives. | * @@ -711,7 +724,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 diff --git a/src/ngSanitize/sanitize.js b/src/ngSanitize/sanitize.js index a816069bcf48..181b419906b6 100644 --- a/src/ngSanitize/sanitize.js +++ b/src/ngSanitize/sanitize.js @@ -158,7 +158,7 @@ function $SanitizeProvider() { return function(html) { var buf = []; htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { - return !/^unsafe:/.test($$sanitizeUri(uri, isImage)); + return !/^unsafe:/.test($$sanitizeUri(uri)); })); return buf.join(''); }; @@ -457,9 +457,8 @@ function $SanitizeProvider() { out(tag); forEach(attrs, function(value, key) { var lkey = lowercase(key); - var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); if (validAttrs[lkey] === true && - (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { + (uriAttrs[lkey] !== true || uriValidator(value))) { out(' '); out(key); out('="'); diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 4c57925ec5a1..ca30924674f1 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -10306,16 +10306,34 @@ describe('$compile', function() { expect(element.attr('title')).toBe('javascript:doEvilStuff()'); })); - it('should use $$sanitizeUriProvider for reconfiguration of the src whitelist', function() { + it('should not have endless digests when given arrays in concatenable context', inject(function($compile, $rootScope) { + /* jshint scripturl:true */ + // Any non-resource-url href is URL context + element = $compile('' + + '')($rootScope); + $rootScope.testUrl = [1]; + $rootScope.$digest(); + + $rootScope.testUrl = []; + $rootScope.$digest(); + + $rootScope.testUrl = {a:'b'}; + $rootScope.$digest(); + + $rootScope.testUrl = {}; + $rootScope.$digest(); + })); + + it('should use $$sanitizeUriProvider for reconfiguration of the URL whitelist', function() { module(function($compileProvider, $$sanitizeUriProvider) { var newRe = /javascript:/, returnVal; - expect($compileProvider.imgSrcSanitizationWhitelist()).toBe($$sanitizeUriProvider.imgSrcSanitizationWhitelist()); + expect($compileProvider.uriSanitizationWhitelist()).toBe($$sanitizeUriProvider.uriSanitizationWhitelist()); - returnVal = $compileProvider.imgSrcSanitizationWhitelist(newRe); + returnVal = $compileProvider.uriSanitizationWhitelist(newRe); expect(returnVal).toBe($compileProvider); - expect($$sanitizeUriProvider.imgSrcSanitizationWhitelist()).toBe(newRe); - expect($compileProvider.imgSrcSanitizationWhitelist()).toBe(newRe); + expect($$sanitizeUriProvider.uriSanitizationWhitelist()).toBe(newRe); + expect($compileProvider.uriSanitizationWhitelist()).toBe(newRe); }); inject(function() { // needed to the module definition above is run... @@ -10334,7 +10352,48 @@ describe('$compile', function() { $$sanitizeUri.and.returnValue('someSanitizedUrl'); $rootScope.$apply(); expect(element.attr('src')).toBe('someSanitizedUrl'); - expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, true); + expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl); + }); + }); + + + it('should sanitize concatenated trusted values', function() { + /* jshint scripturl:true */ + var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri'); + module(function($provide) { + $provide.value('$$sanitizeUri', $$sanitizeUri); + }); + inject(function($compile, $rootScope, $sce) { + $$sanitizeUri.and.returnValue('someSanitizedUrl'); + element = $compile('')($rootScope); + $rootScope.testUrl = $sce.trustAsUrl('javascript:foo();'); + $rootScope.$digest(); + expect(element.attr('src')).toEqual('someSanitizedUrl'); + + element = $compile('')($rootScope); + $rootScope.testUrl = $sce.trustAsUrl('javascript:foo();'); + $rootScope.$digest(); + expect(element.attr('src')).toEqual('someSanitizedUrl'); + }); + }); + + it('should not use $$sanitizeUri with trusted values', function() { + /* jshint scripturl:true */ + var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri'); + module(function($provide) { + $provide.value('$$sanitizeUri', $$sanitizeUri); + }); + inject(function($compile, $rootScope, $sce) { + + element = $compile('')($rootScope); + // Assigning javascript:foo to src makes at least IE9-11 complain, so use another + // protocol name. + $rootScope.testUrl = $sce.trustAsUrl('someUnsafeThing:foo();'); + + $$sanitizeUri.and.throwError('Should not have been called'); + $rootScope.$apply(); + + expect(element.attr('src')).toEqual('someUnsafeThing:foo();'); }); }); }); @@ -10371,6 +10430,22 @@ describe('$compile', function() { $rootScope.testUrl = $sce.trustAsUrl('http://example.com/image2.png'); $rootScope.$digest(); expect(element.attr('srcset')).toEqual('http://example.com/image2.png'); + + })); + + // Limitation of the approach used for srcset. Use trustAsHtml and ng-bind-html to bypass it. + it('does not work with trusted values', inject(function($rootScope, $compile, $sce) { + /* jshint scripturl:true */ + element = $compile('')($rootScope); + $rootScope.testUrl = $sce.trustAsUrl('javascript:something'); + $rootScope.$digest(); + expect(element.attr('srcset')).toEqual('unsafe:javascript:something'); + + element = $compile('')($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() { @@ -10379,13 +10454,24 @@ describe('$compile', function() { $provide.value('$$sanitizeUri', $$sanitizeUri); }); inject(function($compile, $rootScope) { + /* jshint scripturl:true */ element = $compile('')($rootScope); $rootScope.testUrl = 'someUrl'; $$sanitizeUri.and.returnValue('someSanitizedUrl'); $rootScope.$apply(); expect(element.attr('srcset')).toBe('someSanitizedUrl'); - expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, true); + expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl); + + element = $compile('')($rootScope); + $rootScope.testUrl = 'javascript:yay'; + $rootScope.$apply(); + expect(element.attr('srcset')).toEqual('someSanitizedUrl ,someSanitizedUrl'); + + element = $compile('')($rootScope); + $rootScope.testUrl = 'script:yay, javascript:nay'; + $rootScope.$apply(); + expect(element.attr('srcset')).toEqual('someSanitizedUrl ,someSanitizedUrl'); }); }); @@ -10446,16 +10532,16 @@ describe('$compile', function() { expect(element.attr('title')).toBe('javascript:doEvilStuff()'); })); - it('should use $$sanitizeUriProvider for reconfiguration of the href whitelist', function() { + it('should use $$sanitizeUriProvider for reconfiguration of the uri whitelist', function() { module(function($compileProvider, $$sanitizeUriProvider) { var newRe = /javascript:/, returnVal; - expect($compileProvider.aHrefSanitizationWhitelist()).toBe($$sanitizeUriProvider.aHrefSanitizationWhitelist()); + expect($compileProvider.uriSanitizationWhitelist()).toBe($$sanitizeUriProvider.uriSanitizationWhitelist()); - returnVal = $compileProvider.aHrefSanitizationWhitelist(newRe); + returnVal = $compileProvider.uriSanitizationWhitelist(newRe); expect(returnVal).toBe($compileProvider); - expect($$sanitizeUriProvider.aHrefSanitizationWhitelist()).toBe(newRe); - expect($compileProvider.aHrefSanitizationWhitelist()).toBe(newRe); + expect($$sanitizeUriProvider.uriSanitizationWhitelist()).toBe(newRe); + expect($compileProvider.uriSanitizationWhitelist()).toBe(newRe); }); inject(function() { // needed to the module definition above is run... @@ -10474,7 +10560,7 @@ describe('$compile', function() { $$sanitizeUri.and.returnValue('someSanitizedUrl'); $rootScope.$apply(); expect(element.attr('href')).toBe('someSanitizedUrl'); - expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, false); + expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl); }); }); @@ -10490,7 +10576,7 @@ describe('$compile', function() { $$sanitizeUri.and.returnValue('someSanitizedUrl'); $rootScope.$apply(); expect(element.attr('href')).toBe('someSanitizedUrl'); - expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, false); + expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl); }); }); @@ -10506,7 +10592,7 @@ describe('$compile', function() { $$sanitizeUri.and.returnValue('someSanitizedUrl'); $rootScope.$apply(); expect(element.find('a').prop('href').baseVal).toBe('someSanitizedUrl'); - expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, false); + expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl); }); }); @@ -10523,7 +10609,7 @@ describe('$compile', function() { $$sanitizeUri.and.returnValue('someSanitizedUrl'); $rootScope.$apply(); expect(element.find('a').prop('href').baseVal).toBe('someSanitizedUrl'); - expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, false); + expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl); }); }); }); @@ -10691,9 +10777,9 @@ describe('$compile', function() { it('should NOT set iframe contents for untrusted values', inject(function($compile, $rootScope, $sce) { element = $compile('')($rootScope); $rootScope.html = '
hello
'; - expect(function() { $rootScope.$digest(); }).toThrowMinErr('$interpolate', 'interr', new RegExp( - /Can't interpolate: {{html}}\n/.source + - /[^[]*\[\$sce:unsafe\] Attempting to use an unsafe value in a safe context./.source)); + expect(function() { $rootScope.$digest(); }).toThrowMinErr( + '$interpolate', 'interr', 'Can\'t interpolate: {{html}}\nError: [$sce:unsafe] ' + + 'Attempting to use an unsafe value in a safe context.'); })); it('should NOT set html for wrongly typed values', inject(function($rootScope, $compile, $sce) { diff --git a/test/ng/directive/booleanAttrsSpec.js b/test/ng/directive/booleanAttrsSpec.js index ed74c6f99b21..055e33c288c0 100644 --- a/test/ng/directive/booleanAttrsSpec.js +++ b/test/ng/directive/booleanAttrsSpec.js @@ -118,7 +118,7 @@ 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('
')($rootScope); + var element = $compile('')($rootScope); $rootScope.$digest(); expect(element.attr('src')).toBeUndefined(); @@ -133,7 +133,7 @@ describe('ngSrc', function() { it('should interpolate the expression and bind to src with a trusted value', inject(function($compile, $rootScope, $sce) { - var element = $compile('
')($rootScope); + var element = $compile('')($rootScope); $rootScope.$digest(); expect(element.attr('src')).toBeUndefined(); @@ -147,14 +147,27 @@ describe('ngSrc', function() { })); - it('should NOT interpolate a multi-part expression for non-img src attribute', inject(function($compile, $rootScope) { + it('should NOT interpolate a multi-part expression for non-URL context src attribute', inject(function($compile, $rootScope) { expect(function() { - var element = $compile('
')($rootScope); + var element = $compile('')($rootScope); + $rootScope.$apply(function() { + $rootScope.id = 1; + }); 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 img src attribute (URL context)', inject(function($compile, $rootScope) { + var element = $compile('')($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'); })); @@ -171,7 +184,7 @@ describe('ngSrc', function() { it('should NOT interpolate a wrongly typed expression', inject(function($compile, $rootScope, $sce) { expect(function() { - var element = $compile('
')($rootScope); + var element = $compile('')($rootScope); $rootScope.$apply(function() { $rootScope.id = $sce.trustAsUrl('http://somewhere'); }); @@ -182,40 +195,38 @@ describe('ngSrc', function() { })); - 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 + 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('
')($rootScope); + var element = $compile('')($rootScope); - $rootScope.$digest(); - expect(element.prop('src')).toBeUndefined(); - dealoc(element); + $rootScope.$digest(); + expect(element.prop('src')).toBe(''); + dealoc(element); - element = $compile('
')($rootScope); + element = $compile('')($rootScope); - $rootScope.$digest(); - expect(element.prop('src')).toEqual('some/'); - dealoc(element); + $rootScope.$digest(); + expect(element.prop('src')).toContain('some/'); + dealoc(element); - element = $compile('
')($rootScope); - $rootScope.$apply(function() { - $rootScope.id = $sce.trustAsResourceUrl('http://somewhere'); - }); - expect(element.prop('src')).toEqual('http://somewhere'); + element = $compile('')($rootScope); + $rootScope.$apply(function() { + $rootScope.id = $sce.trustAsResourceUrl('http://somewhere'); + }); + expect(element.prop('src')).toEqual('http://somewhere/'); - dealoc(element); - })); - } + dealoc(element); + })); }); describe('ngSrcset', function() { it('should interpolate the expression and bind to srcset', inject(function($compile, $rootScope) { - var element = $compile('
')($rootScope); + var element = $compile('
')($rootScope); $rootScope.$digest(); expect(element.attr('srcset')).toBeUndefined(); @@ -239,7 +250,7 @@ describe('ngHref', function() { it('should interpolate the expression and bind to href', inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); + element = $compile('
')($rootScope); $rootScope.$digest(); expect(element.attr('href')).toEqual('some/'); @@ -287,9 +298,8 @@ describe('ngHref', function() { // IE11/10/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('')($rootScope); - expect(function() { + element = $compile('')($rootScope); $rootScope.$digest(); }).toThrow(); })); diff --git a/test/ng/interpolateSpec.js b/test/ng/interpolateSpec.js index f8ed846d93d2..e5bb839e3391 100644 --- a/test/ng/interpolateSpec.js +++ b/test/ng/interpolateSpec.js @@ -251,7 +251,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) { @@ -260,7 +262,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) { @@ -275,17 +279,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' + - 'Strict Contextual Escaping disallows interpolations that concatenate multiple ' + - 'expressions when a trusted value is required. See ' + - 'http://docs.angularjs.org/api/ng.$sce'); + '$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'); })); }); @@ -364,26 +367,34 @@ 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; + // The error messages are interpolate:noconcat rewrapped into interpolate:interr. 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'); + '$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); + $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) { @@ -391,6 +402,24 @@ 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) { + /* jshint scripturl:true */ + expect($interpolate('some/{{id}}', true, $sce.URL)({})).toEqual('some/'); + expect($interpolate('some/{{id}}', true, $sce.URL)({id: 'weird:'})).toEqual('some/weird:'); + expect($interpolate('{{foo}}{{bar}}', true, $sce.URL)({foo: 'weird:', bar: 'weird:'})).toEqual('unsafe:weird:weird:'); + })); + + + }); diff --git a/test/ng/sanitizeUriSpec.js b/test/ng/sanitizeUriSpec.js index 7d01e3c4ba64..c6eedb93c5a7 100644 --- a/test/ng/sanitizeUriSpec.js +++ b/test/ng/sanitizeUriSpec.js @@ -3,17 +3,14 @@ /* eslint-disable no-script-url */ describe('sanitizeUri', function() { - var sanitizeHref, sanitizeImg, sanitizeUriProvider, testUrl; + var sanitize, sanitizeUriProvider, testUrl; beforeEach(function() { module(function(_$$sanitizeUriProvider_) { sanitizeUriProvider = _$$sanitizeUriProvider_; }); inject(function($$sanitizeUri) { - sanitizeHref = function(uri) { - return $$sanitizeUri(uri, false); - }; - sanitizeImg = function(uri) { - return $$sanitizeUri(uri, true); + sanitize = function(uri) { + return $$sanitizeUri(uri); }; }); }); @@ -24,55 +21,55 @@ describe('sanitizeUri', function() { return a.href.substring(0, 4) !== 'http'; } - describe('img[src] sanitization', function() { + describe('URL-context sanitization', function() { it('should sanitize javascript: urls', function() { testUrl = 'javascript:doEvilStuff()'; - expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); + expect(sanitize(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); }); it('should sanitize javascript: urls with comments', function() { testUrl = 'javascript:alert(1)//data:image/'; - expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:alert(1)//data:image/'); + expect(sanitize(testUrl)).toBe('unsafe:javascript:alert(1)//data:image/'); }); it('should sanitize non-image data: urls', function() { testUrl = 'data:application/javascript;charset=US-ASCII,alert(\'evil!\');'; - expect(sanitizeImg(testUrl)).toBe('unsafe:data:application/javascript;charset=US-ASCII,alert(\'evil!\');'); + expect(sanitize(testUrl)).toBe('unsafe:data:application/javascript;charset=US-ASCII,alert(\'evil!\');'); testUrl = 'data:,foo'; - expect(sanitizeImg(testUrl)).toBe('unsafe:data:,foo'); + expect(sanitize(testUrl)).toBe('unsafe:data:,foo'); }); - it('should sanitize mailto: urls', function() { + it('should not sanitize mailto: urls', function() { testUrl = 'mailto:foo@bar.com'; - expect(sanitizeImg(testUrl)).toBe('unsafe:mailto:foo@bar.com'); + expect(sanitize(testUrl)).toBe('mailto:foo@bar.com'); }); it('should sanitize obfuscated javascript: urls', function() { // case-sensitive testUrl = 'JaVaScRiPt:doEvilStuff()'; - expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); + expect(sanitize(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); // tab in protocol testUrl = 'java\u0009script:doEvilStuff()'; if (isEvilInCurrentBrowser(testUrl)) { - expect(sanitizeImg(testUrl)).toEqual('unsafe:javascript:doEvilStuff()'); + expect(sanitize(testUrl)).toEqual('unsafe:javascript:doEvilStuff()'); } // space before testUrl = ' javascript:doEvilStuff()'; - expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); + expect(sanitize(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); // ws chars before testUrl = ' \u000e javascript:doEvilStuff()'; if (isEvilInCurrentBrowser(testUrl)) { - expect(sanitizeImg(testUrl)).toEqual('unsafe:javascript:doEvilStuff()'); + expect(sanitize(testUrl)).toEqual('unsafe:javascript:doEvilStuff()'); } // post-fixed with proper url testUrl = 'javascript:doEvilStuff(); http://make.me/look/good'; - expect(sanitizeImg(testUrl)).toBeOneOf( + expect(sanitize(testUrl)).toBeOneOf( 'unsafe:javascript:doEvilStuff(); http://make.me/look/good', 'unsafe:javascript:doEvilStuff();%20http://make.me/look/good' ); @@ -80,62 +77,62 @@ describe('sanitizeUri', function() { it('should sanitize ng-src bindings as well', function() { testUrl = 'javascript:doEvilStuff()'; - expect(sanitizeImg(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); + expect(sanitize(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); }); it('should not sanitize valid urls', function() { testUrl = 'foo/bar'; - expect(sanitizeImg(testUrl)).toBe('foo/bar'); + expect(sanitize(testUrl)).toBe('foo/bar'); testUrl = '/foo/bar'; - expect(sanitizeImg(testUrl)).toBe('/foo/bar'); + expect(sanitize(testUrl)).toBe('/foo/bar'); testUrl = '../foo/bar'; - expect(sanitizeImg(testUrl)).toBe('../foo/bar'); + expect(sanitize(testUrl)).toBe('../foo/bar'); testUrl = '#foo'; - expect(sanitizeImg(testUrl)).toBe('#foo'); + expect(sanitize(testUrl)).toBe('#foo'); testUrl = 'http://foo.com/bar'; - expect(sanitizeImg(testUrl)).toBe('http://foo.com/bar'); + expect(sanitize(testUrl)).toBe('http://foo.com/bar'); testUrl = ' http://foo.com/bar'; - expect(sanitizeImg(testUrl)).toBe(' http://foo.com/bar'); + expect(sanitize(testUrl)).toBe(' http://foo.com/bar'); testUrl = 'https://foo.com/bar'; - expect(sanitizeImg(testUrl)).toBe('https://foo.com/bar'); + expect(sanitize(testUrl)).toBe('https://foo.com/bar'); testUrl = 'ftp://foo.com/bar'; - expect(sanitizeImg(testUrl)).toBe('ftp://foo.com/bar'); + expect(sanitize(testUrl)).toBe('ftp://foo.com/bar'); testUrl = 'file:///foo/bar.html'; - expect(sanitizeImg(testUrl)).toBe('file:///foo/bar.html'); + expect(sanitize(testUrl)).toBe('file:///foo/bar.html'); }); it('should not sanitize blob urls', function() { testUrl = 'blob:///foo/bar.html'; - expect(sanitizeImg(testUrl)).toBe('blob:///foo/bar.html'); + expect(sanitize(testUrl)).toBe('blob:///foo/bar.html'); }); it('should not sanitize data: URIs for images', function() { // image data uri // ref: http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever testUrl = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; - expect(sanitizeImg(testUrl)).toBe('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='); + expect(sanitize(testUrl)).toBe('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='); }); it('should allow reconfiguration of the src whitelist', function() { var returnVal; - expect(sanitizeUriProvider.imgSrcSanitizationWhitelist() instanceof RegExp).toBe(true); - returnVal = sanitizeUriProvider.imgSrcSanitizationWhitelist(/javascript:/); + expect(sanitizeUriProvider.uriSanitizationWhitelist() instanceof RegExp).toBe(true); + returnVal = sanitizeUriProvider.uriSanitizationWhitelist(/javascript:/); expect(returnVal).toBe(sanitizeUriProvider); testUrl = 'javascript:doEvilStuff()'; - expect(sanitizeImg(testUrl)).toBe('javascript:doEvilStuff()'); + expect(sanitize(testUrl)).toBe('javascript:doEvilStuff()'); testUrl = 'http://recon/figured'; - expect(sanitizeImg(testUrl)).toBe('unsafe:http://recon/figured'); + expect(sanitize(testUrl)).toBe('unsafe:http://recon/figured'); }); }); @@ -145,40 +142,40 @@ describe('sanitizeUri', function() { it('should sanitize javascript: urls', inject(function() { testUrl = 'javascript:doEvilStuff()'; - expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); + expect(sanitize(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); })); it('should sanitize data: urls', inject(function() { testUrl = 'data:evilPayload'; - expect(sanitizeHref(testUrl)).toBe('unsafe:data:evilPayload'); + expect(sanitize(testUrl)).toBe('unsafe:data:evilPayload'); })); it('should sanitize obfuscated javascript: urls', inject(function() { // case-sensitive testUrl = 'JaVaScRiPt:doEvilStuff()'; - expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); + expect(sanitize(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); // tab in protocol testUrl = 'java\u0009script:doEvilStuff()'; if (isEvilInCurrentBrowser(testUrl)) { - expect(sanitizeHref(testUrl)).toEqual('unsafe:javascript:doEvilStuff()'); + expect(sanitize(testUrl)).toEqual('unsafe:javascript:doEvilStuff()'); } // space before testUrl = ' javascript:doEvilStuff()'; - expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); + expect(sanitize(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); // ws chars before testUrl = ' \u000e javascript:doEvilStuff()'; if (isEvilInCurrentBrowser(testUrl)) { - expect(sanitizeHref(testUrl)).toEqual('unsafe:javascript:doEvilStuff()'); + expect(sanitize(testUrl)).toEqual('unsafe:javascript:doEvilStuff()'); } // post-fixed with proper url testUrl = 'javascript:doEvilStuff(); http://make.me/look/good'; - expect(sanitizeHref(testUrl)).toBeOneOf( + expect(sanitize(testUrl)).toBeOneOf( 'unsafe:javascript:doEvilStuff(); http://make.me/look/good', 'unsafe:javascript:doEvilStuff();%20http://make.me/look/good' ); @@ -187,53 +184,53 @@ describe('sanitizeUri', function() { it('should sanitize ngHref bindings as well', inject(function() { testUrl = 'javascript:doEvilStuff()'; - expect(sanitizeHref(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); + expect(sanitize(testUrl)).toBe('unsafe:javascript:doEvilStuff()'); })); it('should not sanitize valid urls', inject(function() { testUrl = 'foo/bar'; - expect(sanitizeHref(testUrl)).toBe('foo/bar'); + expect(sanitize(testUrl)).toBe('foo/bar'); testUrl = '/foo/bar'; - expect(sanitizeHref(testUrl)).toBe('/foo/bar'); + expect(sanitize(testUrl)).toBe('/foo/bar'); testUrl = '../foo/bar'; - expect(sanitizeHref(testUrl)).toBe('../foo/bar'); + expect(sanitize(testUrl)).toBe('../foo/bar'); testUrl = '#foo'; - expect(sanitizeHref(testUrl)).toBe('#foo'); + expect(sanitize(testUrl)).toBe('#foo'); testUrl = 'http://foo/bar'; - expect(sanitizeHref(testUrl)).toBe('http://foo/bar'); + expect(sanitize(testUrl)).toBe('http://foo/bar'); testUrl = ' http://foo/bar'; - expect(sanitizeHref(testUrl)).toBe(' http://foo/bar'); + expect(sanitize(testUrl)).toBe(' http://foo/bar'); testUrl = 'https://foo/bar'; - expect(sanitizeHref(testUrl)).toBe('https://foo/bar'); + expect(sanitize(testUrl)).toBe('https://foo/bar'); testUrl = 'ftp://foo/bar'; - expect(sanitizeHref(testUrl)).toBe('ftp://foo/bar'); + expect(sanitize(testUrl)).toBe('ftp://foo/bar'); testUrl = 'mailto:foo@bar.com'; - expect(sanitizeHref(testUrl)).toBe('mailto:foo@bar.com'); + expect(sanitize(testUrl)).toBe('mailto:foo@bar.com'); testUrl = 'file:///foo/bar.html'; - expect(sanitizeHref(testUrl)).toBe('file:///foo/bar.html'); + expect(sanitize(testUrl)).toBe('file:///foo/bar.html'); })); it('should allow reconfiguration of the href whitelist', function() { var returnVal; - expect(sanitizeUriProvider.aHrefSanitizationWhitelist() instanceof RegExp).toBe(true); - returnVal = sanitizeUriProvider.aHrefSanitizationWhitelist(/javascript:/); + expect(sanitizeUriProvider.uriSanitizationWhitelist() instanceof RegExp).toBe(true); + returnVal = sanitizeUriProvider.uriSanitizationWhitelist(/javascript:/); expect(returnVal).toBe(sanitizeUriProvider); testUrl = 'javascript:doEvilStuff()'; - expect(sanitizeHref(testUrl)).toBe('javascript:doEvilStuff()'); + expect(sanitize(testUrl)).toBe('javascript:doEvilStuff()'); testUrl = 'http://recon/figured'; - expect(sanitizeHref(testUrl)).toBe('unsafe:http://recon/figured'); + expect(sanitize(testUrl)).toBe('unsafe:http://recon/figured'); }); }); diff --git a/test/ng/sceSpecs.js b/test/ng/sceSpecs.js index a5aeea7afe09..fce5db5b303e 100644 --- a/test/ng/sceSpecs.js +++ b/test/ng/sceSpecs.js @@ -211,7 +211,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 +236,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. @@ -488,6 +513,18 @@ describe('SCE', function() { )); }); + describe('URL-context sanitization', function() { + it('sanitizes string-valued', inject(function($sce) { + /* jshint scripturl:true */ + expect($sce.getTrustedUrl('weird:foo')).toEqual('unsafe:weird:foo'); + })); + + it('does not sanitize trusted types', inject(function($sce) { + /* jshint scripturl:true */ + expect($sce.getTrustedUrl($sce.trustAsUrl('weird:foo'))).toEqual('weird:foo'); + })); + }); + describe('sanitizing html', function() { describe('when $sanitize is NOT available', function() { it('should throw an exception for getTrusted(string) values', inject(function($sce) { @@ -498,9 +535,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('abc')).toBe('abc'); })); + + // 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('')($rootScope); + // $rootScope.html = 'noyes'; + // $rootScope.$digest(); + // expect(angular.lowercase(element.attr('srcdoc'))).toEqual('yes'); + // })); + // } }); }); }); diff --git a/test/ngSanitize/sanitizeSpec.js b/test/ngSanitize/sanitizeSpec.js index 3c6e63829ec9..ef3cc8268f7a 100644 --- a/test/ngSanitize/sanitizeSpec.js +++ b/test/ngSanitize/sanitizeSpec.js @@ -390,10 +390,10 @@ describe('HTML', function() { describe('uri validation', function() { it('should call the uri validator', function() { writer.start('a', {href:'someUrl'}, false); - expect(uriValidator).toHaveBeenCalledWith('someUrl', false); + expect(uriValidator).toHaveBeenCalledWith('someUrl'); uriValidator.calls.reset(); writer.start('img', {src:'someImgUrl'}, false); - expect(uriValidator).toHaveBeenCalledWith('someImgUrl', true); + expect(uriValidator).toHaveBeenCalledWith('someImgUrl'); uriValidator.calls.reset(); writer.start('someTag', {src:'someNonUrl'}, false); expect(uriValidator).not.toHaveBeenCalled(); @@ -439,7 +439,7 @@ describe('HTML', function() { $$sanitizeUri.and.returnValue('someUri'); expectHTML('').toEqual(''); - expect($$sanitizeUri).toHaveBeenCalledWith('someUri', false); + expect($$sanitizeUri).toHaveBeenCalledWith('someUri'); $$sanitizeUri.and.returnValue('unsafe:someUri'); expectHTML('').toEqual(''); @@ -455,7 +455,7 @@ describe('HTML', function() { $$sanitizeUri.and.returnValue('someUri'); expectHTML('').toEqual(''); - expect($$sanitizeUri).toHaveBeenCalledWith('someUri', true); + expect($$sanitizeUri).toHaveBeenCalledWith('someUri'); $$sanitizeUri.and.returnValue('unsafe:someUri'); expectHTML('').toEqual('');