Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
AngularJS TIPS

AngularJS TIPS

ビューにHTML文書をバインドするには?(ng-bind-html)

2015年4月2日

文字列をデータバインドした際に、標準で実施されるサニタイズ処理について紹介。また、サニタイズせずにHTMLのままのビューに反映させる方法も説明する。

  • このエントリーをはてなブックマークに追加

 AngularJSでは、{{...}}(=エクスプレッション)によるテキストの埋め込みにも、セキュリティ上の考慮がなされています。例えば以下のようなコードを見てみましょう。以下は、タグ(HTML)を含んだ文字列をビューにバインドする例です。

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文字列をビューにバインドするためのコード(bindHtml.html)
HTML文字列はただの文字列として表示される
HTML文字列はただの文字列として表示される

 HTML文字列はきちんとエスケープ処理されて、単なる文字列として表示されるわけです。これはもちろん正しい挙動なのですが、時として、動的に生成された文字列をHTMLとして反映させたい、ということもあるでしょう。

 そのような場合には、ng-bind-html属性を利用して、文字列をバインドします。

ng-bind-html属性によるデータバインディング

 以下は、上記のHTMLソースをng-bind-html属性を使うように書き換えたものと、そのページの表示結果です。

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属性でHTML文書をバインドする例(bindHtml2.html)
HTML文字列をHTMLとして解釈して表示
HTML文字列をHTMLとして解釈して表示

 ng-bind-html属性そのものの利用方法は、別稿「モデルをビューにバインドするには?」でも触れたng-model属性と同様、属性値としてバインドすべきモデル(=スコープオブジェクトのプロパティ)を指定するだけです(1)。

 ただし、ng-bind-html属性を利用する場合には、あらかじめngSanitizeモジュールをインポートし(2)、モジュールからも依存関係を設定しておく必要があります(3)。ngSanitizeモジュール*1は、特定のタグ/属性を除去し、無害化(sanitize)するための機能を提供します。この例であれば、HTML文字列に含まれる<script>要素やアンカー(=<a>)タグのonclick属性、<button>要素などが除去されていることが確認できます。

 AngularJSでは、このようにHTML文字列をバインドする際にも、最低限、危険と思われる要素/属性を排除することで、意図せぬセキュリティホールの発生を防いでいるわけです。

 ngSanitizeモジュールへの依存関係を設定せずにng-bind-html属性を付与した場合、以下のようなエラーが発生しますので注意してください。

ngSanitizeモジュールをインポートしなかった場合(開発者ツール)

補足:文字列が安全であることを保証する

 もっとも、すでに別のライブラリで処理済みである、または、内容を把握しており「安全な」HTMLであることが明らかな場合、<button><script>などの要素を勝手に除去されてしまうのは望ましくない場合があります。

 そのような場合には、文字列にあらかじめ信頼済みマークを付与しておくことで、ngSanitizeモジュールによるサニタイズを回避できます。これには、AngularJS標準で提供されているサービスである$sce*2trustAsHtmlメソッドを利用します。

  • *2 SCE(Strict Contextual Escaping)とは、文脈に応じて安全でない文字列、もしくは信頼されていないドメインからのデータを制限するための仕組みです。

 例えば先ほどの例であれば、コントローラーの中身を以下のように書き換えます。

JavaScript
……省略……
.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);
}]);
文字列が安全であることを保証するコード
スクリプトや<button>要素なども維持されている(開発者ツール)

 これによって、変数msgの内容は信頼済み(trust)であることがマークされましたので、ng-bind-html属性は変数msgの内容をそのままページに反映させます。ただし、trustAsHtmlメソッドは、内容をチェックしているわけではなく、ただ信頼済みであることをマーキングしているだけなので、trustAsHtmlメソッドを利用する場合には、その文字列が安全であることを開発者自身が保証できなければなりません(さもなければ、そのままセキュリティホールになります!)。

 なお、trustAsHtmlメソッドはより汎用的なtrustAsメソッドで置き換えることも可能です(次のコードはその例)。

JavaScript
$scope.msg  = $sce.trustAs($sce.HTML, msg);
trustAsメソッドで置き換えたコード
処理対象:データバインディング(Data Binding) カテゴリ:基本
処理対象:データバインディング(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]を参照してください。

AngularJS TIPS
5. モデルをテキストボックスなどのフォーム要素にバインドするには?(ng-model)

ビューの変更をモデルに反映させ、逆にモデルの変更をビューに反映させる、AngularJSの双方向データバインディングの基本を解説する。デフォルト値の設定方法も説明。

AngularJS TIPS
6. モデルをビューにバインドするには?(ng-bind/ng-cloak)

エクスプレッションが一瞬表示されてしまう不具合を解消して、AngularJSの双方向データバインディングを実現する方法を説明する。

AngularJS TIPS
7. 【現在、表示中】≫ ビューにHTML文書をバインドするには?(ng-bind-html)

文字列をデータバインドした際に、標準で実施されるサニタイズ処理について紹介。また、サニタイズせずにHTMLのままのビューに反映させる方法も説明する。

AngularJS TIPS
8. イベントリスナーを登録するには?(ng-clickなど)

AngularJSで、ディレクティブを使ってイベントリスナーを設定する方法を解説。また、イベントリスナーにイベントオブジェクトを渡して参照する方法も説明する。

AngularJS TIPS
9. ハイパーリンクを動的に生成するには?(ng-href)

スコープオブジェクト経由で動的に生成したハイパーリンクのURL設定が適切に処理されるように、ng-href属性を使用する方法を解説する。

サイトからのお知らせ

Twitterでつぶやこう!