【问题标题】:How to upload binary files in mod_perl with CGI.pm?如何使用 CGI.pm 在 mod_perl 中上传二进制文件?
【发布时间】:2011-09-17 23:01:00
【问题描述】:

我有一大段生产代码,它可以工作。但是在我在虚拟机中设置了一个新环境后,我遇到了一个问题——每次我需要上传一个二进制文件时,它都会被 unicode 转换弄乱。

所以有一个子,问题是:

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..^..

更新2 运行软件为centos 5.6、perl 5.8.8、apache 2.2.3

【问题讨论】:

  • Nitpick,文件中没有 Unicode 字符之类的东西,Unicode 必须经过编码才能存在于文件中。常见的编码有 UTF-16、UTF-8 等。听起来你有 UTF-8 字符。
  • 感谢您的注意,我编辑了问题
  • 你的文本文件的编码是什么?
  • zip 中的文件以 CP1251 编码(这无关紧要,因为 zip 文件本身是二进制的)。脚本源文件采用 koi8-r 编码。

标签: perl unicode binary cgi mod-perl


【解决方案1】:

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

binmode(STDIN, ':raw') ;

【讨论】:

    【解决方案2】:

    据我所知,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;
    

    【讨论】:

    • 绝对,我在 xxd 中直观地比较了它们,替换字符在传输文件中的 11 个字节后开始出现,而在原始文件中则没有。我还可以跟踪,某些字节未更改(位于前 127 位范围内的字节)。
    • "\x{FFFD}" 替换了哪些字符?还是插入了"\x{FFFD}"
    • 我用这两个字节创建了文件,它们传输为“\x{efbfbdefbfbd}”
    • 如果我将它作为一个标量直接放到 $buffer 中,那么 syswrite 会抱怨宽字符。如果我把“\x{FF}\x{FD}”改为,它会直接进入文件原样(FF FD)。
    • "\x{FF}\x{FD}""\x{FFFD}" 不同。第一个是带有分音符号的拉丁文小写字母 Y 和带有 ACUTE 的拉丁文小写字母 Y。第二个是替换字符。 "\x{}" 表单使用 Unicode 代码点。代码点和字节之间没有直接关系。您必须知道用于编写代码点的编码。
    【解决方案3】:

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

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

    【讨论】:

    • encoding 是一个术语,适用于文本文件,这个是二进制的,实际上 ":raw" 应该有帮助。但正如我提到的问题 - 它没有
    • centos 5.6、perl 5.8.8、apache 2.2.3
    • @kravitz - 你用更新的 perl 版本尝试过代码吗?
    • 问题是我没有权限在主服务器上更新它,所以我需要让它在这个版本上工作。我有一种感觉,这个问题可能出在不正确的 mod_perl 配置上,因为我在主机上没有看到。
    【解决方案4】:

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

    试试binmode $fh, ":utf8" ;

    【讨论】:

    • 不,同样的事情。此外,我不需要 utf-8 编码,我想按原样复制字节,事实上,我的问题是它试图在 utf-8 中编码来自 sysread 的所有内容。
    猜你喜欢
    • 2012-11-08
    • 2011-02-24
    • 1970-01-01
    • 1970-01-01
    • 2015-05-13
    • 1970-01-01
    • 2015-01-24
    • 2012-07-07
    相关资源
    最近更新 更多