File Coverage

blib/lib/FusionInventory/Agent/HTTP/Client.pm
Criterion Covered Total %
statement 71 79 89.8
branch 25 38 65.7
condition 16 26 61.5
subroutine 11 11 100.0
pod 2 2 100.0
total 125 156 80.1


line stmt bran cond sub pod time code
1             package FusionInventory::Agent::HTTP::Client;
2              
3 30     30   14194652 use strict;
  30         36  
  30         699  
4 30     30   107 use warnings;
  30         30  
  30         732  
5              
6 30     30   486 use English qw(-no_match_vars);
  30         3278  
  30         200  
7 30     30   18973 use HTTP::Status;
  30         60179  
  30         5999  
8 30     30   13073 use LWP::UserAgent;
  30         496774  
  30         283  
9 30     30   1420 use UNIVERSAL::require;
  30         1827  
  30         176  
10              
11 30     30   6907 use FusionInventory::Agent;
  30         50  
  30         255  
12 30     30   4150 use FusionInventory::Agent::Logger;
  30         44  
  30         18476  
13              
14             my $log_prefix = "[http client] ";
15              
16             sub new {
17 74     74 1 6072643 my ($class, %params) = @_;
18              
19             die "non-existing certificate file $params{ca_cert_file}"
20 74 100 100     931 if $params{ca_cert_file} && ! -f $params{ca_cert_file};
21              
22             die "non-existing certificate directory $params{ca_cert_dir}"
23 73 100 66     315 if $params{ca_cert_dir} && ! -d $params{ca_cert_dir};
24              
25             my $self = {
26             logger => $params{logger} ||
27             FusionInventory::Agent::Logger->new(),
28             user => $params{user},
29             password => $params{password},
30             ssl_set => 0,
31             no_ssl_check => $params{no_ssl_check},
32             ca_cert_dir => $params{ca_cert_dir},
33             ca_cert_file => $params{ca_cert_file}
34 72   66     698 };
35 72         169 bless $self, $class;
36              
37             # create user agent
38             $self->{ua} = LWP::UserAgent->new(
39             requests_redirectable => ['POST', 'GET', 'HEAD'],
40             agent => $FusionInventory::Agent::AGENT_STRING,
41 72   50     1008 timeout => $params{timeout} || 180,
42             parse_head => 0, # No need to parse HTML
43             keep_alive => 1,
44             );
45              
46 72 100       25240 if ($params{proxy}) {
47 10         116 $self->{ua}->proxy(['http', 'https'], $params{proxy});
48             } else {
49 62         350 $self->{ua}->env_proxy();
50             }
51              
52 72         35133 return $self;
53             }
54              
55             sub request {
56 68     68 1 8106232 my ($self, $request, $file) = @_;
57              
58 68         157 my $logger = $self->{logger};
59              
60 68         193 my $url = $request->uri();
61 68         574 my $scheme = $url->scheme();
62 68 100 100     1995 $self->_setSSLOptions() if $scheme eq 'https' && !$self->{ssl_set};
63              
64 68         465 my $result = HTTP::Response->new( 500 );
65 68         2196 eval {
66 68 50 33     349 if ($OSNAME eq 'MSWin32' && $scheme eq 'https') {
67 0         0 alarm $self->{ua}->timeout();
68             }
69 68         455 $result = $self->{ua}->request($request, $file);
70 68         1328964 alarm 0;
71             };
72              
73             # check result first
74 68 100       404 if (!$result->is_success()) {
75             # authentication required
76 38 100       421 if ($result->code() == 401) {
77 30 100 66     490 if ($self->{user} && $self->{password}) {
78 15         169 $logger->debug(
79             $log_prefix .
80             "authentication required, submitting credentials"
81             );
82             # compute authentication parameters
83 15         45 my $header = $result->header('www-authenticate');
84 15         542 my ($realm) = $header =~ /^Basic realm="(.*)"/;
85 15         55 my $host = $url->host();
86 15   33     308 my $port = $url->port() ||
87             ($scheme eq 'https' ? 443 : 80);
88             $self->{ua}->credentials(
89             "$host:$port",
90             $realm,
91             $self->{user},
92             $self->{password}
93 15         331 );
94             # replay request
95 15         133 eval {
96 15 50 33     72 if ($OSNAME eq 'MSWin32' && $scheme eq 'https') {
97 0         0 alarm $self->{ua}->{timeout};
98             }
99 15         74 $result = $self->{ua}->request($request, $file);
100 15         435744 alarm 0;
101             };
102 15 50       68 if (!$result->is_success()) {
103 0         0 $logger->error(
104             $log_prefix .
105             "authentication required, wrong credentials"
106             );
107             }
108             } else {
109             # abort
110 15         153 $logger->error(
111             $log_prefix .
112             "authentication required, no credentials available"
113             );
114             }
115             } else {
116 8         146 $logger->error(
117             $log_prefix .
118             "communication error: " . $result->status_line()
119             );
120             }
121             }
122              
123 68         933 return $result;
124             }
125              
126             sub _setSSLOptions {
127 32     32   103 my ($self) = @_;
128              
129             # SSL handling
130 32 100       157 if ($self->{no_ssl_check}) {
131             # LWP 6 default behaviour is to check hostname
132             # Fedora also backported this behaviour change in its LWP5 package, so
133             # just checking on LWP version is not enough
134             $self->{ua}->ssl_opts(verify_hostname => 0, SSL_verify_mode => 0)
135 16 50       282 if $self->{ua}->can('ssl_opts');
136             } else {
137             # only IO::Socket::SSL can perform full server certificate validation,
138             # Net::SSL is only able to check certification authority, and not
139             # certificate hostname
140 16         284 IO::Socket::SSL->require();
141 16 50       783 die
142             "IO::Socket::SSL Perl module not available, " .
143             "unable to validate SSL certificates " .
144             "(workaround: use 'no-ssl-check' configuration parameter)"
145             if $EVAL_ERROR;
146              
147 16 50       79 if ($self->{logger}{verbosity} > LOG_DEBUG2) {
148 0         0 $Net::SSLeay::trace = 2;
149             }
150              
151 16 50       57 if ($LWP::VERSION >= 6) {
152             $self->{ua}->ssl_opts(SSL_ca_file => $self->{ca_cert_file})
153 16 50       106 if $self->{ca_cert_file};
154             $self->{ua}->ssl_opts(SSL_ca_path => $self->{ca_cert_dir})
155 16 50       444 if $self->{ca_cert_dir};
156             } else {
157             # SSL_verifycn_scheme and SSL_verifycn_name are required
158 0 0       0 die
159             "IO::Socket::SSL Perl module too old " .
160             "(available: $IO::Socket::SSL::VERSION, required: 1.14), " .
161             "unable to validate SSL certificates " .
162             "(workaround: use 'no-ssl-check' configuration parameter)"
163             if $IO::Socket::SSL::VERSION < 1.14;
164              
165             # use a custom HTTPS handler to workaround default LWP5 behaviour
166             FusionInventory::Agent::HTTP::Protocol::https->use(
167             ca_cert_file => $self->{ca_cert_file},
168             ca_cert_dir => $self->{ca_cert_dir},
169 0         0 );
170              
171 0         0 LWP::Protocol::implementor(
172             'https', 'FusionInventory::Agent::HTTP::Protocol::https'
173             );
174              
175             # abuse user agent internal to pass values to the handler, so
176             # as to have different behaviors in the same process
177 0 0       0 $self->{ua}->{ssl_check} = $self->{no_ssl_check} ? 0 : 1;
178             }
179             }
180              
181 32         463 $self->{ssl_set} = 1;
182             }
183              
184             1;
185             __END__