angular源码分析:angular中入境检察官$sce

Advertisement

一、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

Similar Posts:

  • [android源码分析]hci_init_req中的各种command和event的交互

    在蓝牙中,host和controller之间的command和event的交互是底层各种工作开展的基础,在初始化的过程中必然也存在着类似的操作.本章会详细分析在hci_init_req过程中所涉及到的所有command和event的交互.至于command和event的格式意义,请参见bluetooth的core spec,这里不做详细介绍,若想详细了解,spec的研读是必不可少的. static void hci_init_req(struct hci_dev *hdev, unsigned

  • [mina源码分析]mina中的reactor模式(一)

    吴峥涛,架构师,研发中心 mina中的aceptor模式实现参考了Doug Lea 在<Scalable IO in Java>中的reactor. 从上面来两个图可以看出:与传统的单个Reactor模式实现不同,mina中采用了Multiple Reactor的方式,由NioSocketAcceptor和IoProcessor分别承担多个Reactor的职责.NioSocketAcceptor和NioProcessor使用不同selector,能够更加充分的榨取服务器的性能. acctpto

  • Live555源码分析RTSPServer中的用户认证

    http://blog.csdn.NET/njzhujinhua @20140601 说到鉴权,这是我多年来工作中的一部分,但这里rtsp中的认证简单多了,只是最基本的digest鉴权的策略. 在Live555的实现中, 用户信息由如下类维护,其提供增删查的接口.realm默认值为"LIVE555 Streaming Media" [cpp] view plain copy class UserAuthenticationDatabase {   public:     UserAut

  • Spark技术内幕:Stage划分及提交源码分析

    http://blog.csdn.net/anzhsoft/article/details/39859463 当触发一个RDD的action后,以count为例,调用关系如下: org.apache.spark.rdd.RDD#count org.apache.spark.SparkContext#runJob org.apache.spark.scheduler.DAGScheduler#runJob org.apache.spark.scheduler.DAGScheduler#submit

  • Spark源码分析 &ndash; Dependency

    Dependency 依赖, 用于表示RDD之间的因果关系, 一个dependency表示一个parent rdd, 所以在RDD中使用Seq[Dependency[_]]来表示所有的依赖关系 Dependency的base class 可见Dependency唯一的成员就是rdd, 即所依赖的rdd, 或parent rdd /** * Base class for dependencies. */ abstract class Dependency[T](val rdd: RDD[T]) e

  • Collection架构源码分析(基于1.8)

    Collection接口有三个子接口,我们主要来分析一下其中的两种:List和Set List:有序集合,其中元素可以重复. Set:无序集合,元素不可以重复. List和Set两个接口都各自的抽象实现类. Collection源码分析 源码中的API: public interface Collection<E> extends Iterable<E> { int size(): boolean isEmpty()(); boolean contains(Object o); I

  • LinkedHashMap源码分析

    之前文章<HashMap源码分析>中我们分析了HashMap的源码,本篇我们来分析LinkedHashMap的源码.同样进一步阅读之前强烈建议先浏览一下之前文章<<Java Generics and Collections>笔记-Lists/Maps>中关于Maps的部分,并熟悉<HashMap源码分析>中关于HashMap的介绍. 我们知道HashMap与LinkedHashMap的最大区别就是后者可以记录(访问或者插入)顺序.本篇我们重点分析它是如何实现

  • Flume源码分析—数据流转框架分析(五)

    Flume-NG中主要由source.channel及sink三个组件完成目标数据的收集.传递及整理过程,本文主要通过其源码来分析flume是如何将这些组件有机的整合在一起,完成数据的流转过程,从而为我们开发相似的框架提供借鉴.源码的版本还是使用的apache-flume-1.6.0-src . 一.组件参数准备与启动 1.首先在根据配置文件启动Flume时,在org.apache.flume.node中的application.java文件中的main函数,会读取配置文件的相关信息,如sour

  • angular源码学习第一篇 setupModuleLoader方法

    angular源码其实结构非常清晰,划分的有条有理的,大概就是这样子: (function(window,document,jquery,undefined){ //一些工具函数 //EXPR 编译器 自执行 //setupModuleLoader方法,公司内部的框架是vxsetup方法,(只是定义,没有调用) //moduler方法() //angular初始化方法,公司内部的框架是vxinit方法 //bootstrap //createInjector //一系列指令,服务,过滤器等指令

  • 探索angular源码--启动(1)

    前言 angular2.0已经出来了,本来应该是研究最新的angular源码,但毕竟用了angular1这么久了,一直对其源码实现十分好奇,再加上研究源码主要目的是学习,版本不是特别重要,因此就1.3版源码进行研究,在以后的研究中,主要会针对angular的内置指令的实现和一些常用的方法进行探究. 初始化–bindJQuery 既然要研究源码,那肯定应该从启动开始看,打开angular.js,好家伙,2w多行代码,跟一般的框架一样,angular首先就定义了一个自执行函数,从头扫下来,都是一些局

Tags: