因为 CGI.pm 默认将文件上传字段当作普通参数处理,$cgi->param('file') 只返回文件名(甚至为空),必须用 $cgi->upload('file_field_name') 获取文件句柄;表单需设 enctype="multipart/form-data",否则 upload() 返回 undef。
$cgi->param('file') 返回空?因为 CGI.pm 默认把文件上传字段当作普通参数处理,实际文件内容需要通过 $cgi->upload() 获取。直接用 param() 只能得到文件名(在部分浏览器下甚至为空),不是文件句柄。
$cgi->upload('file_field_name') 获取可读文件句柄,而非 $cgi->param()
的 name 属性值必须和 upload() 的参数一致enctype="multipart/form-data",否则 upload() 返回 undef
不能直接用用户提交的原始文件名,需过滤路径遍历(如 ../etc/passwd)和非法字符。Perl 自身不自动清理文件名,得手动处理。
File::Basename::basename() 提取纯文件名,丢弃路径部分s/[^a-zA-Z0-9._-]+//g 删除危险字符(保留字母、数字、点、下划线、短横)qw(jpg png pdf txt)),避免执行型文件上传open my $fh, '>', $safe_path 写入,不要 用 >> 追加,防止覆盖关键文件use File::Basename;
my $upload = $cgi->upload('myfile');
my $filename = basename($cgi->param('myfile'));
$filename =~ s/[^a-zA-Z0-9._-]+//g;
my $ext = lc((split /\./, $filename)[-1] // '');
die "Invalid extension" unless grep { $_ eq $ext } qw(txt jpg png pdf);
open my $out, '>', "/var/www/uploads/$filename" or die "Cannot open: $!";
binmode $out;
while (my $bytesread = read($upload, my $buffer, 8192)) {
print $out $buffer;
}
close $out;
upload() 返回什么类型?返回一个 Perl 文件句柄(GLOB ref),行为类似 open FH, ' 得到的句柄,支持 read()、binmode(),但不支持 行读取(因是二进制流)。

binmode($fh),否则 Windows 换行或非 ASCII 字符会损坏read($fh, $buf, $len) 是推荐读法;sysread() 也可用,但需自行处理 EOF 和错误upload() 返回 undef,需提前检查upload() 不会重置位置,也不会重新读取CGI.pm 自 Perl 5.20 起标记为 deprecated,5.32+ 默认不安装,且不支持 PSGI/Plack,无法用于 FastCGI、mod_perl 或现代 Web 服务器部署。
Plack::Request(配合 Plack 中间件):上传文件自动解析为临时文件或 IO::Handle 对象CGI::Simple(轻量兼容)或手写 parse_multipart()(用 HTTP::Body)enctype="multipart/form-data" 和正确 Content-Length 头最易忽略的一点:CGI.pm 在调试时不会报错提示 enctype 缺失,只会让 upload() 静默返回 undef —— 建议始终先 defined $fh or die "No file uploaded"。