[Python] HTMLのエスケープライブラリ bleach を使ってみた
2018-08-05

目次

@hirokiky に HTMLのエスケープ用のライブラリを教えてもらったので使ってみました。

Mozilla が開発してるということで信頼性もバッチリですね。

使い方は簡単でオプションも多くないので ドキュメント を読めば使い方はすぐにわかります。

備考

  • Python: 3.6
  • bleach: 2.1.3

Installation

インストールは pip で

$ pip install bleach
Collecting bleach
  Using cached https://files.pythonhosted.org/packages/30/b6/a8cffbb9ab4b62b557c22703163735210e9cd857d533740c64e1467d228e/bleach-2.1.3-py2.py3-none-any.whl
Collecting html5lib!=1.0b1,!=1.0b2,!=1.0b3,!=1.0b4,!=1.0b5,!=1.0b6,!=1.0b7,!=1.0b8,>=0.99999999pre (from bleach)
  Using cached https://files.pythonhosted.org/packages/a5/62/bbd2be0e7943ec8504b517e62bab011b4946e1258842bc159e5dfde15b96/html5lib-1.0.1-py2.py3-none-any.whl
Collecting six (from bleach)
  Using cached https://files.pythonhosted.org/packages/67/4b/141a581104b1f6397bfa78ac9d43d8ad29a7ca43ea90a2d863fe3056e86a/six-1.11.0-py2.py3-none-any.whl
Collecting webencodings (from html5lib!=1.0b1,!=1.0b2,!=1.0b3,!=1.0b4,!=1.0b5,!=1.0b6,!=1.0b7,!=1.0b8,>=0.99999999pre->bleach)
  Using cached https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl
Installing collected packages: six, webencodings, html5lib, bleach
Successfully installed bleach-2.1.3 html5lib-1.0.1 six-1.11.0 webencodings-0.5.1

clean funciton

基本的には bleach.clean 関数の第一引数に対象文字列を与えるとエスケープされた文字列が返却されるだけです。

>>> import bleach
>>> text = 'a<div title="test" onclick="alert(0)" style="color:red;">b<script>alert(1)</script>c</div>d'
>>> bleach.clean(text)
'a&lt;div onclick="alert(0)" style="color:red;" title="test"&gt;b&lt;script&gt;alert(1)&lt;/script&gt;c&lt;/div&gt;d'

通常はマークアップ文字列が実体参照に変換されるのですが、 strip=True を指定すると タグ記号を削って出力します。

>>> text = 'a<div title="test" onclick="alert(0)" style="color:red;">b<script>alert(1)</script>c</div>d'
>>> bleach.clean(text, strip=True)
'abalert(1)cd'

Allow tags & attributes

デフォルトだとすべてのHTMLタグと属性をエスケープするのですが、 「タグ」と「タグと属性の組み合わせ」を ホワイトリスト で指定できます。

タグは tags引数に list で指定します

>>> bleach.clean(text, tags=['div'])
'a<div>b&lt;script&gt;alert(1)&lt;/script&gt;c</div>d'

タグと属性の組み合わせは attributes引数に タグ名をキーとすると辞書で指定します。値は属性名の list です。

>>> bleach.clean(text, tags=['div'], attributes={'div': ['title']})
'a<div title="test">b&lt;script&gt;alert(1)&lt;/script&gt;c</div>d'

許可しない属性はトリミングされます。

ただし、すべての属性で同じルールを適用したい場合に同じキーを列挙するのはめんどくさいですね。 そういう場合は * をキーに指定します。

>>> text2 = '<div title="a" style="color: blue;"></div> <span title="b"></span> <p title="c"></p>'
>>> bleach.clean(text2, tags=['div', 'span', 'p'], attributes={'*': ['title']})
'<div title="a"></div> <span title="b"></span> <p title="c"></p>'

実は attributes引数には関数も指定できます。 例えば on で始まるイベントハンドラを削りたい場合以下のようにします。

>>> text3 = '<div title="a" onclick="alert(1)" onmouseover="alert(2)">a</div>'
>>> bleach.clean(text3, tags=['div'], attributes=lambda tag, name, value: not name.startswith('on'))
'<div title="a">a</div>'

CSS selector

通常 style 属性の指定は許可したくないものですが、一部のセレクタだけ許可したいかもしれません。

styles 引数にセレクタ名を list で指定します。

>>> text4 = '<div style="color:#00f;position:fixed;width:100px;">a</div>'
>>> bleach.clean(text4, tags=['div'], attributes={'div': ['style']}, styles=['color', 'width'])
'<div style="color: #00f; width: 100px;">a</div>'

Filter

attributes 属性は 出力可否を真偽値でしか指定できませんでしたが、手を加えたいことがあるかもしれません。

Filter を継承したクラスを定義することで制御できます。

例えばすべての属性値を moo に変えるフィルタは次のように __iter__ を書きます。(ほぼドキュメントのまんま

>>> from bleach.sanitizer import Cleaner
>>> from html5lib.filters.base import Filter

>>> class MooFilter(Filter):
...     def __iter__(self):
...         for token in Filter.__iter__(self):
...             if token['type'] in ['StartTag', 'EmptyTag'] and token['data']:
...                 for attr, value in token['data'].items():
...                     token['data'][attr] = 'moo'
...             yield token

>>> cleaner = Cleaner(tags=['span', 'img'], attributes={'img': ['rel', 'src'], 'span': ['class']}, filters=[MooFilter])
>>> dirty = '<span class="a">this is cute!</span> <img src="http://example.com/puppy.jpg" rel="nofollow">'
>>> cleaner.clean(dirty)
'<span class="moo">this is cute!</span> <img rel="moo" src="moo">'

Filter を指定するためには bleach.clean ではなく、 Cleaner インスタンス作成時にコンストラクタに指定する必要があります。

なお、__iter__ 返す token は 辞書で、以下のキーを持ちます。

type:

エレメントの種類です。 StartTag, EndTag, Characters, SpaceCharacters のいずれかです。

name:

タグ名です。タグじゃない場合は キーがありません。

namespace:

わかりません(気力なし

data:

属性です。

以上。