File Coverage

lib/Archive/Any/Lite.pm
Criterion Covered Total %
statement 77 100 77.0
branch 31 62 50.0
condition 15 21 71.4
subroutine 15 17 88.2
pod 6 6 100.0
total 144 206 69.9


line stmt bran cond sub pod time code
1             package Archive::Any::Lite;
2              
3 4     4   103635 use strict;
  4         11  
  4         149  
4 4     4   22 use warnings;
  4         6  
  4         107  
5 4     4   22 use File::Spec;
  4         12  
  4         4478  
6              
7             our $VERSION = '0.10';
8             our $IGNORE_SYMLINK;
9              
10             sub new {
11 11     11 1 69219   my ($class, $file, $opts) = @_;
12              
13 11         454   $file = File::Spec->rel2abs($file);
14 11 100       433   unless (-f $file) {
15 1         171     warn "$file not found\n";
16 1         10     return;
17               }
18              
19             # just for undocumented backward compat
20 10 50       44   my $type = !ref $opts ? $opts : '';
21              
22             # XXX: trust file extensions until I manage to make File::MMagic
23             # more reliable while fork()ing or I happen to find a decent
24             # and portable alternative to File::MMagic.
25              
26 10 50 100     266   my $handler =
    100 66        
27                 ($type && lc $type eq 'tar') || $file =~ /\.(?:tar|tar\.(?:gz|bz2)|gtar|tgz)$/ ? 'Archive::Any::Lite::Tar' :
28                 ($type && lc $type eq 'zip') || $file =~ /\.(?:zip)$/ ? 'Archive::Any::Lite::Zip' : undef;
29 10 50       34   unless ($handler) {
30 0         0     warn "No handler available for $file\n";
31 0         0     return;
32               }
33              
34               bless {
35 10 50       88     file => $file,
36                 handler => $handler,
37                 opts => ref $opts ? $opts : undef,
38               }, $class;
39             }
40              
41             sub extract {
42 6     6 1 20   my ($self, $dir, $opts) = @_;
43              
44 6   33     101   $self->{handler}->extract($self->{file}, $dir, $opts || $self->{opts});
45             }
46              
47             sub files {
48 42     42 1 20500   my $self = shift;
49 42         451   $self->{handler}->files($self->{file});
50             }
51              
52             sub is_impolite {
53 16     16 1 35   my $self = shift;
54              
55 16         41   my @files = $self->files;
56 16         113389   my $first_file = $files[0];
57 16         242   my ($first_dir) = File::Spec->splitdir($first_file);
58              
59 16 100       476   return grep( !/^\Q$first_dir\E/, @files ) ? 1 : 0;
60             }
61              
62             sub is_naughty {
63 14     14 1 145   my ($self) = shift;
64 14 100       58   return ( grep { m{^(?:/|(?:\./)*\.\./)} } $self->files ) ? 1 : 0;
  104         131774  
65             }
66              
67             sub type {
68 8     8 1 87143   my $self = shift;
69 8         80   my ($type) = lc $self->{handler} =~ /::(\w+)$/;
70 8         39   return $type;
71             }
72              
73             package Archive::Any::Lite::Tar;
74 4     4   6058 use Archive::Tar;
  4         616761  
  4         3873  
75              
76             sub files {
77 30     30   63   my ($self, $file) = @_;
78 30         199   Archive::Tar->list_archive($file);
79             }
80              
81             sub extract {
82 3     3   10   my ($self, $file, $dir, $opts) = @_;
83 3 100       13   $dir = '.' unless defined $dir;
84 3         21   my $tar = Archive::Tar->new;
85 3         37   my $fh;
86 3 50       54   if ($file =~ /\.(tgz|tar\.gz)$/) {
    0          
87 3         36     require IO::Zlib;
88 3 50       29     $fh = IO::Zlib->new($file, "rb") or do { warn "$file: $!"; return };
  0         0  
  0         0  
89               }
90               elsif ($file =~ /\.tar.bz2$/) {
91 0         0     require IO::Uncompress::Bunzip2;
92 0 0       0     $fh = IO::Uncompress::Bunzip2->new($file) or do { warn "$file: $!"; return };
  0         0  
  0         0  
93               }
94               else {
95 0 0       0     open $fh, '<', $file or do { warn "$file: $!"; return };
  0         0  
  0         0  
96 0         0     binmode $fh;
97               }
98              
99             # Archive::Tar is too noisy when an archive has minor glitches.
100             # Note also that $file can't hold the last error.
101 3         12847   local $Archive::Tar::WARN;
102 3         10   my %errors;
103               my $has_extracted;
104 3         11   my %read_opts = (limit => 1);
105 3 50       12   if ($opts) {
106 0         0     for (qw/limit md5 filter filter_cb extract/) {
107 0 0       0       if (exists $opts->{"tar_$_"}) {
    0          
108 0         0         $read_opts{$_} = $opts->{"tar_$_"};
109                   }
110                   elsif (exists $opts->{$_}) {
111 0         0         $read_opts{$_} = $opts->{$_};
112                   }
113                 }
114               }
115 3         21   until (eof $fh) {
116 17         1689     my @files = $tar->read($fh, undef, \%read_opts);
117 17 50       40151     if (my $error = $tar->error) {
118 0 0       0       warn $error unless $errors{$error}++;
119                 }
120 17 50 66     346     if (!@files && !$has_extracted) {
121 0         0       warn "No data could be read from $file";
122 0         0       return;
123                 }
124 17         45     for my $file (@files) {
125 20 100 66     73545       next if $IGNORE_SYMLINK && ($file->is_symlink or $file->is_hardlink);
      66        
126 19         372       my $path = File::Spec->catfile($dir, $file->prefix, $file->name);
127 19 50       878       $tar->extract_file($file, File::Spec->canonpath($path)) or do {
128 0 0       0         if (my $error = $tar->error) {
129 0 0       0           warn $error unless $errors{$error}++;
130                     }
131                   };
132                 }
133 17         141933     $has_extracted += @files;
134               }
135 3 50       409   return if %errors;
136 3         91   return 1;
137             }
138              
139 0     0   0 sub type { 'tar' }
140              
141             package Archive::Any::Lite::Zip;
142 4     4   10570 use Archive::Zip qw/:ERROR_CODES/;
  4         301130  
  4         1613  
143              
144             sub files {
145 12     12   28   my ($self, $file) = @_;
146 12 50       101   my $zip = Archive::Zip->new($file) or return;
147 12         74115   $zip->memberNames;
148             }
149              
150             sub extract {
151 3     3   11   my ($self, $file, $dir, $opts) = @_;
152 3 50       25   my $zip = Archive::Zip->new($file) or return;
153 3 100       11928   $dir = '.' unless defined $dir;
154 3         8   my $error = 0;
155 3         13   for my $member ($zip->members) {
156 20 100 100     101     next if $IGNORE_SYMLINK && $member->isSymbolicLink;
157 19         145     my $path = File::Spec->catfile($dir, $member->fileName);
158 19         562     my $ret = $member->extractToFileNamed(File::Spec->canonpath($path));
159 19 50       34096     $error++ if $ret != AZ_OK;
160               }
161 3 50       48   return if $error;
162 3         1386   return 1;
163             }
164              
165 0     0     sub type { 'zip' }
166              
167             1;
168              
169             __END__
170            
171             =head1 NAME
172            
173             Archive::Any::Lite - simple CPAN package extractor
174            
175             =head1 SYNOPSIS
176            
177             use strict;
178             use warnings;
179             use Archive::Any::Lite;
180            
181             local $Archive::Any::Lite::IGNORE_SYMLINK = 1; # for safety
182            
183             my $tarball = 'foo.tar.gz';
184             my $archive = Archive::Any::Lite->new($tarball);
185             $archive->extract('into/some/directory/');
186            
187             =head1 DESCRIPTION
188            
189             This is a fork of L<Archive::Any> by Michael Schwern and Clint Moore. The main difference is this works properly even when you fork(), and may require less memory to extract a tarball. On the other hand, this isn't pluggable (this only supports file formats used in the CPAN toolchains), and this doesn't check mime types (at least as of this writing).
190            
191             =head1 METHODS
192            
193             =head2 new
194            
195             my $archive = Archive::Any::Lite->new($archive_file);
196             my $archive = Archive::Any::Lite->new($archive_file, {tar_filter => qr/foo/});
197            
198             Creates an object.
199             You can pass an optional hash reference for finer control.
200            
201             =head2 extract
202            
203             $archive->extract;
204             $archive->extract($directory);
205             $archive->extract($directory, {tar_filter => qr/foo/});
206            
207             Extracts the files in the archive to the given $directory. If no $directory is given, it will go into the current working directory.
208            
209             You can pass an optional hash reference for finer control. If passed, options passed in C<new> will be ignored.
210            
211             =head2 files
212            
213             my @file = $archive->files;
214            
215             A list of files in the archive.
216            
217             =head2 is_impolite
218            
219             my $is_impolite = $archive->is_impolite;
220            
221             Checks to see if this archive is going to unpack into the current directory rather than create its own.
222            
223             =head2 is_naughty
224            
225             my $is_naughty = $archive->is_naughty;
226            
227             Checks to see if this archive is going to unpack outside the current directory.
228            
229             =head2 type
230            
231             Deprecated. For backward compatibility only.
232            
233             =head1 GLOBAL VARIABLE
234            
235             =head2 $IGNORE_SYMLINK
236            
237             If set to true, symlinks (and hardlinks for tarball) will be ignored.
238            
239             =head1 SEE ALSO
240            
241             L<Archive::Any>, L<Archive::Tar::Streamed>
242            
243             =head1 AUTHOR
244            
245             L<Archive::Any> is written by Michael G Schwern and Clint Moore.
246            
247             Kenichi Ishigaki, E<lt>ishigaki@cpan.orgE<gt>
248            
249             =head1 COPYRIGHT AND LICENSE
250            
251             Copyright (C) 2012 by Kenichi Ishigaki.
252            
253             This program is free software; you can redistribute it and/or
254             modify it under the same terms as Perl itself.
255            
256             =cut
257