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 20     20   11769048 use strict;
  20         41  
  20         708  
4 20     20   91 use warnings;
  20         29  
  20         625  
5              
6 20     20   613 use English qw(-no_match_vars);
  20         3418  
  20         189  
7 20     20   19107 use HTTP::Status;
  20         72852  
  20         5441  
8 20     20   13118 use LWP::UserAgent;
  20         566966  
  20         268  
9 20     20   1580 use UNIVERSAL::require;
  20         2194  
  20         182  
10              
11 20     20   8416 use FusionInventory::Agent;
  20         62  
  20         216  
12 20     20   2565 use FusionInventory::Agent::Logger;
  20         36  
  20         17193  
13              
14             my $log_prefix = "[http client] ";
15              
16             sub new {
17 59     59 1 6115812 my ($class, %params) = @_;
18              
19 59 100 100     1350 die "non-existing certificate file $params{ca_cert_file}"
20             if $params{ca_cert_file} && ! -f $params{ca_cert_file};
21              
22 58 100 66     315 die "non-existing certificate directory $params{ca_cert_dir}"
23             if $params{ca_cert_dir} && ! -d $params{ca_cert_dir};
24              
25 57   66     925 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             };
35 57         267 bless $self, $class;
36              
37             # create user agent
38 57   50     1429 $self->{ua} = LWP::UserAgent->new(
39             requests_redirectable => ['POST', 'GET', 'HEAD'],
40             agent => $FusionInventory::Agent::AGENT_STRING,
41             timeout => $params{timeout} || 180,
42             parse_head => 0, # No need to parse HTML
43             keep_alive => 1,
44             );
45              
46 57 100       26881 if ($params{proxy}) {
47 12         207 $self->{ua}->proxy(['http', 'https'], $params{proxy});
48             } else {
49 45         303 $self->{ua}->env_proxy();
50             }
51              
52 57         35901 return $self;
53             }
54              
55             sub request {
56 68     68 1 13195692 my ($self, $request, $file) = @_;
57              
58 68         275 my $logger = $self->{logger};
59              
60 68         215 my $url = $request->uri();
61 68         1248 my $scheme = $url->scheme();
62 68 100 100     4268 $self->_setSSLOptions() if $scheme eq 'https' && !$self->{ssl_set};
63              
64 68         768 my $result = HTTP::Response->new( 500 );
65 68         3865 eval {
66 68 50 33     574 if ($OSNAME eq 'MSWin32' && $scheme eq 'https') {
67 0         0 alarm $self->{ua}->timeout();
68             }
69 68         663 $result = $self->{ua}->request($request, $file);
70 68         6742253 alarm 0;
71             };
72              
73             # check result first
74 68 100       424 if (!$result->is_success()) {
75             # authentication required
76 41 100       616 if ($result->code() == 401) {
77 30 100 66     635 if ($self->{user} && $self->{password}) {
78 15         209 $logger->debug(
79             $log_prefix .
80             "authentication required, submitting credentials"
81             );
82             # compute authentication parameters
83 15         54 my $header = $result->header('www-authenticate');
84 15         660 my ($realm) = $header =~ /^Basic realm="(.*)"/;
85 15         57 my $host = $url->host();
86 15   33     350 my $port = $url->port() ||
87             ($scheme eq 'https' ? 443 : 80);
88 15         345 $self->{ua}->credentials(
89             "$host:$port",
90             $realm,
91             $self->{user},
92             $self->{password}
93             );
94             # replay request
95 15         161 eval {
96 15 50 33     120 if ($OSNAME eq 'MSWin32' && $scheme eq 'https') {
97 0         0 alarm $self->{timeout};
98             }
99 15         72 $result = $self->{ua}->request($request, $file);
100 15         735679 alarm 0;
101             };
102 15 50       67 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         208 $logger->error(
111             $log_prefix .
112             "authentication required, no credentials available"
113             );
114             }
115             } else {
116 11         191 $logger->error(
117             $log_prefix .
118             "communication error: " . $result->status_line()
119             );
120             }
121             }
122              
123 68         1346 return $result;
124             }
125              
126             sub _setSSLOptions {
127 35     35   159 my ($self) = @_;
128              
129             # SSL handling
130 35 100       242 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 17 50       439 $self->{ua}->ssl_opts(verify_hostname => 0, SSL_verify_mode => 0)
135             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 18         587 IO::Socket::SSL->require();
141 18 50       1072 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 18 50       117 if ($self->{logger}{verbosity} > LOG_DEBUG2) {
148 0         0 $Net::SSLeay::trace = 2;
149             }
150              
151 18 50       120 if ($LWP::VERSION >= 6) {
152 18 50       190 $self->{ua}->ssl_opts(SSL_ca_file => $self->{ca_cert_file})
153             if $self->{ca_cert_file};
154 18 50       746 $self->{ua}->ssl_opts(SSL_ca_path => $self->{ca_cert_dir})
155             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 0         0 FusionInventory::Agent::HTTP::Protocol::https->use(
167             ca_cert_file => $self->{ca_cert_file},
168             ca_cert_dir => $self->{ca_cert_dir},
169             );
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 35         667 $self->{ssl_set} = 1;
182             }
183              
184             1;
185             __END__