如何使用CGI.pm上传mod_perl中的二进制文件?

时间:2011-06-18 22:18:05

标签: perl unicode binary cgi mod-perl

我有一大块生产代码,有效。但是在我在虚拟机中设置一个新环境后,我遇到了一个问题 - 每次我需要上传一个二进制文件时,它都会变成乱码转换。

所以有一个sub,问题是:

sub save_uploaded_file
{
    # $file is obtained by param(zip) 
    my ($file) = @_;
    my ($fh, $fname) = tmpnam;
    my ($br, $buffer);
    # commenting out next 2 lines doesn't help either
    binmode $file, ':raw';
    binmode $fh, ':raw';
    while ($br = sysread($file, $buffer, 16384))
    {
        syswrite($fh, $buffer, $br);
    }
    close $fh;
    return $fname;
}

它用于上传zip档案,但它们上传为格式不正确(它们的大小总是比原始大)我用十六进制编辑器查看它们内部,发现有很多unicode替换字符,用utf-8编码,内部(EF BF BD)。

我发现读取的字节总数大于原始文件。所以问题始于sysread。

文本文件上传效果很好。

更新: 传输文件的前几个字节有二进制表示:

0000000: 504b 0304 1400 0000 0800 efbf bd1c efbf  PK..............
0000010: bd3e efbf bd1d 3aef bfbd efbf bd02 0000  .>....:.........
0000020: efbf bd05 0000 0500 1c00 422e 786d 6c55  ..........B.xmlU
0000030: 5409 0003 5cef bfbd efbf bd4d 18ef bfbd  T...\......M....
0000040: efbf bd4d 7578 0b00 0104 efbf bd03 0000  ...Mux..........
0000050: 0404 0000 00ef bfbd efbf bdef bfbd 6bef  ..............k.

原来的那个:

0000000: 504b 0304 1400 0000 0800 b81c d33e df1d  PK...........>..
0000010: 3aa0 8102 0000 a405 0000 0500 1c00 422e  :.............B.
0000020: 786d 6c55 5409 0003 5cd4 fc4d 18c7 fc4d  xmlUT...\..M...M
0000030: 7578 0b00 0104 e803 0000 0404 0000 008d  ux..............
0000040: 94df 6bdb 3010 c7df 03f9 1f0e e1bd 254e  ..k.0.........%N
0000050: ec74 6c85 d825 2bac 9442 379a c25e ca8a  .tl..%+..B7..^..

UPDATE2 正在运行的软件是centos 5.6,perl 5.8.8,apache 2.2.3

4 个答案:

答案 0 :(得分:0)

tmpnam是否返回标记为utf8的文件句柄?我想不是!

尝试binmode $fh, ":utf8" ;

答案 1 :(得分:0)

据我所知,Perl 5不会在其任何io层中交换替换字符。他们只有我知道的转换是换行转换(即文本图层)。您确定源文件不包含那些字节序列吗?

这段代码对我有用,对你有用吗?

#!/usr/bin/perl

use strict;
use warnings;

use File::Temp qw/:POSIX/;

sub save_uploaded_file {
    # $file is obtained by param(zip) 
    my ($file) = @_;
    my ($fh, $fname) = tmpnam;
    my ($br, $buffer);
    # commenting out next 2 lines doesn't help either
    binmode $file, ':raw'
        or die "could not change input file to raw: $!";
    binmode $fh, ':raw'
        or die "could not change tempfile to raw: $!";
    while ($br = sysread($file, $buffer, 16384)) {
        syswrite($fh, $buffer, $br);
    }
    close $fh
        or die "could not close tempfile: $!";
    return $fname;
}

sub check {
    my $input_file = shift;

    print "$input_file is ", -s $input_file, " bytes long\n"; 

    open my $fh, "<:raw", $input_file
        or die "could not open $input_file for reading: $!";

    my $bytes = sysread $fh, my $buf, 4096;

    print "read $bytes bytes: ", 
        join(", ", map { sprintf "%02x", $_ } unpack "C*", $buf),
        "\n";
}

my $input_file = "test.bin";

open my $fh, ">:raw", $input_file
    or die "could not open $input_file for writing: $!";

print $fh pack "CC", 0xFF, 0xFD
    or die "could not write to $input_file: $!";

close $fh
    or die "could not close $input_file: $!";

check $input_file;

open my $newfh, "<", $input_file
    or die "could not open $input_file: $!";
my $new_file = save_uploaded_file $newfh;

check $new_file;

答案 2 :(得分:0)

sysread正在以utf8的形式读取文件,但该文件不是utf8!前十个字节在“基本拉丁范围”(00-7F)中,因此它们被解释为相同的字节。下一个字节'b8'不在有效范围内,并且被'efbfbd'&lt; =&gt;取代。 \ x {FFFD}(表示解码错误的特殊字符)。 所有大于7F的字节都被\ x {FFFD}替换。

您使用的perl版本和操作系统是什么? 有一个标题为binmode $fh, ":raw" doesn't undo :utf8 on win32的报告(perl bug 75106)!

答案 3 :(得分:0)

我认为是同样的问题。错误似乎很早就发生了,因为当客户端尝试加载二进制文件时,我的代码都没有执行过。我通过在脚本的顶部将 STDIN 设置为“raw”(二进制)来修复它...

binmode(STDIN,':raw');