class.soap_server.php (39941B)
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 */ 16 class 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 */ 1123 class soap_server extends nusoap_server { 1124 } 1125 1126 1127 ?>