zoukankan      html  css  js  c++  java
  • angular源码分析:angular中入境检察官$sce

    一、ng-bing-html指令问题

    需求:我需要将一个变量$scope.x = '<a href="http://www.cnblogs.com/web2-developer/">王大鹏</a>'绑定到angular的视图上,希望视图上显示的一个链接.

    1.如果,我采用ng-bind="x",或者{{x}},我在视图看到的结果就是上面那个字符串,就说里面的<和>都被转义了.
    2.如果,我在用ng-bind-html,视图上什么都没有,并且会抛出一个错误:"Attempting to use an unsafe value in a safe context."
    问题来了,该怎么解决呢?

    二、SCE

    针对上面的问题,官方文档给出了解决方法:方法1,引入ngSanitize模块,方法而利用$sce.trustAsHtml将要绑定的值变成一个可信任的值。

    那么,问题来了:$sce到底是什么鬼?

    SCE是Strict Contextual Escaping的缩写,不知道怎么翻译,从$sce干的事情来看就是将语境中存在的跨站攻击的风险给干掉.SCE是一种模式,用于满足angular在某些情况下需要绑定一个上下文被标记为安全上下文的值.其中一个例子就是"ng-bind-html"这个指令,要绑定任意的html,我们参考上下文特权和SCE的上下文.(原文,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. We refer to these contexts as privileged or SCE contexts.)

    $sce提供了一种将可能存在跨站风险的内容(包括html,url,css,js,resourceUrl)标记为被信任的内容。这是为什么呢?因为,在angular中,默认的这些内容是不被信任,所以,在绑定数据的时候,这些内容会被认为不安全。但是如果我们的确有这样的需求,就需要用$sce来做标记处理。

    三、$sce如何使用

    1.$sce提供的方法:

    $sce.getTrustedXXX,获取被信任的数据值。其中的XXX代表Hmtl,Css,Js,Url,ResourceUrl。
    $sce.trustXXX,让绑定内容,成为受信任的XXX

    2.将$sce用于指令编写

    在指令值,一般需要操作dom,在添加元素时,如果要将传入的变量直接作为dom元素进行添加,就会可能会带来跨站风险,这时就需要,用$sce.getTrustedXXX从变量中获取受信任的数据。

    3.在controller中使用。

    使用$sce.trustXXX来将确实需要被信任的数据标记为信任数据。
    请慎用!

    四、$sce的代码实现

    1.$sce是依赖于$sceDelegate,$sce的实现就是调用$sceDelegate来完成,$sceDelegate是$sce的代理者,这里的设计采用了代理模式,所以我们可以通过修改$sceDelegate来完成对$sce的功能增强。

        sce.trustAs = $sceDelegate.trustAs;
        sce.getTrusted = $sceDelegate.getTrusted;
        sce.valueOf = $sceDelegate.valueOf;
    

    2.$sce的基础方法就只有trustAs,getTrusted,valueOf,其他的方法都是这三个方法的"快捷方式"

        // Shorthand delegations.
        var parse = sce.parseAs,
            getTrusted = sce.getTrusted,
            trustAs = sce.trustAs;
    
        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);
          };
        });
    

    3.$sce.parse

        sce.parseAs = function sceParseAs(type, expr) {
          var parsed = $parse(expr);
          if (parsed.literal && parsed.constant) {
            return parsed;
          } else {
            return $parse(expr, function(value) {
              return sce.getTrusted(type, value);
            });
          }
        };
    

    五、$sceDelegate的实现

    $sceDelegate实现三个函数:trustAs, getTrusted , valueOf,如果想实现一些自定义的安全策略,可以修改$sceDelegate或对这三个方法进行重载。

    1.资源地址的白名单和黑名单

    在资源的处理上,$sceDelegate引入了白黑名单机制,可以允许用户编写不同的安全策略来控制不同域名的不同权限。

    function adjustMatcher(matcher) {
      if (matcher === 'self') {
        return matcher;
      } else if (isString(matcher)) {
        // Strings match exactly except for 2 wildcards - '*' and '**'.
        // '*' matches any character except those from the set ':/.?&'.
        // '**' matches any character (like .* in a RegExp).
        // More than 2 *'s raises an error as it's ill defined.
        if (matcher.indexOf('***') > -1) {
          throw $sceMinErr('iwcard',
              'Illegal sequence *** in string matcher.  String: {0}', matcher);
        }
        matcher = escapeForRegexp(matcher).
                      replace('\*\*', '.*').//两个*号,将匹配任意打印字符
                      replace('\*', '[^:/.?&;]*');//一个*,只能匹配url中的分隔符间的内容
        return new RegExp('^' + matcher + '$');
      } else if (isRegExp(matcher)) {
        // The only other type of matcher allowed is a Regexp.
        // Match entire URL / disallow partial matches.
        // Flags are reset (i.e. no global, ignoreCase or multiline)
        return new RegExp('^' + matcher.source + '$');//转正则式
      } else {
        throw $sceMinErr('imatcher',
            'Matchers may only be "self", string patterns or RegExp objects');
      }
    }
    
    
    function adjustMatchers(matchers) {//工具函数,将配置转换成正则表达式数组
      var adjustedMatchers = [];
      if (isDefined(matchers)) {
        forEach(matchers, function(matcher) {
          adjustedMatchers.push(adjustMatcher(matcher));//调用上面的工具函数,将使用通配符方式的配置转成正则表达式
        });
      }
      return adjustedMatchers;
    }
    
      this.resourceUrlWhitelist = function(value) {//提供$sceDelegate.resourceUrlWhitelist 配置白名单
        if (arguments.length) {
          resourceUrlWhitelist = adjustMatchers(value);//调用上面的工具函数
        }
        return resourceUrlWhitelist;
      };
    
      this.resourceUrlBlacklist = function(value) {//提供$sceDelegate.resourceUrlBlacklist 配置黑名单
        if (arguments.length) {
          resourceUrlBlacklist = adjustMatchers(value);//调用上面的工具函数
        }
        return resourceUrlBlacklist;
      };
    
    
        function matchUrl(matcher, parsedUrl) {//url匹配函数
          if (matcher === 'self') {
            return urlIsSameOrigin(parsedUrl);
          } else {
            // definitely a regex.  See adjustMatchers()
            return !!matcher.exec(parsedUrl.href);//双!限制,返回的只能是bool值
          }
        }
    
    function isResourceUrlAllowedByPolicy(url) {//执行白黑名单策略:只允许在白名单中且不再黑名单中的内容
          var parsedUrl = urlResolve(url.toString());
          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;
        }
    

    2.下面byType将是什么?

        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(), //这里trustedValueHolderBase 将是构造函数TrustedValueHolderType,且没有绑定原型
            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]);
    
    

    上面的代码执行后的结果是:

    3.trustAs,valueOf和getTrusted

        var htmlSanitizer = function htmlSanitizer(html) {
          throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
        };
    
        if ($injector.has('$sanitize')) {//这里检查是否有$sanitize
          htmlSanitizer = $injector.get('$sanitize');
        }
    
        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 || isUndefined(trustedValue) || 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);//将一个值标记为可信,就是用相应的构造函数进行包装
        }
    
        
        function valueOf(maybeTrusted) {
          if (maybeTrusted instanceof trustedValueHolderBase) {
            return maybeTrusted.$$unwrapTrustedValue();
          } else {
            return maybeTrusted;
          }
        }
    
        function getTrusted(type, maybeTrusted) {
          if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') {
            return maybeTrusted;
          }
          var constructor = (byType.hasOwnProperty(type) ? byType[type] : null);
          if (constructor && maybeTrusted instanceof constructor) {
            return maybeTrusted.$$unwrapTrustedValue();
          }
          // If we get here, then we may only take one of two actions.
          // 1. sanitize the value for the requested type, or
          // 2. throw an exception.
          if (type === SCE_CONTEXTS.RESOURCE_URL) {
            if (isResourceUrlAllowedByPolicy(maybeTrusted)) {
              return maybeTrusted;
            } else {
              throw $sceMinErr('insecurl',
                  'Blocked loading resource from url not allowed by $sceDelegate policy.  URL: {0}',
                  maybeTrusted.toString());
            }
          } else if (type === SCE_CONTEXTS.HTML) {
            return htmlSanitizer(maybeTrusted);//如果htmlSanitizer = $injector.get('$sanitize');,这里就调用了$sanitize
          }
          throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.');
        }
    
        return { trustAs: trustAs,
                 getTrusted: getTrusted,
                 valueOf: valueOf };
      }];
    
    

    上一期:angular源码分析:angular中脏活累活承担者之$parse
    下期预告:angular源码分析:angular中脏活累活的承担者之$interpolate

  • 相关阅读:
    poj2263
    poj2304
    低调是态度,也是智慧
    股票操作記錄2
    治病記錄(2013年)
    过年了
    治病記錄
    近段時間學習記錄
    新的一年
    關于設計
  • 原文地址:https://www.cnblogs.com/web2-developer/p/angular-11.html
Copyright © 2011-2022 走看看