AngularJS TIPS
ビューにHTML文書をバインドするには?(ng-bind-html)
文字列をデータバインドした際に、標準で実施されるサニタイズ処理について紹介。また、サニタイズせずにHTMLのままのビューに反映させる方法も説明する。
AngularJSでは、{{...}}
(=エクスプレッション)によるテキストの埋め込みにも、セキュリティ上の考慮がなされています。例えば以下のようなコードを見てみましょう。以下は、タグ(HTML)を含んだ文字列をビューにバインドする例です。
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="UTF-8" />
<title>AngularJS TIPS</title>
</head>
<body>
<form ng-controller="myController">
<div ng-bind="msg"></div>
</form>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js"></script>
<script>
angular.module('myApp', [])
.controller('myController', ['$scope', function($scope) {
$scope.msg = '<script>window.alert("危険!");<'
+ '/script><p>こんにちは、世界</p>'
+ '<a href="#" onclick="alert(xxx)">危険なリンク</a>'
+ '<button>おっす</button>'
+ '<font color="Red">こんにちは、世界</font>';
}]);
</script>
</body>
</html>
|
HTML文字列はきちんとエスケープ処理されて、単なる文字列として表示されるわけです。これはもちろん正しい挙動なのですが、時として、動的に生成された文字列をHTMLとして反映させたい、ということもあるでしょう。
そのような場合には、ng-bind-html
属性を利用して、文字列をバインドします。
ng-bind-html属性によるデータバインディング
以下は、上記のHTMLソースをng-bind-html
属性を使うように書き換えたものと、そのページの表示結果です。
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="UTF-8" />
<title>AngularJS TIPS</title>
</head>
<body>
<form ng-controller="myController">
<!--1HTML文書をバインド-->
<div ng-bind-html="msg"></div>
</form>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min.js"></script>
<!--2ngSanitizeモジュールをインポート-->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular-sanitize.min.js"></script>
<script>
// 3myAppモジュールをngSanizeモジュールに依存
angular.module('myApp', [ 'ngSanitize' ])
.controller('myController', ['$scope', function($scope) {
$scope.msg = '<script>window.alert("危険!");<'
+ '/script><p>こんにちは、世界</p>'
+ '<a href="#" onclick="alert(xxx)">危険なリンク</a>'
+ '<button>おっす</button>'
+ '<font color="Red">こんにちは、世界</font>';
}]);
</script>
</body>
</html>
|
ng-bind-html
属性そのものの利用方法は、別稿「モデルをビューにバインドするには?」でも触れたng-model
属性と同様、属性値としてバインドすべきモデル(=スコープオブジェクトのプロパティ)を指定するだけです(1)。
ただし、ng-bind-html
属性を利用する場合には、あらかじめngSanitize
モジュールをインポートし(2)、モジュールからも依存関係を設定しておく必要があります(3)。ngSanitize
モジュール*1は、特定のタグ/属性を除去し、無害化(sanitize)するための機能を提供します。この例であれば、HTML文字列に含まれる<script>
要素やアンカー(=<a>
)タグのonclick
属性、<button>
要素などが除去されていることが確認できます。
- *1 AngularJSのモジュールについては、別稿「TIPS: AngularJSでモジュールを定義するには?」も参照してください。
AngularJSでは、このようにHTML文字列をバインドする際にも、最低限、危険と思われる要素/属性を排除することで、意図せぬセキュリティホールの発生を防いでいるわけです。
ngSanitize
モジュールへの依存関係を設定せずにng-bind-html
属性を付与した場合、以下のようなエラーが発生しますので注意してください。
補足:文字列が安全であることを保証する
もっとも、すでに別のライブラリで処理済みである、または、内容を把握しており「安全な」HTMLであることが明らかな場合、<button>
/<script>
などの要素を勝手に除去されてしまうのは望ましくない場合があります。
そのような場合には、文字列にあらかじめ信頼済みマークを付与しておくことで、ngSanitize
モジュールによるサニタイズを回避できます。これには、AngularJS標準で提供されているサービスである$sce
*2のtrustAsHtml
メソッドを利用します。
- *2 SCE(Strict Contextual Escaping)とは、文脈に応じて安全でない文字列、もしくは信頼されていないドメインからのデータを制限するための仕組みです。
例えば先ほどの例であれば、コントローラーの中身を以下のように書き換えます。
……省略……
.controller('myController', ['$scope', '$sce', function($scope, $sce) {
var msg = '<script>console.log("危険!");<'
+ '/script><p>こんにちは、世界</p>'
+ '<a href="#" onclick="alert(\'こんにちは!\')">危険なリンク</a>'
+ '<button>おっす</button>'
+ '<font color="Red">こんにちは、世界</font>';
$scope.msg = $sce.trustAsHtml(msg);
}]);
|
これによって、変数msg
の内容は信頼済み(trust)であることがマークされましたので、ng-bind-html
属性は変数msg
の内容をそのままページに反映させます。ただし、trustAsHtml
メソッドは、内容をチェックしているわけではなく、ただ信頼済みであることをマーキングしているだけなので、trustAsHtml
メソッドを利用する場合には、その文字列が安全であることを開発者自身が保証できなければなりません(さもなければ、そのままセキュリティホールになります!)。
なお、trustAsHtml
メソッドはより汎用的なtrustAs
メソッドで置き換えることも可能です(次のコードはその例)。
$scope.msg = $sce.trustAs($sce.HTML, msg);
|
処理対象:データバインディング(Data Binding) カテゴリ:基本
処理対象:{{プロパティ名}} カテゴリ:式(Expressions)
API:ngBind(ng-bind) カテゴリ:ng(コアモジュール) > directive(ディレクティブ)
API:ngBindHtml(ng-bind-html) カテゴリ:ng(コアモジュール) > directive(ディレクティブ)
API:ngSanitize カテゴリ:モジュール(Modules)
API:$sce カテゴリ:ng(コアモジュール) > service(サービス)
※以下では、本稿の前後を合わせて5回分(第5回~第9回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
5. モデルをテキストボックスなどのフォーム要素にバインドするには?(ng-model)
ビューの変更をモデルに反映させ、逆にモデルの変更をビューに反映させる、AngularJSの双方向データバインディングの基本を解説する。デフォルト値の設定方法も説明。
6. モデルをビューにバインドするには?(ng-bind/ng-cloak)
エクスプレッションが一瞬表示されてしまう不具合を解消して、AngularJSの双方向データバインディングを実現する方法を説明する。
7. 【現在、表示中】≫ ビューにHTML文書をバインドするには?(ng-bind-html)
文字列をデータバインドした際に、標準で実施されるサニタイズ処理について紹介。また、サニタイズせずにHTMLのままのビューに反映させる方法も説明する。
8. イベントリスナーを登録するには?(ng-clickなど)
AngularJSで、ディレクティブを使ってイベントリスナーを設定する方法を解説。また、イベントリスナーにイベントオブジェクトを渡して参照する方法も説明する。