Как загрузить двоичные файлы в mod_perl с CGI.pm?
У меня есть большой кусок производственного кода, который работает. Но после того, как я настроил новую среду на виртуальной машине, у меня возникла одна проблема - каждый раз, когда мне нужно было загрузить двоичный файл, он путался с преобразованиями в Юникод.
Таким образом, есть саб, где проблема:
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-архивов, но они загружены как искаженные (их размер всегда больше, чем в оригинале), и я заглянул внутрь них с помощью шестнадцатеричного редактора и обнаружил, что есть много заменяющих символов Юникода, закодированных в 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.
4 ответа
Насколько я знаю, 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;
У меня было то, что я считаю такой же проблемой. Ошибка, казалось, возникала очень рано, потому что ни один из моего кода никогда не выполнялся, когда клиент пытался загрузить двоичный файл. Я исправил это, установив STDIN в "raw" (бинарный), в верхней части скрипта…
binmode (STDIN, ': raw');
sysread читает файл как utf8, но файл не utf8! первые десять байтов находятся в "базовом латинском диапазоне" (00-7F), поэтому они интерпретируются как один и тот же байт. Следующий байт 'b8' находится вне допустимого диапазона и заменяется на 'efbfbd' <=> \x{FFFD} (специальный символ, указывающий на ошибку декодирования). Все байты больше 7F заменяются на \x{FFFD}.
Какую версию Perl и ОС вы используете? Есть отчет (ошибка perl 75106) с заголовком binmode $fh, ":raw" doesn't undo :utf8 on win32
!
Есть ли tmpnam
возвращает дескриптор файла, помеченный как utf8? Думаю, нет!
пытаться binmode $fh, ":utf8" ;