File Coverage

blib/lib/CPAN/Mirror/Tiny/Archive.pm
Criterion Covered Total %
statement 26 210 12.3
branch 0 86 0.0
condition 0 48 0.0
subroutine 9 25 36.0
pod 0 4 0.0
total 35 373 9.3


line stmt bran cond sub pod time code
1             package CPAN::Mirror::Tiny::Archive;
2 1     1   16 use v5.24;
  1         4  
3 1     1   7 use warnings;
  1         2  
  1         70  
4 1     1   7 use experimental qw(lexical_subs signatures);
  1         2  
  1         13  
5              
6 1     1   196 use File::Basename ();
  1         2  
  1         18  
7 1     1   1132 use File::Temp ();
  1         20243  
  1         28  
8 1     1   363 use File::Which ();
  1         936  
  1         18  
9 1     1   480 use IPC::Run3 ();
  1         9458  
  1         1257  
10              
11 0     0 0   sub run3 ($cmd, $outfile = undef) {
  0            
  0            
  0            
12 0           my $out;
13 0 0         IPC::Run3::run3 $cmd, \undef, ($outfile ? $outfile : \$out), \my $err;
14 0           return ($?, $out, $err);
15             }
16              
17 0     0 0   sub new ($class, %argv) {
  0            
  0            
  0            
18 0           my $self = bless \%argv, $class;
19 0           $self->_init_untar;
20 0           $self->_init_unzip;
21 0           $self;
22             }
23              
24 0     0 0   sub unpack ($self, $file) {
  0            
  0            
  0            
25 0 0         my $method = $file =~ /\.zip$/ ? $self->{method}{unzip} : $self->{method}{untar};
26 0           $self->$method($file);
27             }
28              
29 0     0 0   sub describe ($self) {
  0            
  0            
30             +{
31 0           map { ($_, $self->{$_}) }
32 0           grep $self->{$_},
33             qw(tar gzip bzip2 Archive::Tar unzip Archive::Zip),
34             };
35             }
36              
37 0     0     sub _init_untar ($self) {
  0            
  0            
38              
39 0   0       my $tar = $self->{tar} = File::Which::which('gtar') || File::Which::which("tar");
40 0 0         if ($tar) {
41 0           my ($exit, $out, $err) = run3 [$tar, '--version'];
42 0 0         $self->{tar_kind} = $out =~ /bsdtar/ ? "bsd" : "gnu";
43 0 0 0       $self->{tar_bad} = 1 if $out =~ /GNU.*1\.13/i || $^O eq 'MSWin32' || $^O eq 'solaris' || $^O eq 'hpux';
      0        
      0        
44             }
45              
46 0 0 0       if ($tar and !$self->{tar_bad}) {
47 0           $self->{method}{untar} = *_untar;
48 0 0         return if !$self->{_init_all};
49             }
50              
51 0           my $gzip = $self->{gzip} = File::Which::which("gzip");
52 0           my $bzip2 = $self->{bzip2} = File::Which::which("bzip2");
53              
54 0 0 0       if ($tar && $gzip && $bzip2) {
      0        
55 0           $self->{method}{untar} = *_untar_bad;
56 0 0         return if !$self->{_init_all};
57             }
58              
59 0 0         if (eval { require Archive::Tar }) {
  0            
60 0           $self->{"Archive::Tar"} = Archive::Tar->VERSION;
61 0           $self->{method}{untar} = *_untar_module;
62 0 0         return if !$self->{_init_all};
63             }
64              
65 0 0         return if $self->{_init_all};
66 0     0     $self->{method}{untar} = sub (@) { die "There is no backend for untar" };
  0            
  0            
  0            
67             }
68              
69 0     0     sub _init_unzip ($self) {
  0            
  0            
70              
71 0           my $unzip = $self->{unzip} = File::Which::which("unzip");
72 0 0         if ($unzip) {
73 0           $self->{method}{unzip} = *_unzip;
74 0 0         return if !$self->{_init_all};
75             }
76              
77 0 0         if (eval { require Archive::Zip }) {
  0            
78 0           $self->{"Archive::Zip"} = Archive::Zip->VERSION;
79 0           $self->{method}{unzip} = *_unzip_module;
80 0 0         return if !$self->{_init_all};
81             }
82              
83 0 0         return if $self->{_init_all};
84 0     0     $self->{method}{unzip} = sub (@) { die "There is no backend for unzip" };
  0            
  0            
  0            
85             }
86              
87 0     0     sub _untar ($self, $file) {
  0            
  0            
  0            
88 0           my $wantarray = wantarray;
89              
90 0           my ($exit, $out, $err);
91             {
92 0 0         my $ar = $file =~ /\.bz2$/ ? 'j' : 'z';
  0            
93 0           ($exit, $out, $err) = run3 [$self->{tar}, "${ar}tf", $file];
94 0 0         last if $exit != 0;
95 0           my $root = $self->_find_tarroot(split /\r?\n/, $out);
96 0           ($exit, $out, $err) = run3 [$self->{tar}, "${ar}xf", $file, "-o"];
97 0 0 0       return $root if $exit == 0 and -d $root;
98             }
99 0 0         return if !$wantarray;
100 0   0       return (undef, $err || $out);
101             }
102              
103 0     0     sub _untar_bad ($self, $file) {
  0            
  0            
  0            
104 0           my $wantarray = wantarray;
105 0           my ($exit, $out, $err);
106             {
107 0 0         my $ar = $file =~ /\.bz2$/ ? $self->{bzip2} : $self->{gzip};
  0            
108 0           my $temp = File::Temp->new(SUFFIX => '.tar', EXLOCK => 0);
109 0           ($exit, $out, $err) = run3 [$ar, "-dc", $file], $temp->filename;
110 0 0         last if $exit != 0;
111              
112             # XXX /usr/bin/tar: Cannot connect to C: resolve failed
113 0 0 0       my @opt = $^O eq 'MSWin32' && $self->{tar_kind} ne "bsd" ? ('--force-local') : ();
114 0           ($exit, $out, $err) = run3 [$self->{tar}, @opt, "-tf", $temp->filename];
115 0 0 0       last if $exit != 0 || !$out;
116 0           my $root = $self->_find_tarroot(split /\r?\n/, $out);
117 0           ($exit, $out, $err) = run3 [$self->{tar}, @opt, "-xf", $temp->filename, "-o"];
118 0 0 0       return $root if $exit == 0 and -d $root;
119             }
120 0 0         return if !$wantarray;
121 0   0       return (undef, $err || $out);
122             }
123              
124 0     0     sub _untar_module ($self, $file) {
  0            
  0            
  0            
125 0           my $wantarray = wantarray;
126 1     1   7 no warnings 'once';
  1         1  
  1         472  
127 0           local $Archive::Tar::WARN = 0;
128 0           my $t = Archive::Tar->new;
129             {
130 0           my $ok = $t->read($file);
  0            
131 0 0         last if !$ok;
132 0           my $root = $self->_find_tarroot($t->list_files);
133 0           my @file = $t->extract;
134 0 0 0       return $root if @file and -d $root;
135             }
136 0 0         return if !$wantarray;
137 0           return (undef, $t->error);
138             }
139              
140 0     0     sub _find_tarroot ($self, $root, @others) {
  0            
  0            
  0            
  0            
141             FILE: {
142 0           chomp $root;
  0            
143 0           $root =~ s!^\./!!;
144 0           $root =~ s{^(.+?)/.*$}{$1};
145 0 0         if (!length $root) { # archive had ./ as the first entry, so try again
146 0           $root = shift @others;
147 0 0         redo FILE if $root;
148             }
149             }
150 0           $root;
151             }
152              
153 0     0     sub _unzip ($self, $file) {
  0            
  0            
  0            
154 0           my $wantarray = wantarray;
155              
156 0           my ($exit, $out, $err);
157             {
158 0           ($exit, $out, $err) = run3 [$self->{unzip}, '-t', $file];
  0            
159 0 0         last if $exit != 0;
160 0           my $root = $self->_find_ziproot(split /\r?\n/, $out);
161 0           ($exit, $out, $err) = run3 [$self->{unzip}, '-q', $file];
162 0 0 0       return $root if $exit == 0 and -d $root;
163             }
164 0 0         return if !$wantarray;
165 0   0       return (undef, $err || $out);
166             }
167              
168 0     0     sub _unzip_module ($self, $file) {
  0            
  0            
  0            
169 0           my $wantarray = wantarray;
170              
171 1     1   6 no warnings 'once';
  1         2  
  1         437  
172 0     0     my $err = ''; local $Archive::Zip::ErrorHandler = sub (@args) { $err .= "@args" };
  0            
  0            
  0            
  0            
  0            
173 0           my $zip = Archive::Zip->new;
174             UNZIP: {
175 0           my $status = $zip->read($file);
  0            
176 0 0         last UNZIP if $status != Archive::Zip::AZ_OK();
177 0           for my $member ($zip->members) {
178 0           my $af = $member->fileName;
179 0 0         next if $af =~ m!^(/|\.\./)!;
180 0           my $status = $member->extractToFileNamed($af);
181 0 0         last UNZIP if $status != Archive::Zip::AZ_OK();
182             }
183 0           my ($root) = $zip->membersMatching(qr{^[^/]+/$});
184 0 0         last UNZIP if !$root;
185 0           $root = $root->fileName;
186 0           $root =~ s{/$}{};
187 0 0         return $root if -d $root;
188             }
189 0 0         return if !$wantarray;
190 0           return (undef, $err);
191             }
192              
193 0     0     sub _find_ziproot ($self, $ignored, $root, @others) {
  0            
  0            
  0            
  0            
  0            
194             FILE: {
195 0           chomp $root;
  0            
196 0 0         if ($root !~ s{^\s+testing:\s+([^/]+)/.*?\s+OK$}{$1}) {
197 0           $root = shift @others;
198 0 0         redo FILE if $root;
199             }
200             }
201 0           $root;
202             }
203              
204             1;
205              
206             __END__