Directory structure refactoring.
[siap.git] / ws / nusoap / lib / class.soap_server.php
CommitLineData
1c21f490
MS
1<?php
2
3
4
5
6/**
7*
8* nusoap_server allows the user to create a SOAP server
9* that is capable of receiving messages and returning responses
10*
11* @author Dietrich Ayala <dietrich@ganx4.com>
12* @author Scott Nichol <snichol@users.sourceforge.net>
13* @version $Id: class.soap_server.php,v 1.63 2010/04/26 20:15:08 snichol Exp $
14* @access public
15*/
16class nusoap_server extends nusoap_base {
17 /**
18 * HTTP headers of request
19 * @var array
20 * @access private
21 */
22 var $headers = array();
23 /**
24 * HTTP request
25 * @var string
26 * @access private
27 */
28 var $request = '';
29 /**
30 * SOAP headers from request (incomplete namespace resolution; special characters not escaped) (text)
31 * @var string
32 * @access public
33 */
34 var $requestHeaders = '';
35 /**
36 * SOAP Headers from request (parsed)
37 * @var mixed
38 * @access public
39 */
40 var $requestHeader = NULL;
41 /**
42 * SOAP body request portion (incomplete namespace resolution; special characters not escaped) (text)
43 * @var string
44 * @access public
45 */
46 var $document = '';
47 /**
48 * SOAP payload for request (text)
49 * @var string
50 * @access public
51 */
52 var $requestSOAP = '';
53 /**
54 * requested method namespace URI
55 * @var string
56 * @access private
57 */
58 var $methodURI = '';
59 /**
60 * name of method requested
61 * @var string
62 * @access private
63 */
64 var $methodname = '';
65 /**
66 * method parameters from request
67 * @var array
68 * @access private
69 */
70 var $methodparams = array();
71 /**
72 * SOAP Action from request
73 * @var string
74 * @access private
75 */
76 var $SOAPAction = '';
77 /**
78 * character set encoding of incoming (request) messages
79 * @var string
80 * @access public
81 */
82 var $xml_encoding = '';
83 /**
84 * toggles whether the parser decodes element content w/ utf8_decode()
85 * @var boolean
86 * @access public
87 */
88 var $decode_utf8 = true;
89
90 /**
91 * HTTP headers of response
92 * @var array
93 * @access public
94 */
95 var $outgoing_headers = array();
96 /**
97 * HTTP response
98 * @var string
99 * @access private
100 */
101 var $response = '';
102 /**
103 * SOAP headers for response (text or array of soapval or associative array)
104 * @var mixed
105 * @access public
106 */
107 var $responseHeaders = '';
108 /**
109 * SOAP payload for response (text)
110 * @var string
111 * @access private
112 */
113 var $responseSOAP = '';
114 /**
115 * method return value to place in response
116 * @var mixed
117 * @access private
118 */
119 var $methodreturn = false;
120 /**
121 * whether $methodreturn is a string of literal XML
122 * @var boolean
123 * @access public
124 */
125 var $methodreturnisliteralxml = false;
126 /**
127 * SOAP fault for response (or false)
128 * @var mixed
129 * @access private
130 */
131 var $fault = false;
132 /**
133 * text indication of result (for debugging)
134 * @var string
135 * @access private
136 */
137 var $result = 'successful';
138
139 /**
140 * assoc array of operations => opData; operations are added by the register()
141 * method or by parsing an external WSDL definition
142 * @var array
143 * @access private
144 */
145 var $operations = array();
146 /**
147 * wsdl instance (if one)
148 * @var mixed
149 * @access private
150 */
151 var $wsdl = false;
152 /**
153 * URL for WSDL (if one)
154 * @var mixed
155 * @access private
156 */
157 var $externalWSDLURL = false;
158 /**
159 * whether to append debug to response as XML comment
160 * @var boolean
161 * @access public
162 */
163 var $debug_flag = false;
164
165
166 /**
167 * constructor
168 * the optional parameter is a path to a WSDL file that you'd like to bind the server instance to.
169 *
170 * @param mixed $wsdl file path or URL (string), or wsdl instance (object)
171 * @access public
172 */
173 function nusoap_server($wsdl=false){
174 parent::nusoap_base();
175 // turn on debugging?
176 global $debug;
177 global $HTTP_SERVER_VARS;
178
179 if (isset($_SERVER)) {
180 $this->debug("_SERVER is defined:");
181 $this->appendDebug($this->varDump($_SERVER));
182 } elseif (isset($HTTP_SERVER_VARS)) {
183 $this->debug("HTTP_SERVER_VARS is defined:");
184 $this->appendDebug($this->varDump($HTTP_SERVER_VARS));
185 } else {
186 $this->debug("Neither _SERVER nor HTTP_SERVER_VARS is defined.");
187 }
188
189 if (isset($debug)) {
190 $this->debug("In nusoap_server, set debug_flag=$debug based on global flag");
191 $this->debug_flag = $debug;
192 } elseif (isset($_SERVER['QUERY_STRING'])) {
193 $qs = explode('&', $_SERVER['QUERY_STRING']);
194 foreach ($qs as $v) {
195 if (substr($v, 0, 6) == 'debug=') {
196 $this->debug("In nusoap_server, set debug_flag=" . substr($v, 6) . " based on query string #1");
197 $this->debug_flag = substr($v, 6);
198 }
199 }
200 } elseif (isset($HTTP_SERVER_VARS['QUERY_STRING'])) {
201 $qs = explode('&', $HTTP_SERVER_VARS['QUERY_STRING']);
202 foreach ($qs as $v) {
203 if (substr($v, 0, 6) == 'debug=') {
204 $this->debug("In nusoap_server, set debug_flag=" . substr($v, 6) . " based on query string #2");
205 $this->debug_flag = substr($v, 6);
206 }
207 }
208 }
209
210 // wsdl
211 if($wsdl){
212 $this->debug("In nusoap_server, WSDL is specified");
213 if (is_object($wsdl) && (get_class($wsdl) == 'wsdl')) {
214 $this->wsdl = $wsdl;
215 $this->externalWSDLURL = $this->wsdl->wsdl;
216 $this->debug('Use existing wsdl instance from ' . $this->externalWSDLURL);
217 } else {
218 $this->debug('Create wsdl from ' . $wsdl);
219 $this->wsdl = new wsdl($wsdl);
220 $this->externalWSDLURL = $wsdl;
221 }
222 $this->appendDebug($this->wsdl->getDebug());
223 $this->wsdl->clearDebug();
224 if($err = $this->wsdl->getError()){
225 die('WSDL ERROR: '.$err);
226 }
227 }
228 }
229
230 /**
231 * processes request and returns response
232 *
233 * @param string $data usually is the value of $HTTP_RAW_POST_DATA
234 * @access public
235 */
236 function service($data){
237 global $HTTP_SERVER_VARS;
238
239 if (isset($_SERVER['REQUEST_METHOD'])) {
240 $rm = $_SERVER['REQUEST_METHOD'];
241 } elseif (isset($HTTP_SERVER_VARS['REQUEST_METHOD'])) {
242 $rm = $HTTP_SERVER_VARS['REQUEST_METHOD'];
243 } else {
244 $rm = '';
245 }
246
247 if (isset($_SERVER['QUERY_STRING'])) {
248 $qs = $_SERVER['QUERY_STRING'];
249 } elseif (isset($HTTP_SERVER_VARS['QUERY_STRING'])) {
250 $qs = $HTTP_SERVER_VARS['QUERY_STRING'];
251 } else {
252 $qs = '';
253 }
254 $this->debug("In service, request method=$rm query string=$qs strlen(\$data)=" . strlen($data));
255
256 if ($rm == 'POST') {
257 $this->debug("In service, invoke the request");
258 $this->parse_request($data);
259 if (! $this->fault) {
260 $this->invoke_method();
261 }
262 if (! $this->fault) {
263 $this->serialize_return();
264 }
265 $this->send_response();
266 } elseif (preg_match('/wsdl/', $qs) ){
267 $this->debug("In service, this is a request for WSDL");
268 if ($this->externalWSDLURL){
269 if (strpos($this->externalWSDLURL, "http://") !== false) { // assume URL
270 $this->debug("In service, re-direct for WSDL");
271 header('Location: '.$this->externalWSDLURL);
272 } else { // assume file
273 $this->debug("In service, use file passthru for WSDL");
274 header("Content-Type: text/xml\r\n");
275 $pos = strpos($this->externalWSDLURL, "file://");
276 if ($pos === false) {
277 $filename = $this->externalWSDLURL;
278 } else {
279 $filename = substr($this->externalWSDLURL, $pos + 7);
280 }
281 $fp = fopen($this->externalWSDLURL, 'r');
282 fpassthru($fp);
283 }
284 } elseif ($this->wsdl) {
285 $this->debug("In service, serialize WSDL");
286 header("Content-Type: text/xml; charset=ISO-8859-1\r\n");
287 print $this->wsdl->serialize($this->debug_flag);
288 if ($this->debug_flag) {
289 $this->debug('wsdl:');
290 $this->appendDebug($this->varDump($this->wsdl));
291 print $this->getDebugAsXMLComment();
292 }
293 } else {
294 $this->debug("In service, there is no WSDL");
295 header("Content-Type: text/html; charset=ISO-8859-1\r\n");
296 print "This service does not provide WSDL";
297 }
298 } elseif ($this->wsdl) {
299 $this->debug("In service, return Web description");
300 print $this->wsdl->webDescription();
301 } else {
302 $this->debug("In service, no Web description");
303 header("Content-Type: text/html; charset=ISO-8859-1\r\n");
304 print "This service does not provide a Web description";
305 }
306 }
307
308 /**
309 * parses HTTP request headers.
310 *
311 * The following fields are set by this function (when successful)
312 *
313 * headers
314 * request
315 * xml_encoding
316 * SOAPAction
317 *
318 * @access private
319 */
320 function parse_http_headers() {
321 global $HTTP_SERVER_VARS;
322
323 $this->request = '';
324 $this->SOAPAction = '';
325 if(function_exists('getallheaders')){
326 $this->debug("In parse_http_headers, use getallheaders");
327 $headers = getallheaders();
328 foreach($headers as $k=>$v){
329 $k = strtolower($k);
330 $this->headers[$k] = $v;
331 $this->request .= "$k: $v\r\n";
332 $this->debug("$k: $v");
333 }
334 // get SOAPAction header
335 if(isset($this->headers['soapaction'])){
336 $this->SOAPAction = str_replace('"','',$this->headers['soapaction']);
337 }
338 // get the character encoding of the incoming request
339 if(isset($this->headers['content-type']) && strpos($this->headers['content-type'],'=')){
340 $enc = str_replace('"','',substr(strstr($this->headers["content-type"],'='),1));
341 if(preg_match('/^(ISO-8859-1|US-ASCII|UTF-8)$/i',$enc)){
342 $this->xml_encoding = strtoupper($enc);
343 } else {
344 $this->xml_encoding = 'US-ASCII';
345 }
346 } else {
347 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
348 $this->xml_encoding = 'ISO-8859-1';
349 }
350 } elseif(isset($_SERVER) && is_array($_SERVER)){
351 $this->debug("In parse_http_headers, use _SERVER");
352 foreach ($_SERVER as $k => $v) {
353 if (substr($k, 0, 5) == 'HTTP_') {
354 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($k, 5))));
355 } else {
356 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', $k)));
357 }
358 if ($k == 'soapaction') {
359 // get SOAPAction header
360 $k = 'SOAPAction';
361 $v = str_replace('"', '', $v);
362 $v = str_replace('\\', '', $v);
363 $this->SOAPAction = $v;
364 } else if ($k == 'content-type') {
365 // get the character encoding of the incoming request
366 if (strpos($v, '=')) {
367 $enc = substr(strstr($v, '='), 1);
368 $enc = str_replace('"', '', $enc);
369 $enc = str_replace('\\', '', $enc);
370 if (preg_match('/^(ISO-8859-1|US-ASCII|UTF-8)$/i',$enc)) {
371 $this->xml_encoding = strtoupper($enc);
372 } else {
373 $this->xml_encoding = 'US-ASCII';
374 }
375 } else {
376 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
377 $this->xml_encoding = 'ISO-8859-1';
378 }
379 }
380 $this->headers[$k] = $v;
381 $this->request .= "$k: $v\r\n";
382 $this->debug("$k: $v");
383 }
384 } elseif (is_array($HTTP_SERVER_VARS)) {
385 $this->debug("In parse_http_headers, use HTTP_SERVER_VARS");
386 foreach ($HTTP_SERVER_VARS as $k => $v) {
387 if (substr($k, 0, 5) == 'HTTP_') {
388 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', substr($k, 5)))); $k = strtolower(substr($k, 5));
389 } else {
390 $k = str_replace(' ', '-', strtolower(str_replace('_', ' ', $k))); $k = strtolower($k);
391 }
392 if ($k == 'soapaction') {
393 // get SOAPAction header
394 $k = 'SOAPAction';
395 $v = str_replace('"', '', $v);
396 $v = str_replace('\\', '', $v);
397 $this->SOAPAction = $v;
398 } else if ($k == 'content-type') {
399 // get the character encoding of the incoming request
400 if (strpos($v, '=')) {
401 $enc = substr(strstr($v, '='), 1);
402 $enc = str_replace('"', '', $enc);
403 $enc = str_replace('\\', '', $enc);
404 if (preg_match('/^(ISO-8859-1|US-ASCII|UTF-8)$/i',$enc)) {
405 $this->xml_encoding = strtoupper($enc);
406 } else {
407 $this->xml_encoding = 'US-ASCII';
408 }
409 } else {
410 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
411 $this->xml_encoding = 'ISO-8859-1';
412 }
413 }
414 $this->headers[$k] = $v;
415 $this->request .= "$k: $v\r\n";
416 $this->debug("$k: $v");
417 }
418 } else {
419 $this->debug("In parse_http_headers, HTTP headers not accessible");
420 $this->setError("HTTP headers not accessible");
421 }
422 }
423
424 /**
425 * parses a request
426 *
427 * The following fields are set by this function (when successful)
428 *
429 * headers
430 * request
431 * xml_encoding
432 * SOAPAction
433 * request
434 * requestSOAP
435 * methodURI
436 * methodname
437 * methodparams
438 * requestHeaders
439 * document
440 *
441 * This sets the fault field on error
442 *
443 * @param string $data XML string
444 * @access private
445 */
446 function parse_request($data='') {
447 $this->debug('entering parse_request()');
448 $this->parse_http_headers();
449 $this->debug('got character encoding: '.$this->xml_encoding);
450 // uncompress if necessary
451 if (isset($this->headers['content-encoding']) && $this->headers['content-encoding'] != '') {
452 $this->debug('got content encoding: ' . $this->headers['content-encoding']);
453 if ($this->headers['content-encoding'] == 'deflate' || $this->headers['content-encoding'] == 'gzip') {
454 // if decoding works, use it. else assume data wasn't gzencoded
455 if (function_exists('gzuncompress')) {
456 if ($this->headers['content-encoding'] == 'deflate' && $degzdata = @gzuncompress($data)) {
457 $data = $degzdata;
458 } elseif ($this->headers['content-encoding'] == 'gzip' && $degzdata = gzinflate(substr($data, 10))) {
459 $data = $degzdata;
460 } else {
461 $this->fault('SOAP-ENV:Client', 'Errors occurred when trying to decode the data');
462 return;
463 }
464 } else {
465 $this->fault('SOAP-ENV:Client', 'This Server does not support compressed data');
466 return;
467 }
468 }
469 }
470 $this->request .= "\r\n".$data;
471 $data = $this->parseRequest($this->headers, $data);
472 $this->requestSOAP = $data;
473 $this->debug('leaving parse_request');
474 }
475
476 /**
477 * invokes a PHP function for the requested SOAP method
478 *
479 * The following fields are set by this function (when successful)
480 *
481 * methodreturn
482 *
483 * Note that the PHP function that is called may also set the following
484 * fields to affect the response sent to the client
485 *
486 * responseHeaders
487 * outgoing_headers
488 *
489 * This sets the fault field on error
490 *
491 * @access private
492 */
493 function invoke_method() {
494 $this->debug('in invoke_method, methodname=' . $this->methodname . ' methodURI=' . $this->methodURI . ' SOAPAction=' . $this->SOAPAction);
495
496 //
497 // if you are debugging in this area of the code, your service uses a class to implement methods,
498 // you use SOAP RPC, and the client is .NET, please be aware of the following...
499 // when the .NET wsdl.exe utility generates a proxy, it will remove the '.' or '..' from the
500 // method name. that is fine for naming the .NET methods. it is not fine for properly constructing
501 // the XML request and reading the XML response. you need to add the RequestElementName and
502 // ResponseElementName to the System.Web.Services.Protocols.SoapRpcMethodAttribute that wsdl.exe
503 // generates for the method. these parameters are used to specify the correct XML element names
504 // for .NET to use, i.e. the names with the '.' in them.
505 //
506 $orig_methodname = $this->methodname;
507 if ($this->wsdl) {
508 if ($this->opData = $this->wsdl->getOperationData($this->methodname)) {
509 $this->debug('in invoke_method, found WSDL operation=' . $this->methodname);
510 $this->appendDebug('opData=' . $this->varDump($this->opData));
511 } elseif ($this->opData = $this->wsdl->getOperationDataForSoapAction($this->SOAPAction)) {
512 // Note: hopefully this case will only be used for doc/lit, since rpc services should have wrapper element
513 $this->debug('in invoke_method, found WSDL soapAction=' . $this->SOAPAction . ' for operation=' . $this->opData['name']);
514 $this->appendDebug('opData=' . $this->varDump($this->opData));
515 $this->methodname = $this->opData['name'];
516 } else {
517 $this->debug('in invoke_method, no WSDL for operation=' . $this->methodname);
518 $this->fault('SOAP-ENV:Client', "Operation '" . $this->methodname . "' is not defined in the WSDL for this service");
519 return;
520 }
521 } else {
522 $this->debug('in invoke_method, no WSDL to validate method');
523 }
524
525 // if a . is present in $this->methodname, we see if there is a class in scope,
526 // which could be referred to. We will also distinguish between two deliminators,
527 // to allow methods to be called a the class or an instance
528 if (strpos($this->methodname, '..') > 0) {
529 $delim = '..';
530 } else if (strpos($this->methodname, '.') > 0) {
531 $delim = '.';
532 } else {
533 $delim = '';
534 }
535 $this->debug("in invoke_method, delim=$delim");
536
537 $class = '';
538 $method = '';
539 if (strlen($delim) > 0 && substr_count($this->methodname, $delim) == 1) {
540 $try_class = substr($this->methodname, 0, strpos($this->methodname, $delim));
541 if (class_exists($try_class)) {
542 // get the class and method name
543 $class = $try_class;
544 $method = substr($this->methodname, strpos($this->methodname, $delim) + strlen($delim));
545 $this->debug("in invoke_method, class=$class method=$method delim=$delim");
546 } else {
547 $this->debug("in invoke_method, class=$try_class not found");
548 }
549 } else {
550 $try_class = '';
551 $this->debug("in invoke_method, no class to try");
552 }
553
554 // does method exist?
555 if ($class == '') {
556 if (!function_exists($this->methodname)) {
557 $this->debug("in invoke_method, function '$this->methodname' not found!");
558 $this->result = 'fault: method not found';
559 $this->fault('SOAP-ENV:Client',"method '$this->methodname'('$orig_methodname') not defined in service('$try_class' '$delim')");
560 return;
561 }
562 } else {
563 $method_to_compare = (substr(phpversion(), 0, 2) == '4.') ? strtolower($method) : $method;
564 if (!in_array($method_to_compare, get_class_methods($class))) {
565 $this->debug("in invoke_method, method '$this->methodname' not found in class '$class'!");
566 $this->result = 'fault: method not found';
567 $this->fault('SOAP-ENV:Client',"method '$this->methodname'/'$method_to_compare'('$orig_methodname') not defined in service/'$class'('$try_class' '$delim')");
568 return;
569 }
570 }
571
572 // evaluate message, getting back parameters
573 // verify that request parameters match the method's signature
574 if(! $this->verify_method($this->methodname,$this->methodparams)){
575 // debug
576 $this->debug('ERROR: request not verified against method signature');
577 $this->result = 'fault: request failed validation against method signature';
578 // return fault
579 $this->fault('SOAP-ENV:Client',"Operation '$this->methodname' not defined in service.");
580 return;
581 }
582
583 // if there are parameters to pass
584 $this->debug('in invoke_method, params:');
585 $this->appendDebug($this->varDump($this->methodparams));
586 $this->debug("in invoke_method, calling '$this->methodname'");
587 if (!function_exists('call_user_func_array')) {
588 if ($class == '') {
589 $this->debug('in invoke_method, calling function using eval()');
590 $funcCall = "\$this->methodreturn = $this->methodname(";
591 } else {
592 if ($delim == '..') {
593 $this->debug('in invoke_method, calling class method using eval()');
594 $funcCall = "\$this->methodreturn = ".$class."::".$method."(";
595 } else {
596 $this->debug('in invoke_method, calling instance method using eval()');
597 // generate unique instance name
598 $instname = "\$inst_".time();
599 $funcCall = $instname." = new ".$class."(); ";
600 $funcCall .= "\$this->methodreturn = ".$instname."->".$method."(";
601 }
602 }
603 if ($this->methodparams) {
604 foreach ($this->methodparams as $param) {
605 if (is_array($param) || is_object($param)) {
606 $this->fault('SOAP-ENV:Client', 'NuSOAP does not handle complexType parameters correctly when using eval; call_user_func_array must be available');
607 return;
608 }
609 $funcCall .= "\"$param\",";
610 }
611 $funcCall = substr($funcCall, 0, -1);
612 }
613 $funcCall .= ');';
614 $this->debug('in invoke_method, function call: '.$funcCall);
615 @eval($funcCall);
616 } else {
617 if ($class == '') {
618 $this->debug('in invoke_method, calling function using call_user_func_array()');
619 $call_arg = "$this->methodname"; // straight assignment changes $this->methodname to lower case after call_user_func_array()
620 } elseif ($delim == '..') {
621 $this->debug('in invoke_method, calling class method using call_user_func_array()');
622 $call_arg = array ($class, $method);
623 } else {
624 $this->debug('in invoke_method, calling instance method using call_user_func_array()');
625 $instance = new $class ();
626 $call_arg = array(&$instance, $method);
627 }
628 if (is_array($this->methodparams)) {
629 $this->methodreturn = call_user_func_array($call_arg, array_values($this->methodparams));
630 } else {
631 $this->methodreturn = call_user_func_array($call_arg, array());
632 }
633 }
634 $this->debug('in invoke_method, methodreturn:');
635 $this->appendDebug($this->varDump($this->methodreturn));
636 $this->debug("in invoke_method, called method $this->methodname, received data of type ".gettype($this->methodreturn));
637 }
638
639 /**
640 * serializes the return value from a PHP function into a full SOAP Envelope
641 *
642 * The following fields are set by this function (when successful)
643 *
644 * responseSOAP
645 *
646 * This sets the fault field on error
647 *
648 * @access private
649 */
650 function serialize_return() {
651 $this->debug('Entering serialize_return methodname: ' . $this->methodname . ' methodURI: ' . $this->methodURI);
652 // if fault
653 if (isset($this->methodreturn) && is_object($this->methodreturn) && ((get_class($this->methodreturn) == 'soap_fault') || (get_class($this->methodreturn) == 'nusoap_fault'))) {
654 $this->debug('got a fault object from method');
655 $this->fault = $this->methodreturn;
656 return;
657 } elseif ($this->methodreturnisliteralxml) {
658 $return_val = $this->methodreturn;
659 // returned value(s)
660 } else {
661 $this->debug('got a(n) '.gettype($this->methodreturn).' from method');
662 $this->debug('serializing return value');
663 if($this->wsdl){
664 if (sizeof($this->opData['output']['parts']) > 1) {
665 $this->debug('more than one output part, so use the method return unchanged');
666 $opParams = $this->methodreturn;
667 } elseif (sizeof($this->opData['output']['parts']) == 1) {
668 $this->debug('exactly one output part, so wrap the method return in a simple array');
669 // TODO: verify that it is not already wrapped!
670 //foreach ($this->opData['output']['parts'] as $name => $type) {
671 // $this->debug('wrap in element named ' . $name);
672 //}
673 $opParams = array($this->methodreturn);
674 }
675 $return_val = $this->wsdl->serializeRPCParameters($this->methodname,'output',$opParams);
676 $this->appendDebug($this->wsdl->getDebug());
677 $this->wsdl->clearDebug();
678 if($errstr = $this->wsdl->getError()){
679 $this->debug('got wsdl error: '.$errstr);
680 $this->fault('SOAP-ENV:Server', 'unable to serialize result');
681 return;
682 }
683 } else {
684 if (isset($this->methodreturn)) {
685 $return_val = $this->serialize_val($this->methodreturn, 'return');
686 } else {
687 $return_val = '';
688 $this->debug('in absence of WSDL, assume void return for backward compatibility');
689 }
690 }
691 }
692 $this->debug('return value:');
693 $this->appendDebug($this->varDump($return_val));
694
695 $this->debug('serializing response');
696 if ($this->wsdl) {
697 $this->debug('have WSDL for serialization: style is ' . $this->opData['style']);
698 if ($this->opData['style'] == 'rpc') {
699 $this->debug('style is rpc for serialization: use is ' . $this->opData['output']['use']);
700 if ($this->opData['output']['use'] == 'literal') {
701 // http://www.ws-i.org/Profiles/BasicProfile-1.1-2004-08-24.html R2735 says rpc/literal accessor elements should not be in a namespace
702 if ($this->methodURI) {
703 $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
704 } else {
705 $payload = '<'.$this->methodname.'Response>'.$return_val.'</'.$this->methodname.'Response>';
706 }
707 } else {
708 if ($this->methodURI) {
709 $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
710 } else {
711 $payload = '<'.$this->methodname.'Response>'.$return_val.'</'.$this->methodname.'Response>';
712 }
713 }
714 } else {
715 $this->debug('style is not rpc for serialization: assume document');
716 $payload = $return_val;
717 }
718 } else {
719 $this->debug('do not have WSDL for serialization: assume rpc/encoded');
720 $payload = '<ns1:'.$this->methodname.'Response xmlns:ns1="'.$this->methodURI.'">'.$return_val.'</ns1:'.$this->methodname."Response>";
721 }
722 $this->result = 'successful';
723 if($this->wsdl){
724 //if($this->debug_flag){
725 $this->appendDebug($this->wsdl->getDebug());
726 // }
727 if (isset($this->opData['output']['encodingStyle'])) {
728 $encodingStyle = $this->opData['output']['encodingStyle'];
729 } else {
730 $encodingStyle = '';
731 }
732 // Added: In case we use a WSDL, return a serialized env. WITH the usedNamespaces.
733 $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders,$this->wsdl->usedNamespaces,$this->opData['style'],$this->opData['output']['use'],$encodingStyle);
734 } else {
735 $this->responseSOAP = $this->serializeEnvelope($payload,$this->responseHeaders);
736 }
737 $this->debug("Leaving serialize_return");
738 }
739
740 /**
741 * sends an HTTP response
742 *
743 * The following fields are set by this function (when successful)
744 *
745 * outgoing_headers
746 * response
747 *
748 * @access private
749 */
750 function send_response() {
751 $this->debug('Enter send_response');
752 if ($this->fault) {
753 $payload = $this->fault->serialize();
754 $this->outgoing_headers[] = "HTTP/1.0 500 Internal Server Error";
755 $this->outgoing_headers[] = "Status: 500 Internal Server Error";
756 } else {
757 $payload = $this->responseSOAP;
758 // Some combinations of PHP+Web server allow the Status
759 // to come through as a header. Since OK is the default
760 // just do nothing.
761 // $this->outgoing_headers[] = "HTTP/1.0 200 OK";
762 // $this->outgoing_headers[] = "Status: 200 OK";
763 }
764 // add debug data if in debug mode
765 if(isset($this->debug_flag) && $this->debug_flag){
766 $payload .= $this->getDebugAsXMLComment();
767 }
768 $this->outgoing_headers[] = "Server: $this->title Server v$this->version";
769 preg_match('/\$Revisio' . 'n: ([^ ]+)/', $this->revision, $rev);
770 $this->outgoing_headers[] = "X-SOAP-Server: $this->title/$this->version (".$rev[1].")";
771 // Let the Web server decide about this
772 //$this->outgoing_headers[] = "Connection: Close\r\n";
773 $payload = $this->getHTTPBody($payload);
774 $type = $this->getHTTPContentType();
775 $charset = $this->getHTTPContentTypeCharset();
776 $this->outgoing_headers[] = "Content-Type: $type" . ($charset ? '; charset=' . $charset : '');
777 //begin code to compress payload - by John
778 // NOTE: there is no way to know whether the Web server will also compress
779 // this data.
780 if (strlen($payload) > 1024 && isset($this->headers) && isset($this->headers['accept-encoding'])) {
781 if (strstr($this->headers['accept-encoding'], 'gzip')) {
782 if (function_exists('gzencode')) {
783 if (isset($this->debug_flag) && $this->debug_flag) {
784 $payload .= "<!-- Content being gzipped -->";
785 }
786 $this->outgoing_headers[] = "Content-Encoding: gzip";
787 $payload = gzencode($payload);
788 } else {
789 if (isset($this->debug_flag) && $this->debug_flag) {
790 $payload .= "<!-- Content will not be gzipped: no gzencode -->";
791 }
792 }
793 } elseif (strstr($this->headers['accept-encoding'], 'deflate')) {
794 // Note: MSIE requires gzdeflate output (no Zlib header and checksum),
795 // instead of gzcompress output,
796 // which conflicts with HTTP 1.1 spec (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.5)
797 if (function_exists('gzdeflate')) {
798 if (isset($this->debug_flag) && $this->debug_flag) {
799 $payload .= "<!-- Content being deflated -->";
800 }
801 $this->outgoing_headers[] = "Content-Encoding: deflate";
802 $payload = gzdeflate($payload);
803 } else {
804 if (isset($this->debug_flag) && $this->debug_flag) {
805 $payload .= "<!-- Content will not be deflated: no gzcompress -->";
806 }
807 }
808 }
809 }
810 //end code
811 $this->outgoing_headers[] = "Content-Length: ".strlen($payload);
812 reset($this->outgoing_headers);
813 foreach($this->outgoing_headers as $hdr){
814 header($hdr, false);
815 }
816 print $payload;
817 $this->response = join("\r\n",$this->outgoing_headers)."\r\n\r\n".$payload;
818 }
819
820 /**
821 * takes the value that was created by parsing the request
822 * and compares to the method's signature, if available.
823 *
824 * @param string $operation The operation to be invoked
825 * @param array $request The array of parameter values
826 * @return boolean Whether the operation was found
827 * @access private
828 */
829 function verify_method($operation,$request){
830 if(isset($this->wsdl) && is_object($this->wsdl)){
831 if($this->wsdl->getOperationData($operation)){
832 return true;
833 }
834 } elseif(isset($this->operations[$operation])){
835 return true;
836 }
837 return false;
838 }
839
840 /**
841 * processes SOAP message received from client
842 *
843 * @param array $headers The HTTP headers
844 * @param string $data unprocessed request data from client
845 * @return mixed value of the message, decoded into a PHP type
846 * @access private
847 */
848 function parseRequest($headers, $data) {
849 $this->debug('Entering parseRequest() for data of length ' . strlen($data) . ' headers:');
850 $this->appendDebug($this->varDump($headers));
851 if (!isset($headers['content-type'])) {
852 $this->setError('Request not of type text/xml (no content-type header)');
853 return false;
854 }
855 if (!strstr($headers['content-type'], 'text/xml')) {
856 $this->setError('Request not of type text/xml');
857 return false;
858 }
859 if (strpos($headers['content-type'], '=')) {
860 $enc = str_replace('"', '', substr(strstr($headers["content-type"], '='), 1));
861 $this->debug('Got response encoding: ' . $enc);
862 if(preg_match('/^(ISO-8859-1|US-ASCII|UTF-8)$/i',$enc)){
863 $this->xml_encoding = strtoupper($enc);
864 } else {
865 $this->xml_encoding = 'US-ASCII';
866 }
867 } else {
868 // should be US-ASCII for HTTP 1.0 or ISO-8859-1 for HTTP 1.1
869 $this->xml_encoding = 'ISO-8859-1';
870 }
871 $this->debug('Use encoding: ' . $this->xml_encoding . ' when creating nusoap_parser');
872 // parse response, get soap parser obj
873 $parser = new nusoap_parser($data,$this->xml_encoding,'',$this->decode_utf8);
874 // parser debug
875 $this->debug("parser debug: \n".$parser->getDebug());
876 // if fault occurred during message parsing
877 if($err = $parser->getError()){
878 $this->result = 'fault: error in msg parsing: '.$err;
879 $this->fault('SOAP-ENV:Client',"error in msg parsing:\n".$err);
880 // else successfully parsed request into soapval object
881 } else {
882 // get/set methodname
883 $this->methodURI = $parser->root_struct_namespace;
884 $this->methodname = $parser->root_struct_name;
885 $this->debug('methodname: '.$this->methodname.' methodURI: '.$this->methodURI);
886 $this->debug('calling parser->get_soapbody()');
887 $this->methodparams = $parser->get_soapbody();
888 // get SOAP headers
889 $this->requestHeaders = $parser->getHeaders();
890 // get SOAP Header
891 $this->requestHeader = $parser->get_soapheader();
892 // add document for doclit support
893 $this->document = $parser->document;
894 }
895 }
896
897 /**
898 * gets the HTTP body for the current response.
899 *
900 * @param string $soapmsg The SOAP payload
901 * @return string The HTTP body, which includes the SOAP payload
902 * @access private
903 */
904 function getHTTPBody($soapmsg) {
905 return $soapmsg;
906 }
907
908 /**
909 * gets the HTTP content type for the current response.
910 *
911 * Note: getHTTPBody must be called before this.
912 *
913 * @return string the HTTP content type for the current response.
914 * @access private
915 */
916 function getHTTPContentType() {
917 return 'text/xml';
918 }
919
920 /**
921 * gets the HTTP content type charset for the current response.
922 * returns false for non-text content types.
923 *
924 * Note: getHTTPBody must be called before this.
925 *
926 * @return string the HTTP content type charset for the current response.
927 * @access private
928 */
929 function getHTTPContentTypeCharset() {
930 return $this->soap_defencoding;
931 }
932
933 /**
934 * add a method to the dispatch map (this has been replaced by the register method)
935 *
936 * @param string $methodname
937 * @param string $in array of input values
938 * @param string $out array of output values
939 * @access public
940 * @deprecated
941 */
942 function add_to_map($methodname,$in,$out){
943 $this->operations[$methodname] = array('name' => $methodname,'in' => $in,'out' => $out);
944 }
945
946 /**
947 * register a service function with the server
948 *
949 * @param string $name the name of the PHP function, class.method or class..method
950 * @param array $in assoc array of input values: key = param name, value = param type
951 * @param array $out assoc array of output values: key = param name, value = param type
952 * @param mixed $namespace the element namespace for the method or false
953 * @param mixed $soapaction the soapaction for the method or false
954 * @param mixed $style optional (rpc|document) or false Note: when 'document' is specified, parameter and return wrappers are created for you automatically
955 * @param mixed $use optional (encoded|literal) or false
956 * @param string $documentation optional Description to include in WSDL
957 * @param string $encodingStyle optional (usually 'http://schemas.xmlsoap.org/soap/encoding/' for encoded)
958 * @access public
959 */
960 function register($name,$in=array(),$out=array(),$namespace=false,$soapaction=false,$style=false,$use=false,$documentation='',$encodingStyle=''){
961 global $HTTP_SERVER_VARS;
962
963 if($this->externalWSDLURL){
964 die('You cannot bind to an external WSDL file, and register methods outside of it! Please choose either WSDL or no WSDL.');
965 }
966 if (! $name) {
967 die('You must specify a name when you register an operation');
968 }
969 if (!is_array($in)) {
970 die('You must provide an array for operation inputs');
971 }
972 if (!is_array($out)) {
973 die('You must provide an array for operation outputs');
974 }
975 if(false == $namespace) {
976 }
977 if(false == $soapaction) {
978 if (isset($_SERVER)) {
979 $SERVER_NAME = $_SERVER['SERVER_NAME'];
980 $SCRIPT_NAME = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
981 $HTTPS = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : (isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off');
982 } elseif (isset($HTTP_SERVER_VARS)) {
983 $SERVER_NAME = $HTTP_SERVER_VARS['SERVER_NAME'];
984 $SCRIPT_NAME = isset($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : $HTTP_SERVER_VARS['SCRIPT_NAME'];
985 $HTTPS = isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off';
986 } else {
987 $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available");
988 }
989 if ($HTTPS == '1' || $HTTPS == 'on') {
990 $SCHEME = 'https';
991 } else {
992 $SCHEME = 'http';
993 }
994 $soapaction = "$SCHEME://$SERVER_NAME$SCRIPT_NAME/$name";
995 }
996 if(false == $style) {
997 $style = "rpc";
998 }
999 if(false == $use) {
1000 $use = "encoded";
1001 }
1002 if ($use == 'encoded' && $encodingStyle == '') {
1003 $encodingStyle = 'http://schemas.xmlsoap.org/soap/encoding/';
1004 }
1005
1006 $this->operations[$name] = array(
1007 'name' => $name,
1008 'in' => $in,
1009 'out' => $out,
1010 'namespace' => $namespace,
1011 'soapaction' => $soapaction,
1012 'style' => $style);
1013 if($this->wsdl){
1014 $this->wsdl->addOperation($name,$in,$out,$namespace,$soapaction,$style,$use,$documentation,$encodingStyle);
1015 }
1016 return true;
1017 }
1018
1019 /**
1020 * Specify a fault to be returned to the client.
1021 * This also acts as a flag to the server that a fault has occured.
1022 *
1023 * @param string $faultcode
1024 * @param string $faultstring
1025 * @param string $faultactor
1026 * @param string $faultdetail
1027 * @access public
1028 */
1029 function fault($faultcode,$faultstring,$faultactor='',$faultdetail=''){
1030 if ($faultdetail == '' && $this->debug_flag) {
1031 $faultdetail = $this->getDebug();
1032 }
1033 $this->fault = new nusoap_fault($faultcode,$faultactor,$faultstring,$faultdetail);
1034 $this->fault->soap_defencoding = $this->soap_defencoding;
1035 }
1036
1037 /**
1038 * Sets up wsdl object.
1039 * Acts as a flag to enable internal WSDL generation
1040 *
1041 * @param string $serviceName, name of the service
1042 * @param mixed $namespace optional 'tns' service namespace or false
1043 * @param mixed $endpoint optional URL of service endpoint or false
1044 * @param string $style optional (rpc|document) WSDL style (also specified by operation)
1045 * @param string $transport optional SOAP transport
1046 * @param mixed $schemaTargetNamespace optional 'types' targetNamespace for service schema or false
1047 */
1048 function configureWSDL($serviceName,$namespace = false,$endpoint = false,$style='rpc', $transport = 'http://schemas.xmlsoap.org/soap/http', $schemaTargetNamespace = false)
1049 {
1050 global $HTTP_SERVER_VARS;
1051
1052 if (isset($_SERVER)) {
1053 $SERVER_NAME = $_SERVER['SERVER_NAME'];
1054 $SERVER_PORT = $_SERVER['SERVER_PORT'];
1055 $SCRIPT_NAME = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
1056 $HTTPS = isset($_SERVER['HTTPS']) ? $_SERVER['HTTPS'] : (isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off');
1057 } elseif (isset($HTTP_SERVER_VARS)) {
1058 $SERVER_NAME = $HTTP_SERVER_VARS['SERVER_NAME'];
1059 $SERVER_PORT = $HTTP_SERVER_VARS['SERVER_PORT'];
1060 $SCRIPT_NAME = isset($HTTP_SERVER_VARS['PHP_SELF']) ? $HTTP_SERVER_VARS['PHP_SELF'] : $HTTP_SERVER_VARS['SCRIPT_NAME'];
1061 $HTTPS = isset($HTTP_SERVER_VARS['HTTPS']) ? $HTTP_SERVER_VARS['HTTPS'] : 'off';
1062 } else {
1063 $this->setError("Neither _SERVER nor HTTP_SERVER_VARS is available");
1064 }
1065 // If server name has port number attached then strip it (else port number gets duplicated in WSDL output) (occurred using lighttpd and FastCGI)
1066 $colon = strpos($SERVER_NAME,":");
1067 if ($colon) {
1068 $SERVER_NAME = substr($SERVER_NAME, 0, $colon);
1069 }
1070 if ($SERVER_PORT == 80) {
1071 $SERVER_PORT = '';
1072 } else {
1073 $SERVER_PORT = ':' . $SERVER_PORT;
1074 }
1075 if(false == $namespace) {
1076 $namespace = "http://$SERVER_NAME/soap/$serviceName";
1077 }
1078
1079 if(false == $endpoint) {
1080 if ($HTTPS == '1' || $HTTPS == 'on') {
1081 $SCHEME = 'https';
1082 } else {
1083 $SCHEME = 'http';
1084 }
1085 $endpoint = "$SCHEME://$SERVER_NAME$SERVER_PORT$SCRIPT_NAME";
1086 }
1087
1088 if(false == $schemaTargetNamespace) {
1089 $schemaTargetNamespace = $namespace;
1090 }
1091
1092 $this->wsdl = new wsdl;
1093 $this->wsdl->serviceName = $serviceName;
1094 $this->wsdl->endpoint = $endpoint;
1095 $this->wsdl->namespaces['tns'] = $namespace;
1096 $this->wsdl->namespaces['soap'] = 'http://schemas.xmlsoap.org/wsdl/soap/';
1097 $this->wsdl->namespaces['wsdl'] = 'http://schemas.xmlsoap.org/wsdl/';
1098 if ($schemaTargetNamespace != $namespace) {
1099 $this->wsdl->namespaces['types'] = $schemaTargetNamespace;
1100 }
1101 $this->wsdl->schemas[$schemaTargetNamespace][0] = new nusoap_xmlschema('', '', $this->wsdl->namespaces);
1102 if ($style == 'document') {
1103 $this->wsdl->schemas[$schemaTargetNamespace][0]->schemaInfo['elementFormDefault'] = 'qualified';
1104 }
1105 $this->wsdl->schemas[$schemaTargetNamespace][0]->schemaTargetNamespace = $schemaTargetNamespace;
1106 $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/soap/encoding/'][0] = array('location' => '', 'loaded' => true);
1107 $this->wsdl->schemas[$schemaTargetNamespace][0]->imports['http://schemas.xmlsoap.org/wsdl/'][0] = array('location' => '', 'loaded' => true);
1108 $this->wsdl->bindings[$serviceName.'Binding'] = array(
1109 'name'=>$serviceName.'Binding',
1110 'style'=>$style,
1111 'transport'=>$transport,
1112 'portType'=>$serviceName.'PortType');
1113 $this->wsdl->ports[$serviceName.'Port'] = array(
1114 'binding'=>$serviceName.'Binding',
1115 'location'=>$endpoint,
1116 'bindingType'=>'http://schemas.xmlsoap.org/wsdl/soap/');
1117 }
1118}
1119
1120/**
1121 * Backward compatibility
1122 */
1123class soap_server extends nusoap_server {
1124}
1125
1126
1127?>