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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM