動作検証環境
- CentOS7
- Apache/2.4.6
- PHP 5.4
起きた問題
ファイルダウンロード時に500エラーが起きる。
名前をつけて保存用に、ヘッダーのfilenameにユーザーファイルの名前ほぼそのままの値を入れている。
header('Content-Disposition: attachment; filename="' . $filename . '";'
Apacheのエラーログには
[http:error] [pid 29947] [client 192.168.1.1:57556] AH02430: Response header 'Content-Disposition' value of 'attachment; filename="~\x1d~' contains invalid characters, aborting request
と出ている。
その他手動デコードなどの結果、\x1d
が制御文字であり、これがエラーの原因と見た。
ひとまず安直に解決方法
$filename = preg_replace('/\p{Cc}/u', '', $filename);
filenameをBase64エンコード・URLエンコードしていないことが諸悪の根源なのですが、filename*への移行期間中なので…filenameくんにはあまり大胆な変更をせずに役割を終えてもらいたいです。
U+001Dとはなにか 制御文字だよ
ざっくりと
制御文字 - Wikipedia
のGroup Separator(^]
)です。
なんじゃそりゃ。どうやって入ったんでしょうね。
wikiを少し縮めると、おそらく
ASCIIの制御文字 = 0から21および127(C0制御コード)
拡張ASCIIの制御文字 = ASCIIの制御文字 + 128から159(C1制御コード)
Unicodeの制御文字 = 拡張ASCIIの制御文字(Cc)
という区分になる。
が、一般?の方なら遭遇頻度的にも、制御コードと言えばC0制御コード(C0集合)かな。
127のDeleteについては、C0の中でも飛んでいることもあり、端折られることもあるように見受けられた。
ぼけーと気をつけることと言えば、制御文字にはCRLFでおなじみな
\r\n\t
が含まれているので、テキスト系に使うときはレイアウト崩れてから思い出そう。
除去方法
幾つか見つかった。
制御文字を取り除く方法(改行コードは保持) - Qiita
最初はこれだけど、改行コード保持のためちょっと長くなっている。
preg_replaceでutf8文字列からコントロール文字を削除する。 - gounx2の日記
もっと\s
のようなメタ文字でばばっと消したい要望に応えられた。
PHP: Unicode 文字プロパティ - Manual
UTF-8モードなんて使ったこと無いっす。
見た目とググりやすそうな使い方(+ファイル名に改行は無い)から解決方法ではこれを採用している。
制御文字がDBに入らないようにする - Qiita#コメント
からPHP: Filter 関数 - ManualのPHP: 除去フィルタ - Manualを使えばいいよとの啓示を受けるが、フラグの説明が無く、どれを使えば過不足無いかがよくわからなかった。
filter_var, filter_input でよく使うもの - Qiita#除去フィルター
を見るに、FILTER_FLAG_STRIP_LOW
がキモそうともう少しググって
PHPのFilter関数の除去フィルタにある「FILTER_SANITIZE_ENCODED」の意味が分か... - Yahoo!知恵袋
から、(知恵袋ですがご安心ください)
$var = filter_var($var, FILTER_SANITIZE_ENCODED, FILTER_FLAG_STRIP_LOW);
これは以下と等価です。文字コード0x0~0x1fまでの制御文字を取り除きます。
$var = urlencode(preg_replace('/[\x0-\x1f]/', '', $var));
`FILTER_FLAG_STRIP_LOW`が(`\x7F`を除く)C0制御コードを除去するものだと認識しました。
提示されているように`urlencode`しちゃえばいいですが、制御コード除去のみしたければ、
```php
filter_var($var, FILTER_UNSAFE_RAW, FILTER_FLAG_STRIP_LOW);
でしょうか。
わざわざ正規表現せずとも~というのはごもっともですが、
除去フィルタのフラグの仕様が公式にささっと検索できない限りはまだ、正規表現の方が他人に伝わりやすいかなあと思いました。
その他 参考
URLにファイル名入れられるならこれが良さそうなのですが。ちょっとまだ良くわかってない。
User-Agent分岐版。filenameにエンコードした文字列を入れられることを知らなかった。
現状、正解かわかりませんがUser-Agentを見ずに併記して移行期間としてますが、filename*では正常に処理されててもfilenameが文字化け以前にヘッダーとして不正ならどうしようもないねという問題でした。
参考になりそうな有名なサイトからちっちゃいサイト含め、日本語ファイルをダウンロードさせるサイトが全然見つからなくて、他所はどうしているかが観測しづらいです。