環境
Django 1.9
Android Chrome ( おそらくドルフィンとかでも )
現象
Django のログインフォームなどに Android の Chrome でアクセスした時、CSRF のエラー (403) が出てログインできない。
Django ログには CSRF cookie not set.
のメッセージが残る。
概要
おそらく、リクエストクッキーにマルチバイトが含まれてサーバに送られています。
僕のケースでは、Google Analytics が入れたクッキー __utmz
に日本語が入っていて、不具合が出てました。
それを Django が受取り、Python の http ライブラリのクッキーパーサ http.cookies
の正規表現 http.cookies._CookiePattern
でパースしますが、その正規表現パターンは flags
に re.ASCII
が指定されているので、マルチバイトが出てきた所でパースが終わってるようです。
対応
僕は WSGIRequest の COOKIES をパッチしました。マルチバイトをURLエンコードしてみました。
def patch_wsgi_request():
"""
クッキーに日本語が含まれている場合、クッキーのパースが失敗するので
パース前にURLエンコードする
Android + Google Analytics でよく出る
__utmz に日本語が入る
"""
import re
from urllib.parse import quote
from django.core.handlers.wsgi import WSGIRequest
from django import http
from django.core.handlers.wsgi import get_str_from_wsgi
from django.utils.functional import cached_property
def _quote(s):
return quote(s.group(0))
def COOKIES(self):
raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '')
re_multibyte = re.compile(r'[^\x00-\x7F]+')
raw_cookie_fixed = re_multibyte.sub(_quote, raw_cookie)
return http.parse_cookie(raw_cookie_fixed)
setattr(WSGIRequest, 'COOKIES', cached_property(COOKIES))
patch_wsgi_request()
このコードをどこか (urls.py の評価タイミングとか)で実行