2018-08-05

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

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

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

使い方は簡単でオプションも多くないのでドキュメントを読めば使い方はすぐにわかります。 Bleach — Bleach 6.1.0 20231006 documentationhttps://bleach.readthedocs.io/

info
  • 使ったバージョンについて
    • Python: 3.6
    • bleach: 2.1.3

Installation

インストールは pip で

$ pip install bleach

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
  • 属性です。

以上。