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 29     29   15095892 use strict;
  29         70  
  29         837  
4 29     29   153 use warnings;
  29         61  
  29         1018  
5              
6 29     29   949 use English qw(-no_match_vars);
  29         5568  
  29         285  
7 29     29   33865 use HTTP::Status;
  29         90912  
  29         10263  
8 29     29   21286 use LWP::UserAgent;
  29         822195  
  29         436  
9 29     29   2502 use UNIVERSAL::require;
  29         2953  
  29         281  
10              
11 29     29   10883 use FusionInventory::Agent;
  29         88  
  29         320  
12 29     29   7023 use FusionInventory::Agent::Logger;
  29         62  
  29         28824  
13              
14             my $log_prefix = "[http client] ";
15              
16             sub new {
17 73     73 1 6135961 my ($class, %params) = @_;
18              
19             die "non-existing certificate file $params{ca_cert_file}"
20 73 100 100     1538 if $params{ca_cert_file} && ! -f $params{ca_cert_file};
21              
22             die "non-existing certificate directory $params{ca_cert_dir}"
23 72 100 66     368 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 71   66     1233 };
35 71         295 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 71   50     1702 timeout => $params{timeout} || 180,
42             parse_head => 0, # No need to parse HTML
43             keep_alive => 1,
44             );
45              
46 71 100       39351 if ($params{proxy}) {
47 10         185 $self->{ua}->proxy(['http', 'https'], $params{proxy});
48             } else {
49 61         476 $self->{ua}->env_proxy();
50             }
51              
52 71         56020 return $self;
53             }
54              
55             sub request {
56 62     62 1 8202270 my ($self, $request, $file) = @_;
57              
58 62         228 my $logger = $self->{logger};
59              
60 62         304 my $url = $request->uri();
61 62         1283 my $scheme = $url->scheme();
62 62 100 100     3630 $self->_setSSLOptions() if $scheme eq 'https' && !$self->{ssl_set};
63              
64 62         888 my $result = HTTP::Response->new( 500 );
65 62         4101 eval {
66 62 50 33     466 if ($OSNAME eq 'MSWin32' && $scheme eq 'https') {
67 0         0 alarm $self->{ua}->timeout();
68             }
69 62         815 $result = $self->{ua}->request($request, $file);
70 62         2075223 alarm 0;
71             };
72              
73             # check result first
74 62 100       431 if (!$result->is_success()) {
75             # authentication required
76 37 100       628 if ($result->code() == 401) {
77 30 100 66     680 if ($self->{user} && $self->{password}) {
78 15         575 $logger->debug(
79             $log_prefix .
80             "authentication required, submitting credentials"
81             );
82             # compute authentication parameters
83 15         69 my $header = $result->header('www-authenticate');
84 15         870 my ($realm) = $header =~ /^Basic realm="(.*)"/;
85 15         72 my $host = $url->host();
86 15   33     467 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         545 );
94             # replay request
95 15         206 eval {
96 15 50 33     96 if ($OSNAME eq 'MSWin32' && $scheme eq 'https') {
97 0         0 alarm $self->{timeout};
98             }
99 15         68 $result = $self->{ua}->request($request, $file);
100 15         681030 alarm 0;
101             };
102 15 50       87 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         360 $logger->error(
111             $log_prefix .
112             "authentication required, no credentials available"
113             );
114             }
115             } else {
116 7         169 $logger->error(
117             $log_prefix .
118             "communication error: " . $result->status_line()
119             );
120             }
121             }
122              
123 62         1443 return $result;
124             }
125              
126             sub _setSSLOptions {
127 32     32   207 my ($self) = @_;
128              
129             # SSL handling
130 32 100       338 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       607 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         503 IO::Socket::SSL->require();
141 16 50       1069 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       101 if ($self->{logger}{verbosity} > LOG_DEBUG2) {
148 0         0 $Net::SSLeay::trace = 2;
149             }
150              
151 16 50       108 if ($LWP::VERSION >= 6) {
152             $self->{ua}->ssl_opts(SSL_ca_file => $self->{ca_cert_file})
153 16 50       181 if $self->{ca_cert_file};
154             $self->{ua}->ssl_opts(SSL_ca_path => $self->{ca_cert_dir})
155 16 50       522 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         676 $self->{ssl_set} = 1;
182             }
183              
184             1;
185             __END__