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