File Coverage

blib/lib/XML/Compile/SOAP/Daemon.pm
Criterion Covered Total %
statement 86 168 51.1
branch 23 76 30.2
condition 8 40 20.0
subroutine 18 27 66.6
pod 15 17 88.2
total 150 328 45.7


line stmt bran cond sub pod time code
1             # Copyrights 2007-2018 by [Mark Overmeer ].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5             # This code is part of distribution XML-Compile-SOAP-Daemon. Meta-POD
6             # processed with OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package XML::Compile::SOAP::Daemon;
10 2     2   349682 use vars '$VERSION';
  2         6  
  2         113  
11             $VERSION = '3.14';
12              
13              
14 2     2   13 use warnings;
  2         5  
  2         53  
15 2     2   11 use strict;
  2         5  
  2         45  
16              
17 2     2   10 use Log::Report 'xml-compile-soap-daemon';
  2         4  
  2         18  
18              
19 2     2   796 use XML::LibXML ();
  2         5  
  2         50  
20 2     2   13 use XML::Compile::Util qw/type_of_node/;
  2         4  
  2         126  
21 2     2   13 use XML::Compile::SOAP ();
  2         5  
  2         80  
22              
23             # We use HTTP status definitions for each soap protocol, but HTTP::Status
24             # may not be installed.
25             use constant
26 2         4416 { RC_SEE_OTHER => 303
27             , RC_FORBIDDEN => 403
28             , RC_NOT_FOUND => 404
29             , RC_UNPROCESSABLE_ENTITY => 422
30             , RC_NOT_IMPLEMENTED => 501
31 2     2   13 };
  2         4  
32              
33             my $parser = XML::LibXML->new;
34              
35              
36             sub new(@)
37 1     1 1 376 { my $class = shift;
38 1 50       5 $class ne __PACKAGE__
39             or error __x"you can only use extensions of {pkg}", pkg => __PACKAGE__;
40 1         5 (bless {}, $class)->init( {@_} );
41             }
42              
43             sub init($)
44 1     1 0 3 { my ($self, $args) = @_;
45             $self->{accept_slow_select}
46 1 50       6 = exists $args->{accept_slow_select} ? $args->{accept_slow_select} : 1;
47              
48 1         6 $self->addWsaTable(INPUT => $args->{wsa_action_input});
49 1         4 $self->addWsaTable(OUTPUT => $args->{wsa_action_output});
50 1         6 $self->addSoapAction($args->{soap_action_input});
51              
52 1 50       4 if(my $support = delete $args->{support_soap})
53             { # simply only load the protocol versions you want to accept.
54 0         0 error __x"new(support_soap} removed in 2.00";
55             }
56              
57 1         7 my @classes = XML::Compile::SOAP->registered;
58             @classes # explicit load required since 2.00
59 1 50       8 or warning "No protocol modules loaded. Need XML::Compile::SOAP11?";
60              
61 1   50     5 $self->{output_charset} = delete $args->{output_charset} || 'UTF-8';
62 1         3 $self->{handler} = {};
63 1         3 $self;
64             }
65              
66             #-----------
67              
68 0     0 1 0 sub outputCharset() {shift->{output_charset}}
69              
70              
71             sub addWsaTable($@)
72 2     2 1 5 { my ($self, $dir) = (shift, shift);
73 2 50       7 my $h = @_==1 ? shift : { @_ };
74             my $t = $dir eq 'INPUT' ? ($self->{wsa_input} ||= {})
75             : $dir eq 'OUTPUT' ? ($self->{wsa_output} ||= {})
76 2 50 50     13 : error __x("addWsaTable requires 'INPUT' or 'OUTPUT', not {got}"
    100 50        
77             , got => $dir);
78              
79 2   0     8 while(my($op, $action) = each %$h) { $t->{$op} ||= $action }
  0         0  
80 2         5 $t;
81             }
82              
83              
84             sub addSoapAction(@)
85 1     1 1 2 { my $self = shift;
86 1 50       3 my $h = @_==1 ? shift : { @_ };
87 1   50     5 my $t = $self->{sa_input} ||= {};
88 1   50     4 my $r = $self->{sa_input_rev} ||= {};
89 1         5 while(my($op, $action) = each %$h)
90 0   0     0 { $t->{$op} ||= $action;
91 0   0     0 $r->{$action} ||= $op;
92             }
93 1         2 $t;
94             }
95              
96             #------------------
97              
98             sub run(@)
99 0     0 1 0 { my ($self, %args) = @_;
100             notice __x"WSA module loaded, but not used"
101 0 0 0     0 if XML::Compile::SOAP::WSA->can('new') && !keys %{$self->{wsa_input}};
  0         0  
102              
103 0         0 $self->{wsa_input_rev} = +{ reverse %{$self->{wsa_input}} };
  0         0  
104 0         0 $self->_run(\%args);
105             }
106              
107              
108             # defined by Net::Server
109 0     0 0 0 sub process_request(@) { panic "must be extended" }
110              
111             sub process($)
112 4     4 1 9 { my ($self, $input, $req, $soapaction) = @_;
113              
114 4         7 my $xmlin;
115 4 50       13 if(! defined $input)
    50          
116 0         0 { return $self->faultNotSoapMessage('No input');
117             }
118             elsif(ref $input eq 'SCALAR')
119 4     4   22 { $xmlin = try { $parser->parse_string($$input) };
  4         1214  
120 4 100       1385 return $self->faultInvalidXML($@->wasFatal) if $@;
121             }
122             else
123 0         0 { $xmlin = $input;
124             }
125            
126 3 50       57 $xmlin = $xmlin->documentElement
127             if $xmlin->isa('XML::LibXML::Document');
128              
129 3         17 my $local = $xmlin->localName;
130 3 100       13 $local eq 'Envelope'
131             or return $self->faultNotSoapMessage(type_of_node $xmlin);
132              
133 2   50     11 my $envns = $xmlin->namespaceURI || '';
134 2 100       12 my $proto = XML::Compile::SOAP->fromEnvelope($envns)
135             or return $self->faultUnsupportedSoapVersion($envns);
136             # proto is a XML::Compile::SOAP*::Operation
137 1         15 my $server = $proto->serverClass;
138              
139 1         8 my $info = XML::Compile::SOAP->messageStructure($xmlin);
140 1         126 my $version = $info->{soap_version} = $proto->version;
141 1   50     8 my $handlers = $self->{handler}{$version} || {};
142              
143             # Try to resolve operation via WSA
144 1         3 my $wsa_in = $self->{wsa_input_rev};
145 1 50       3 if(my $wsa_action = $info->{wsa_action})
146 0 0       0 { if(my $name = $wsa_in->{$wsa_action})
147 0         0 { my $handler = $handlers->{$name};
148 0         0 local $info->{selected_by} = 'wsa-action';
149 0         0 my ($rc, $msg, $xmlout) = $handler->($name, $xmlin, $info, $req);
150 0 0       0 if($xmlout)
151 0         0 { trace "data ready for $version $name, via wsa $wsa_action";
152 0         0 return ($rc, $msg, $xmlout);
153             }
154             }
155             }
156              
157             # Try to resolve operation via soapAction
158 1         2 my $sa = $self->{sa_input_rev};
159 1 50       4 if(defined $soapaction)
160 1 50       3 { if(my $name = $sa->{$soapaction})
161 0         0 { my $handler = $handlers->{$name};
162 0         0 local $info->{selected_by} = 'soap-action';
163 0         0 my ($rc, $msg, $xmlout) = $handler->($name, $xmlin, $info, $req);
164 0 0       0 if($xmlout)
165 0         0 { trace "data ready for $version $name, via sa '$soapaction'";
166 0         0 return ($rc, $msg, $xmlout);
167             }
168             }
169             }
170              
171             # Last resort, try each of the operations for the first which
172             # can be parsed correctly.
173 1 50       4 if($self->{accept_slow_select})
174 1         3 { keys %$handlers; # reset each()
175 1         2 $info->{selected_by} = 'attempt all';
176 1         5 while(my ($name, $handler) = each %$handlers)
177 0         0 { my ($rc, $msg, $xmlout) = $handler->($name, $xmlin, $info, $req);
178 0 0       0 defined $xmlout or next;
179              
180 0         0 trace "data ready for $version $name";
181 0         0 return ($rc, $msg, $xmlout);
182             }
183             }
184              
185 1   50     4 my $bodyel = $info->{body}[0] || '(none)';
186 1 0       7 my @other = sort grep {$_ ne $version && keys %{$self->{$_}}}
  0         0  
  0         0  
187             $self->soapVersions;
188              
189 1 50       3 return (RC_SEE_OTHER, 'SOAP protocol not in use'
190             , $server->faultTryOtherProtocol($bodyel, \@other))
191             if @other;
192              
193             # we do not have the names of the request body elements here :(
194 1         3 my @ports = sort keys %$handlers;
195              
196 1         9 ( RC_NOT_FOUND, 'message not recognized'
197             , $server->faultMessageNotRecognized($bodyel, $soapaction, \@ports)
198             );
199             }
200              
201             #------------------
202              
203             sub operationsFromWSDL($@)
204 0     0 1 0 { my ($self, $wsdl, %args) = @_;
205 0 0       0 my %callbacks = $args{callbacks} ? %{$args{callbacks}} : ();
  0         0  
206 0         0 my %names;
207              
208 0         0 my $default_cb = $args{default_callback};
209 0         0 my $wsa_input = $self->{wsa_input};
210 0         0 my $wsa_output = $self->{wsa_output};
211              
212 0         0 my $ops = $args{operations};
213 0 0       0 my @ops = $ops ? @$ops : $wsdl->operations(%args);
214 0 0       0 @ops or return; # none selected
215              
216 0         0 foreach my $op (@ops)
217 0         0 { my $name = $op->name;
218             warning __x"multiple operations with name `{name}'", name => $name
219 0 0       0 if $names{$name}++;
220              
221 0         0 my $code;
222 0 0       0 if(my $callback = $callbacks{$name})
223 0 0       0 { UNIVERSAL::isa($callback, 'CODE')
224             or error __x"callback {name} must provide a CODE ref"
225             , name => $name;
226              
227 0         0 trace __x"add handler for operation `{name}'", name => $name;
228 0         0 $code = $op->compileHandler(callback => $callback);
229             }
230             else
231 0         0 { trace __x"add stub handler for operation `{name}'", name => $name;
232             my $handler = $default_cb
233 0   0 0   0 || sub { $_[0]->faultNotImplemented($name) };
  0         0  
234              
235 0         0 $code = $op->compileHandler(callback => $handler);
236             }
237              
238 0         0 $self->addHandler($name, $op, $code);
239              
240 0 0       0 if($op->can('wsaAction'))
241 0         0 { my $in = $op->wsaAction('INPUT');
242 0 0       0 $wsa_input->{$name} = $in if defined $in;
243 0         0 my $out = $op->wsaAction('OUTPUT');
244 0 0       0 $wsa_output->{$name} = $out if defined $out;
245             }
246 0         0 $self->addSoapAction($name, $op->soapAction);
247             }
248              
249 0         0 info __x"added {nr} operations from WSDL", nr => (scalar @ops);
250              
251 0 0       0 if(keys %names != keys %callbacks)
252             { $names{$_}
253             or warning __x"no operation for callback handler `{name}'",name=>$_
254 0   0     0 for sort keys %callbacks;
255             }
256              
257 0         0 $self;
258             }
259              
260              
261             sub addHandler($$$)
262 0     0 1 0 { my ($self, $name, $soap, $code) = @_;
263              
264 0 0       0 my $version = ref $soap ? $soap->version : $soap;
265 0         0 $self->{handler}{$version}{$name} = $code;
266             }
267              
268              
269             sub setWsdlResponse($;$)
270 0     0 1 0 { my ($self, $filename, $type) = @_;
271 0   0     0 panic "not implemented by backend {pkg}", pkg => (ref $self || $self);
272             }
273              
274             #------------------
275              
276             sub handlers($)
277 0     0 1 0 { my ($self, $soap) = @_;
278 0 0       0 my $version = ref $soap ? $soap->version : $soap;
279 0   0     0 my $table = $self->{handler}{$version} || {};
280 0         0 keys %$table;
281             }
282              
283              
284 1     1 1 2 sub soapVersions() { sort keys %{shift->{handler}} }
  1         4  
285              
286              
287             sub printIndex(;$)
288 0     0 1 0 { my $self = shift;
289 0   0     0 my $fh = shift || \*STDOUT;
290              
291 0         0 foreach my $version ($self->soapVersions)
292 0         0 { my @handlers = $self->handlers($version);
293 0 0       0 @handlers or next;
294              
295 0         0 local $" = "\n ";
296 0         0 $fh->print("$version:\n @handlers\n");
297             }
298             }
299              
300              
301             sub faultInvalidXML($)
302 1     1 1 74 { my ($self, $error) = @_;
303 1         4 ( RC_UNPROCESSABLE_ENTITY, 'XML syntax error'
304             , __x("The XML cannot be parsed: {error}", error => $error));
305             }
306              
307              
308             sub faultNotSoapMessage($)
309 1     1 1 72 { my ($self, $type) = @_;
310 1         5 ( RC_FORBIDDEN, 'message not SOAP'
311             , __x( "The message was XML, but not SOAP; not an Envelope but `{type}'"
312             , type => $type));
313             }
314              
315              
316             sub faultUnsupportedSoapVersion($)
317 1     1 1 14 { my ($self, $envns) = @_;
318 1         4 ( RC_NOT_IMPLEMENTED, 'SOAP version not supported'
319             , __x("The soap version `{envns}' is not supported", envns => $envns));
320             }
321              
322             #------------------
323              
324             1;