序文
mod_rewrite
のRewriteRule
では[CO=xx]
という命令を使うとクッキー発行できる、ということになっているがこの仕様が糞すぎて使いものにならないので解決編の設定を使ったほうが良いよというお話。
まとめエントリ的には最初に結論を書いておいた方が後で見る人に親切だろうから解決策を最初においておく。
解決編:CO=なんて要らんかったんや!
で、RewriteRuleの糞仕様に散々悩まされた結果、そんなん使わなきゃいいんじゃね?てことでCO=
を使わないで自分でヘッダ出すことにした。これなら完全に自分でヘッダ内容コントロールできるし最初からこうしてれば良かったわ。
RewriteRule . - [E=cookie_foo:bar]
Header add Set-Cookie "foo=%{cookie_foo}e; path=/; max-age=1800" env=cookie_foo
- クッキーに入れたい値は一旦環境変数に入れて、環境変数の有無を見て
Header add
する。 - Set-Cookieは複数出力されるのが普通な仕様のヘッダなので、
set
でもappend
でもなくadd
で出力。 - この例では固定値だけど本来はRewriteCondやRewriteRuleのマッチで作ったグループを
$1
とか%1
とかで後方参照してクッキーにセットするのが目的なのでmod_rewriteを使っている。その場合[E=foo:$1]
とかすれば良い。
CO=の問題点
こっから先は色々試した軌跡なだけなので気になる人以外見なくて良いです。
- domain指定が無いクッキーを発行できない
コレに尽きる。いや他にもpathも無しに出来ないとか幾つか突っ込みどころはあるけどそれらは元々指定するだろうしそんなに困ることはないのでどうでもいい。
CookieのDomain属性は 指定しない が一番安全 | 徳丸浩の日記
でも指摘されているように、domain属性は無いのが一番範囲限定できて望ましい。
なのに CO= では domain 属性が必須になっているためにそのホスト名のみに範囲を限定することが出来ない。
例えば www.example.com でドメイン指定なしで有効期限30分のクッキーを発行したい場合の設定を色々試してみよう、とても残念な結果が得られる。
1.本来やりたいことをイメージした設定
RewriteRule . - [CO=foo1:bar::1800:/]
Set-Cookie: foo1=bar; path=/; domain=1800; expires=Wed, 25-Nov-2015 07:58:31 GMT
ファッ!?なぜかドメイン部に有効期限の値が使われとる!ちなみにexpiresは30分後じゃなく現在時刻(多分/が数字にパース出来なかったときのデフォルト)が入ってる。pathも多分未指定に解釈された筈だがコレも何故か省略不可な仕様なのでデフォルトのpath=/
が帰ってきていると思われる。ちなみにソース確認したところセパレータ(:
または;
)が連続した場合の処理が適当で空カラムは存在しない事になってた。もう色々だめだこれ…。
2.ドメイン部分に空の代わりに-とか書いてみる
RewriteRule . - [CO=foo2:bar:-:1800:/]
Set-Cookie: foo2=bar; path=/; domain=-; expires=Wed, 25-Nov-2015 08:28:31 GMT
ちょ、domain=-
とかそのまま出すなよww 当然ブラウザはこんなクッキーは食ってくれないので意味が無いヘッダになる。
3.どうせabc.www.example.com
とか使ってないし妥協でホスト名を付けてみる
RewriteRule . - [CO=foo3:bar:www.example.com:1800:/]
Set-Cookie: foo3=bar; path=/; domain=www.example.com; expires=Wed, 25-Nov-2015 08:28:31 GMT
HTTPの仕様ではこれで *.www.example.com
と www.example.com
の範囲でクッキーを送ってもらえるはずなので、使用する予定がないサブドメインに溢れるのは目をつぶってこれで解決としよう。
→**ダウト!!**実はブラウザ実装の問題でこれじゃ解決しません!
どうやら一部ブラウザは domain=www.example.com
と指定すると *.www.example.com
のみにクッキーを送り、www.example.com
には送ってくれない奴らがいるようです。
PCブラウザは大抵大丈夫っぽいけど、スマホでクッキーを送ってくれない奴が居ます。詳しくバージョンとか集計してないけどAndroidでもiPhoneでもポツポツといるようです。
ということでこの設定はボツ。
4.とりあえず確実にクッキー食わせたいので妥協策…
RewriteRule . - [CO=foo4:bar:.example.com:1800:/]
Set-Cookie: foo4=bar; path=/; domain=.example.com; expires=Wed, 25-Nov-2015 08:28:31 GMT
domain指定がおかしいブラウザの為に、1個上のドメインのワイルドカード指定でクッキーがダダ漏れるホスト名範囲がめちゃめちゃ広くなって気分悪いけど、もうCO=に付き合いたくない、諦めた、これでいいだろっていうやり投げ感漂う設定となってます。
なお、www.example.com
のホスト上で domain=example.com
なクッキーを発行すると今度は権限がないホスト名と判断する駄目ブラウザがいてクッキー食ってくれない実装がいて駄目なので、domain=.example.com
ならどうだ!?て試したら何となく大体みんな食ってくれてるようなのでこうした。
蛇足
jsとかphpでクッキーセットしたら?
コンテンツは触らずにサーバ設定だけでどうにかするのが要件だったんよ。
mod_luaでクッキー出力とかは?
それも考えた。mod_lua
って早くてパワフルだし凄い便利だよね。だけど httpd-2.4 じゃなくて httpd-2.2 しか使えない環境だってん…。
そもそもどういう要件だったの?
- 環境
- コンテンツは触れない
- サーバは httpd-2.2
- 既に既存で凄い複雑な設定がしてあって httpd-2.2 を捨てる選択肢やコストは今は無い
- やりたいこと
- サイト内の何処のURLとかは決まってないけど、ランディングページのURLに afid=xxx なクエリが付いてたら、afid_track=xxx なクッキーを発行して後の処理に引き継いで元のxxxの値を使えるようにお膳立てすることがミッション内容。
で実際には最終的に、以下の様な設定にして落ち着きました。
# CO=を使わないのには理由があるのでこちら参照 https://qiita.com/kawaz/items/3a862e3dba9b8577be21
RewriteCond %{QUERY_STRING} (^|&)afid=([a-zA-Z0-9]+)
RewriteRule . - [E=afid_track:%2]
Header add Set-Cookie "afid_track=%{afid_track}e; path=/; max-age=7776000" env=afid_track
というのが本エントリを書いた経緯です。