urlencode と、escape を間違えて、ひどい思いをしたので記載します。
基本的に、この記事上登場するのは、以下スクリプトに記載された実装の話になります。
django/html.py at master · django/django
django/defaultfilters.py at master · django/django
前提
以下の環境で作業は実施しています。
-
OS
% sw_vers ProductName: Mac OS X ProductVersion: 10.12.6 BuildVersion: 16G29
-
python の verion
% python -V Python 2.7.10
-
django の version
% pip list | grep Django Django (1.10.7)
参考
django の デフォルトの動作について
基本的にデフォルト動作だと、エスケープが発動します。
発動を止めるには、{% autoescape off %}
を使うか、safe
フィルタ を使う必要があります。
動作については、以下の記事にまとまっております。
【メモ】Djangoでhtmlをエスケープせずそのまま出力する - 気ままなタンス プログラミングなどのノートブック
escape について
HTML で 特殊文字として扱われる 文字列エスケープするメソッドです。
基本的に、django のデフォルト動作でエスケープされるので、使用する機会があるとしたら、
{% autoescape off %}
...........
{% endautoescape %}
{{ blog_post.title|escape}}
として記載するのかと思います。
&
&
を入力すると、
from __future__ import print_function
from django.utils import html
print(html.escape("&"))
エスケープされ、
&
>
>
を入力すると、
from __future__ import print_function
from django.utils import html
print(html.escape(">"))
エスケープされ
>
HTML の構文として、問題が現実的に出るであろう <
>
'
"
&
のみを特殊文字列に置換します。
from __future__ import print_function
from django.utils import html
print(html.escape("<>'\"&"))
<>'"&
<bean:write>
タグの挙動でした。
Struts リファレンス<bean:write>
HTML タグのエスケープ方法は10年くらいは変わっていなさそうです。
それだけ息の長い仕様なのだと思いました。
ちなみに、wicket の escape メソッドもデフォルト挙動では、django、struts と同じ挙動となります。
追加の機能で、オプションでスペースのエスケープ有無、ユニコード文字列のエスケープ有無を選択できるようです。
wicket/Strings.java at master · apache/wicket の escapeMarkup
メソッドがエスケープ処理を行います。
django の escape
メソッド自体の実装を見ると、以下のような実装になっています。
HTML で 特殊文字として扱われる文字列をエスケープして、エスケープした後に、mark_safe
を実行し、2回 escape されないようにしています。
ただ、escape
を 2度呼び出すと、それは強制 escape になり、2度目だったら escape
しない場合は、conditional_escape
を使う必要があります。
- escape メソッド
_html_escapes = { ord('&'): '&', ord('<'): '<', ord('>'): '>', ord('"'): '"', ord("'"): ''', } @keep_lazy(str, SafeText) def escape(text): """ Return the given text with ampersands, quotes and angle brackets encoded for use in HTML. Always escape input, even if it's already escaped and marked as such. This may result in double-escaping. If this is a concern, use conditional_escape() instead. """ return mark_safe(str(text).translate(_html_escapes))
conditional_escape について
ドキュメント読んでいたら見つけました。
escape とは似ているが少し違うと記載されています。
違いは、一度エスケープしていたら、しない。
扱う値がエスケープされているかされていないかが把握できない時に使用するメソッドかと思います。
escapejsについて
template 内に javascript を書き付けたい場合があります。
その際に使用するのがこのフィルターです。
ダブルクォート、シングルクォートあたりを置換するだけかと思っていましたが、実装上はその他の文字列もエスケープの対象になっていました。
- escapejsの対象文字列の抜粋
_js_escapes = { ord('\\'): '\\u005C', ord('\''): '\\u0027', ord('"'): '\\u0022', ord('>'): '\\u003E', ord('<'): '\\u003C', ord('&'): '\\u0026', ord('='): '\\u003D', ord('-'): '\\u002D', ord(';'): '\\u003B', ord('`'): '\\u0060', ord('\u2028'): '\\u2028', ord('\u2029'): '\\u2029' } # Escape every ASCII character with a value less than 32. _js_escapes.update((ord('%c' % z), '\\u%04X' % z) for z in range(32))
実際の変換には、上記の辞書を、translate
メソッドの引数として使っています。
* escapejs
@keep_lazy(str, SafeText)
def escapejs(value):
"""Hex encode characters for use in JavaScript strings."""
return mark_safe(str(value).translate(_js_escapes))
translate
を知りませんでしたが、以下が参考になりました。 Pythonでの文字列置換をマスターする - orangain flavor
また、escape
は置換文字列をHTMLの文字実体参照に変換していますが、escapejs
は、置換対象文字列をUnicodeコードポイントに変換しています。
発動想定箇所の違いで、実装の具合が異なりますね。
参考記事へのリンクを貼りますが、私は記載内容についてまだよく理解できておりません。
JavaScriptでのサロゲートペア文字列のメモ - Qiita
escape、escapejs 後に、autoescape は発動しない。
Built-in template tags and filters | Django documentation | Django
に記載がありますが、escape 後、escapejs 後は、autoescapeによるescapeは行われず、conditional_escape の同様の動作となるようです。
urlencodeについて
内部実装は以下のようになっています。
urllib.parse.quote
を使ってますね。
- urlencodeメソッド
@register.filter(is_safe=False) @stringfilter def urlencode(value, safe=None): """ Escape a value for use in a URL. The ``safe`` parameter determines the characters which should not be escaped by Python's quote() function. If not provided, use the default safe characters (but an empty string can be provided when *all* characters should be escaped). """ kwargs = {} if safe is not None: kwargs['safe'] = safe return quote(value, **kwargs)
urlencode の対象文字列は、URIで使用できる文字 - CyberLibrarian に記載されている文字種と、後は全角文字列だと思います。
urlencode 処理は mark_safe
していないので、autoescape の対象にはなりますが、<
等がパーセントエンコーディングの対象となるため結局はautoescape は発動しない動作となりそうです。
セキュリティ観点で念のために、autoescape にも処理を流すようにしているのかと思います。
まとめ
一言、エスケープとか、エンコードはややこしい。
SQL 等のクエリ系のもあるでしょうし、どこでやるべきか等は揉めそうですが、mark_safe
という考えは参考になりました。
以上です。
コメント