File Coverage

blib/lib/XML/RPC.pm
Criterion Covered Total %
statement 21 129 16.2
branch 0 36 0.0
condition 0 14 0.0
subroutine 6 25 24.0
pod 6 20 30.0
total 33 224 14.7


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 DESCRIPTION
33              
34             XML::RPC module provides simple Pure Perl methods for XML-RPC communication.
35             It's goals are simplicity and flexibility. XML::RPC uses XML::TreePP
36             for parsing.
37              
38             This version of XML::RPC merges the changes from XML::RPC::CustomUA.
39              
40             =head1 CONSTRUCTOR AND OPTIONS
41              
42             =head2 $xmlrpc = XML::RPC->new();
43              
44             This constructor method returns a new XML::RPC object. Usable for XML-RPC servers.
45              
46             =head2 $xmlrpc = XML::RPC->new( 'http://betty.userland.com/RPC2', %options );
47              
48             Its first argument is the full URL for your server. The second argument
49             is for options passing to XML::TreePP, for example: output_encoding => 'ISO-8859-1'
50             (default is UTF-8).
51              
52             You can also define the UserAgent string, for example:
53              
54             my $rpcfoo = XML::RPC->new($apiurl, ('User-Agent' => 'Baz/3000 (Mozilla/1.0; FooBar phone app)'));
55              
56             =head1 METHODS
57              
58             =head2 $xmlrpc->credentials( 'username', 'password );
59              
60             Set Credentials for HTTP Basic Authentication. This is only
61             secure over HTTPS.
62              
63             Please, please, please do not use this over unencrypted connections!
64              
65             =head2 $xmlrpc->call( 'method_name', @arguments );
66              
67             This method calls the provides XML-RPC server's method_name with
68             @arguments. It will return the server method's response.
69              
70             =head2 $xmlrpc->receive( $xml, \&handler );
71              
72             This parses an incoming XML-RPC methodCall and call the \&handler subref
73             with parameters: $methodName and @parameters.
74              
75             =head2 $xmlrpc->xml_in();
76              
77             Returns the last XML that went in the client.
78              
79             =head2 $xmlrpc->xml_out();
80              
81             Returns the last XML that went out the client.
82              
83             =head1 CUSTOM TYPES
84              
85             =head2 $xmlrpc->call( 'method_name', { data => sub { { 'base64' => encode_base64($data) } } } );
86              
87             When passing a CODEREF to a value XML::RPC will simply use the returned hashref as a type => value pair.
88              
89             =head1 ERROR HANDLING
90              
91             To provide an error response you can simply die() in the \&handler
92             function. Also you can set the $XML::RPC::faultCode variable to a (int) value
93             just before dieing.
94              
95             =head1 PROXY SUPPORT
96              
97             Default XML::RPC will try to use LWP::Useragent for requests,
98             you can set the environment variable: CGI_HTTP_PROXY to
99             set a proxy.
100              
101             =head1 LIMITATIONS
102              
103             XML::RPC will not create "bool", "dateTime.iso8601" or "base64" types
104             automatically. They will be parsed as "int" or "string". You can use the
105             CODE ref to create these types.
106              
107             =head1 AUTHOR
108              
109             Original author: Niek Albers, http://www.daansystems.com/
110             Current author: Rene Schickbauer, https://www.cavac.at
111              
112             =head1 COPYRIGHT AND LICENSE
113              
114             Copyright (c) 2007-2008 Niek Albers. All rights reserved. This program
115              
116             Copyright (c) 2012-2017 Rene Schickbauer
117              
118             This program is free software; you can redistribute it and/or modify it under the same
119             terms as Perl itself.
120             =cut
121              
122             package XML::RPC;
123              
124 2     2   169627 use strict;
  2         13  
  2         65  
125 2     2   1050 use XML::TreePP;
  2         18374  
  2         102  
126 2     2   1251 use MIME::Base64;
  2         1610  
  2         208  
127 2     2   22 use vars qw($VERSION $faultCode);
  2         6  
  2         120  
128 2     2   17 no strict 'refs';
  2         5  
  2         3543  
129              
130             $VERSION = 1.1;
131             $faultCode = 0;
132              
133             sub new {
134 1     1 1 117 my $package = shift;
135 1         4 my $self = {};
136 1         3 bless $self, $package;
137 1         9 $self->{url} = shift;
138 1         11 $self->{tpp} = XML::TreePP->new(@_);
139 1         16 return $self;
140             }
141              
142             sub credentials {
143 0     0 1   my ($self, $username, $password) = @_;
144              
145 0           my $authtoken = 'Basic ' . encode_base64($username . ':' . $password, '');
146              
147 0           $self->{authtoken} = $authtoken;
148              
149 0           return;
150             }
151              
152             sub call {
153 0     0 1   my $self = shift;
154 0           my ( $methodname, @params ) = @_;
155              
156 0 0         die 'no url' if ( !$self->{url} );
157              
158 0           $faultCode = 0;
159 0           my $xml_out = $self->create_call_xml( $methodname, @params );
160              
161 0           $self->{xml_out} = $xml_out;
162              
163             my %header = (
164             'Content-Type' => 'text/xml',
165 0 0         'User-Agent' => defined($self->{tpp}->{'User-Agent'}) ? $self->{tpp}->{'User-Agent'} : 'XML-RPC/' . $VERSION,
166             'Content-Length' => length($xml_out)
167             );
168              
169 0 0         if(defined($self->{authtoken})) {
170             $header{'Authorization'} = $self->{authtoken}
171 0           }
172              
173             my ( $result, $xml_in ) = $self->{tpp}->parsehttp(
174             POST => $self->{url},
175 0           $xml_out,
176             \%header,
177             );
178              
179 0           $self->{xml_in} = $xml_in;
180              
181 0           my @data = $self->unparse_response($result);
182 0 0         return @data == 1 ? $data[0] : @data;
183             }
184              
185             sub receive {
186 0     0 1   my $self = shift;
187 0           my $result = eval {
188 0   0       my $xml_in = shift || die 'no xml';
189 0           $self->{xml_in} = $xml_in;
190 0   0       my $handler = shift || die 'no handler';
191 0           my $hash = $self->{tpp}->parse($xml_in);
192 0           my ( $methodname, @params ) = $self->unparse_call($hash);
193 0           $self->create_response_xml( $handler->( $methodname, @params ) );
194             };
195              
196 0 0         $result = $self->create_fault_xml($@) if ($@);
197 0           $self->{xml_out} = $result;
198 0           return $result;
199              
200             }
201              
202             sub create_fault_xml {
203 0     0 0   my $self = shift;
204 0           my $error = shift;
205 0           chomp($error);
206             return $self->{tpp}
207 0           ->write( { methodResponse => { fault => $self->parse( { faultString => $error, faultCode => int($faultCode) } ) } } );
208             }
209              
210             sub create_call_xml {
211 0     0 0   my $self = shift;
212 0           my ( $methodname, @params ) = @_;
213              
214             return $self->{tpp}->write(
215             {
216             methodCall => {
217             methodName => $methodname,
218 0           params => { param => [ map { $self->parse($_) } @params ] }
  0            
219             }
220             }
221             );
222             }
223              
224             sub create_response_xml {
225 0     0 0   my $self = shift;
226 0           my @params = @_;
227              
228 0           return $self->{tpp}->write( { methodResponse => { params => { param => [ map { $self->parse($_) } @params ] } } } );
  0            
229             }
230              
231             sub parse {
232 0     0 0   my $self = shift;
233 0           my $p = shift;
234 0           my $result;
235              
236 0 0         if ( ref($p) eq 'HASH' ) {
    0          
    0          
237 0           $result = $self->parse_struct($p);
238             }
239             elsif ( ref($p) eq 'ARRAY' ) {
240 0           $result = $self->parse_array($p);
241             }
242             elsif ( ref($p) eq 'CODE' ) {
243 0           $result = $p->();
244             }
245             else {
246 0           $result = $self->parse_scalar($p);
247             }
248              
249 0           return { value => $result };
250             }
251              
252             sub parse_scalar {
253 0     0 0   my $self = shift;
254 0           my $scalar = shift;
255 0           local $^W = undef;
256              
257 0 0 0       if ( ( $scalar =~ m/^[\-+]?(0|[1-9]\d*)$/ )
    0          
258             && ( abs($scalar) <= ( 0xffffffff >> 1 ) ) )
259             {
260 0           return { i4 => $scalar };
261             }
262             elsif ( $scalar =~ m/^[\-+]?\d+\.\d+$/ ) {
263 0           return { double => $scalar };
264             }
265             else {
266 0           return { string => \$scalar };
267             }
268             }
269              
270             sub parse_struct {
271 0     0 0   my $self = shift;
272 0           my $hash = shift;
273              
274 0           return { struct => { member => [ map { { name => $_, %{ $self->parse( $hash->{$_} ) } } } keys(%$hash) ] } };
  0            
  0            
275             }
276              
277             sub parse_array {
278 0     0 0   my $self = shift;
279 0           my $array = shift;
280              
281 0           return { array => { data => { value => [ map { $self->parse($_)->{value} } $self->list($array) ] } } };
  0            
282             }
283              
284             sub unparse_response {
285 0     0 0   my $self = shift;
286 0           my $hash = shift;
287              
288 0   0       my $response = $hash->{methodResponse} || die 'no data';
289              
290 0 0         if ( $response->{fault} ) {
291 0           return $self->unparse_value( $response->{fault}->{value} );
292             }
293             else {
294 0           return map { $self->unparse_value( $_->{value} ) } $self->list( $response->{params}->{param} );
  0            
295             }
296             }
297              
298             sub unparse_call {
299 0     0 0   my $self = shift;
300 0           my $hash = shift;
301              
302 0   0       my $response = $hash->{methodCall} || die 'no data';
303              
304 0           my $methodname = $response->{methodName};
305             my @args =
306 0           map { $self->unparse_value( $_->{value} ) } $self->list( $response->{params}->{param} );
  0            
307 0           return ( $methodname, @args );
308             }
309              
310             sub unparse_value {
311 0     0 0   my $self = shift;
312 0           my $value = shift;
313 0           my $result;
314              
315 0 0         return $value if ( ref($value) ne 'HASH' ); # for unspecified params
316 0 0         if ( $value->{struct} ) {
    0          
317 0           $result = $self->unparse_struct( $value->{struct} );
318 0 0         return !%$result
319             ? undef
320             : $result; # fix for empty hashrefs from XML::TreePP
321             }
322             elsif ( $value->{array} ) {
323 0           return $self->unparse_array( $value->{array} );
324             }
325             else {
326 0           return $self->unparse_scalar($value);
327             }
328             }
329              
330             sub unparse_scalar {
331 0     0 0   my $self = shift;
332 0           my $scalar = shift;
333 0           my ($result) = values(%$scalar);
334 0 0 0       return ( ref($result) eq 'HASH' && !%$result )
335             ? undef
336             : $result; # fix for empty hashrefs from XML::TreePP
337             }
338              
339             sub unparse_struct {
340 0     0 0   my $self = shift;
341 0           my $struct = shift;
342              
343 0           return { map { $_->{name} => $self->unparse_value( $_->{value} ) } $self->list( $struct->{member} ) };
  0            
344             }
345              
346             sub unparse_array {
347 0     0 0   my $self = shift;
348 0           my $array = shift;
349 0           my $data = $array->{data};
350              
351 0           return [ map { $self->unparse_value($_) } $self->list( $data->{value} ) ];
  0            
352             }
353              
354             sub list {
355 0     0 0   my $self = shift;
356 0           my $param = shift;
357 0 0         return () if ( !$param );
358 0 0         return @$param if ( ref($param) eq 'ARRAY' );
359 0           return ($param);
360             }
361              
362 0     0 1   sub xml_in { shift->{xml_in} }
363              
364 0     0 1   sub xml_out { shift->{xml_out} }
365              
366             1;