django:sendgridでメール本文において&が&に変換されてしまう件について
状況
Checkクラスにはurlを格納する属性があり、一定時間ごとに各インスタンスurlを利用し、ウェブサイトにアクセスする。そして特定の情報が存在した場合にsendgridを使って自分のメールに送信する仕組みを設けた。メール本文にはアクセスしたurlを載せる。メール本文の作成は大まかに以下のようになっている。
from django.core.mail import send_mail from django.template.loader import render_to_string class CheckView(View): def get(self, request, *args, **kwargs): ・・・ check_objs = Check.objects.all() for obj in check_objs: url = obj.url ・・・ subject = "チェック結果" context = { "url" : url} message = render_to_string("mytemplate.txt", context) send_mail( subject, message, ["mynamesasasasa@gmail.com",], fail_silently=False, ) ・・・
この状況でurlの&部分がすべて& amp;に変換されてしまった。
この問題の対処
この原因がどこによるモノなのか分からなかった。sendgridの諸設定によるものかもしれないし、djangoによるものなのかもしれない。
いろいろ試した結果、メール本文にはテンプレートと変数のレンダリングする方式ではなく、そのまま文字列(url)を送る方法を採用した。これにより&の&変換問題が解消された。
コードは以下のような感じになる。
from django.core.mail import send_mail from django.template.loader import render_to_string class CheckView(View): def get(self, request, *args, **kwargs): ・・・ check_objs = Check.objects.all() for obj in check_objs: url = obj.url ・・・ subject = "チェック結果" # context = { "url" : url} ←削除 message = url send_mail( subject, message, ["mynamesasasasa@gmail.com",], fail_silently=False, ) ・・・
この結果sendgridから送ったメールがそのまま送ることができたので、原因はdjango側にある可能性が高いと推定した。
今回の原因と周辺情報
https://docs.djangoproject.com/ja/2.2/ref/templates/language/#automatic-html-escaping
要点にまとめると、djangoにはクロスサイトスクリプティング対策がされており、テンプレートレンダリングする場合には変数タグの出力を自動的にエスケープする仕様になっている。つまり常時エスケープ機能がonになっているので、今回の件で別案の解決方法としてエスケープ機能をオフにするときだけ設定する方法もあった。
エスケープ機能については具体的にはエスケープのオフにはエスケープオフの範囲によってタグを使い分ける。
変数単位でエスケープする場合、テンプレートまたはテンプレートの部分にエスケープする場合の2パターンがある。 前者は{{ data | safe }}とすればエスケープ機能がオフになる。後者はテンプレート内で{% autoescape off %}と{% endautoescape %}を挟んだ範囲が機能オフになる。
クロスサイトスクリプティングやセキュリティについて理解を深めたいならば、『体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践』を読むと良いだろう。