diff options
Diffstat (limited to 'src/ng')
| -rw-r--r-- | src/ng/compile.js | 60 | ||||
| -rw-r--r-- | src/ng/directive/booleanAttrs.js | 5 | 
2 files changed, 56 insertions, 9 deletions
diff --git a/src/ng/compile.js b/src/ng/compile.js index 788231ab..11a86f3f 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -155,7 +155,8 @@ function $CompileProvider($provide) {        Suffix = 'Directive',        COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,        CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/, -      MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: '; +      MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ', +      urlSanitizationWhitelist = /^\s*(https?|ftp|mailto):/;    /** @@ -209,11 +210,41 @@ function $CompileProvider($provide) {    }; +  /** +   * @ngdoc function +   * @name ng.$compileProvider#urlSanitizationWhitelist +   * @methodOf ng.$compileProvider +   * @function +   * +   * @description +   * Retrieves or overrides the default regular expression that is used for whitelisting of safe +   * urls during a[href] sanitization. +   * +   * The sanitization is a security measure aimed at prevent XSS attacks via html links. +   * +   * Any url about to be assigned to a[href] via data-binding is first normalized and turned into an +   * absolute url. Afterwards the url is matched against the `urlSanitizationWhitelist` regular +   * expression. If a match is found the original url is written into the dom. Otherwise the +   * absolute url is prefixed with `'unsafe:'` string and only then it is written into the DOM. +   * +   * @param {RegExp=} regexp New regexp to whitelist urls with. +   * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for +   *    chaining otherwise. +   */ +  this.urlSanitizationWhitelist = function(regexp) { +    if (isDefined(regexp)) { +      urlSanitizationWhitelist = regexp; +      return this; +    } +    return urlSanitizationWhitelist; +  }; + +    this.$get = [              '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', -            '$controller', '$rootScope', +            '$controller', '$rootScope', '$document',      function($injector,   $interpolate,   $exceptionHandler,   $http,   $templateCache,   $parse, -             $controller,   $rootScope) { +             $controller,   $rootScope,   $document) {      var Attributes = function(element, attr) {        this.$$element = element; @@ -235,7 +266,8 @@ function $CompileProvider($provide) {         */        $set: function(key, value, writeAttr, attrName) {          var booleanKey = getBooleanAttrName(this.$$element[0], key), -            $$observers = this.$$observers; +            $$observers = this.$$observers, +            normalizedVal;          if (booleanKey) {            this.$$element.prop(key, value); @@ -254,6 +286,19 @@ function $CompileProvider($provide) {            }          } + +        // sanitize a[href] values +        if (nodeName_(this.$$element[0]) === 'A' && key === 'href') { +          urlSanitizationNode.setAttribute('href', value); + +          // href property always returns normalized absolute url, so we can match against that +          normalizedVal = urlSanitizationNode.href; +          if (!normalizedVal.match(urlSanitizationWhitelist)) { +            this[key] = value = 'unsafe:' + normalizedVal; +          } +        } + +          if (writeAttr !== false) {            if (value === null || value === undefined) {              this.$$element.removeAttr(attrName); @@ -297,7 +342,8 @@ function $CompileProvider($provide) {        }      }; -    var startSymbol = $interpolate.startSymbol(), +    var urlSanitizationNode = $document[0].createElement('a'), +        startSymbol = $interpolate.startSymbol(),          endSymbol = $interpolate.endSymbol(),          denormalizeTemplate = (startSymbol == '{{' || endSymbol  == '}}')              ? identity @@ -748,7 +794,7 @@ function $CompileProvider($provide) {                  }                  break;                } -               +                case '=': {                  parentGet = $parse(attrs[attrName]);                  parentSet = parentGet.assign || function() { @@ -1029,10 +1075,10 @@ function $CompileProvider($provide) {      function addAttrInterpolateDirective(node, directives, value, name) {        var interpolateFn = $interpolate(value, true); -        // no interpolation found -> ignore        if (!interpolateFn) return; +        directives.push({          priority: 100,          compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) { diff --git a/src/ng/directive/booleanAttrs.js b/src/ng/directive/booleanAttrs.js index 739c539a..7e0e3a42 100644 --- a/src/ng/directive/booleanAttrs.js +++ b/src/ng/directive/booleanAttrs.js @@ -340,8 +340,9 @@ forEach(['src', 'href'], function(attrName) {            // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist            // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need -          // to set the property as well to achieve the desired effect -          if (msie) element.prop(attrName, value); +          // to set the property as well to achieve the desired effect. +          // we use attr[attrName] value since $set can sanitize the url. +          if (msie) element.prop(attrName, attr[attrName]);          });        }      };  | 
