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