File Coverage

blib/lib/XML/RPC.pm
Criterion Covered Total %
statement 25 160 15.6
branch 0 42 0.0
condition 0 16 0.0
subroutine 7 37 18.9
pod 15 29 51.7
total 47 284 16.5


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             XML::RPC -- Pure Perl implementation for an XML-RPC client and server.
5              
6             =head1 SYNOPSIS
7              
8             make a call to an XML-RPC server:
9              
10             use XML::RPC;
11              
12             my $xmlrpc = XML::RPC->new('http://betty.userland.com/RPC2');
13             my $result = $xmlrpc->call( 'examples.getStateStruct', { state1 => 12, state2 => 28 } );
14              
15             create an XML-RPC service:
16              
17             use XML::RPC;
18             use CGI;
19              
20             my $q = new CGI;
21             my $xmlrpc = XML::RPC->new();
22             my $xml = $q->param('POSTDATA');
23              
24             print $q->header( -type => 'text/xml', -charset => 'UTF-8' );
25             print $xmlrpc->receive( $xml, \&handler );
26              
27             sub handler {
28             my ( $methodname, @params ) = @_;
29             return { you_called => $methodname, with_params => \@params };
30             }
31              
32             =head1 WARNING
33              
34             Very little maintainance goes into this module. While it continues to work, is has certain quirks that may or may not be fixable without breaking backward compatibility.
35              
36             I strongly recommend that, before deciding what to use in a new project, you look into Randy Ray's L module. This seems to be a much more modern approach.
37              
38             =head1 DESCRIPTION
39              
40             XML::RPC module provides simple Pure Perl methods for XML-RPC communication.
41             It's goals are simplicity and flexibility. XML::RPC uses XML::TreePP
42             for parsing.
43              
44             This version of XML::RPC merges the changes from XML::RPC::CustomUA.
45              
46             =head1 CONSTRUCTOR AND OPTIONS
47              
48             =head2 $xmlrpc = XML::RPC->new();
49              
50             This constructor method returns a new XML::RPC object. Usable for XML-RPC servers.
51              
52             =head2 $xmlrpc = XML::RPC->new( 'http://betty.userland.com/RPC2', %options );
53              
54             Its first argument is the full URL for your server. The second argument
55             is for options passing to XML::TreePP, for example: output_encoding => 'ISO-8859-1'
56             (default is UTF-8).
57              
58             You can also define the UserAgent string, for example:
59              
60             my $rpcfoo = XML::RPC->new($apiurl, ('User-Agent' => 'Baz/3000 (Mozilla/1.0; FooBar phone app)'));
61              
62             =head1 METHODS
63              
64             =head2 $xmlrpc->credentials( 'username', 'password );
65              
66             Set Credentials for HTTP Basic Authentication. This is only
67             secure over HTTPS.
68              
69             Please, please, please do not use this over unencrypted connections!
70              
71             =head2 $xmlrpc->call( 'method_name', @arguments );
72              
73             This method calls the provides XML-RPC server's method_name with
74             @arguments. It will return the server method's response.
75              
76             =head2 $xmlrpc->receive( $xml, \&handler );
77              
78             This parses an incoming XML-RPC methodCall and call the \&handler subref
79             with parameters: $methodName and @parameters.
80              
81             =head2 $xmlrpc->errstr();
82              
83             Returns the last HTTP status code (200 when no remote call has happened yet). Can return 999 for some internal errors
84              
85             =head2 $xmlrpc->xml_in();
86              
87             Returns the last XML that went in the client.
88              
89             =head2 $xmlrpc->xml_out();
90              
91             Returns the last XML that went out the client.
92              
93             =head2 $xmlrpc->indent(indentsize);
94              
95             Sets the xmlout indentation
96              
97             =head1 CUSTOM TYPES
98              
99             =head2 $xmlrpc->call( 'method_name', { data => sub { { 'base64' => encode_base64($data) } } } );
100              
101             When passing a CODEREF to a value XML::RPC will simply use the returned hashref as a type => value pair.
102              
103             =head1 TYPECASTING
104              
105             Sometimes a value type might not be clear from the value alone, typecasting provides a way to "force" a value to a certain type
106              
107             =head2 as_string
108              
109             Forces a value to be cast as string.
110              
111             $xmlrpc->call( 'gimmeallyourmoney', { cardnumber => as_string( 12345 ) } );
112              
113             =head2 as_int
114              
115             Forces a value to be cast as int
116              
117             =head2 as_i4
118              
119             Forces a value to be cast as i4
120              
121             =head2 as_double
122              
123             Forces a value to be cast as double
124              
125             =head2 as_boolean
126              
127             Forces a value to be cast as boolean
128              
129             =head2 as_base64
130              
131             Forces a value to be cast as base64
132              
133             =head2 as_dateTime_iso8601
134              
135             Forces a value to be cast as ISO8601 Datetime
136              
137              
138             =head1 ERROR HANDLING
139              
140             To provide an error response you can simply die() in the \&handler
141             function. Also you can set the $XML::RPC::faultCode variable to a (int) value
142             just before dieing.
143              
144             =head1 PROXY SUPPORT
145              
146             Default XML::RPC will try to use LWP::Useragent for requests,
147             you can set the environment variable: CGI_HTTP_PROXY to
148             set a proxy.
149              
150             =head1 LIMITATIONS
151              
152             XML::RPC will not create "bool", "dateTime.iso8601" or "base64" types
153             automatically. They will be parsed as "int" or "string". You can use the
154             CODE ref to create these types.
155              
156             =head1 AUTHOR
157              
158             Original author: Niek Albers, http://www.daansystems.com/
159             Current author: Rene Schickbauer, https://cavac.at
160              
161             =head1 COPYRIGHT AND LICENSE
162              
163             Copyright (c) 2007-2008 Niek Albers. All rights reserved. This program
164              
165             Copyright (c) 2012-2022 Rene Schickbauer
166              
167             This program is free software; you can redistribute it and/or modify it under the same
168             terms as Perl itself.
169             =cut
170              
171             package XML::RPC;
172              
173 2     2   247307 use strict;
  2         5  
  2         83  
174 2     2   1606 use XML::TreePP;
  2         25683  
  2         97  
175 2     2   1256 use MIME::Base64;
  2         2085  
  2         164  
176 2     2   1176 use Time::Local;
  2         6676  
  2         173  
177 2     2   18 use vars qw($VERSION $faultCode);
  2         18  
  2         129  
178 2     2   12 no strict 'refs';
  2         4  
  2         5401  
179              
180             $VERSION = 2.1;
181             $faultCode = 0;
182              
183             sub new {
184 1     1 1 214633 my $package = shift;
185 1         3 my $self = {};
186 1         3 bless $self, $package;
187 1         7 $self->{url} = shift;
188 1         12 $self->{tpp} = XML::TreePP->new(@_);
189 1         13 $self->{laststatus} = '200';
190 1         4 return $self;
191             }
192              
193             sub indent {
194 0   0 0 1   my $self = shift || return;
195 0           $self->{tpp}->set( indent => shift );
196             }
197              
198             sub credentials {
199 0     0 1   my ($self, $username, $password) = @_;
200              
201 0           my $authtoken = 'Basic ' . encode_base64($username . ':' . $password, '');
202              
203 0           $self->{authtoken} = $authtoken;
204              
205 0           return;
206             }
207              
208             sub call {
209 0     0 1   my $self = shift;
210 0           my ( $methodname, @params ) = @_;
211              
212 0 0         die 'no url' if ( !$self->{url} );
213              
214 0           $faultCode = 0;
215 0           my $xml_out = $self->create_call_xml( $methodname, @params );
216              
217 0           $self->{xml_out} = $xml_out;
218              
219             my %header = (
220             'Content-Type' => 'text/xml',
221 0 0         'User-Agent' => defined($self->{tpp}->{'User-Agent'}) ? $self->{tpp}->{'User-Agent'} : 'XML-RPC/' . $VERSION,
222             'Content-Length' => length($xml_out)
223             );
224              
225 0 0         if(defined($self->{authtoken})) {
226             $header{'Authorization'} = $self->{authtoken}
227 0           }
228              
229 0           my ( $result, $xml_in, $httpstatus );
230              
231 0           my $parseok = 0;
232 0           eval {
233             ( $result, $xml_in, $httpstatus ) = $self->{tpp}->parsehttp(
234             POST => $self->{url},
235 0           $xml_out,
236             \%header,
237             );
238 0           $parseok = 1;
239             };
240              
241 0 0         if(!$parseok) {
242 0           my $err = $@;
243 0           $httpstatus = '999';
244 0           die($err);
245             };
246              
247 0           $self->{laststatus} = $httpstatus;
248              
249 0           $self->{xml_in} = $xml_in;
250              
251 0           my @data = $self->unparse_response($result);
252 0 0         return @data == 1 ? $data[0] : @data;
253             }
254              
255             sub errstr {
256 0     0 1   my $self = shift;
257              
258 0           return $self->{laststatus};
259             }
260              
261             sub receive {
262 0     0 1   my $self = shift;
263 0           my $result = eval {
264 0   0       my $xml_in = shift || die 'no xml';
265 0           $self->{xml_in} = $xml_in;
266 0   0       my $handler = shift || die 'no handler';
267 0           my $hash = $self->{tpp}->parse($xml_in);
268 0           my ( $methodname, @params ) = $self->unparse_call($hash);
269 0           $self->create_response_xml( $handler->( $methodname, @params ) );
270             };
271              
272 0 0         $result = $self->create_fault_xml($@) if ($@);
273 0           $self->{xml_out} = $result;
274 0           return $result;
275              
276             }
277              
278             sub create_fault_xml {
279 0     0 0   my $self = shift;
280 0           my $error = shift;
281 0           chomp($error);
282             return $self->{tpp}
283 0           ->write( { methodResponse => { fault => $self->parse( { faultString => $error, faultCode => int($faultCode) } ) } } );
284             }
285              
286             sub create_call_xml {
287 0     0 0   my $self = shift;
288 0           my ( $methodname, @params ) = @_;
289              
290             return $self->{tpp}->write(
291             {
292             methodCall => {
293             methodName => $methodname,
294 0           params => { param => [ map { $self->parse($_) } @params ] }
  0            
295             }
296             }
297             );
298             }
299              
300             sub create_response_xml {
301 0     0 0   my $self = shift;
302 0           my @params = @_;
303              
304 0           return $self->{tpp}->write( { methodResponse => { params => { param => [ map { $self->parse($_) } @params ] } } } );
  0            
305             }
306              
307             sub parse {
308 0     0 0   my $self = shift;
309 0           my $p = shift;
310 0           my $result;
311              
312 0 0         if ( ref($p) eq 'HASH' ) {
    0          
    0          
313 0           $result = $self->parse_struct($p);
314             }
315             elsif ( ref($p) eq 'ARRAY' ) {
316 0           $result = $self->parse_array($p);
317             }
318             elsif ( ref($p) eq 'CODE' ) {
319 0           $result = $p->();
320             }
321             else {
322 0           $result = $self->parse_scalar($p);
323             }
324              
325 0           return { value => $result };
326             }
327              
328             sub parse_scalar {
329 0     0 0   my $self = shift;
330 0           my $scalar = shift;
331 0           local $^W = undef;
332              
333 0 0 0       if ( ( $scalar =~ m/^[\-+]?(0|[1-9]\d*)$/ )
    0          
334             && ( abs($scalar) <= ( 0xffffffff >> 1 ) ) )
335             {
336 0           return { i4 => $scalar };
337             }
338             elsif ( $scalar =~ m/^[\-+]?\d+\.\d+$/ ) {
339 0           return { double => $scalar };
340             }
341             else {
342 0           return { string => \$scalar };
343             }
344             }
345              
346             sub parse_struct {
347 0     0 0   my $self = shift;
348 0           my $hash = shift;
349              
350 0           return { struct => { member => [ map { { name => $_, %{ $self->parse( $hash->{$_} ) } } } keys(%$hash) ] } };
  0            
  0            
351             }
352              
353             sub parse_array {
354 0     0 0   my $self = shift;
355 0           my $array = shift;
356              
357 0           return { array => { data => { value => [ map { $self->parse($_)->{value} } $self->list($array) ] } } };
  0            
358             }
359              
360             sub unparse_response {
361 0     0 0   my $self = shift;
362 0           my $hash = shift;
363              
364 0   0       my $response = $hash->{methodResponse} || die 'no data';
365              
366 0 0         if ( $response->{fault} ) {
367 0           return $self->unparse_value( $response->{fault}->{value} );
368             }
369             else {
370 0           return map { $self->unparse_value( $_->{value} ) } $self->list( $response->{params}->{param} );
  0            
371             }
372             }
373              
374             sub unparse_call {
375 0     0 0   my $self = shift;
376 0           my $hash = shift;
377              
378 0   0       my $response = $hash->{methodCall} || die 'no data';
379              
380 0           my $methodname = $response->{methodName};
381             my @args =
382 0           map { $self->unparse_value( $_->{value} ) } $self->list( $response->{params}->{param} );
  0            
383 0           return ( $methodname, @args );
384             }
385              
386             sub unparse_value {
387 0     0 0   my $self = shift;
388 0           my $value = shift;
389 0           my $result;
390              
391 0 0         return $value if ( ref($value) ne 'HASH' ); # for unspecified params
392 0 0         if ( $value->{struct} ) {
    0          
393 0           $result = $self->unparse_struct( $value->{struct} );
394 0 0         return !%$result
395             ? undef
396             : $result; # fix for empty hashrefs from XML::TreePP
397             }
398             elsif ( $value->{array} ) {
399 0           return $self->unparse_array( $value->{array} );
400             }
401             else {
402 0           return $self->unparse_scalar($value);
403             }
404             }
405              
406             sub unparse_scalar {
407 0     0 0   my $self = shift;
408 0           my $scalar = shift;
409 0           my ($result) = values(%$scalar);
410 0 0 0       return ( ref($result) eq 'HASH' && !%$result )
411             ? undef
412             : $result; # fix for empty hashrefs from XML::TreePP
413             }
414              
415             sub unparse_struct {
416 0     0 0   my $self = shift;
417 0           my $struct = shift;
418              
419 0           return { map { $_->{name} => $self->unparse_value( $_->{value} ) } $self->list( $struct->{member} ) };
  0            
420             }
421              
422             sub unparse_array {
423 0     0 0   my $self = shift;
424 0           my $array = shift;
425 0           my $data = $array->{data};
426              
427 0           return [ map { $self->unparse_value($_) } $self->list( $data->{value} ) ];
  0            
428             }
429              
430             sub list {
431 0     0 0   my $self = shift;
432 0           my $param = shift;
433 0 0         return () if ( !$param );
434 0 0         return @$param if ( ref($param) eq 'ARRAY' );
435 0           return ($param);
436             }
437              
438 0     0 1   sub xml_in { shift->{xml_in} }
439              
440 0     0 1   sub xml_out { shift->{xml_out} }
441              
442             # private helper function to create specialised closure
443             sub _cast {
444 0     0     my ($type, $val) = @_;
445 0     0     return sub { return { "$type" => $val }; };
  0            
446             }
447              
448             sub as_string {
449 0     0 1   return _cast( 'string', shift );
450             }
451              
452             sub as_int {
453 0     0 1   return _cast( 'int', int shift );
454             }
455              
456             sub as_i4 {
457 0     0 1   return _cast( 'i4', int shift );
458             }
459              
460             sub as_double {
461 0     0 1   return _cast( 'double', sprintf('%g', shift) );
462             }
463              
464             sub as_boolean {
465 0 0   0 1   return _cast( 'boolean', (shift) ? '1' : '0' );
466             }
467              
468             sub as_base64 {
469 0     0 1   chomp( my $base64 = encode_base64( shift ) );
470 0           return _cast( 'base64', $base64 );
471             }
472              
473             # converts epoch (or current time if undef) to dateTime.iso8601 (UTC)
474             sub as_dateTime_iso8601 {
475 0     0 1   my $epoch = shift;
476 0 0         $epoch = time() unless defined $epoch; # could be: "shift // time" with modern perl versions
477 0           my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = gmtime( $epoch );
478              
479 0           return _cast( 'dateTime.iso8601',
480             sprintf('%4d%02d%02dT%02d:%02d:%02dZ',
481             $year + 1900, $mon + 1, $mday, $hour, $min, $sec)
482             );
483             }
484              
485             1;