class.soap_server.php 37 KB

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