AngularJS の Markdown表示用 directive を使ってみる。
bower
bower で依存入手
bower install --save angular
bower install --save angular-marked
{
..省略..
"dependencies": {
"angular": "~1.4.5",
"angular-marked": "~0.0.21"
}
}
適当なフォームなどを作り動作確認
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Sample1</title>
</head>
<body ng-app="app">
<h2>入力文字列</h2>
<textarea ng-model="text" cols="80" rows="5"></textarea>
<h2>変換前</h2>
<div ng-bind="text"></div>
<h2>変換済みMarkdown</h2>
<div marked="text"></div>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/marked/marked.min.js"></script>
<script src="bower_components/angular-marked/angular-marked.js"></script>
<script>
app = angular.module('app', [
'hc.marked'
]);
</script>
</body>
</html>
XSS対策
しかし、これでは XSS 対策が行われていない。
以下では試しにbタグを使っているが、scriptタグなんかも問答無用で動作しちゃう。
Makredはデフォルトでは、HTMLタグのエスケープを行わない。これを対処するために以下の行をスクリプトに追加する。
markedProvider.setOptions({
sanitize: true
});
これを設定後は以下のようになる。
デフォルトでは以下のことが行われる
- escape処理
-
javascript:
から始まるリンクの無効化 -
vbscript:
から始まるリンクの無効化
デフォルトのエスケープ処理はこんな感じ
function escape(html, encode) {
return html
.replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
このエスケープ処理は markedProvider.setOptions()
で設定することができる sanitizer
オプションにより自前のものに変更することができる。
何はともあれ、ユーザに何か入力させて表示させる場合は sanitize: true
にしないとダメですね。
勝手に外の画像埋め込まれたくない時
Markdown では 
による画像埋め込みに対応しているが、それを禁止したり、特定のドメインの画像に限定したいときは、画像タグの出力方法を変更することにより対処できる。
app.config(['markedProvider', function(markedProvider) {
renderer = new marked.Renderer();
renderer.image = function(href, title, text) {
return '';
};
markedProvider.setOptions({
renderer: renderer,
sanitize: true
});
}]);
上の例では、image を出力しようとした時に、問答無用で空文字を返すように変更したもの。
もし、画像ファイルのソースURLのバリデーションを行いたい場合は元々のRenderの挙動を変更するとよいだろう。以下がオリジナルの挙動になる。
Renderer.prototype.image = function(href, title, text) {
var out = '<img src="' + href + '" alt="' + text + '"';
if (title) {
out += ' title="' + title + '"';
}
out += this.options.xhtml ? '/>' : '>';
return out;
};
というわけで、href をバリデーションばあいのコードが以下になる。
app.config(['markedProvider', function(markedProvider) {
renderer = new marked.Renderer();
renderer.image = function(href, title, text) {
if (href.indexOf('https://example.com/images/') === -1) {
return ''; // https://example.com/images/ 下にない画像埋め込みはしない
}
var out = '<img src="' + href + '" alt="' + text + '"';
if (title) {
out += ' title="' + title + '"';
}
out += this.options.xhtml ? '/>' : '>';
return out;
};
markedProvider.setOptions({
renderer: renderer,
sanitize: true
});
}]);
同じように、link などもよくわからんリンクを防いだり、クッションページ入れ込んだりなどなどいろいろカスタマイズが利いたりする。