Subject: | [PATCH] add support for Archive::Zip->addFileHandle |
This allows secure adding of temp files on systems unfortunate enough to
be vulnerable to temp file attacks.
Subject: | archive_zip_add_file_handle.patch |
diff -Naur old/lib/Archive/Zip/Archive.pm new/lib/Archive/Zip/Archive.pm
--- old/lib/Archive/Zip/Archive.pm 2012-01-23 17:26:28.000000000 +1100
+++ new/lib/Archive/Zip/Archive.pm 2012-02-25 19:22:16.563035873 +1100
@@ -273,6 +273,30 @@
return $newMember;
}
+sub addFileHandle {
+ my $self = shift;
+
+ my ( $handle, $name, $compressionLevel );
+ if ( ref( $_[0] ) eq 'HASH' ) {
+ $handle = $_[0]->{fileHandle};
+ $name = $_[0]->{zipName};
+ $compressionLevel = $_[0]->{compressionLevel};
+ }
+ else {
+ ( $handle, $name, $compressionLevel ) = @_;;
+ }
+
+ my $newMember = $self->ZIPMEMBERCLASS->newFromFileHandle(
+ $handle, $name
+ );
+ if (defined $newMember) {
+ $newMember->desiredCompressionLevel($compressionLevel);
+ return $self->addMember($newMember);
+ } else {
+ return undef;
+ }
+}
+
sub addString {
my $self = shift;
diff -Naur old/lib/Archive/Zip/Member.pm new/lib/Archive/Zip/Member.pm
--- old/lib/Archive/Zip/Member.pm 2012-01-23 17:26:28.000000000 +1100
+++ new/lib/Archive/Zip/Member.pm 2012-02-24 21:39:00.573469221 +1100
@@ -83,6 +83,23 @@
return $self;
}
+sub newFromFileHandle {
+ my $class = shift;
+
+ my ( $fileHandle, $zipName );
+ if ( ref( $_[0] ) eq 'HASH' ) {
+ $fileHandle = $_[0]->{fileHandle};
+ $zipName = $_[0]->{zipName};
+ }
+ else {
+ ( $fileHandle, $zipName ) = @_;
+ }
+
+ my $self = $class->NEWFILEMEMBERCLASS->_newFromFileHandle( $fileHandle,
+ $zipName );
+ return $self;
+}
+
sub newDirectoryNamed {
my $class = shift;
diff -Naur old/lib/Archive/Zip/NewFileMember.pm new/lib/Archive/Zip/NewFileMember.pm
--- old/lib/Archive/Zip/NewFileMember.pm 2012-01-23 17:26:28.000000000 +1100
+++ new/lib/Archive/Zip/NewFileMember.pm 2012-02-25 19:22:01.826845197 +1100
@@ -38,6 +38,32 @@
return $self;
}
+# Given a file handle, set up for eventual writing.
+sub _newFromFileHandle {
+ my $class = shift;
+ my $fileHandle = shift; # local FS format
+ my $newName = shift;
+ return undef unless ( (ref $fileHandle) eq 'GLOB' );
+ return undef unless ( defined $newName );
+ return undef if ( ref $newName );
+ return undef unless ( stat($fileHandle) && -r _ && !-d _ );
+ my $self = $class->new(@_);
+ $self->{'fileName'} = $newName;
+ $self->{'fh'} = $fileHandle;
+ $self->{'compressionMethod'} = COMPRESSION_STORED;
+ my @stat = stat(_);
+ $self->{'compressedSize'} = $self->{'uncompressedSize'} = $stat[7];
+ $self->desiredCompressionMethod(
+ ( $self->compressedSize() > 0 )
+ ? COMPRESSION_DEFLATED
+ : COMPRESSION_STORED
+ );
+ $self->unixFileAttributes( $stat[2] );
+ $self->setLastModFileDateTimeFromUnix( $stat[9] );
+ $self->isTextFile( -T _ );
+ return $self;
+}
+
sub rewindData {
my $self = shift;
diff -Naur old/lib/Archive/Zip.pm new/lib/Archive/Zip.pm
--- old/lib/Archive/Zip.pm 2012-01-23 17:26:28.000000000 +1100
+++ new/lib/Archive/Zip.pm 2012-02-24 22:15:13.115009959 +1100
@@ -587,6 +587,10 @@
my $string_member = $zip->addString( 'This is a test', 'stringMember.txt' );
$string_member->desiredCompressionMethod( COMPRESSION_DEFLATED );
+ # Add a file from file handle
+ my $handle = FileHandle->new( 'abc.pl', Fcntl::O_RDONLY() ) or die 'open error';
+ my $handle_member = $zip->addFileHandle( $handle, 'AddedbyHandle.pl' );
+
# Add a file from disk
my $file_member = $zip->addFile( 'xyz.pl', 'AnotherName.pl' );
@@ -1146,8 +1150,8 @@
Append a member (possibly from another zip file) to the zip
file. Returns the new member. Generally, you will use
-addFile(), addDirectory(), addFileOrDirectory(), addString(),
-or read() to add members.
+addFile(), addFileHandle, addDirectory(), addFileOrDirectory(),
+addString(), or read() to add members.
# Move member named 'abc' to end of zip:
my $member = $zip->removeMember( 'abc' );
@@ -1189,6 +1193,20 @@
tools as well as introduce a security hole and make the zip
harder to use.
+=item addFileHandle( $fileHandle, $newName [, $compressionLevel ] )
+
+=item addFileHandle( { fileHandle => $fileHandle,
+ zipName => $newName [, compressionLevel => $compressionLevel } ] )
+
+Append a member whose data comes from an external file handle,
+returning the member or undef. The member will have its file
+name set to the value of $newName, and its desiredCompressionMethod
+set to COMPRESSION_DEFLATED. The file attributes and last modification
+time will be set from the file handle.
+C<$newName> must be in Zip name format (i.e. Unix).
+The text mode bit will be set if the contents appears to be
+text (as returned by the C<-T> perl operator).
+
=item addDirectory( $directoryName [, $fileName ] )
=item addDirectory( { directoryName => $directoryName
@@ -1605,7 +1623,7 @@
=head2 Member Class Methods
Several constructors allow you to construct members without adding
-them to a zip archive. These work the same as the addFile(),
+them to a zip archive. These work the same as the addFile(), addFileHandle(),
addDirectory(), and addString() zip instance methods described above,
but they don't add the new members to a zip.
@@ -1630,6 +1648,15 @@
my $member = Archive::Zip::Member->newFromFile( 'xyz.txt' );
+=item newFromFileHandle( $fileHandle [, $zipName ] )
+
+=item newFromFileHandle( { fileHandle => $fileHandle [, zipName => $zipName ] } )
+
+Construct a new member from the given handle. Returns undef on
+error.
+
+ my $member = Archive::Zip::Member->newFromFileHandle( FileHandle->new( 'xyz.txt', Fcntl::O_RDONLY() ) );
+
=item newDirectoryNamed( $directoryName [, $zipname ] )
=item newDirectoryNamed( { directoryName => $directoryName
diff -Naur old/README new/README
--- old/README 2012-01-23 17:26:28.000000000 +1100
+++ new/README 2012-02-24 21:39:29.539807915 +1100
@@ -863,6 +863,14 @@
my $member = Archive::Zip::Member->newFromFile( 'xyz.txt' );
+ newFromFileHandle( $fileHandle [, $zipName ] )
+ newFromFileHandle( { fileHandle => $fileHandle [, zipName => $zipName ] } )
+
+ Construct a new member from the given handle. Returns undef on error.
+
+ my $member = Archive::Zip::Member->newFromFileHandle(
+ FileHandle->new( 'xyz.txt', Fcntl::O_RDONLY() ) );
+
newDirectoryNamed( $directoryName [, $zipname ] )
newDirectoryNamed( { directoryName => $directoryName [, zipName =>
$zipname ] } )
diff -Naur old/t/17_handle.t new/t/17_handle.t
--- old/t/17_handle.t 1970-01-01 10:00:00.000000000 +1000
+++ new/t/17_handle.t 2012-02-25 19:23:14.075781320 +1100
@@ -0,0 +1,49 @@
+#!/usr/bin/perl
+
+# Main testing for Archive::Zip
+
+use strict;
+BEGIN {
+ $| = 1;
+ $^W = 1;
+}
+
+use Archive::Zip qw( :ERROR_CODES :CONSTANTS );
+use FileHandle;
+use File::Path;
+use File::Spec;
+use File::Temp;
+use Fcntl;
+
+use Test::More tests => 16;
+
+my $zip = Archive::Zip->new();
+isa_ok( $zip, 'Archive::Zip' );
+
+# members # Archive::Zip::Archive
+my @members = $zip->members;
+is(scalar(@members), 0, '->members is 0' );
+
+my $addHandle = File::Temp::tempfile('ArchiveZip_17_1_XXXXXXXXXXX');
+isa_ok( $addHandle, 'GLOB');
+my $addFileContents = "This is the \r\ncontents of the first file";
+ok($addHandle->print($addFileContents), 'Added content to add file handle');
+ok($addHandle->seek(0, Fcntl::SEEK_SET()), 'Moved add file handle back to start of file');
+my $first_file_name = 'file_1.txt';
+ok(!defined $zip->addFileHandle('not a glob', $first_file_name), 'Archive::Zip::addFileHandle orrectly returned undef when the first argument is not a glob');
+ok(!defined $zip->addFileHandle($addHandle), 'Archive::Zip::addFileHandle correctly returned undef when the second argument is missing');
+ok(!defined $zip->addFileHandle($addHandle, {}), 'Archive::Zip::addFileHandle correctly returned undef when the second argument is a reference');
+my $member = $zip->addFileHandle($addHandle, $first_file_name);
+ok(defined($member), "Added add file handle to zip at '$first_file_name'");
+is($member->desiredCompressionMethod(), COMPRESSION_DEFLATED, 'Correctly added the add file handle as deflated');
+@members = $zip->members;
+is(scalar(@members), 1, '->members is 1' );
+$member = $zip->memberNamed($first_file_name);
+isa_ok($member, 'Archive::Zip::Member', "Correctly extracted '$first_file_name' as a member");
+my $extractHandle = File::Temp::tempfile('ArchiveZip_17_2_XXXXXXXXXXX');
+ok($member->extractToFileHandle($extractHandle) == AZ_OK(), "Extracted '$first_file_name' as a file handle");
+ok($extractHandle->seek(0, Fcntl::SEEK_SET()), 'Moved extracted file handle back to start of file');
+my $extractContent;
+my $length = length($addFileContents) * 2;
+ok($extractHandle->read($extractContent, $length), "Attempted to read $length bytes from extracted '$first_file_name' file handle:$!");
+ok($extractContent eq $addFileContents, "Extracted content matches added content");