diff options
28 files changed, 1853 insertions, 110 deletions
diff --git a/angularFiles.js b/angularFiles.js index b93283a7..694bde47 100755 --- a/angularFiles.js +++ b/angularFiles.js @@ -18,19 +18,20 @@ angularFiles = { 'src/ng/controller.js', 'src/ng/document.js', 'src/ng/exceptionHandler.js', + 'src/ng/http.js', + 'src/ng/httpBackend.js', 'src/ng/interpolate.js', + 'src/ng/locale.js', 'src/ng/location.js', 'src/ng/log.js', 'src/ng/parse.js', 'src/ng/q.js', 'src/ng/rootScope.js', + 'src/ng/sce.js', 'src/ng/sniffer.js', - 'src/ng/window.js', - 'src/ng/http.js', - 'src/ng/httpBackend.js', - 'src/ng/locale.js', 'src/ng/timeout.js', 'src/ng/urlUtils.js', + 'src/ng/window.js', 'src/ng/filter.js', 'src/ng/filter/filter.js', diff --git a/docs/content/error/sce/icontext.ngdoc b/docs/content/error/sce/icontext.ngdoc new file mode 100644 index 00000000..af629040 --- /dev/null +++ b/docs/content/error/sce/icontext.ngdoc @@ -0,0 +1,6 @@ +@ngdoc error +@name $sce:icontext +@fullName Invalid / Unknown SCE context +@description +The context enum passed to {@link api/ng.$sce#trustAs $sce.trustAs} was not recognized. Refer the +list of {@link api/ng.$sce#contexts supported Strict Contextual Escaping (SCE) contexts}. diff --git a/docs/content/error/sce/iequirks.ngdoc b/docs/content/error/sce/iequirks.ngdoc new file mode 100644 index 00000000..be873344 --- /dev/null +++ b/docs/content/error/sce/iequirks.ngdoc @@ -0,0 +1,16 @@ +@ngdoc error +@name $sce:iequirks +@fullName IE8 in quirks mode is unsupported. +@description +You are using AngularJS with {@link api/ng.$sce#strictcontextualescaping Strict Contextual Escaping +(SCE)} mode enabled (the default) on IE8 or lower in quirks mode. In this mode, IE8 allows one to +execute arbitrary javascript by the use of the `expression()` syntax and is not supported. Refer +{@link http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx MSDN Blogs > IEBlog > +Ending Expressions} to learn more about them. + +### Recommended solution +Add the doctype + + <!doctype html> + +to the top of your HTML document. This switches the document from quirks mode to standards mode. diff --git a/docs/content/error/sce/isecrurl.ngdoc b/docs/content/error/sce/isecrurl.ngdoc new file mode 100644 index 00000000..c5a5445d --- /dev/null +++ b/docs/content/error/sce/isecrurl.ngdoc @@ -0,0 +1,30 @@ +@ngdoc error +@name $sce:isecrurl +@fullName Blocked loading an untrusted resource +@description + +AngularJS' {@link api/ng.$sce#strictcontextualescaping Strict Contextual Escaping +(SCE)} mode (enabled by default) has blocked loading a resource from an insecure URL. + +Typically, this would occur if you're attempting to load an Angular template from a different +domain. It's also possible that a custom directive threw this error for a similar reason. + +Angular only loads templates from trusted URLs (by calling {@link api/ng.$sce#getTrustedResourceUrl +$sce.getTrustedResourceUrl} on the template URL.). + +By default, only URLs to the same domain with the same protocol as the application document are +considered to be trusted. + +The {@link api/ng.directive:ngInclude ng-include} directive and {@link guide/directive directives} +that specify a `templateUrl` require a trusted resource URL. + +To load templates from other domains and/or protocols, either adjust the {@link +api/ng.$sceDelegateProvider#resourceUrlWhitelist whitelist}/ {@link +api/ng.$sceDelegateProvider#resourceUrlBlacklist blacklist} or wrap the URL with a call to {@link +api/ng.$sce#trustAsResourceUrl $sce.trustAsResourceUrl}. + +**Note**: The browser's {@link +https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest Same Origin +Policy} and {@link http://www.w3.org/TR/cors/ Cross-Origin Resource Sharing (CORS)} policy apply +that may further restrict whether the template is successfully loaded. (e.g. neither cross-domain +requests won't work on all browsers nor `file://` requests on some browsers) diff --git a/docs/content/error/sce/itype.ngdoc b/docs/content/error/sce/itype.ngdoc new file mode 100644 index 00000000..90555156 --- /dev/null +++ b/docs/content/error/sce/itype.ngdoc @@ -0,0 +1,6 @@ +@ngdoc error +@name $sce:itype +@fullName String value required for SCE trust call. +@description +{@link api/ng.$sce#trustAs $sce.trustAs} requires a string value. Read more about {@link +api/ng.$sce#strictcontextualescaping Strict Contextual Escaping (SCE)} in AngularJS. diff --git a/docs/content/error/sce/unsafe.ngdoc b/docs/content/error/sce/unsafe.ngdoc new file mode 100644 index 00000000..908033d2 --- /dev/null +++ b/docs/content/error/sce/unsafe.ngdoc @@ -0,0 +1,15 @@ +@ngdoc error +@name $sce:unsafe +@fullName Require a safe/trusted value +@description + +The value provided for use in a specific context was not found to be safe/trusted for use. + +Angular's {@link api/ng.$sce#strictcontextualescaping Strict Contextual Escaping (SCE)} mode +(enabled by default), requires bindings in certain +contexts to result in a value that is trusted as safe for use in such a context. (e.g. loading an +Angular template from a URL requires that the URL is one considered safe for loading resources.) + +This helps prevent XSS and other security issues. Read more at {@link +api/ng.$sce#strictcontextualescaping Strict Contextual Escaping (SCE)} + diff --git a/docs/content/guide/directive.ngdoc b/docs/content/guide/directive.ngdoc index 73c7ead7..5e682d58 100644 --- a/docs/content/guide/directive.ngdoc +++ b/docs/content/guide/directive.ngdoc @@ -415,8 +415,8 @@ compiler}. The attributes are: {@link guide/directive#Components Creating Components} section below for more information. You can specify `template` as a string representing the template or as a function which takes - two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns - a string value representing the template. + two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and + returns a string value representing the template. * `templateUrl` - Same as `template` but the template is loaded from the specified URL. Because the template loading is asynchronous the compilation/linking is suspended until the template @@ -424,7 +424,8 @@ compiler}. The attributes are: You can specify `templateUrl` as a string representing the URL or as a function which takes two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns - a string value representing the url. + a string value representing the url. In either case, the template URL is passed through {@link + api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. * `replace` - if set to `true` then the template will replace the current element, rather than append the template to the element. diff --git a/docs/src/example.js b/docs/src/example.js index eadc218f..cdbc24a7 100644 --- a/docs/src/example.js +++ b/docs/src/example.js @@ -20,6 +20,7 @@ exports.Example = function(scenarios) { this.html = []; this.css = []; this.js = []; + this.json = []; this.unit = []; this.scenario = []; this.scenarios = scenarios; @@ -88,6 +89,7 @@ exports.Example.prototype.toHtmlEdit = function() { out.push(' source-edit-html="' + ids(this.html) + '"'); out.push(' source-edit-css="' + ids(this.css) + '"'); out.push(' source-edit-js="' + ids(this.js) + '"'); + out.push(' source-edit-json="' + ids(this.json) + '"'); out.push(' source-edit-unit="' + ids(this.unit) + '"'); out.push(' source-edit-scenario="' + ids(this.scenario) + '"'); out.push('></div>\n'); @@ -102,6 +104,7 @@ exports.Example.prototype.toHtmlTabs = function() { htmlTabs(this.html); htmlTabs(this.css); htmlTabs(this.js); + htmlTabs(this.json); htmlTabs(this.unit); htmlTabs(this.scenario); out.push('</div>'); diff --git a/docs/src/templates/js/docs.js b/docs/src/templates/js/docs.js index a14237fa..7cac6a9a 100644 --- a/docs/src/templates/js/docs.js +++ b/docs/src/templates/js/docs.js @@ -216,6 +216,7 @@ docsApp.directive.sourceEdit = function(getEmbeddedTemplate) { html: read($attrs.sourceEditHtml), css: read($attrs.sourceEditCss), js: read($attrs.sourceEditJs), + json: read($attrs.sourceEditJson), unit: read($attrs.sourceEditUnit), scenario: read($attrs.sourceEditScenario) }; @@ -358,7 +359,7 @@ docsApp.serviceFactory.formPostData = function($document) { docsApp.serviceFactory.openPlunkr = function(templateMerge, formPostData, angularUrls) { return function(content) { - var allFiles = [].concat(content.js, content.css, content.html); + var allFiles = [].concat(content.js, content.css, content.html, content.json); var indexHtmlContent = '<!doctype html>\n' + '<html ng-app="{{module}}">\n' + ' <head>\n' + diff --git a/src/AngularPublic.js b/src/AngularPublic.js index 99f6cdbd..25e21b2c 100755 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -122,6 +122,8 @@ function publishExternalAPI(angular){ $parse: $ParseProvider, $rootScope: $RootScopeProvider, $q: $QProvider, + $sce: $SceProvider, + $sceDelegate: $SceDelegateProvider, $sniffer: $SnifferProvider, $templateCache: $TemplateCacheProvider, $timeout: $TimeoutProvider, diff --git a/src/ng/compile.js b/src/ng/compile.js index c059af47..82976822 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -274,9 +274,9 @@ function $CompileProvider($provide) { this.$get = [ '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', - '$controller', '$rootScope', '$document', '$$urlUtils', + '$controller', '$rootScope', '$document', '$sce', '$$urlUtils', function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, - $controller, $rootScope, $document, $$urlUtils) { + $controller, $rootScope, $document, $sce, $$urlUtils) { var Attributes = function(element, attr) { this.$$element = element; @@ -1095,7 +1095,7 @@ function $CompileProvider($provide) { $compileNode.html(''); - $http.get(templateUrl, {cache: $templateCache}). + $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}). success(function(content) { var compileNode, tempTemplateAttrs, $template; @@ -1203,12 +1203,12 @@ function $CompileProvider($provide) { } - function isTrustedContext(node, attrNormalizedName) { + function getTrustedContext(node, attrNormalizedName) { // maction[xlink:href] can source SVG. It's not limited to <maction>. if (attrNormalizedName == "xlinkHref" || (nodeName_(node) != "IMG" && (attrNormalizedName == "src" || attrNormalizedName == "ngSrc"))) { - return true; + return $sce.RESOURCE_URL; } } @@ -1238,7 +1238,7 @@ function $CompileProvider($provide) { // we need to interpolate again, in case the attribute value has been updated // (e.g. by another directive's compile function) - interpolateFn = $interpolate(attr[name], true, isTrustedContext(node, name)); + interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name)); // if attribute was updated so that there is no interpolation going on we don't want to // register any observers diff --git a/src/ng/directive/ngBind.js b/src/ng/directive/ngBind.js index f1cf4c70..fc54adcf 100644 --- a/src/ng/directive/ngBind.js +++ b/src/ng/directive/ngBind.js @@ -129,10 +129,10 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) { * @element ANY * @param {expression} ngBindHtmlUnsafe {@link guide/expression Expression} to evaluate. */ -var ngBindHtmlUnsafeDirective = [function() { +var ngBindHtmlUnsafeDirective = ['$sce', function($sce) { return function(scope, element, attr) { element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe); - scope.$watch(attr.ngBindHtmlUnsafe, function ngBindHtmlUnsafeWatchAction(value) { + scope.$watch($sce.parseAsHtml(attr.ngBindHtmlUnsafe), function ngBindHtmlUnsafeWatchAction(value) { element.html(value || ''); }); }; diff --git a/src/ng/directive/ngInclude.js b/src/ng/directive/ngInclude.js index adcc46e5..72b5af08 100644 --- a/src/ng/directive/ngInclude.js +++ b/src/ng/directive/ngInclude.js @@ -8,9 +8,20 @@ * @description * Fetches, compiles and includes an external HTML fragment. * - * Keep in mind that Same Origin Policy applies to included resources - * (e.g. ngInclude won't work for cross-domain requests on all browsers and for - * file:// access on some browsers). + * Keep in mind that: + * + * - by default, the template URL is restricted to the same domain and protocol as the + * application document. This is done by calling {@link ng.$sce#getTrustedResourceUrl + * $sce.getTrustedResourceUrl} on it. To load templates from other domains and/or protocols, + * you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or + * {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. Refer Angular's {@link + * ng.$sce Strict Contextual Escaping}. + * - in addition, the browser's + * {@link https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest + * Same Origin Policy} and {@link http://www.w3.org/TR/cors/ Cross-Origin Resource Sharing + * (CORS)} policy apply that may further restrict whether the template is successfully loaded. + * (e.g. ngInclude won't work for cross-domain requests on all browsers and for `file://` + * access on some browsers) * * Additionally, you can also provide animations via the ngAnimate attribute to animate the **enter** * and **leave** effects. @@ -132,8 +143,8 @@ * @description * Emitted every time the ngInclude content is reloaded. */ -var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', '$animator', - function($http, $templateCache, $anchorScroll, $compile, $animator) { +var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', '$animator', '$sce', + function($http, $templateCache, $anchorScroll, $compile, $animator, $sce) { return { restrict: 'ECA', terminal: true, @@ -155,7 +166,7 @@ var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile' animate.leave(element.contents(), element); }; - scope.$watch(srcExp, function ngIncludeWatchAction(src) { + scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { var thisChangeId = ++changeCounter; if (src) { diff --git a/src/ng/interpolate.js b/src/ng/interpolate.js index 8e94fe24..ade5ce69 100644 --- a/src/ng/interpolate.js +++ b/src/ng/interpolate.js @@ -54,7 +54,7 @@ function $InterpolateProvider() { }; - this.$get = ['$parse', '$exceptionHandler', function($parse, $exceptionHandler) { + this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { var startSymbolLength = startSymbol.length, endSymbolLength = endSymbol.length; @@ -64,6 +64,7 @@ function $InterpolateProvider() { * @function * * @requires $parse + * @requires $sce * * @description * @@ -84,12 +85,10 @@ function $InterpolateProvider() { * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have * embedded expression in order to return an interpolation function. Strings with no * embedded expression will return null for the interpolation function. - * @param {boolean=} isTrustedContext when true, requires that the interpolation string does not - * contain any concatenations - i.e. the interpolation string is a single expression. - * Interpolations for *[src] and *[ng-src] (except IMG, since itwhich sanitizes its value) - * pass true for this parameter. This helps avoid hunting through the template code to - * figure out of some iframe[src], object[src], etc. was interpolated with a concatenation - * that ended up introducing a XSS. + * @param {string=} trustedContext when provided, the returned function passes the interpolated + * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, + * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that + * provides Strict Contextual Escaping for details. * @returns {function(context)} an interpolation function which is used to compute the interpolated * string. The function has these parameters: * @@ -97,7 +96,7 @@ function $InterpolateProvider() { * against. * */ - function $interpolate(text, mustHaveExpression, isTrustedContext) { + function $interpolate(text, mustHaveExpression, trustedContext) { var startIndex, endIndex, index = 0, @@ -135,10 +134,11 @@ function $InterpolateProvider() { // 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 (isTrustedContext && parts.length > 1) { + if (trustedContext && parts.length > 1) { throw $interpolateMinErr('noconcat', - "Error while interpolating: {0}\nYou may not use multiple expressions when " + - "interpolating this expression.", text); + "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + + "interpolations that concatenate multiple expressions when a trusted value is " + + "required. See http://docs.angularjs.org/api/ng.$sce", text); } if (!mustHaveExpression || hasInterpolation) { @@ -148,6 +148,11 @@ function $InterpolateProvider() { for(var i = 0, ii = length, part; i<ii; i++) { if (typeof (part = parts[i]) == 'function') { part = part(context); + if (trustedContext) { + part = $sce.getTrusted(trustedContext, part); + } else { + part = $sce.valueOf(part); + } if (part == null || part == undefined) { part = ''; } else if (typeof part != 'string') { diff --git a/src/ng/sce.js b/src/ng/sce.js new file mode 100644 index 00000000..ab3d2208 --- /dev/null +++ b/src/ng/sce.js @@ -0,0 +1,959 @@ +'use strict'; + +var $sceMinErr = minErr('$sce'); + +var SCE_CONTEXTS = { + HTML: 'html', + CSS: 'css', + URL: 'url', + // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a + // url. (e.g. ng-include, script src, templateUrl) + RESOURCE_URL: 'resourceUrl', + JS: 'js' +}; + + +/** + * @ngdoc service + * @name ng.$sceDelegate + * @function + * + * @description + * + * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict + * Contextual Escaping (SCE)} services to AngularJS. + * + * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of + * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is + * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to + * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things + * work because `$sce` delegates to `$sceDelegate` for these operations. + * + * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. + * + * The default instance of `$sceDelegate` should work out of the box with little pain. While you + * can override it completely to change the behavior of `$sce`, the common case would + * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting + * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as + * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist + * $sceDelegateProvider.resourceUrlWhitelist} and {@link + * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} + */ + +/** + * @ngdoc object + * @name ng.$sceDelegateProvider + * @description + * + * The $sceDelegateProvider provider allows developers to configure the {@link ng.$sceDelegate + * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure + * that URLs used for sourcing Angular templates are safe. Refer {@link + * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and + * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} + * + * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. + */ + +function $SceDelegateProvider() { + this.SCE_CONTEXTS = SCE_CONTEXTS; + + // Resource URLs can also be trusted by policy. + var resourceUrlWhitelist = ['self'], + resourceUrlBlacklist = []; + + /** + * @ngdoc function + * @name ng.sceDelegateProvider#resourceUrlWhitelist + * @methodOf ng.$sceDelegateProvider + * @function + * + * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value + * provided. This must be an array. + * + * Each element of this array must either be a regex or the special string `'self'`. + * + * When a regex is used, it is matched against the normalized / absolute URL of the resource + * being tested. + * + * The **special string** `'self'` can be used to match against all URLs of the same domain as the + * application document with the same protocol (allows sourcing https resources from http documents.) + * + * Please note that **an empty whitelist array will block all URLs**! + * + * @return {Array} the currently set whitelist array. + * + * The **default value** when no whitelist has been explicitly set is `['self']`. + * + * @description + * Sets/Gets the whitelist of trusted resource URLs. + */ + this.resourceUrlWhitelist = function (value) { + if (arguments.length) { + resourceUrlWhitelist = value; + } + return resourceUrlWhitelist; + }; + + /** + * @ngdoc function + * @name ng.sceDelegateProvider#resourceUrlBlacklist + * @methodOf ng.$sceDelegateProvider + * @function + * + * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value + * provided. This must be an array. + * + * Each element of this array must either be a regex or the special string `'self'` (see + * `resourceUrlWhitelist` for meaning - it's only really useful there.) + * + * When a regex is used, it is matched against the normalized / absolute URL of the resource + * being tested. + * + * The typical usage for the blacklist is to **block [open redirects](http://cwe.mitre.org/data/definitions/601.html)** + * served by your domain as these would otherwise be trusted but actually return content from the redirected + * domain. + * + * Finally, **the blacklist overrides the whitelist** and has the final say. + * + * @return {Array} the currently set blacklist array. + * + * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there is + * no blacklist.) + * + * @description + * Sets/Gets the blacklist of trusted resource URLs. + */ + + this.resourceUrlBlacklist = function (value) { + if (arguments.length) { + resourceUrlBlacklist = value; + } + return resourceUrlBlacklist; + }; + + // Helper functions for matching resource urls by policy. + function isCompatibleProtocol(documentProtocol, resourceProtocol) { + return ((documentProtocol === resourceProtocol) || + (documentProtocol === "http:" && resourceProtocol === "https:")); + } + + this.$get = ['$log', '$document', '$$urlUtils', function( + $log, $document, $$urlUtils) { + + function matchUrl(matcher, parsedUrl) { + if (matcher === 'self') { + return $$urlUtils.isSameOrigin(parsedUrl); + } else { + return !!parsedUrl.href.match(matcher); + } + } + + function isResourceUrlAllowedByPolicy(url) { + var parsedUrl = $$urlUtils.resolve(url.toString(), true); + var i, n, allowed = false; + // Ensure that at least one item from the whitelist allows this url. + for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { + if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { + allowed = true; + break; + } + } + if (allowed) { + // Ensure that no item from the blacklist blocked this url. + for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { + if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { + allowed = false; + break; + } + } + } + return allowed; + } + + function generateHolderType(base) { + var holderType = function TrustedValueHolderType(trustedValue) { + this.$$unwrapTrustedValue = function() { + return trustedValue; + }; + }; + if (base) { + holderType.prototype = new base(); + } + holderType.prototype.valueOf = function sceValueOf() { + return this.$$unwrapTrustedValue(); + } + holderType.prototype.toString = function sceToString() { + return this.$$unwrapTrustedValue().toString(); + } + return holderType; + } + + var trustedValueHolderBase = generateHolderType(), + byType = {}; + + byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); + byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); + + /** + * @ngdoc method + * @name ng.$sceDelegate#trustAs + * @methodOf ng.$sceDelegate + * + * @description + * Returns an object that is trusted by angular for use in specified strict + * contextual escaping contexts (such as ng-html-bind-unsafe, ng-include, any src + * attribute interpolation, any dom event binding attribute interpolation + * such as for onclick, etc.) that uses the provided value. + * See {@link ng.$sce $sce} for enabling strict contextual escaping. + * + * @param {string} type The kind of context in which this value is safe for use. e.g. url, + * resourceUrl, html, js and css. + * @param {*} value The value that that should be considered trusted/safe. + * @returns {*} A value that can be used to stand in for the provided `value` in places + * where Angular expects a $sce.trustAs() return value. + */ + function trustAs(type, trustedValue) { + var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); + if (!constructor) { + throw $sceMinErr('icontext', 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', + type, trustedValue); + } + if (trustedValue === null || trustedValue === undefined || trustedValue === '') { + return trustedValue; + } + // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting + // mutable objects, we ensure here that the value passed in is actually a string. + if (typeof trustedValue !== 'string') { + throw $sceMinErr('itype', + 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', + type); + } + return new constructor(trustedValue); + } + + /** + * @ngdoc method + * @name ng.$sceDelegate#valueOf + * @methodOf ng.$sceDelegate + * + * @description + * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs + * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link + * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. + * + * If the passed parameter is not a value that had been returned by {@link + * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. + * + * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} + * call or anything else. + * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs + * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns `value` + * unchanged. + */ + function valueOf(maybeTrusted) { + if (maybeTrusted instanceof trustedValueHolderBase) { + return maybeTrusted.$$unwrapTrustedValue(); + } else { + return maybeTrusted; + } + } + + /** + * @ngdoc method + * @name ng.$sceDelegate#getTrusted + * @methodOf ng.$sceDelegate + * + * @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. + * + * @param {string} type The kind of context in which this value is to be used. + * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs + * `$sceDelegate.trustAs`} call. + * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs + * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. + */ + function getTrusted(type, maybeTrusted) { + if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { + return maybeTrusted; + } + var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); + if (constructor && maybeTrusted instanceof constructor) { + return maybeTrusted.$$unwrapTrustedValue(); + } + if (type === SCE_CONTEXTS.RESOURCE_URL) { + if (isResourceUrlAllowedByPolicy(maybeTrusted)) { + return maybeTrusted; + } else { + throw $sceMinErr('isecrurl', + 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', maybeTrusted.toString()); + return; + } + } + throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); + } + + return { trustAs: trustAs, + getTrusted: getTrusted, + valueOf: valueOf }; + }]; +} + + +/** + * @ngdoc object + * @name ng.$sceProvider + * @description + * + * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. + * - enable/disable Strict Contextual Escaping (SCE) in a module + * - override the default implementation with a custom delegate + * + * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. + */ + +/** + * @ngdoc service + * @name ng.$sce + * @function + * + * @description + * + * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. + * + * # Strict Contextual Escaping + * + * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain + * contexts to result in a value that is marked as safe to use for that context One example of such + * a context is binding arbitrary html controlled by the user via `ng-bind-html-unsafe`. We refer + * to these contexts as privileged or SCE contexts. + * + * As of version 1.2, Angular ships with SCE enabled by default. + * + * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows + * one to execute arbitrary javascript by the use of the expression() syntax. Refer + * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them. + * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` + * to the top of your HTML document. + * + * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for + * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. + * + * Here's an example of a binding in a privileged context: + * + * <pre class="prettyprint"> + * <input ng-model="userHtml"> + * <div ng-bind-html-unsafe="{{userHtml}}"> + * </pre> + * + * Notice that `ng-bind-html-unsafe` is bound to `{{userHtml}}` controlled by the user. With SCE + * disabled, this application allows the user to render arbitrary HTML into the DIV. + * In a more realistic example, one may be rendering user comments, blog articles, etc. via + * bindings. (HTML is just one example of a context where rendering user controlled input creates + * security vulnerabilities.) + * + * For the case of HTML, you might use a library, either on the client side, or on the server side, + * to sanitize unsafe HTML before binding to the value and rendering it in the document. + * + * How would you ensure that every place that used these types of bindings was bound to a value that + * was sanitized by your library (or returned as safe for rendering by your server?) How can you + * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some + * properties/fields and forgot to update the binding to the sanitized value? + * + * To be secure by default, you want to ensure that any such bindings are disallowed unless you can + * determine that something explicitly says it's safe to use a value for binding in that + * context. You can then audit your code (a simple grep would do) to ensure that this is only done + * for those values that you can easily tell are safe - because they were received from your server, + * sanitized by your library, etc. You can organize your codebase to help with this - perhaps + * allowing only the files in a specific directory to do this. Ensuring that the internal API + * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. + * + * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} (and shorthand + * methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to obtain values that will be + * accepted by SCE / privileged contexts. + * + * + * ## How does it work? + * + * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted + * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link + * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the + * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. + * + * As an example, {@link ng.directive:ngBindHtmlUnsafe ngBindHtmlUnsafe} uses {@link + * ng.$sce#parseHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly + * simplified): + * + * <pre class="prettyprint"> + * var ngBindHtmlUnsafeDirective = ['$sce', function($sce) { + * return function(scope, element, attr) { + * scope.$watch($sce.parseAsHtml(attr.ngBindHtmlUnsafe), function(value) { + * element.html(value || ''); + * }); + * }; + * }]; + * </pre> + * + * ## Impact on loading templates + * + * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as + * `templateUrl`'s specified by {@link guide/directive directives}. + * + * By default, Angular only loads templates from the same domain and protocol as the application + * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl + * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or + * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist + * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. + * + * *Please note*: + * The browser's + * {@link https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest + * Same Origin Policy} and {@link http://www.w3.org/TR/cors/ Cross-Origin Resource Sharing (CORS)} + * policy apply in addition to this and may further restrict whether the template is successfully + * loaded. This means that without the right CORS policy, loading templates from a different domain + * won't work on all browsers. Also, loading templates from `file://` URL does not work on some + * browsers. + * + * ## This feels like too much overhead for the developer? + * + * It's important to remember that SCE only applies to interpolation expressions. + * + * 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-html-bind-unsafe="'<b>implicitly trusted</b>'"></div>`) 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 + * served document. You can change these by setting your own custom {@link + * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link + * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. + * + * This significantly reduces the overhead. It is far easier to pay the small overhead and have an + * application that's secure and can be audited to verify that with much more ease than bolting + * security onto an application later. + * + * ## What trusted context types are supported?<a name="contexts"></a> + * + * | Context | Notes | + * |=====================|================| + * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtmlUnsafe ngBindHtmlUnsafe} directive uses this context for bindings. | + * | `$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 (`<a href=` and `<img src=` sanitize their urls and don't consititute an SCE context. | + * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contens 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` 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. | + * + * ## Show me an example. + * + * + * + * @example + <example module="mySceApp"> + <file name="index.html"> + <div ng-controller="myAppController as myCtrl"> + <button ng-click="myCtrl.fetchUserComments()" id="fetchBtn">Fetch Comments</button> + <div ng-show="myCtrl.errorMsg">Error: {{myCtrl.errorMsg}}</div> + <div ng-repeat="userComment in myCtrl.userComments"> + <hr> + <b>{{userComment.name}}</b>: + <span ng-bind-html-unsafe="userComment.htmlComment" class="htmlComment"></span> + </div> + <div ng-bind-html-unsafe="myCtrl.someHtml" id="someHtml"></div> + </div> + </file> + + <file name="script.js"> + // These types of functions would be in the data access layer of your application code. + function fetchUserCommentsFromServer($http, $q, $templateCache, $sce) { + var deferred = $q.defer(); + $http({method: "GET", url: "test_data.json", cache: $templateCache}). + success(function(userComments, status) { + // The comments coming from the server have been sanitized by the server and can be + // trusted. + angular.forEach(userComments, function(userComment) { + userComment.htmlComment = $sce.trustAsHtml(userComment.htmlComment); + }); + deferred.resolve(userComments); + }). + error(function (data, status) { + deferred.reject("HTTP status code " + status + ": " + data); + }); + return deferred.promise; + }; + + var mySceApp = angular.module('mySceApp', []); + + mySceApp.controller("myAppController", function myAppController($injector) { + var self = this; + + self.someHtml = "This might have been any binding including an input element " + + "controlled by the user."; + + self.fetchUserComments = function() { + $injector.invoke(fetchUserCommentsFromServer).then( + function onSuccess(userComments) { + self.errorMsg = null; + self.userComments = userComments; + }, + function onFailure(errorMsg) { + self.errorMsg = errorMsg; + }); + } + }); + </file> + + <file name="test_data.json"> + [ + { "name": "Alice", + "htmlComment": "Is <i>anyone</i> reading this?" + }, + { "name": "Bob", + "htmlComment": "<i>Yes!</i> Am I the only other one?" + } + ] + </file> + + <file name="scenario.js"> + describe('SCE doc demo', function() { + it('should bind trusted values', function() { + element('#fetchBtn').click(); + expect(element('.htmlComment').html()).toBe('Is <i>anyone</i> reading this?'); + }); + it('should NOT bind arbitrary values', function() { + expect(element('#someHtml').html()).toBe(''); + }); + }); + </file> + </example> + * + * + * + * ## Can I disable SCE completely? + * + * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits + * for little coding overhead. It will be much harder to take an SCE disabled application and + * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE + * for cases where you have a lot of existing code that was written before SCE was introduced and + * you're migrating them a module at a time. + * + * That said, here's how you can completely disable SCE: + * + * <pre class="prettyprint"> + * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { + * // Completely disable SCE. For demonstration purposes only! + * // Do not use in new projects. + * $sceProvider.enabled(false); + * }); + * </pre> + * + */ + +function $SceProvider() { + var enabled = true; + + /** + * @ngdoc function + * @name ng.sceProvider#enabled + * @methodOf ng.$sceProvider + * @function + * + * @param {boolean=} value If provided, then enables/disables SCE. + * @return {boolean} true if SCE is enabled, false otherwise. + * + * @description + * Enables/disables SCE and returns the current value. + */ + this.enabled = function (value) { + if (arguments.length) { + enabled = !!value; + } + return enabled; + }; + + + /* Design notes on the default implementation for SCE. + * + * The API contract for the SCE delegate + * ------------------------------------- + * The SCE delegate object must provide the following 3 methods: + * + * - trustAs(contextEnum, value) + * This method is used to tell the SCE service that the provided value is OK to use in the + * contexts specified by contextEnum. It must return an object that will be accepted by + * getTrusted() for a compatible contextEnum and return this value. + * + * - valueOf(value) + * For values that were not produced by trustAs(), return them as is. For values that were + * produced by trustAs(), return the corresponding input value to trustAs. Basically, if + * trustAs is wrapping the given values into some type, this operation unwraps it when given + * such a value. + * + * - getTrusted(contextEnum, value) + * This function should return the a 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 opaque + * or wrapped in some holder object. That happens to be an implementation detail. For instance, + * an implementation could maintain a registry of all trusted objects by context. In such a case, + * trustAs() would return the same object that was passed in. getTrusted() would return the same + * object passed in if it was found in the registry under a compatible context or throw an + * exception otherwise. An implementation might only wrap values some of the time based on + * some criteria. getTrusted() might return a value and not throw an exception for special + * constants or objects even if not wrapped. All such implementations fulfill this contract. + * + * + * A note on the inheritance model for SCE contexts + * ------------------------------------------------ + * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This + * is purely an implementation details. + * + * The contract is simply this: + * + * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) + * will also succeed. + * + * Inheritance happens to capture this in a natural way. In some future, we + * may not use inheritance anymore. That is OK because no code outside of + * sce.js and sceSpecs.js would need to be aware of this detail. + */ + + this.$get = ['$parse', '$document', '$sceDelegate', function( + $parse, $document, $sceDelegate) { + // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows + // the "expression(javascript expression)" syntax which is insecure. + if (enabled && msie) { + var documentMode = $document[0].documentMode; + if (documentMode !== undefined && documentMode < 8) { + throw $sceMinErr('iequirks', + 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + + 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + + 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); + } + } + + var sce = copy(SCE_CONTEXTS); + + /** + * @ngdoc function + * @name ng.sce#isEnabled + * @methodOf ng.$sce + * @function + * + * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you + * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. + * + * @description + * Returns a boolean indicating if SCE is enabled. + */ + sce.isEnabled = function () { + return enabled; + }; + sce.trustAs = $sceDelegate.trustAs; + sce.getTrusted = $sceDelegate.getTrusted; + sce.valueOf = $sceDelegate.valueOf; + + if (!enabled) { + sce.trustAs = sce.getTrusted = function(type, value) { return value; }, + sce.valueOf = identity + } + + /** + * @ngdoc method + * @name ng.$sce#parse + * @methodOf ng.$sce + * + * @description + * Converts Angular {@link guide/expression expression} into a function. This is like {@link + * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it + * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, + * *result*)} + * + * @param {string} type The kind of SCE context in which this result will be used. + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + sce.parseAs = function sceParseAs(type, expr) { + var parsed = $parse(expr); + if (parsed.literal && parsed.constant) { + return parsed; + } else { + return function sceParseAsTrusted(self, locals) { + return sce.getTrusted(type, parsed(self, locals)); + } + } + }; + + /** + * @ngdoc method + * @name ng.$sce#trustAs + * @methodOf ng.$sce + * + * @description + * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, returns an object + * that is trusted by angular for use in specified strict contextual escaping contexts (such as + * ng-html-bind-unsafe, ng-include, any src attribute interpolation, any dom event binding + * attribute interpolation such as for onclick, etc.) that uses the provided value. See * + * {@link ng.$sce $sce} for enabling strict contextual escaping. + * + * @param {string} type The kind of context in which this value is safe for use. e.g. url, + * resource_url, html, js and css. + * @param {*} value The value that that should be considered trusted/safe. + * @returns {*} A value that can be used to stand in for the provided `value` in places + * where Angular expects a $sce.trustAs() return value. + */ + + /** + * @ngdoc method + * @name ng.$sce#trustAsHtml + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.trustAsHtml(value)` → {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml + * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the + * return value of {@link ng.$sce#trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name ng.$sce#trustAsUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.trustAsUrl(value)` → {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl + * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the + * return value of {@link ng.$sce#trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name ng.$sce#trustAsResourceUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.trustAsResourceUrl(value)` → {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl + * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the return + * value of {@link ng.$sce#trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name ng.$sce#trustAsJs + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.trustAsJs(value)` → {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} + * + * @param {*} value The value to trustAs. + * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs + * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives + * only accept expressions that are either literal constants or are the + * return value of {@link ng.$sce#trustAs $sce.trustAs}.) + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrusted + * @methodOf ng.$sce + * + * @description + * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, takes + * the result of a {@link ng.$sce#trustAs `$sce.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. + * + * @param {string} type The kind of context in which this value is to be used. + * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} call. + * @returns {*} The value the was originally provided to {@link ng.$sce#trustAs `$sce.trustAs`} if + * valid in this context. Otherwise, throws an exception. + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrustedHtml + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.getTrustedHtml(value)` → {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrustedCss + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.getTrustedCss(value)` → {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrustedUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.getTrustedUrl(value)` → {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrustedResourceUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.getTrustedResourceUrl(value)` → {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} + * + * @param {*} value The value to pass to `$sceDelegate.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` + */ + + /** + * @ngdoc method + * @name ng.$sce#getTrustedJs + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.getTrustedJs(value)` → {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} + * + * @param {*} value The value to pass to `$sce.getTrusted`. + * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` + */ + + /** + * @ngdoc method + * @name ng.$sce#parseAsHtml + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.parseAsHtml(expression string)` → {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name ng.$sce#parseAsCss + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.parseAsCss(value)` → {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name ng.$sce#parseAsUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.parseAsUrl(value)` → {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name ng.$sce#parseAsResourceUrl + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.parseAsResourceUrl(value)` → {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + /** + * @ngdoc method + * @name ng.$sce#parseAsJs + * @methodOf ng.$sce + * + * @description + * Shorthand method. `$sce.parseAsJs(value)` → {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`} + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (typically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + */ + + // Shorthand delegations. + var parse = sce.parseAs, + getTrusted = sce.getTrusted, + trustAs = sce.trustAs; + + angular.forEach(SCE_CONTEXTS, function (enumValue, name) { + var lName = lowercase(name); + sce[camelCase("parse_as_" + lName)] = function (expr) { + return parse(enumValue, expr); + } + sce[camelCase("get_trusted_" + lName)] = function (value) { + return getTrusted(enumValue, value); + } + sce[camelCase("trust_as_" + lName)] = function (value) { + return trustAs(enumValue, value); + } + }); + + return sce; + }]; +} diff --git a/src/ng/urlUtils.js b/src/ng/urlUtils.js index 5402b500..af2d913f 100644 --- a/src/ng/urlUtils.js +++ b/src/ng/urlUtils.js @@ -105,11 +105,12 @@ function $$UrlUtilsProvider() { /** * Parse a request URL and determine whether this is a same-origin request as the application document. * - * @param {string} requestUrl The url of the request. + * @param {string|object} requestUrl The url of the request as a string that will be resolved + * or a parsed URL object. * @returns {boolean} Whether the request is for the same origin as the application document. */ isSameOrigin: function isSameOrigin(requestUrl) { - var parsed = resolve(requestUrl, true); + var parsed = (typeof requestUrl === 'string') ? resolve(requestUrl, true) : requestUrl; return (parsed.protocol === originUrl.protocol && parsed.host === originUrl.host); } diff --git a/src/ngRoute/route.js b/src/ngRoute/route.js index de0d9016..743897d6 100644 --- a/src/ngRoute/route.js +++ b/src/ngRoute/route.js @@ -56,9 +56,9 @@ function $RouteProvider(){ * if passed as a string. * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be * published to scope under the `controllerAs` name. - * - `template` – `{string=|function()=}` – html template as a string or function that returns - * an html template as a string which should be used by {@link ngRoute.directive:ngView ngView} or - * {@link ng.directive:ngInclude ngInclude} directives. + * - `template` – `{string=|function()=}` – html template as a string or a function that + * returns an html template as a string which should be used by {@link + * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. * This property takes precedence over `templateUrl`. * * If `template` is a function, it will be called with the following parameters: @@ -149,8 +149,8 @@ function $RouteProvider(){ }; - this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache', - function( $rootScope, $location, $routeParams, $q, $injector, $http, $templateCache) { + this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache', '$sce', + function( $rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) { /** * @ngdoc object @@ -437,7 +437,7 @@ function $RouteProvider(){ then(function() { if (next) { var locals = extend({}, next.resolve), - template; + template, templateUrl; forEach(locals, function(value, key) { locals[key] = isString(value) ? $injector.get(value) : $injector.invoke(value); @@ -447,13 +447,14 @@ function $RouteProvider(){ if (isFunction(template)) { template = template(next.params); } - } else if (isDefined(template = next.templateUrl)) { - if (isFunction(template)) { - template = template(next.params); + } else if (isDefined(templateUrl = next.templateUrl)) { + if (isFunction(templateUrl)) { + templateUrl = templateUrl(next.params); } - if (isDefined(template)) { - next.loadedTemplateUrl = template; - template = $http.get(template, {cache: $templateCache}). + templateUrl = $sce.getTrustedResourceUrl(templateUrl); + if (isDefined(templateUrl)) { + next.loadedTemplateUrl = templateUrl; + template = $http.get(templateUrl, {cache: $templateCache}). then(function(response) { return response.data; }); } } diff --git a/src/ngSanitize/sanitize.js b/src/ngSanitize/sanitize.js index 049a6821..110b3a64 100644 --- a/src/ngSanitize/sanitize.js +++ b/src/ngSanitize/sanitize.js @@ -63,11 +63,15 @@ var ngSanitizeMinErr = angular.$$minErr('ngSanitize'); <doc:example module="ngSanitize"> <doc:source> <script> - function Ctrl($scope) { + function Ctrl($scope, $sce) { $scope.snippet = '<p style="color:blue">an html\n' + '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' + 'snippet</p>'; + // ng-bind-html-unsafe requires a $sce trusted value of type $sce.HTML. + $scope.getSceSnippet = function() { + return $sce.trustAsHtml($scope.snippet); + }; } </script> <div ng-controller="Ctrl"> @@ -94,8 +98,8 @@ var ngSanitizeMinErr = angular.$$minErr('ngSanitize'); </tr> <tr id="html-unsafe-filter"> <td>unsafe html filter</td> - <td><pre><div ng-bind-html-unsafe="snippet"><br/></div></pre></td> - <td><div ng-bind-html-unsafe="snippet"></div></td> + <td><pre><div ng-bind-html-unsafe="getSceSnippet()"><br/></div></pre></td> + <td><div ng-bind-html-unsafe="getSceSnippet()"></div></td> </tr> </table> </div> @@ -120,11 +124,11 @@ var ngSanitizeMinErr = angular.$$minErr('ngSanitize'); "snippet</p>"); }); - it('should update', function() { + it('should update', function($sce) { input('snippet').enter('new <b>text</b>'); expect(using('#html-filter').binding('snippet')).toBe('new <b>text</b>'); expect(using('#escaped-html').element('div').html()).toBe("new <b>text</b>"); - expect(using('#html-unsafe-filter').binding("snippet")).toBe('new <b>text</b>'); + expect(using('#html-unsafe-filter').element('div').html()).toBe('new <b>text</b>'); }); </doc:scenario> </doc:example> diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index c7821878..1f5aae95 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -681,9 +681,17 @@ describe('$compile', function() { restrict: 'CAM', templateUrl: 'hello.html', transclude: true })); directive('cau', valueFn({ - restrict: 'CAM', templateUrl:'cau.html' + restrict: 'CAM', templateUrl: 'cau.html' })); - + directive('crossDomainTemplate', valueFn({ + restrict: 'CAM', templateUrl: 'http://example.com/should-not-load.html' + })); + directive('trustedTemplate', function($sce) { return { + restrict: 'CAM', + templateUrl: function() { + return $sce.trustAsResourceUrl('http://example.com/trusted-template.html'); + }}; + }); directive('cError', valueFn({ restrict: 'CAM', templateUrl:'error.html', @@ -735,6 +743,24 @@ describe('$compile', function() { } )); + it('should not load cross domain templates by default', inject( + function($compile, $rootScope, $templateCache, $sce) { + expect(function() { + $templateCache.put('http://example.com/should-not-load.html', 'Should not load even if in cache.'); + $compile('<div class="crossDomainTemplate"></div>')($rootScope); + }).toThrow('[$sce:isecrurl] Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example.com/should-not-load.html'); + })); + + it('should load cross domain templates when trusted', inject( + function($compile, $httpBackend, $rootScope, $sce) { + $httpBackend.expect('GET', 'http://example.com/trusted-template.html').respond('<span>example.com/trusted_template_contents</span>'); + element = $compile('<div class="trustedTemplate"></div>')($rootScope); + expect(sortedHtml(element)). + toEqual('<div class="trustedTemplate"></div>'); + $httpBackend.flush(); + expect(sortedHtml(element)). + toEqual('<div class="trustedTemplate"><span>example.com/trusted_template_contents</span></div>'); + })); it('should append template via $http and cache it in $templateCache', inject( function($compile, $httpBackend, $templateCache, $rootScope, $browser) { @@ -1521,6 +1547,16 @@ describe('$compile', function() { expect(element.attr('name')).toEqual('attr: angular'); })); + describe('SCE values', function() { + it('should resolve compile and link both attribute and text bindings', inject( + function($rootScope, $compile, $sce) { + $rootScope.name = $sce.trustAsHtml('angular'); + element = $compile('<div name="attr: {{name}}">text: {{name}}</div>')($rootScope); + $rootScope.$digest(); + expect(element.text()).toEqual('text: angular'); + expect(element.attr('name')).toEqual('attr: angular'); + })); + }); it('should decorate the binding with ng-binding and interpolation function', inject( function($compile, $rootScope) { @@ -2625,12 +2661,16 @@ describe('$compile', function() { }); - describe('img[src] sanitization', function() { - it('should NOT require trusted values for img src', inject(function($rootScope, $compile) { + describe('img[src] sanitization', function($sce) { + 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'); })); it('should sanitize javascript: urls', inject(function($compile, $rootScope) { @@ -2965,6 +3005,48 @@ describe('$compile', function() { })); }); + describe('iframe[src]', function() { + it('should pass through src attributes for the same domain', inject(function($compile, $rootScope, $sce) { + element = $compile('<iframe src="{{testUrl}}"></iframe>')($rootScope); + $rootScope.testUrl = "different_page"; + $rootScope.$apply(); + expect(element.attr('src')).toEqual('different_page'); + })); + + it('should clear out src attributes for a different domain', inject(function($compile, $rootScope, $sce) { + element = $compile('<iframe src="{{testUrl}}"></iframe>')($rootScope); + $rootScope.testUrl = "http://a.different.domain.example.com"; + expect(function() { $rootScope.$apply() }).toThrow( + "[$interpolate:interr] Can't interpolate: {{testUrl}}\nError: [$sce:isecrurl] Blocked " + + "loading resource from url not allowed by $sceDelegate policy. URL: " + + "http://a.different.domain.example.com"); + })); + + it('should clear out JS src attributes', inject(function($compile, $rootScope, $sce) { + element = $compile('<iframe src="{{testUrl}}"></iframe>')($rootScope); + $rootScope.testUrl = "javascript:alert(1);"; + expect(function() { $rootScope.$apply() }).toThrow( + "[$interpolate:interr] Can't interpolate: {{testUrl}}\nError: [$sce:isecrurl] Blocked " + + "loading resource from url not allowed by $sceDelegate policy. URL: " + + "javascript:alert(1);"); + })); + + it('should clear out non-resource_url src attributes', inject(function($compile, $rootScope, $sce) { + element = $compile('<iframe src="{{testUrl}}"></iframe>')($rootScope); + $rootScope.testUrl = $sce.trustAsUrl("javascript:doTrustedStuff()"); + expect($rootScope.$apply).toThrow( + "[$interpolate:interr] Can't interpolate: {{testUrl}}\nError: [$sce:isecrurl] Blocked " + + "loading resource from url not allowed by $sceDelegate policy. URL: javascript:doTrustedStuff()"); + })); + + it('should pass through $sce.trustAs() values in src attributes', inject(function($compile, $rootScope, $sce) { + element = $compile('<iframe src="{{testUrl}}"></iframe>')($rootScope); + $rootScope.testUrl = $sce.trustAsResourceUrl("javascript:doTrustedStuff()"); + $rootScope.$apply(); + + expect(element.attr('src')).toEqual('javascript:doTrustedStuff()'); + })); + }); describe('ngAttr* attribute binding', function() { diff --git a/test/ng/directive/booleanAttrsSpec.js b/test/ng/directive/booleanAttrsSpec.js index be2dfb60..93e8cc20 100644 --- a/test/ng/directive/booleanAttrsSpec.js +++ b/test/ng/directive/booleanAttrsSpec.js @@ -102,61 +102,99 @@ describe('boolean attr directives', function() { describe('ngSrc', function() { - it('should interpolate the expression and bind to src', inject(function($compile, $rootScope) { + 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 = 1; + $rootScope.id = $sce.trustAsResourceUrl('http://somewhere'); }); - expect(element.attr('src')).toEqual('1'); + expect(element.attr('src')).toEqual('http://somewhere'); dealoc(element); })); - describe('isTrustedContext', function() { - 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); - }).toThrow( - "[$interpolate:noconcat] Error while interpolating: some/{{id}}\nYou may not use " + - "multiple expressions when interpolating this expression."); - })); - 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/'); + 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); + }).toThrow( + "[$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 = 1; + $rootScope.id = $sce.trustAsUrl('http://somewhere'); }); - expect(element.attr('foo')).toEqual('some/1'); - })); + element.attr('src'); + }).toThrow( + "[$interpolate:interr] Can't interpolate: {{id}}\nError: [$sce:isecrurl] Blocked " + + "loading resource from url not allowed by $sceDelegate policy. URL: http://somewhere"); + })); - }); if (msie) { it('should update the element property as well as the attribute', inject( - function($compile, $rootScope) { - // 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 + 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); + var element = $compile('<div ng-src="{{id}}"></div>')($rootScope); - $rootScope.$digest(); - expect(element.prop('src')).toBeUndefined(); + $rootScope.$digest(); + expect(element.prop('src')).toBeUndefined(); + dealoc(element); - $rootScope.$apply(function() { - $rootScope.id = 1; - }); - expect(element.prop('src')).toEqual('1'); + element = $compile('<div ng-src="some/"></div>')($rootScope); - dealoc(element); - })); + $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); + })); } }); diff --git a/test/ng/directive/ngBindSpec.js b/test/ng/directive/ngBindSpec.js index da291fa4..1d8f8ef4 100644 --- a/test/ng/directive/ngBindSpec.js +++ b/test/ng/directive/ngBindSpec.js @@ -69,11 +69,47 @@ describe('ngBind*', function() { describe('ngBindHtmlUnsafe', function() { - it('should set unsafe html', inject(function($rootScope, $compile) { - element = $compile('<div ng-bind-html-unsafe="html"></div>')($rootScope); - $rootScope.html = '<div onclick="">hello</div>'; - $rootScope.$digest(); - expect(angular.lowercase(element.html())).toEqual('<div onclick="">hello</div>'); - })); + function configureSce(enabled) { + module(function($provide, $sceProvider) { + $sceProvider.enabled(enabled); + }); + }; + + describe('SCE disabled', function() { + beforeEach(function() {configureSce(false)}); + + it('should set unsafe html', inject(function($rootScope, $compile) { + element = $compile('<div ng-bind-html-unsafe="html"></div>')($rootScope); + $rootScope.html = '<div onclick="">hello</div>'; + $rootScope.$digest(); + expect(angular.lowercase(element.html())).toEqual('<div onclick="">hello</div>'); + })); + }); + + + describe('SCE enabled', function() { + beforeEach(function() {configureSce(true)}); + + it('should NOT set unsafe html for untrusted values', inject(function($rootScope, $compile) { + element = $compile('<div ng-bind-html-unsafe="html"></div>')($rootScope); + $rootScope.html = '<div onclick="">hello</div>'; + expect($rootScope.$digest).toThrow(); + })); + + it('should NOT set unsafe html for wrongly typed values', inject(function($rootScope, $compile, $sce) { + element = $compile('<div ng-bind-html-unsafe="html"></div>')($rootScope); + $rootScope.html = $sce.trustAsCss('<div onclick="">hello</div>'); + expect($rootScope.$digest).toThrow(); + })); + + it('should set unsafe html for trusted values', inject(function($rootScope, $compile, $sce) { + element = $compile('<div ng-bind-html-unsafe="html"></div>')($rootScope); + $rootScope.html = $sce.trustAsHtml('<div onclick="">hello</div>'); + $rootScope.$digest(); + expect(angular.lowercase(element.html())).toEqual('<div onclick="">hello</div>'); + })); + + }); + }); }); diff --git a/test/ng/directive/ngIncludeSpec.js b/test/ng/directive/ngIncludeSpec.js index 93709431..6cb78755 100644 --- a/test/ng/directive/ngIncludeSpec.js +++ b/test/ng/directive/ngIncludeSpec.js @@ -3,7 +3,6 @@ describe('ngInclude', function() { var element; - afterEach(function(){ dealoc(element); }); @@ -16,7 +15,29 @@ describe('ngInclude', function() { } - it('should include on external file', inject(putIntoCache('myUrl', '{{name}}'), + it('should trust and use literal urls', inject(function( + $rootScope, $httpBackend, $compile) { + element = $compile('<div ng-include="\'url\'"></div>')($rootScope); + $httpBackend.expect('GET', 'url').respond('template text'); + $rootScope.$digest(); + $httpBackend.flush(); + expect(element.text()).toEqual('template text'); + dealoc($rootScope); + })); + + + it('should trust and use trusted urls', inject(function($rootScope, $httpBackend, $compile, $sce) { + element = $compile('<div ng-include="fooUrl"></div>')($rootScope); + $httpBackend.expect('GET', 'http://foo.bar/url').respond('template text'); + $rootScope.fooUrl = $sce.trustAsResourceUrl('http://foo.bar/url'); + $rootScope.$digest(); + $httpBackend.flush(); + expect(element.text()).toEqual('template text'); + dealoc($rootScope); + })); + + + it('should include an external file', inject(putIntoCache('myUrl', '{{name}}'), function($rootScope, $compile) { element = jqLite('<ng:include src="url"></ng:include>'); jqLite(document.body).append(element); @@ -42,6 +63,29 @@ describe('ngInclude', function() { })); + it('should NOT use untrusted expressions ', inject(putIntoCache('myUrl', '{{name}} text'), + function($rootScope, $compile, $sce) { + element = jqLite('<ng:include src="url"></ng:include>'); + jqLite(document.body).append(element); + element = $compile(element)($rootScope); + $rootScope.name = 'chirayu'; + $rootScope.url = 'myUrl'; + expect($rootScope.$digest).toThrow(); + jqLite(document.body).html(''); + })); + + + it('should NOT use mistyped expressions ', inject(putIntoCache('myUrl', '{{name}} text'), + function($rootScope, $compile, $sce) { + element = jqLite('<ng:include src="url"></ng:include>'); + jqLite(document.body).append(element); + element = $compile(element)($rootScope); + $rootScope.name = 'chirayu'; + $rootScope.url = $sce.trustAsUrl('myUrl'); + expect($rootScope.$digest).toThrow(); + jqLite(document.body).html(''); + })); + it('should remove previously included text if a falsy value is bound to src', inject( putIntoCache('myUrl', '{{name}}'), function($rootScope, $compile) { @@ -308,7 +352,7 @@ describe('ngInclude ngAnimate', function() { } function applyCSS(element, cssProp, cssValue) { - element.css(cssProp, cssValue); + element.css(cssProp, cssValue); element.css(vendorPrefix + cssProp, cssValue); } diff --git a/test/ng/directive/ngSrcSpec.js b/test/ng/directive/ngSrcSpec.js index a917c511..23ace7ee 100644 --- a/test/ng/directive/ngSrcSpec.js +++ b/test/ng/directive/ngSrcSpec.js @@ -14,4 +14,48 @@ describe('ngSrc', function() { expect(element.attr('src')).not.toBe(''); expect(element.attr('src')).toBe(undefined); })); + + describe('iframe[ng-src]', function() { + it('should pass through src attributes for the same domain', inject(function($compile, $rootScope) { + element = $compile('<iframe ng-src="{{testUrl}}"></iframe>')($rootScope); + $rootScope.testUrl = "different_page"; + $rootScope.$apply(); + expect(element.attr('src')).toEqual('different_page'); + })); + + it('should error on src attributes for a different domain', inject(function($compile, $rootScope) { + element = $compile('<iframe ng-src="{{testUrl}}"></iframe>')($rootScope); + $rootScope.testUrl = "http://a.different.domain.example.com"; + expect(function() { $rootScope.$apply() }).toThrow( + "[$interpolate:interr] Can't interpolate: {{testUrl}}\nError: [$sce:isecrurl] Blocked " + + "loading resource from url not allowed by $sceDelegate policy. URL: " + + "http://a.different.domain.example.com"); + })); + + it('should error on JS src attributes', inject(function($compile, $rootScope) { + element = $compile('<iframe ng-src="{{testUrl}}"></iframe>')($rootScope); + $rootScope.testUrl = "javascript:alert(1);"; + expect(function() { $rootScope.$apply() }).toThrow( + "[$interpolate:interr] Can't interpolate: {{testUrl}}\nError: [$sce:isecrurl] Blocked " + + "loading resource from url not allowed by $sceDelegate policy. URL: " + + "javascript:alert(1);"); + })); + + it('should error on non-resource_url src attributes', inject(function($compile, $rootScope, $sce) { + element = $compile('<iframe ng-src="{{testUrl}}"></iframe>')($rootScope); + $rootScope.testUrl = $sce.trustAsUrl("javascript:doTrustedStuff()"); + expect($rootScope.$apply).toThrow( + "[$interpolate:interr] Can't interpolate: {{testUrl}}\nError: [$sce:isecrurl] Blocked " + + "loading resource from url not allowed by $sceDelegate policy. URL: " + + "javascript:doTrustedStuff()"); + })); + + it('should pass through $sce.trustAs() values in src attributes', inject(function($compile, $rootScope, $sce) { + element = $compile('<iframe ng-src="{{testUrl}}"></iframe>')($rootScope); + $rootScope.testUrl = $sce.trustAsResourceUrl("javascript:doTrustedStuff()"); + $rootScope.$apply(); + + expect(element.attr('src')).toEqual('javascript:doTrustedStuff()'); + })); + }); }); diff --git a/test/ng/interpolateSpec.js b/test/ng/interpolateSpec.js index 7569c0e2..d74b764a 100644 --- a/test/ng/interpolateSpec.js +++ b/test/ng/interpolateSpec.js @@ -67,6 +67,55 @@ describe('$interpolate', function() { })); + describe('interpolating in a trusted context', function() { + var sce; + beforeEach(function() { + function log() {}; + var fakeLog = {log: log, warn: log, info: log, error: log}; + module(function($provide, $sceProvider) { + $provide.value('$log', fakeLog); + $sceProvider.enabled(true); + }); + inject(['$sce', function($sce) { sce = $sce; }]); + }); + + it('should NOT interpolate non-trusted expressions', inject(function($interpolate) { + var foo = "foo"; + expect($interpolate('{{foo}}', true, sce.CSS)({}, {foo: foo})).toEqual(''); + })); + + it('should NOT interpolate mistyped expressions', inject(function($interpolate) { + var foo = sce.trustAsCss("foo"); + expect($interpolate('{{foo}}', true, sce.HTML)({}, {foo: foo})).toEqual(''); + })); + + it('should interpolate trusted expressions in a regular context', inject(function($interpolate) { + var foo = sce.trustAsCss("foo"); + expect($interpolate('{{foo}}', true)({foo: foo})).toEqual('foo'); + })); + + it('should interpolate trusted expressions in a specific trustedContext', inject(function($interpolate) { + var foo = sce.trustAsCss("foo"); + expect($interpolate('{{foo}}', true, sce.CSS)({foo: foo})).toEqual('foo'); + })); + + // 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.) + 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}); }).toThrow( + "[$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"); + })); + }); + + describe('provider', function() { beforeEach(module(function($interpolateProvider) { $interpolateProvider.startSymbol('--'); @@ -155,13 +204,15 @@ describe('$interpolate', function() { expect(function() { $interpolate('constant/{{var}}', true, isTrustedContext); }).toThrow( - "[$interpolate:noconcat] Error while interpolating: constant/{{var}}\nYou may not use " + - "multiple expressions when interpolating this expression."); + "[$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('{{foo}}{{bar}}', true, isTrustedContext); }).toThrow( - "[$interpolate:noconcat] Error while interpolating: {{foo}}{{bar}}\nYou may not use " + - "multiple expressions when interpolating this expression."); + "[$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) { diff --git a/test/ng/sceSpecs.js b/test/ng/sceSpecs.js new file mode 100644 index 00000000..16525b8d --- /dev/null +++ b/test/ng/sceSpecs.js @@ -0,0 +1,347 @@ +'use strict'; + +describe('SCE', function() { + + describe('when disabled', function() { + beforeEach(function() { + module(function($sceProvider) { + $sceProvider.enabled(false); + }); + }); + + it('should provide the getter for enabled', inject(function($sce) { + expect($sce.isEnabled()).toBe(false); + })); + + it('should not wrap/unwrap any value or throw exception on non-string values', inject(function($sce) { + var originalValue = { foo: "bar" }; + expect($sce.trustAs($sce.JS, originalValue)).toBe(originalValue); + expect($sce.getTrusted($sce.JS, originalValue)).toBe(originalValue); + })); + }); + + describe('IE8 quirks mode', function() { + function runTest(enabled, documentMode, expectException) { + module(function($provide) { + $provide.value('$document', [{ + documentMode: documentMode, + createElement: function() {} + }]); + $provide.value('$sceDelegate', {trustAs: null, valueOf: null, getTrusted: null}); + }); + + inject(function($window, $injector) { + function constructSce() { + var sceProvider = new $SceProvider(); + sceProvider.enabled(enabled); + return $injector.invoke(sceProvider.$get, sceProvider); + } + + var origMsie = $window.msie; + try { + $window.msie = true; + if (expectException) { + expect(constructSce).toThrow( + '[$sce:iequirks] Strict Contextual Escaping does not support Internet Explorer ' + + 'version < 9 in quirks mode. You can fix this by adding the text <!doctype html> to ' + + 'the top of your HTML document. See http://docs.angularjs.org/api/ng.$sce for more ' + + 'information.'); + } else { + // no exception. + constructSce(); + } + } + finally { + $window.msie = origMsie; + } + }); + } + + it('should throw an exception when sce is enabled in quirks mode', function() { + runTest(true, 7, true); + }); + + it('should NOT throw an exception when sce is enabled and in standards mode', function() { + runTest(true, 8, false); + }); + + it('should NOT throw an exception when sce is enabled and documentMode is undefined', function() { + runTest(true, undefined, false); + }); + + it('should NOT throw an exception when sce is disabled even when in quirks mode', function() { + runTest(false, 7, false); + }); + + it('should NOT throw an exception when sce is disabled and in standards mode', function() { + runTest(false, 8, false); + }); + + it('should NOT throw an exception when sce is disabled and documentMode is undefined', function() { + runTest(false, undefined, false); + }); + }); + + describe('when enabled', function() { + it('should wrap string values with TrustedValueHolder', inject(function($sce) { + var originalValue = 'original_value'; + var wrappedValue = $sce.trustAs($sce.HTML, originalValue); + expect(typeof wrappedValue).toBe('object'); + expect($sce.getTrusted($sce.HTML, wrappedValue)).toBe('original_value'); + expect(function() { $sce.getTrusted($sce.CSS, wrappedValue); }).toThrow( + '[$sce:unsafe] Attempting to use an unsafe value in a safe context.'); + wrappedValue = $sce.trustAs($sce.CSS, originalValue); + expect(typeof wrappedValue).toBe('object'); + expect($sce.getTrusted($sce.CSS, wrappedValue)).toBe('original_value'); + expect(function() { $sce.getTrusted($sce.HTML, wrappedValue); }).toThrow( + '[$sce:unsafe] Attempting to use an unsafe value in a safe context.'); + wrappedValue = $sce.trustAs($sce.URL, originalValue); + expect(typeof wrappedValue).toBe('object'); + expect($sce.getTrusted($sce.URL, wrappedValue)).toBe('original_value'); + wrappedValue = $sce.trustAs($sce.JS, originalValue); + expect(typeof wrappedValue).toBe('object'); + expect($sce.getTrusted($sce.JS, wrappedValue)).toBe('original_value'); + })); + + it('should NOT wrap non-string values', inject(function($sce) { + expect(function() { $sce.trustAsCss(123); }).toThrow( + '[$sce:itype] Attempted to trust a non-string value in a content requiring a string: ' + + 'Context: css'); + })); + + it('should NOT wrap unknown contexts', inject(function($sce) { + expect(function() { $sce.trustAs('unknown1' , '123'); }).toThrow( + '[$sce:icontext] Attempted to trust a value in invalid context. Context: unknown1; Value: 123'); + })); + + it('should NOT wrap undefined context', inject(function($sce) { + expect(function() { $sce.trustAs(undefined, '123'); }).toThrow( + '[$sce:icontext] Attempted to trust a value in invalid context. Context: undefined; Value: 123'); + })); + + it('should wrap undefined into undefined', inject(function($sce) { + expect($sce.trustAsHtml(undefined)).toBe(undefined); + })); + + it('should unwrap undefined into undefined', inject(function($sce) { + expect($sce.getTrusted($sce.HTML, undefined)).toBe(undefined); + })); + + it('should wrap null into null', inject(function($sce) { + expect($sce.trustAsHtml(null)).toBe(null); + })); + + it('should unwrap null into null', inject(function($sce) { + expect($sce.getTrusted($sce.HTML, null)).toBe(null); + })); + + it('should wrap "" into ""', inject(function($sce) { + expect($sce.trustAsHtml("")).toBe(""); + })); + + it('should unwrap null into null', inject(function($sce) { + expect($sce.getTrusted($sce.HTML, null)).toBe(null); + })); + + it('should unwrap "" into ""', inject(function($sce) { + expect($sce.getTrusted($sce.HTML, "")).toBe(""); + })); + + it('should unwrap values and return the original', inject(function($sce) { + var originalValue = "originalValue"; + var wrappedValue = $sce.trustAs($sce.HTML, originalValue); + expect($sce.getTrusted($sce.HTML, wrappedValue)).toBe(originalValue); + })); + + it('should NOT unwrap values when the type is different', inject(function($sce) { + var originalValue = "originalValue"; + var wrappedValue = $sce.trustAs($sce.HTML, originalValue); + expect(function () { $sce.getTrusted($sce.CSS, wrappedValue); }).toThrow( + '[$sce:unsafe] Attempting to use an unsafe value in a safe context.'); + })); + + it('should NOT unwrap values that had not been wrapped', inject(function($sce) { + function TrustedValueHolder(trustedValue) { + this.$unwrapTrustedValue = function() { + return trustedValue; + }; + } + var wrappedValue = new TrustedValueHolder("originalValue"); + expect(function() { return $sce.getTrusted($sce.HTML, wrappedValue) }).toThrow( + '[$sce:unsafe] Attempting to use an unsafe value in a safe context.'); + })); + + it('should implement toString on trusted values', inject(function($sce) { + var originalValue = '123', + wrappedValue = $sce.trustAsHtml(originalValue); + expect($sce.getTrustedHtml(wrappedValue)).toBe(originalValue); + expect(wrappedValue.toString()).toBe(originalValue.toString()); + })); + }); + + + describe('replace $sceDelegate', function() { + it('should override the default $sce.trustAs/valueOf/etc.', function() { + module(function($provide) { + $provide.value('$sceDelegate', { + trustAs: function(type, value) { return "wrapped:" + value; }, + getTrusted: function(type, value) { return "unwrapped:" + value; }, + valueOf: function(value) { return "valueOf:" + value; } + }); + }); + + inject(function($sce) { + expect($sce.trustAsJs("value")).toBe("wrapped:value"); + expect($sce.valueOf("value")).toBe("valueOf:value"); + expect($sce.getTrustedJs("value")).toBe("unwrapped:value"); + expect($sce.parseAsJs("name")({name: "chirayu"})).toBe("unwrapped:chirayu"); + }); + }); + }); + + + describe('$sce.parseAs', function($sce) { + it('should parse constant literals as trusted', inject(function($sce) { + expect($sce.parseAsJs('1')()).toBe(1); + expect($sce.parseAsJs('1', $sce.ANY)()).toBe(1); + expect($sce.parseAsJs('1', $sce.HTML)()).toBe(1); + expect($sce.parseAsJs('1', 'UNDEFINED')()).toBe(1); + expect($sce.parseAsJs('true')()).toBe(true); + expect($sce.parseAsJs('false')()).toBe(false); + expect($sce.parseAsJs('null')()).toBe(null); + expect($sce.parseAsJs('undefined')()).toBe(undefined); + expect($sce.parseAsJs('"string"')()).toBe("string"); + })); + + 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. + var exprFn = $sce.parseAsJs('1+1'); + expect(exprFn).toThrow(); + })); + + it('should NOT return untrusted values from expression function', inject(function($sce) { + var exprFn = $sce.parseAs($sce.HTML, 'foo'); + expect(function() { + return exprFn({}, {'foo': true}) + }).toThrow( + '[$sce:unsafe] Attempting to use an unsafe value in a safe context.'); + })); + + it('should NOT return trusted values of the wrong type from expression function', inject(function($sce) { + var exprFn = $sce.parseAs($sce.HTML, 'foo'); + expect(function() { + return exprFn({}, {'foo': $sce.trustAs($sce.JS, '123')}) + }).toThrow( + '[$sce:unsafe] Attempting to use an unsafe value in a safe context.'); + })); + + it('should return trusted values from expression function', inject(function($sce) { + var exprFn = $sce.parseAs($sce.HTML, 'foo'); + expect(exprFn({}, {'foo': $sce.trustAs($sce.HTML, 'trustedValue')})).toBe('trustedValue'); + })); + + it('should support shorthand methods', inject(function($sce) { + // Test shorthand parse methods. + expect($sce.parseAsHtml('1')()).toBe(1); + // Test short trustAs methods. + expect($sce.trustAsAny).toBeUndefined(); + expect(function() { + // mismatched types. + $sce.parseAsCss('foo')({}, {'foo': $sce.trustAsHtml('1')}); + }).toThrow( + '[$sce:unsafe] Attempting to use an unsafe value in a safe context.'); + })); + + }); + + describe('$sceDelegate resource url policies', function() { + function runTest(cfg, testFn) { + return function() { + module(function($sceDelegateProvider) { + if (cfg.whiteList !== undefined) { + $sceDelegateProvider.resourceUrlWhitelist(cfg.whiteList); + } + if (cfg.blackList !== undefined) { + $sceDelegateProvider.resourceUrlBlacklist(cfg.blackList); + } + }); + inject(testFn); + } + } + + it('should default to "self" which allows relative urls', runTest({}, function($sce, $document) { + expect($sce.getTrustedResourceUrl('foo/bar')).toEqual('foo/bar'); + })); + + it('should reject everything when whitelist is empty', runTest( + { + whiteList: [], + blackList: [] + }, function($sce) { + expect(function() { $sce.getTrustedResourceUrl('#'); }).toThrow( + '[$sce:isecrurl] Blocked loading resource from url not allowed by $sceDelegate policy. URL: #'); + })); + + it('should match against normalized urls', runTest( + { + whiteList: [/^foo$/], + blackList: [] + }, function($sce) { + expect(function() { $sce.getTrustedResourceUrl('foo'); }).toThrow( + '[$sce:isecrurl] Blocked loading resource from url not allowed by $sceDelegate policy. URL: foo'); + })); + + it('should support custom regex', runTest( + { + whiteList: [/^http:\/\/example\.com.*/], + blackList: [] + }, function($sce) { + expect($sce.getTrustedResourceUrl('http://example.com/foo')).toEqual('http://example.com/foo'); + expect(function() { $sce.getTrustedResourceUrl('https://example.com/foo'); }).toThrow( + '[$sce:isecrurl] Blocked loading resource from url not allowed by $sceDelegate policy. URL: https://example.com/foo'); + })); + + it('should support the special string "self" in whitelist', runTest( + { + whiteList: ['self'], + blackList: [] + }, function($sce) { + expect($sce.getTrustedResourceUrl('foo')).toEqual('foo'); + })); + + it('should support the special string "self" in blacklist', runTest( + { + whiteList: [/.*/], + blackList: ['self'] + }, function($sce) { + expect(function() { $sce.getTrustedResourceUrl('foo'); }).toThrow( + '[$sce:isecrurl] Blocked loading resource from url not allowed by $sceDelegate policy. URL: foo'); + })); + + it('should have blacklist override the whitelist', runTest( + { + whiteList: ['self'], + blackList: ['self'] + }, function($sce) { + expect(function() { $sce.getTrustedResourceUrl('foo'); }).toThrow( + '[$sce:isecrurl] Blocked loading resource from url not allowed by $sceDelegate policy. URL: foo'); + })); + + it('should support multiple items in both lists', runTest( + { + whiteList: [/^http:\/\/example.com\/1$/, /^http:\/\/example.com\/2$/, /^http:\/\/example.com\/3$/, 'self'], + blackList: [/^http:\/\/example.com\/3$/, /open_redirect/], + }, function($sce) { + expect($sce.getTrustedResourceUrl('same_domain')).toEqual('same_domain'); + expect($sce.getTrustedResourceUrl('http://example.com/1')).toEqual('http://example.com/1'); + expect($sce.getTrustedResourceUrl('http://example.com/2')).toEqual('http://example.com/2'); + expect(function() { $sce.getTrustedResourceUrl('http://example.com/3'); }).toThrow( + '[$sce:isecrurl] Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example.com/3'); + expect(function() { $sce.getTrustedResourceUrl('open_redirect'); }).toThrow( + '[$sce:isecrurl] Blocked loading resource from url not allowed by $sceDelegate policy. URL: open_redirect'); + })); + + }); +}); + diff --git a/test/ng/urlUtilsSpec.js b/test/ng/urlUtilsSpec.js index 57043a5a..3c9bf847 100644 --- a/test/ng/urlUtilsSpec.js +++ b/test/ng/urlUtilsSpec.js @@ -11,21 +11,27 @@ describe('$$urlUtils', function() { expect(parsed.href).toMatch(/https?:\/\//); expect(parsed.protocol).toMatch(/^https?:/); expect(parsed.host).not.toBe(""); + expect(parsed.hostname).not.toBe(""); + expect(parsed.pathname).not.toBe(""); })); }); describe('isSameOrigin', function() { - it('should support various combinations of urls', inject(function($$urlUtils, $document) { - expect($$urlUtils.isSameOrigin('path')).toBe(true); + it('should support various combinations of urls - both string and parsed', inject(function($$urlUtils, $document) { + function expectIsSameOrigin(url, expectedValue) { + expect($$urlUtils.isSameOrigin(url)).toBe(expectedValue); + expect($$urlUtils.isSameOrigin($$urlUtils.resolve(url, true))).toBe(expectedValue); + } + expectIsSameOrigin('path', true); var origin = $$urlUtils.resolve($document[0].location.href, true); - expect($$urlUtils.isSameOrigin('//' + origin.host + '/path')).toBe(true); + expectIsSameOrigin('//' + origin.host + '/path', true); // Different domain. - expect($$urlUtils.isSameOrigin('http://example.com/path')).toBe(false); + expectIsSameOrigin('http://example.com/path', false); // Auto fill protocol. - expect($$urlUtils.isSameOrigin('//example.com/path')).toBe(false); + expectIsSameOrigin('//example.com/path', false); // Should not match when the ports are different. // This assumes that the test is *not* running on port 22 (very unlikely). - expect($$urlUtils.isSameOrigin('//' + origin.hostname + ':22/path')).toBe(false); + expectIsSameOrigin('//' + origin.hostname + ':22/path', false); })); }); }); diff --git a/test/ngRoute/routeSpec.js b/test/ngRoute/routeSpec.js index 300ca2d7..13d149a1 100644 --- a/test/ngRoute/routeSpec.js +++ b/test/ngRoute/routeSpec.js @@ -13,6 +13,7 @@ describe('$route', function() { $httpBackend.when('GET', 'foo.html').respond('foo'); $httpBackend.when('GET', 'baz.html').respond('baz'); $httpBackend.when('GET', 'bar.html').respond('bar'); + $httpBackend.when('GET', 'http://example.com/trusted-template.html').respond('cross domain trusted template'); $httpBackend.when('GET', '404.html').respond('not found'); }; })); @@ -510,6 +511,33 @@ describe('$route', function() { }); }); + it('should NOT load cross domain templates by default', function() { + module(function($routeProvider) { + $routeProvider.when('/foo', { templateUrl: 'http://example.com/foo.html' }); + }); + + inject(function ($route, $location, $rootScope) { + $location.path('/foo'); + expect(function() { + $rootScope.$digest(); + }).toThrow('[$sce:isecrurl] Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example.com/foo.html'); + }); + }); + + it('should load cross domain templates that are trusted', function() { + module(function($routeProvider, $sceDelegateProvider) { + $routeProvider.when('/foo', { templateUrl: 'http://example.com/foo.html' }); + $sceDelegateProvider.resourceUrlWhitelist([/^http:\/\/example\.com\/foo\.html$/]); + }); + + inject(function ($route, $location, $rootScope) { + $httpBackend.whenGET('http://example.com/foo.html').respond('FOO BODY'); + $location.path('/foo'); + $rootScope.$digest(); + $httpBackend.flush(); + expect($route.current.locals.$template).toEqual('FOO BODY'); + }); + }); it('should not update $routeParams until $routeChangeSuccess', function() { module(function($routeProvider) { @@ -904,7 +932,7 @@ describe('$route', function() { return '<h1>' + routePathParams.id + '</h1>'; } - module(function($routeProvider){ + module(function($routeProvider) { $routeProvider.when('/bar/:id/:subid/:subsubid', {templateUrl: 'bar.html'}); $routeProvider.when('/foo/:id', {template: customTemplateFn}); }); diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js index 5fae2817..d97d88a1 100644 --- a/test/testabilityPatch.js +++ b/test/testabilityPatch.js @@ -107,7 +107,12 @@ function dealoc(obj) { function cleanup(element) { element.off().removeData(); - for ( var i = 0, children = element.contents() || []; i < children.length; i++) { + // Note: We aren't using element.contents() here. Under jQuery, element.contents() can fail + // for IFRAME elements. jQuery explicitly uses (element.contentDocument || + // element.contentWindow.document) and both properties are null for IFRAMES that aren't attached + // to a document. + var children = element[0].childNodes || []; + for ( var i = 0; i < children.length; i++) { cleanup(angular.element(children[i])); } } |
