はじめに
(自分で勝手に)いろいろあって,実質1か月ぶりのエントリです><
Plackアプリ内でファイルアップロード処理を実装しているときに,ちょっと気になる挙動があったので以下に記録しておきます.
なお,Plackのバージョンは,現時点で最新の 0.9929 です.
検証用コード
HTML
単なるファイルアップロードフォームです.
<form action="..." method="post" enctype="multipart/form-data" > <input type="file" name="file" /> <input type="submit" name="submit" value="upload" /> </form>
余談ですが,Text::MicroTemplate::Extendedを使うようになってから,HTMLの記述は属性部分で改行を入れるようになりましたね.なんかその方がいろいろ都合がいいので.
Plackアプリ内
実際の検証にはオレオレフレームワークを使っているので,素で書くとこんな感じですかね.まぁ流れはおわかりいただけるかと思います.
# $req: Plack::Request object use Data::Dumper; sub D { print Dumper @_; } my $up = $req->uploads->get('file'); D $req->uploads; D $up; D $up->filename if $up;
サンプル
次の5パタンの名前のファイルをサンプルとしました.
- 「”」を含まない
- 途中に「”」を含んでいる
- 先頭が「”」である
- 途中に「’」を含んでいる(「”」は含まない)
- 先頭が「’」である(「”」は含まない)
結果
「”」を含まない場合
$VAR1 = bless( {
'file' => bless( {
'headers' => bless( {
'content-disposition' => 'form-data; name="file"; filename="とある科学の超電磁砲.txt"',
'content-type' => 'text/plain'
}, 'HTTP::Headers' ),
'filename' => 'とある科学の超電磁砲.txt',
'tempname' => '/tmp/XTTkk0xvth',
'size' => 0
}, 'Plack::Request::Upload' )
}, 'Hash::MultiValue' );
$VAR1 = bless( {
'headers' => bless( {
'content-disposition' => 'form-data; name="file"; filename="とある科学の超電磁砲.txt"',
'content-type' => 'text/plain'
}, 'HTTP::Headers' ),
'filename' => 'とある科学の超電磁砲.txt',
'tempname' => '/tmp/XTTkk0xvth',
'size' => 0
}, 'Plack::Request::Upload' );
$VAR1 = 'とある科学の超電磁砲.txt';途中に「”」を含む場合
$VAR1 = bless( {
'file' => bless( {
'headers' => bless( {
'content-disposition' => 'form-data; name="file"; filename="とある"ラジオ"の超電磁砲.txt"',
'content-type' => 'text/plain'
}, 'HTTP::Headers' ),
'filename' => 'とある',
'tempname' => '/tmp/0dejlikx9B',
'size' => 0
}, 'Plack::Request::Upload' )
}, 'Hash::MultiValue' );
$VAR1 = bless( {
'headers' => bless( {
'content-disposition' => 'form-data; name="file"; filename="とある"ラジオ"の超電磁砲.txt"',
'content-type' => 'text/plain'
}, 'HTTP::Headers' ),
'filename' => 'とある',
'tempname' => '/tmp/0dejlikx9B',
'size' => 0
}, 'Plack::Request::Upload' );
$VAR1 = 'とある';最初が「”」である場合
$VAR1 = bless( {}, 'Hash::MultiValue' );
$VAR1 = undef;途中に「’」を含む場合
$VAR1 = bless( {
'file' => bless( {
'headers' => bless( {
'content-disposition' => 'form-data; name="file"; filename="とある\'ラジオ\'の超電磁砲.txt"',
'content-type' => 'text/plain'
}, 'HTTP::Headers' ),
'filename' => 'とある\'ラジオ\'の超電磁砲.txt',
'tempname' => '/tmp/0EtSHs8dqT',
'size' => 0
}, 'Plack::Request::Upload' )
}, 'Hash::MultiValue' );
$VAR1 = bless( {
'headers' => bless( {
'content-disposition' => 'form-data; name="file"; filename="とある\'ラジオ\'の超電磁砲.txt"',
'content-type' => 'text/plain'
}, 'HTTP::Headers' ),
'filename' => 'とある\'ラジオ\'の超電磁砲.txt',
'tempname' => '/tmp/0EtSHs8dqT',
'size' => 0
}, 'Plack::Request::Upload' );
$VAR1 = 'とある\'ラジオ\'の超電磁砲.txt';最初が「’」である場合
$VAR1 = bless( {
'file' => bless( {
'headers' => bless( {
'content-disposition' => 'form-data; name="file"; filename="\'IE6\' no more.txt"',
'content-type' => 'text/plain'
}, 'HTTP::Headers' ),
'filename' => '\'IE6\' no more.txt',
'tempname' => '/tmp/G7BAW7rx3P',
'size' => 0
}, 'Plack::Request::Upload' )
}, 'Hash::MultiValue' );
$VAR1 = bless( {
'headers' => bless( {
'content-disposition' => 'form-data; name="file"; filename="\'IE6\' no more.txt"',
'content-type' => 'text/plain'
}, 'HTTP::Headers' ),
'filename' => '\'IE6\' no more.txt',
'tempname' => '/tmp/G7BAW7rx3P',
'size' => 0
}, 'Plack::Request::Upload' );
$VAR1 = '\'IE6\' no more.txt';まとめ
以上から,次のことがいえそうです.
- 名前の途中に「”」を含むファイルの場合,対応するPlack::Request::Uploadオブジェクトは存在するものの,
filenameメソッドの値が「”」の手前で終わってしまう - 名前の先頭が「”」なファイルの場合,対応するPlack::Request::Uploadオブジェクト自体取得できない
- 「’」の場合は問題ない
ソースコードを追ってみた
アップロードファイルを操作する Plack::Request::Upload からソースコードを追ってみたところ,HTTP::Body::MultiPart (1.07) にたどりつきました.
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 | sub handler { my ( $self, $part ) = @_; unless ( exists $part->{name} ) { my $disposition = $part->{headers}->{'Content-Disposition'}; my ($name) = $disposition =~ / name="?([^\";]+)"?/; my ($filename) = $disposition =~ / filename="?([^\"]*)"?/; # Need to match empty filenames above, so this part is flagged as an upload type $part->{name} = $name; if ( defined $filename ) { $part->{filename} = $filename; if ( $filename ne "" ) { my $fh = File::Temp->new( UNLINK => 0, DIR => $self->tmpdir ); $part->{fh} = $fh; $part->{tempname} = $fh->filename; } } } ... |
そして,その264行目に,気になる部分がありました.
264 | my ($filename) = $disposition =~ / filename="?([^\"]*)"?/; |
なるほど.
修正してみた
HTTP::Body::MultiPart の264行目を,次のように書き換えてみます.
my ($filename) = $disposition =~ / filename="?(\S*)"?/;
すると,次のような結果になりました.
$VAR1 = bless( {
'file' => bless( {
'headers' => bless( {
'content-disposition' => 'form-data; name="file"; filename="とある"ラジオ"の超電磁砲.txt"',
'content-type' => 'text/plain'
}, 'HTTP::Headers' ),
'filename' => 'とある"ラジオ"の超電磁砲.txt"',
'tempname' => '/tmp/Ttko8g9Fis',
'size' => 0
}, 'Plack::Request::Upload' )
}, 'Hash::MultiValue' );
$VAR1 = bless( {
'headers' => bless( {
'content-disposition' => 'form-data; name="file"; filename="とある"ラジオ"の超電磁砲.txt"',
'content-type' => 'text/plain'
}, 'HTTP::Headers' ),
'filename' => 'とある"ラジオ"の超電磁砲.txt"',
'tempname' => '/tmp/Ttko8g9Fis',
'size' => 0
}, 'Plack::Request::Upload' );
$VAR1 = 'とある"ラジオ"の超電磁砲.txt"';ただ,この修正の場合,スペースを含む名前のファイルが通らなくなっちゃいますね.正規表現は苦手なので,ベストな修正は他力本願しておきます><
改めてまとめ
HTTP::Body::MultiPart の処理方法が,根本的な原因のようですね.
おわりに
以上,名前に「”」を含むファイルをアップロードしたときのPlackアプリの挙動について触れて終わろうかと思ったら,気づけばソースコードを追いかけてしまってしまっていた,というエントリでした.
