LoginSignup
2
2

More than 5 years have passed since last update.

CGI.pmでのファイルアップロード

Last updated at Posted at 2013-09-08

ファイル名の取得でいろいろハマっているのでメモ。

my $fh = $cgi->upload('file');

としてファイルアップロードのパラメータ名を指定するとファイル名が取得できるということですが、これは、あてにならず、

Content-Disposition: form-data; name="file"; filename="hoge\"hoge.pm";

の場合は、ファイル名は、hogeで切れてしまう。

なので、これをもとのヘッダから取得しようと、

my $content_disposition = $cgi->uploadInfo($fh)->{'Content-Disposition'};
my ($filename) = $content_disposition =~ /filename\s*=\s*"(.+)"/;

これで、いいかなと思っていたが、RFC的には違うようで、

 filename*0*=iso-2022-jp'ja'%1B%24B%24%5B%244%24%5B%242%1B%28B;
  filename*1=.jpeg

というextended-parameter形式というUS-ASCHII以外の場合の拡張がRFC2231に規定されているそうです。ここを参考

では、これに対応しようと試みたところで今回の疑問が。
CGI.pmのuploadInfo()で、取得されるヘッダは複数行のヘッダがとれていないのでは?と思っています。手元から、リクエストを生成し投げてみたところ、upload()メソッドでもファイルを取得することができませんでした。
以下がリクエストのスクリプト。

upload_req.pl
#!/usr/bin/perl

use strict;
use warnings;
use IO::Socket::INET;

local $|=1;
my $file = 'sample.txt';
open my $fh, $file
    or die $!;
my $content = do{local $/;<$fh>};
close $fh;
my $sock = IO::Socket::INET->new(
    PeerAddr    => 'hogehoge.com',
    PeerPort    => 80,
    Proto       => 'tcp'
) or die $@;

my $b = time . $$ . time;
my $part1 = qq{--$b\r\n};
$part1 .= qq{Content-Disposition: form-data; name="file"; filename*0*=iso-2022-jp'ja'%1B%24B%24%5B%244%24%5B%242%1B%28B\r\n};
$part1 .= qq{    filename*1=.txt\r\n};
$part1 .= qq{Content-Type: text/plain\r\n};
$part1 .= qq{\r\n};
$part1 .= qq{$content\r\n};
$part1 .= qq{--$b--\r\n};
$part1 .= qq{\r\n};

my $length = length $part1;
my $header = qq{POST /cgi_pm.cgi HTTP/1.1\r\n};
$header .= qq{Host:   hogehoge.com\r\n};
$header .= qq{User-Agent:  Mozilla/5.0\r\n};
$header .= qq{Content-Type:    multipart/form-data; boundary=$b\r\n};
$header .= qq{Content-Length:  $length\r\n};
$header .= qq{\r\n};

my $req = $header . $part1;
$sock->print($req);
while ( <$sock> ){
    print;
}

単一行で、

Content-Disposition: form-data; name="file"; filename="hoge.txt"

のようにするとContent-Dispositionも取れるのですが、複数行のfilenameフィールドには対応してないのかな?

CGI.pmの4017行目にheaderを読んでいるらしき箇所がありますが、このへんが関係しているのかな?これを見ると、複数行は考慮していないように見える..?

    # See RFC 2045 Appendix A and RFC 822 sections 3.4.8
    #   (Folding Long Header Fields), 3.4.3 (Comments)
    #   and 3.4.5 (Quoted-Strings).

    my $token = '[-\w!\#$%&\'*+.^_\`|{}~]';
    $header=~s/$CRLF\s+/ /og;       # merge continuation lines

    while ($header=~/($token+):\s+([^$CRLF]*)/mgox) {
        my ($field_name,$field_value) = ($1,$2);
    $field_name =~ s/\b(\w)/uc($1)/eg; #canonicalize
    $return{$field_name}=$field_value;
    }
    return %return;

どなたか詳しいい方教えて下さい。

追記

rfc2616 19.5.1 Content-Disposition
こちらがhttpにおけるContent-Dispositionを記したもののようでした。
これによると、

filename-parm = "filename" "=" quoted-string

で複数行になることはないようです。

追記

RFC2388ではファイル名が非ASCIIの場合は、RFC2231に従えとある。
CGI.pmでも、複数行の折りたたみ空白のヘッダはパースされ、1行となる。
上記のCGI.pmの

$header=~s/$CRLF\s+/ /og;

ここは、CRLF+空白を除去して1行にしている場所であった。

そして改めてCGI.pmを見てみると、

        # See RFC 1867, 2183, 2045
        # NB: File content will be loaded into memory should
        # content-disposition parsing fail.
        my ($filename) = $header{'Content-Disposition'}
                       =~/ filename=(("[^"]*")|([a-z\d!\#'\*\+,\.^_\`\{\}\|\~]*))/i;

Content-Disposition: form-dataでファイル送信時のパラメーターとしては、
filename=しか解釈されないようでした。
更に付け加えると、filename=として指定しないと、ファイルアップロードとして扱われない
ということでした。

2
2
0

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
2
2