Skip Menu |

This queue is for tickets about the Archive-Zip CPAN distribution.

Report information
The Basics
Id: 75297
Status: new
Priority: 0/
Queue: Archive-Zip

People
Owner: Nobody in particular
Requestors: DDICK [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: Normal
Broken in: (no value)
Fixed in: (no value)



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");