aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xangularFiles.js9
-rw-r--r--docs/content/error/sce/icontext.ngdoc6
-rw-r--r--docs/content/error/sce/iequirks.ngdoc16
-rw-r--r--docs/content/error/sce/isecrurl.ngdoc30
-rw-r--r--docs/content/error/sce/itype.ngdoc6
-rw-r--r--docs/content/error/sce/unsafe.ngdoc15
-rw-r--r--docs/content/guide/directive.ngdoc7
-rw-r--r--docs/src/example.js3
-rw-r--r--docs/src/templates/js/docs.js3
-rwxr-xr-xsrc/AngularPublic.js2
-rw-r--r--src/ng/compile.js12
-rw-r--r--src/ng/directive/ngBind.js4
-rw-r--r--src/ng/directive/ngInclude.js23
-rw-r--r--src/ng/interpolate.js27
-rw-r--r--src/ng/sce.js959
-rw-r--r--src/ng/urlUtils.js5
-rw-r--r--src/ngRoute/route.js25
-rw-r--r--src/ngSanitize/sanitize.js14
-rwxr-xr-xtest/ng/compileSpec.js90
-rw-r--r--test/ng/directive/booleanAttrsSpec.js104
-rw-r--r--test/ng/directive/ngBindSpec.js48
-rw-r--r--test/ng/directive/ngIncludeSpec.js50
-rw-r--r--test/ng/directive/ngSrcSpec.js44
-rw-r--r--test/ng/interpolateSpec.js59
-rw-r--r--test/ng/sceSpecs.js347
-rw-r--r--test/ng/urlUtilsSpec.js18
-rw-r--r--test/ngRoute/routeSpec.js30
-rw-r--r--test/testabilityPatch.js7
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>&lt;div ng-bind-html-unsafe="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
- <td><div ng-bind-html-unsafe="snippet"></div></td>
+ <td><pre>&lt;div ng-bind-html-unsafe="getSceSnippet()"&gt;<br/>&lt;/div&gt;</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 &lt;b&gt;text&lt;/b&gt;");
- 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]));
}
}