3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

U+001DなどUnicode(ASCII,C0)制御文字がHeaderに含まれるとaborting requestするので除去する

Posted at

動作検証環境

  • 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 関数 - ManualPHP: 除去フィルタ - 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が文字化け以前にヘッダーとして不正ならどうしようもないねという問題でした。


参考になりそうな有名なサイトからちっちゃいサイト含め、日本語ファイルをダウンロードさせるサイトが全然見つからなくて、他所はどうしているかが観測しづらいです。

3
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?