RewriteRuleのCO=でドメイン属性なしが出来ない問題とその解決

# 序文
`mod_rewrite`の`RewriteRule`では`[CO=xx]` という命令を使うとクッキー発行できる、ということになっているがこの仕様が糞すぎて使いものにならないので解決編の設定を使ったほうが良いよというお話。

まとめエントリ的には最初に結論を書いておいた方が後で見る人に親切だろうから解決策を最初においておく。


# 解決編：CO=なんて要らんかったんや！

で、RewriteRuleの糞仕様に散々悩まされた結果、そんなん使わなきゃいいんじゃね？てことで`CO=`を使わないで自分でヘッダ出すことにした。これなら完全に自分でヘッダ内容コントロールできるし最初からこうしてれば良かったわ。

```text: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属性は *指定しない* が一番安全 | 徳丸浩の日記](http://blog.tokumaru.org/2011/10/cookiedomain.html)

でも指摘されているように、domain属性は無いのが一番範囲限定できて望ましい。

なのに CO= では domain 属性が必須になっているためにそのホスト名のみに範囲を限定することが出来ない。

例えば www.example.com でドメイン指定なしで有効期限30分のクッキーを発行したい場合の設定を色々試してみよう、とても残念な結果が得られる。

## 1.本来やりたいことをイメージした設定
```text:本来やりたいことをイメージした設定
RewriteRule . - [CO=foo1:bar::1800:/]
```
```text:出力されるヘッダ
Set-Cookie: foo1=bar; path=/; domain=1800; expires=Wed, 25-Nov-2015 07:58:31 GMT
```

ファッ！？なぜかドメイン部に有効期限の値が使われとる！ちなみにexpiresは30分後じゃなく現在時刻(多分/が数字にパース出来なかったときのデフォルト)が入ってる。pathも多分未指定に解釈された筈だがコレも何故か省略不可な仕様なのでデフォルトの`path=/`が帰ってきていると思われる。ちなみにソース確認したところセパレータ(`:`または`;`)が連続した場合の処理が適当で空カラムは存在しない事になってた。もう色々だめだこれ…。

## 2.ドメイン部分に空の代わりに-とか書いてみる
```text:ドメイン部分に空の代わりに-とか書いてみる
RewriteRule . - [CO=foo2:bar:-:1800:/]
```
```text:出力されるヘッダ
Set-Cookie: foo2=bar; path=/; domain=-; expires=Wed, 25-Nov-2015 08:28:31 GMT
```

ちょ、`domain=-` とかそのまま出すなよｗｗ 当然ブラウザはこんなクッキーは食ってくれないので意味が無いヘッダになる。


## 3.どうせ`abc.www.example.com`とか使ってないし妥協でホスト名を付けてみる
```text:どうせabc.www.example.comとか使ってないし妥協でホスト名をそのまま付けてみる
RewriteRule . - [CO=foo3:bar:www.example.com:1800:/]
```
```text:出力されるヘッダ
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.とりあえず確実にクッキー食わせたいので妥協策…
```text:とりあえず確実にクッキー食わせたいので妥協策…
RewriteRule . - [CO=foo4:bar:.example.com:1800:/]
```
```text:出力されるヘッダ
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の値を使えるようにお膳立てすることがミッション内容。

で実際には最終的に、以下の様な設定にして落ち着きました。

```text:クエリにafidがあったらafid_trackクッキーに90日間保持させるよ
# 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
```

というのが本エントリを書いた経緯です。