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