はじめに

(自分で勝手に)いろいろあって,実質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アプリの挙動について触れて終わろうかと思ったら,気づけばソースコードを追いかけてしまってしまっていた,というエントリでした.

こちらもあわせてどうぞ

コメントをどうぞ