diff options
Diffstat (limited to 'src/ng')
| -rw-r--r-- | src/ng/compile.js | 12 | ||||
| -rw-r--r-- | src/ng/directive/ngBind.js | 4 | ||||
| -rw-r--r-- | src/ng/directive/ngInclude.js | 23 | ||||
| -rw-r--r-- | src/ng/interpolate.js | 27 | ||||
| -rw-r--r-- | src/ng/sce.js | 959 | ||||
| -rw-r--r-- | src/ng/urlUtils.js | 5 | 
6 files changed, 1003 insertions, 27 deletions
| 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);        } | 
