File Coverage

blib/lib/Alien/Base/ModuleBuild/Repository/HTTP.pm
Criterion Covered Total %
statement 117 129 90.7
branch 32 48 66.6
condition 22 34 64.7
subroutine 19 19 100.0
pod 0 10 0.0
total 190 240 79.1


line stmt bran cond sub pod time code
1             package Alien::Base::ModuleBuild::Repository::HTTP;
2              
3 3     3   201336 use strict;
  3         11  
  3         76  
4 3     3   14 use warnings;
  3         6  
  3         59  
5 3     3   13 use Carp;
  3         6  
  3         145  
6 3     3   1827 use HTTP::Tiny;
  3         89135  
  3         113  
7 3     3   21 use Scalar::Util qw( blessed );
  3         5  
  3         178  
8 3     3   1570 use URI;
  3         12197  
  3         88  
9 3     3   489 use Alien::Base::ModuleBuild::Utils;
  3         6  
  3         125  
10 3     3   18 use parent 'Alien::Base::ModuleBuild::Repository';
  3         4  
  3         16  
11              
12             # ABSTRACT: HTTP repository handler
13             our $VERSION = '1.16_01'; # TRIAL VERSION
14             $VERSION = eval $VERSION; ## no critic (BuiltinFunctions::ProhibitStringyEval)
15              
16             our $Has_HTML_Parser = eval { require HTML::LinkExtor; 1 };
17              
18 1     1 0 5461 sub is_network_fetch { 1 }
19              
20             sub is_secure_fetch {
21 12     12 0 30 my($self) = @_;
22              
23 12 100 100     119 (defined $self->{exact_filename} && $self->{exact_filename} =~ /^https:/) || ($self->{protocol}||'http') eq 'https';
      100        
24             }
25              
26             sub connection {
27              
28 13     13 0 44 my $self = shift;
29              
30             return $self->{connection}
31 13 100       47 if $self->{connection};
32              
33             # allow easy use of HTTP::Tiny subclass
34 9   100     43 $self->{protocol_class} ||= 'HTTP::Tiny';
35 9         18 my $module = $self->{protocol_class};
36 9         42 $module =~ s{::}{/}g;
37 9         20 $module .= '.pm';
38 9 100       18 eval { require $module; 1 }
  9         621  
  8         25  
39             or croak "Could not load protocol_class '$self->{protocol_class}': $@";
40              
41 8         18 my %args;
42              
43 8 50       62 if($self->{protocol_class}->isa('HTTP::Tiny'))
    0          
44             {
45 8   100     20 $args{agent} = "Alien-Base-ModuleBuild/HTTP::Tiny/@{[ $Alien::Base::ModuleBuild::VERSION || 'dev' ]}";
  8         100  
46 8         770 require Alien::Base::ModuleBuild;
47 8 100       52 $args{verify_SSL} = 1 if Alien::Base::ModuleBuild->alien_download_rule =~ /encrypt/;
48             }
49             elsif($self->{protocol_class}->isa('LWP::UserAgent'))
50             {
51 0   0     0 $args{agent} = "Alien-Base-ModuleBuild/LWP::UserAgent/@{[ $Alien::Base::ModuleBuild::VERSION || 'dev' ]}";
  0         0  
52             # Note this is the default for recent LWP
53 0 0       0 $args{ssl_opts} = { verify_hostname => 1 } if Alien::Base::ModuleBuild->alien_download_rule =~ /encrypt/;
54             }
55             else
56             {
57 0         0 die "unsupported protocol class: @{[ $self->{protocol_class} ]}";
  0         0  
58             }
59              
60 8         57 my $http = $self->{protocol_class}->new(%args);
61              
62 8         616 $self->{connection} = $http;
63              
64 8         40 return $http;
65              
66             }
67              
68             sub get_file {
69 7     7 0 17 my $self = shift;
70 7   33     18 my $file = shift || croak "Must specify file to download";
71              
72 7         21 my $protocol = $self->protocol;
73 7         12 my $host = $self->{host};
74 7         20 my $from = $self->location;
75              
76 7         21 my $uri = $self->build_uri($protocol, $host, $from, $file);
77 7         620 $file = ($uri->path_segments())[-1];
78              
79 7 50 33     251 die "Attempted downgrad from https to http on URL $uri" if $self->is_secure_fetch && $uri !~ /^https:/;
80              
81 7         21 my $res = $self->connection->mirror($uri, $file);
82 7         56 my ( $is_error, $content, $headers ) = $self->check_http_response( $res );
83 7 50       17 croak "Download failed: " . $content if $is_error;
84              
85 7         13 my $disposition = $headers->{"content-disposition"};
86 7 50 66     41 if ( defined($disposition) && ($disposition =~ /filename="([^"]+)"/ || $disposition =~ /filename=([^\s]+)/)) {
      66        
87 4         9 my $new_filename = $1;
88 4         89 rename $file, $new_filename;
89 4         18 $self->{new_filename} = $new_filename;
90             }
91              
92 7         30 return $file;
93             }
94              
95             sub list_files {
96 1     1 0 8 my $self = shift;
97              
98 1         10 my $protocol = $self->protocol;
99 1         6 my $host = $self->host;
100 1         5 my $location = $self->location;
101 1         6 my $uri = $self->build_uri($protocol, $host, $location);
102              
103 1 50 33     97 die "Attempted downgrad from https to http on URL $uri" if $self->is_secure_fetch && $uri !~ /^https:/;
104              
105 1         5 my $res = $self->connection->get($uri);
106              
107 1         153 my ( $is_error, $content, undef, $base_url ) = $self->check_http_response( $res );
108 1 50       3 if ( $is_error ) {
109 0         0 carp $content;
110 0         0 return ();
111             }
112              
113 1         5 $self->{base_url} = $base_url;
114              
115 1         5 my @links = $self->find_links($content);
116              
117 1         15 return @links;
118             }
119              
120             sub find_links {
121 3     3 0 2171 my $self = shift;
122 3         7 my ($html) = @_;
123              
124 3         8 my @links;
125 3 100       8 if ($Has_HTML_Parser) {
126 1         3 push @links, $self->find_links_preferred($html)
127             } else {
128 2         9 push @links, $self->find_links_textbalanced($html)
129             }
130              
131 3         15 return @links;
132             }
133              
134             sub find_links_preferred {
135 2     2 0 316 my $self = shift;
136 2         5 my ($html) = @_;
137              
138 2         3 my @links;
139              
140             my $extor = HTML::LinkExtor->new(
141             sub {
142 8     8   180 my ($tag, %attrs) = @_;
143 8 50       18 return unless $tag eq 'a';
144 8 50       16 return unless defined $attrs{href};
145 8         45 push @links, $attrs{href};
146             },
147 2         16 );
148              
149 2         198 $extor->parse($html);
150              
151 2         24 return @links;
152             }
153              
154             sub find_links_textbalanced {
155 3     3 0 2201 my $self = shift;
156 3         6 my ($html) = @_;
157 3         16 return Alien::Base::ModuleBuild::Utils::find_anchor_targets($html);
158             }
159              
160             sub build_uri {
161 17     17 0 3794 my $self = shift;
162 17         41 my ($protocol, $host, $path, $target) = @_;
163              
164 17         25 my $uri;
165 17 100       41 if (defined $host) {
166 15         28 my $base = $self->{base_url};
167 15 50       34 unless($base)
168             {
169 15         50 $base = URI->new($host);
170 15 100       4829 unless (defined $base->scheme) {
171 14   100     229 $base = URI->new(($protocol || 'http') ."://$host");
172             }
173 15 100       2506 $base->path($path) if defined $path;
174             }
175 15         435 $uri = URI->new_abs($target, $base);
176             }
177             else {
178 2         14 $uri = URI->new($target);
179             }
180 17         3243 return $uri->canonical;
181             }
182              
183             sub check_http_response {
184 11     11 0 6855 my ( $self, $res ) = @_;
185 11 50 33     52 if ( blessed $res && $res->isa( 'HTTP::Response' ) ) {
186 0         0 my %headers = map { lc $_ => $res->header($_) } $res->header_field_names;
  0         0  
187 0 0       0 if ( !$res->is_success ) {
188 0         0 return ( 1, $res->status_line . " " . $res->decoded_content, \%headers, $res->request->uri );
189             }
190 0         0 return ( 0, $res->decoded_content, \%headers, $res->request->uri );
191             }
192             else {
193 11 100       30 if ( !$res->{success} ) {
194 3 100       70 my $reason = $res->{status} == 599 ? $res->{content} : "@{[ $res->{status} ]} @{[ $res->{reason} ]}";
  1         5  
  1         4  
195 3 100 100     18 if($res->{status} == 599 && $reason =~ /https support/)
196             {
197 1         5 $reason .= "See https://github.com/PerlAlien/Alien-Base-ModuleBuild/issues/6#issuecomment-417097485";
198             }
199 3         27 return ( 1, $reason, $res->{headers}, $res->{url} );
200             }
201 8         28 return ( 0, $res->{content}, $res->{headers}, $res->{url} );
202             }
203             }
204              
205             1;
206              
207             __END__