File Coverage

blib/lib/Alien/Build/Plugin/Fetch/CurlCommand.pm
Criterion Covered Total %
statement 69 104 66.3
branch 14 38 36.8
condition 4 9 44.4
subroutine 15 19 78.9
pod 3 3 100.0
total 105 173 60.6


line stmt bran cond sub pod time code
1             package Alien::Build::Plugin::Fetch::CurlCommand;
2              
3 3     3   1704 use strict;
  3         7  
  3         81  
4 3     3   17 use warnings;
  3         5  
  3         70  
5 3     3   51 use 5.008004;
  3         53  
6 3     3   358 use Alien::Build::Plugin;
  3         7  
  3         18  
7 3     3   396 use File::Which qw( which );
  3         889  
  3         167  
8 3     3   18 use Path::Tiny qw( path );
  3         7  
  3         154  
9 3     3   19 use Capture::Tiny qw( capture );
  3         6  
  3         136  
10 3     3   17 use File::Temp qw( tempdir );
  3         6  
  3         171  
11 3     3   19 use List::Util 1.33 qw( any pairmap );
  3         62  
  3         164  
12 3     3   802 use File::chdir;
  3         5173  
  3         3961  
13              
14             # ABSTRACT: Plugin for fetching files using curl
15             our $VERSION = '2.47'; # VERSION
16              
17              
18             sub curl_command
19             {
20 4 50   4 1 24 defined $ENV{CURL} ? scalar which($ENV{CURL}) : scalar which('curl');
21             }
22              
23             has ssl => 0;
24             has _see_headers => 0;
25             has '+url' => '';
26              
27             # when bootstrapping we have to specify this plugin as a prereq
28             # 1 is the default so that when this plugin is used directly
29             # you also get the prereq
30             has bootstrap_ssl => 1;
31              
32              
33             sub protocol_ok
34             {
35 0     0 1 0 my($class, $protocol) = @_;
36 0         0 my $curl = $class->curl_command;
37 0 0       0 return 0 unless defined $curl;
38             my($out, $err, $exit) = capture {
39 0     0   0 system $curl, '--version';
40 0         0 };
41              
42             {
43             # make sure curl supports the -J option.
44             # CentOS 6 for example is recent enough
45             # that it does not. gh#147, gh#148, gh#149
46 0         0 local $CWD = tempdir( CLEANUP => 1 );
  0         0  
47 0         0 my $file1 = path('foo/foo.txt');
48 0         0 $file1->parent->mkpath;
49 0         0 $file1->spew("hello world\n");
50 0         0 my $url = 'file://' . $file1->absolute;
51             my($out, $err, $exit) = capture {
52 0     0   0 system $curl, '-O', '-J', $url;
53 0         0 };
54 0         0 my $file2 = $file1->parent->child($file1->basename);
55 0         0 unlink "$file1";
56 0         0 unlink "$file2";
57 0         0 rmdir($file1->parent);
58 0 0       0 return 0 if $exit;
59             }
60              
61 0         0 foreach my $line (split /\n/, $out)
62             {
63 0 0       0 if($line =~ /^Protocols:\s*(.*)\s*$/)
64             {
65 0         0 my %proto = map { $_ => 1 } split /\s+/, $1;
  0         0  
66 0 0       0 return $proto{$protocol} if $proto{$protocol};
67             }
68             }
69 0         0 return 0;
70             }
71              
72             sub init
73             {
74 1     1 1 3 my($self, $meta) = @_;
75              
76 1   33     4 $meta->prop->{start_url} ||= $self->url;
77 1         3 $self->url($meta->prop->{start_url});
78 1 50       3 $self->url || Carp::croak('url is a required property');
79              
80 1 50       3 $meta->add_requires('configure', 'Alien::Build::Plugin::Fetch::CurlCommand' => '1.19')
81             if $self->bootstrap_ssl;
82              
83             $meta->register_hook(
84             fetch => sub {
85 3     3   9 my($build, $url, %options) = @_;
86 3   66     10 $url ||= $self->url;
87              
88 3         19 my($scheme) = $url =~ /^([a-z0-9]+):/i;
89              
90 3 50       12 if($scheme =~ /^https?$/)
91             {
92 3         10 local $CWD = tempdir( CLEANUP => 1 );
93              
94 3         1005 my @writeout = (
95             "ab-filename :%{filename_effective}",
96             "ab-content_type :%{content_type}",
97             "ab-url :%{url_effective}",
98             );
99              
100 3         23 $build->log("writeout: $_\\n") for @writeout;
101 3         15 path('writeout')->spew(join("\\n", @writeout));
102              
103 3         1188 my @headers;
104 3 50       14 if(my $headers = $options{http_headers})
105             {
106 0 0       0 if(ref $headers eq 'ARRAY')
107             {
108 0         0 @headers = pairmap { -H => "$a: $b" } @$headers;
  0         0  
109             }
110             else
111             {
112 0         0 $build->log("Fetch for $url with http_headers that is not an array reference");
113             }
114             }
115              
116 3         12 my @command = (
117             $self->curl_command,
118             '-L', '-f', '-O', '-J',
119             -w => '@writeout',
120             @headers,
121             );
122              
123 3 50       54 push @command, -D => 'head' if $self->_see_headers;
124              
125 3         9 push @command, $url;
126              
127 3         9 my($stdout, $stderr) = $self->_execute($build, @command);
128              
129 2 50       11 my %h = map { /^ab-(.*?)\s*:(.*)$/ ? ($1 => $2) : () } split /\n/, $stdout;
  6         41  
130              
131 2 50       35 if(-e 'head')
132             {
133 0         0 $build->log(" ~ $_ => $h{$_}") for sort keys %h;
134 0         0 $build->log(" header: $_") for path('headers')->lines;
135             }
136              
137 2         11 my($type) = split /;/, $h{content_type};
138              
139 2 100       7 if($type eq 'text/html')
140             {
141             return {
142             type => 'html',
143             base => $h{url},
144 1         6 content => scalar path($h{filename})->slurp,
145             };
146             }
147             else
148             {
149             return {
150             type => 'file',
151             filename => $h{filename},
152 1         6 path => path($h{filename})->absolute->stringify,
153             };
154             }
155             }
156             # elsif($scheme eq 'ftp')
157             # {
158             # if($url =~ m{/$})
159             # {
160             # my($stdout, $stderr) = $self->_execute($build, $self->curl_command, -l => $url);
161             # chomp $stdout;
162             # return {
163             # type => 'list',
164             # list => [
165             # map { { filename => $_, url => "$url$_" } } sort split /\n/, $stdout,
166             # ],
167             # };
168             # }
169             #
170             # my $first_error;
171             #
172             # {
173             # local $CWD = tempdir( CLEANUP => 1 );
174             #
175             # my($filename) = $url =~ m{/([^/]+)$};
176             # $filename = 'unknown' if (! defined $filename) || ($filename eq '');
177             # my($stdout, $stderr) = eval { $self->_execute($build, $self->curl_command, -o => $filename, $url) };
178             # $first_error = $@;
179             # if($first_error eq '')
180             # {
181             # return {
182             # type => 'file',
183             # filename => $filename,
184             # path => path($filename)->absolute->stringify,
185             # };
186             # }
187             # }
188             #
189             # {
190             # my($stdout, $stderr) = eval { $self->_execute($build, $self->curl_command, -l => "$url/") };
191             # if($@ eq '')
192             # {
193             # chomp $stdout;
194             # return {
195             # type => 'list',
196             # list => [
197             # map { { filename => $_, url => "$url/$_" } } sort split /\n/, $stdout,
198             # ],
199             # };
200             # };
201             # }
202             #
203             # $first_error ||= 'unknown error';
204             # die $first_error;
205             #
206             # }
207             else
208             {
209 0         0 die "scheme $scheme is not supported by the Fetch::CurlCommand plugin";
210             }
211              
212             },
213 1 50       4 ) if $self->curl_command;
214              
215 1         2 $self;
216             }
217              
218             sub _execute
219             {
220 3     3   10 my($self, $build, @command) = @_;
221 3         16 $build->log("+ @command");
222             my($stdout, $stderr, $err) = capture {
223 3     3   2613 system @command;
224 3         1028 $?;
225 3         87 };
226 3 100       2055 if($err)
227             {
228 1         4 chomp $stderr;
229 1         7 $build->log($_) for split /\n/, $stderr;
230 1 50 33 0   9 if($stderr =~ /Remote filename has no length/ && !!(any { /^-O$/ } @command))
  0         0  
231             {
232             my @new_command = map {
233 0 0       0 /^-O$/ ? ( -o => 'index.html' ) : /^-J$/ ? () : ($_)
  0 0       0  
234             } @command;
235 0         0 return $self->_execute($build, @new_command);
236             }
237 1         12 die "error in curl fetch";
238             }
239 2         14 ($stdout, $stderr);
240             }
241              
242             1;
243              
244             __END__