ファイル名の取得でいろいろハマっているのでメモ。
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()メソッドでもファイルを取得することができませんでした。
以下がリクエストのスクリプト。
#!/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=として指定しないと、ファイルアップロードとして扱われない
ということでした。