因为 CGI.pm 默认将文件上传字段当作普通参数处理,$cgi->param('file') 只返回文件名(甚至为空),必须用 $cgi->upload('file_field_name') 获取文件句柄;表单需设 enctype="multipart/form-data",否则 upload() 返回 undef。

CGI.pm 读取上传文件时为何 $cgi->param('file') 返回空?
因为 CGI.pm 默认把文件上传字段当作普通参数处理,实际文件内容需要通过 $cgi->upload() 获取。直接用 param() 只能得到文件名(在部分浏览器下甚至为空),不是文件句柄。
- 必须用
$cgi->upload('file_field_name')获取可读文件句柄,而非$cgi->param() - 表单
的name属性值必须和upload()的参数一致 - HTML 表单 必须 设置
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;
CGI.pm 的 upload() 返回什么类型?
返回一个 Perl 文件句柄(GLOB ref),行为类似 open FH, ' 得到的句柄,支持 read()、binmode(),但不支持 行读取(因是二进制流)。
- 必须调用
binmode($fh),否则 Windows 换行或非 ASCII 字符会损坏 -
read($fh, $buf, $len)是推荐读法;sysread()也可用,但需自行处理 EOF 和错误 - 若上传字段不存在或出错,
upload()返回undef,需提前检查 - 该句柄只可读一次;重复调用
upload()不会重置位置,也不会重新读取
为什么 CGI.pm 已被弃用?现代替代方案是什么?
CGI.pm 自 Perl 5.20 起标记为 deprecated,5.32+ 默认不安装,且不支持 PSGI/Plack,无法用于 FastCGI、mod_perl 或现代 Web 服务器部署。
- 推荐迁移到
Plack::Request(配合 Plack 中间件):上传文件自动解析为临时文件或 IO::Handle 对象 - 若必须维持 CGI 环境,可用
CGI::Simple(轻量兼容)或手写parse_multipart()(用HTTP::Body) - 注意:所有替代方案仍要求表单
enctype="multipart/form-data"和正确Content-Length头
最易忽略的一点:CGI.pm 在调试时不会报错提示 enctype 缺失,只会让 upload() 静默返回 undef —— 建议始终先 defined $fh or die "No file uploaded"。










