PageRenderTime 53ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/phpxmlrpc/xmlrpcs.php

https://bitbucket.org/asosso/joomla15
PHP | 1187 lines | 839 code | 77 blank | 271 comment | 152 complexity | 2f9ae1de3dec8d7f1880e20d3d35cb0f MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0
  1. <?php
  2. // by Edd Dumbill (C) 1999-2002
  3. // <edd@usefulinc.com>
  4. // $Id: xmlrpcs.inc,v 1.67 2007/05/22 21:31:58 ggiunta Exp $
  5. // Copyright (c) 1999,2000,2002 Edd Dumbill.
  6. // All rights reserved.
  7. //
  8. // Redistribution and use in source and binary forms, with or without
  9. // modification, are permitted provided that the following conditions
  10. // are met:
  11. //
  12. // * Redistributions of source code must retain the above copyright
  13. // notice, this list of conditions and the following disclaimer.
  14. //
  15. // * Redistributions in binary form must reproduce the above
  16. // copyright notice, this list of conditions and the following
  17. // disclaimer in the documentation and/or other materials provided
  18. // with the distribution.
  19. //
  20. // * Neither the name of the "XML-RPC for PHP" nor the names of its
  21. // contributors may be used to endorse or promote products derived
  22. // from this software without specific prior written permission.
  23. //
  24. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  25. // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  26. // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  27. // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  28. // REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  29. // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  30. // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  31. // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  32. // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  33. // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  34. // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
  35. // OF THE POSSIBILITY OF SUCH DAMAGE.
  36. // Do not allow direct access
  37. defined( '_JEXEC' ) or die( 'Restricted access' );
  38. // XML RPC Server class
  39. // requires: xmlrpc.inc
  40. $GLOBALS['xmlrpcs_capabilities'] = array(
  41. // xmlrpc spec: always supported
  42. 'xmlrpc' => new xmlrpcval(array(
  43. 'specUrl' => new xmlrpcval('http://www.xmlrpc.com/spec', 'string'),
  44. 'specVersion' => new xmlrpcval(1, 'int')
  45. ), 'struct'),
  46. // if we support system.xxx functions, we always support multicall, too...
  47. // Note that, as of 2006/09/17, the following URL does not respond anymore
  48. 'system.multicall' => new xmlrpcval(array(
  49. 'specUrl' => new xmlrpcval('http://www.xmlrpc.com/discuss/msgReader$1208', 'string'),
  50. 'specVersion' => new xmlrpcval(1, 'int')
  51. ), 'struct'),
  52. // introspection: version 2! we support 'mixed', too
  53. 'introspection' => new xmlrpcval(array(
  54. 'specUrl' => new xmlrpcval('http://phpxmlrpc.sourceforge.net/doc-2/ch10.html', 'string'),
  55. 'specVersion' => new xmlrpcval(2, 'int')
  56. ), 'struct')
  57. );
  58. /* Functions that implement system.XXX methods of xmlrpc servers */
  59. $_xmlrpcs_getCapabilities_sig=array(array($GLOBALS['xmlrpcStruct']));
  60. $_xmlrpcs_getCapabilities_doc='This method lists all the capabilites that the XML-RPC server has: the (more or less standard) extensions to the xmlrpc spec that it adheres to';
  61. $_xmlrpcs_getCapabilities_sdoc=array(array('list of capabilities, described as structs with a version number and url for the spec'));
  62. function _xmlrpcs_getCapabilities($server, $m=null)
  63. {
  64. $outAr = $GLOBALS['xmlrpcs_capabilities'];
  65. // NIL extension
  66. if ($GLOBALS['xmlrpc_null_extension']) {
  67. $outAr['nil'] = new xmlrpcval(array(
  68. 'specUrl' => new xmlrpcval('http://www.ontosys.com/xml-rpc/extensions.php', 'string'),
  69. 'specVersion' => new xmlrpcval(1, 'int')
  70. ), 'struct');
  71. }
  72. return new xmlrpcresp(new xmlrpcval($outAr, 'struct'));
  73. }
  74. // listMethods: signature was either a string, or nothing.
  75. // The useless string variant has been removed
  76. $_xmlrpcs_listMethods_sig=array(array($GLOBALS['xmlrpcArray']));
  77. $_xmlrpcs_listMethods_doc='This method lists all the methods that the XML-RPC server knows how to dispatch';
  78. $_xmlrpcs_listMethods_sdoc=array(array('list of method names'));
  79. function _xmlrpcs_listMethods($server, $m=null) // if called in plain php values mode, second param is missing
  80. {
  81. $outAr=array();
  82. foreach($server->dmap as $key => $val)
  83. {
  84. $outAr[]=&new xmlrpcval($key, 'string');
  85. }
  86. if($server->allow_system_funcs)
  87. {
  88. foreach($GLOBALS['_xmlrpcs_dmap'] as $key => $val)
  89. {
  90. $outAr[]=&new xmlrpcval($key, 'string');
  91. }
  92. }
  93. return new xmlrpcresp(new xmlrpcval($outAr, 'array'));
  94. }
  95. $_xmlrpcs_methodSignature_sig=array(array($GLOBALS['xmlrpcArray'], $GLOBALS['xmlrpcString']));
  96. $_xmlrpcs_methodSignature_doc='Returns an array of known signatures (an array of arrays) for the method name passed. If no signatures are known, returns a none-array (test for type != array to detect missing signature)';
  97. $_xmlrpcs_methodSignature_sdoc=array(array('list of known signatures, each sig being an array of xmlrpc type names', 'name of method to be described'));
  98. function _xmlrpcs_methodSignature($server, $m)
  99. {
  100. // let accept as parameter both an xmlrpcval or string
  101. if (is_object($m))
  102. {
  103. $methName=$m->getParam(0);
  104. $methName=$methName->scalarval();
  105. }
  106. else
  107. {
  108. $methName=$m;
  109. }
  110. if(strpos($methName, "system.") === 0)
  111. {
  112. $dmap=$GLOBALS['_xmlrpcs_dmap']; $sysCall=1;
  113. }
  114. else
  115. {
  116. $dmap=$server->dmap; $sysCall=0;
  117. }
  118. if(isset($dmap[$methName]))
  119. {
  120. if(isset($dmap[$methName]['signature']))
  121. {
  122. $sigs=array();
  123. foreach($dmap[$methName]['signature'] as $inSig)
  124. {
  125. $cursig=array();
  126. foreach($inSig as $sig)
  127. {
  128. $cursig[]=&new xmlrpcval($sig, 'string');
  129. }
  130. $sigs[]=&new xmlrpcval($cursig, 'array');
  131. }
  132. $r=&new xmlrpcresp(new xmlrpcval($sigs, 'array'));
  133. }
  134. else
  135. {
  136. // NB: according to the official docs, we should be returning a
  137. // "none-array" here, which means not-an-array
  138. $r=&new xmlrpcresp(new xmlrpcval('undef', 'string'));
  139. }
  140. }
  141. else
  142. {
  143. $r=&new xmlrpcresp(0,$GLOBALS['xmlrpcerr']['introspect_unknown'], $GLOBALS['xmlrpcstr']['introspect_unknown']);
  144. }
  145. return $r;
  146. }
  147. $_xmlrpcs_methodHelp_sig=array(array($GLOBALS['xmlrpcString'], $GLOBALS['xmlrpcString']));
  148. $_xmlrpcs_methodHelp_doc='Returns help text if defined for the method passed, otherwise returns an empty string';
  149. $_xmlrpcs_methodHelp_sdoc=array(array('method description', 'name of the method to be described'));
  150. function _xmlrpcs_methodHelp($server, $m)
  151. {
  152. // let accept as parameter both an xmlrpcval or string
  153. if (is_object($m))
  154. {
  155. $methName=$m->getParam(0);
  156. $methName=$methName->scalarval();
  157. }
  158. else
  159. {
  160. $methName=$m;
  161. }
  162. if(strpos($methName, "system.") === 0)
  163. {
  164. $dmap=$GLOBALS['_xmlrpcs_dmap']; $sysCall=1;
  165. }
  166. else
  167. {
  168. $dmap=$server->dmap; $sysCall=0;
  169. }
  170. if(isset($dmap[$methName]))
  171. {
  172. if(isset($dmap[$methName]['docstring']))
  173. {
  174. $r=&new xmlrpcresp(new xmlrpcval($dmap[$methName]['docstring']), 'string');
  175. }
  176. else
  177. {
  178. $r=&new xmlrpcresp(new xmlrpcval('', 'string'));
  179. }
  180. }
  181. else
  182. {
  183. $r=&new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['introspect_unknown'], $GLOBALS['xmlrpcstr']['introspect_unknown']);
  184. }
  185. return $r;
  186. }
  187. $_xmlrpcs_multicall_sig = array(array($GLOBALS['xmlrpcArray'], $GLOBALS['xmlrpcArray']));
  188. $_xmlrpcs_multicall_doc = 'Boxcar multiple RPC calls in one request. See http://www.xmlrpc.com/discuss/msgReader$1208 for details';
  189. $_xmlrpcs_multicall_sdoc = array(array('list of response structs, where each struct has the usual members', 'list of calls, with each call being represented as a struct, with members "methodname" and "params"'));
  190. function _xmlrpcs_multicall_error($err)
  191. {
  192. if(is_string($err))
  193. {
  194. $str = $GLOBALS['xmlrpcstr']["multicall_${err}"];
  195. $code = $GLOBALS['xmlrpcerr']["multicall_${err}"];
  196. }
  197. else
  198. {
  199. $code = $err->faultCode();
  200. $str = $err->faultString();
  201. }
  202. $struct = array();
  203. $struct['faultCode'] =& new xmlrpcval($code, 'int');
  204. $struct['faultString'] =& new xmlrpcval($str, 'string');
  205. return new xmlrpcval($struct, 'struct');
  206. }
  207. function _xmlrpcs_multicall_do_call($server, $call)
  208. {
  209. if($call->kindOf() != 'struct')
  210. {
  211. return _xmlrpcs_multicall_error('notstruct');
  212. }
  213. $methName = @$call->structmem('methodName');
  214. if(!$methName)
  215. {
  216. return _xmlrpcs_multicall_error('nomethod');
  217. }
  218. if($methName->kindOf() != 'scalar' || $methName->scalartyp() != 'string')
  219. {
  220. return _xmlrpcs_multicall_error('notstring');
  221. }
  222. if($methName->scalarval() == 'system.multicall')
  223. {
  224. return _xmlrpcs_multicall_error('recursion');
  225. }
  226. $params = @$call->structmem('params');
  227. if(!$params)
  228. {
  229. return _xmlrpcs_multicall_error('noparams');
  230. }
  231. if($params->kindOf() != 'array')
  232. {
  233. return _xmlrpcs_multicall_error('notarray');
  234. }
  235. $numParams = $params->arraysize();
  236. $msg =& new xmlrpcmsg($methName->scalarval());
  237. for($i = 0; $i < $numParams; $i++)
  238. {
  239. if(!$msg->addParam($params->arraymem($i)))
  240. {
  241. $i++;
  242. return _xmlrpcs_multicall_error(new xmlrpcresp(0,
  243. $GLOBALS['xmlrpcerr']['incorrect_params'],
  244. $GLOBALS['xmlrpcstr']['incorrect_params'] . ": probable xml error in param " . $i));
  245. }
  246. }
  247. $result = $server->execute($msg);
  248. if($result->faultCode() != 0)
  249. {
  250. return _xmlrpcs_multicall_error($result); // Method returned fault.
  251. }
  252. return new xmlrpcval(array($result->value()), 'array');
  253. }
  254. function _xmlrpcs_multicall_do_call_phpvals($server, $call)
  255. {
  256. if(!is_array($call))
  257. {
  258. return _xmlrpcs_multicall_error('notstruct');
  259. }
  260. if(!array_key_exists('methodName', $call))
  261. {
  262. return _xmlrpcs_multicall_error('nomethod');
  263. }
  264. if (!is_string($call['methodName']))
  265. {
  266. return _xmlrpcs_multicall_error('notstring');
  267. }
  268. if($call['methodName'] == 'system.multicall')
  269. {
  270. return _xmlrpcs_multicall_error('recursion');
  271. }
  272. if(!array_key_exists('params', $call))
  273. {
  274. return _xmlrpcs_multicall_error('noparams');
  275. }
  276. if(!is_array($call['params']))
  277. {
  278. return _xmlrpcs_multicall_error('notarray');
  279. }
  280. // this is a real dirty and simplistic hack, since we might have received a
  281. // base64 or datetime values, but they will be listed as strings here...
  282. $numParams = count($call['params']);
  283. $pt = array();
  284. foreach($call['params'] as $val)
  285. $pt[] = php_2_xmlrpc_type(gettype($val));
  286. $result = $server->execute($call['methodName'], $call['params'], $pt);
  287. if($result->faultCode() != 0)
  288. {
  289. return _xmlrpcs_multicall_error($result); // Method returned fault.
  290. }
  291. return new xmlrpcval(array($result->value()), 'array');
  292. }
  293. function _xmlrpcs_multicall($server, $m)
  294. {
  295. $result = array();
  296. // let accept a plain list of php parameters, beside a single xmlrpc msg object
  297. if (is_object($m))
  298. {
  299. $calls = $m->getParam(0);
  300. $numCalls = $calls->arraysize();
  301. for($i = 0; $i < $numCalls; $i++)
  302. {
  303. $call = $calls->arraymem($i);
  304. $result[$i] = _xmlrpcs_multicall_do_call($server, $call);
  305. }
  306. }
  307. else
  308. {
  309. $numCalls=count($m);
  310. for($i = 0; $i < $numCalls; $i++)
  311. {
  312. $result[$i] = _xmlrpcs_multicall_do_call_phpvals($server, $m[$i]);
  313. }
  314. }
  315. return new xmlrpcresp(new xmlrpcval($result, 'array'));
  316. }
  317. $GLOBALS['_xmlrpcs_dmap']=array(
  318. 'system.listMethods' => array(
  319. 'function' => '_xmlrpcs_listMethods',
  320. 'signature' => $_xmlrpcs_listMethods_sig,
  321. 'docstring' => $_xmlrpcs_listMethods_doc,
  322. 'signature_docs' => $_xmlrpcs_listMethods_sdoc),
  323. 'system.methodHelp' => array(
  324. 'function' => '_xmlrpcs_methodHelp',
  325. 'signature' => $_xmlrpcs_methodHelp_sig,
  326. 'docstring' => $_xmlrpcs_methodHelp_doc,
  327. 'signature_docs' => $_xmlrpcs_methodHelp_sdoc),
  328. 'system.methodSignature' => array(
  329. 'function' => '_xmlrpcs_methodSignature',
  330. 'signature' => $_xmlrpcs_methodSignature_sig,
  331. 'docstring' => $_xmlrpcs_methodSignature_doc,
  332. 'signature_docs' => $_xmlrpcs_methodSignature_sdoc),
  333. 'system.multicall' => array(
  334. 'function' => '_xmlrpcs_multicall',
  335. 'signature' => $_xmlrpcs_multicall_sig,
  336. 'docstring' => $_xmlrpcs_multicall_doc,
  337. 'signature_docs' => $_xmlrpcs_multicall_sdoc),
  338. 'system.getCapabilities' => array(
  339. 'function' => '_xmlrpcs_getCapabilities',
  340. 'signature' => $_xmlrpcs_getCapabilities_sig,
  341. 'docstring' => $_xmlrpcs_getCapabilities_doc,
  342. 'signature_docs' => $_xmlrpcs_getCapabilities_sdoc)
  343. );
  344. $GLOBALS['_xmlrpcs_occurred_errors'] = '';
  345. $GLOBALS['_xmlrpcs_prev_ehandler'] = '';
  346. /**
  347. * Error handler used to track errors that occur during server-side execution of PHP code.
  348. * This allows to report back to the client whether an internal error has occurred or not
  349. * using an xmlrpc response object, instead of letting the client deal with the html junk
  350. * that a PHP execution error on the server generally entails.
  351. *
  352. * NB: in fact a user defined error handler can only handle WARNING, NOTICE and USER_* errors.
  353. *
  354. */
  355. function _xmlrpcs_errorHandler($errcode, $errstring, $filename=null, $lineno=null, $context=null)
  356. {
  357. // obey the @ protocol
  358. if (error_reporting() == 0)
  359. return;
  360. //if($errcode != E_NOTICE && $errcode != E_WARNING && $errcode != E_USER_NOTICE && $errcode != E_USER_WARNING)
  361. if($errcode != 2048) // do not use E_STRICT by name, since on PHP 4 it will not be defined
  362. {
  363. $GLOBALS['_xmlrpcs_occurred_errors'] = $GLOBALS['_xmlrpcs_occurred_errors'] . $errstring . "\n";
  364. }
  365. // Try to avoid as much as possible disruption to the previous error handling
  366. // mechanism in place
  367. if($GLOBALS['_xmlrpcs_prev_ehandler'] == '')
  368. {
  369. // The previous error handler was the default: all we should do is log error
  370. // to the default error log (if level high enough)
  371. if(ini_get('log_errors') && (intval(ini_get('error_reporting')) & $errcode))
  372. {
  373. error_log($errstring);
  374. }
  375. }
  376. else
  377. {
  378. // Pass control on to previous error handler, trying to avoid loops...
  379. if($GLOBALS['_xmlrpcs_prev_ehandler'] != '_xmlrpcs_errorHandler')
  380. {
  381. // NB: this code will NOT work on php < 4.0.2: only 2 params were used for error handlers
  382. if(is_array($GLOBALS['_xmlrpcs_prev_ehandler']))
  383. {
  384. $GLOBALS['_xmlrpcs_prev_ehandler'][0]->$GLOBALS['_xmlrpcs_prev_ehandler'][1]($errcode, $errstring, $filename, $lineno, $context);
  385. }
  386. else
  387. {
  388. $GLOBALS['_xmlrpcs_prev_ehandler']($errcode, $errstring, $filename, $lineno, $context);
  389. }
  390. }
  391. }
  392. }
  393. $GLOBALS['_xmlrpc_debuginfo']='';
  394. /**
  395. * Add a string to the debug info that can be later seralized by the server
  396. * as part of the response message.
  397. * Note that for best compatbility, the debug string should be encoded using
  398. * the $GLOBALS['xmlrpc_internalencoding'] character set.
  399. * @param string $m
  400. * @access public
  401. */
  402. function xmlrpc_debugmsg($m)
  403. {
  404. $GLOBALS['_xmlrpc_debuginfo'] .= $m . "\n";
  405. }
  406. class xmlrpc_server
  407. {
  408. /// array defining php functions exposed as xmlrpc methods by this server
  409. var $dmap=array();
  410. /**
  411. * Defines how functions in dmap will be invokde: either using an xmlrpc msg object
  412. * or plain php values.
  413. * valid strings are 'xmlrpcvals', 'phpvals' or 'epivals'
  414. */
  415. var $functions_parameters_type='xmlrpcvals';
  416. /// controls wether the server is going to echo debugging messages back to the client as comments in response body. valid values: 0,1,2,3
  417. var $debug = 1;
  418. /**
  419. * When set to true, it will enable HTTP compression of the response, in case
  420. * the client has declared its support for compression in the request.
  421. */
  422. var $compress_response = false;
  423. /**
  424. * List of http compression methods accepted by the server for requests.
  425. * NB: PHP supports deflate, gzip compressions out of the box if compiled w. zlib
  426. */
  427. var $accepted_compression = array();
  428. /// shall we serve calls to system.* methods?
  429. var $allow_system_funcs = true;
  430. /// list of charset encodings natively accepted for requests
  431. var $accepted_charset_encodings = array();
  432. /**
  433. * charset encoding to be used for response.
  434. * NB: if we can, we will convert the generated response from internal_encoding to the intended one.
  435. * can be: a supported xml encoding (only UTF-8 and ISO-8859-1 at present, unless mbstring is enabled),
  436. * null (leave unspecified in response, convert output stream to US_ASCII),
  437. * 'default' (use xmlrpc library default as specified in xmlrpc.inc, convert output stream if needed),
  438. * or 'auto' (use client-specified charset encoding or same as request if request headers do not specify it (unless request is US-ASCII: then use library default anyway).
  439. * NB: pretty dangerous if you accept every charset and do not have mbstring enabled)
  440. */
  441. var $response_charset_encoding = '';
  442. /// storage for internal debug info
  443. var $debug_info = '';
  444. /// extra data passed at runtime to method handling functions. Used only by EPI layer
  445. var $user_data = null;
  446. /**
  447. * @param array $dispmap the dispatch map withd efinition of exposed services
  448. * @param boolean $servicenow set to false to prevent the server from runnung upon construction
  449. */
  450. function xmlrpc_server($dispMap=null, $serviceNow=true)
  451. {
  452. // if ZLIB is enabled, let the server by default accept compressed requests,
  453. // and compress responses sent to clients that support them
  454. if(function_exists('gzinflate'))
  455. {
  456. $this->accepted_compression = array('gzip', 'deflate');
  457. $this->compress_response = true;
  458. }
  459. // by default the xml parser can support these 3 charset encodings
  460. $this->accepted_charset_encodings = array('UTF-8', 'ISO-8859-1', 'US-ASCII');
  461. // dispMap is a dispatch array of methods
  462. // mapped to function names and signatures
  463. // if a method
  464. // doesn't appear in the map then an unknown
  465. // method error is generated
  466. /* milosch - changed to make passing dispMap optional.
  467. * instead, you can use the class add_to_map() function
  468. * to add functions manually (borrowed from SOAPX4)
  469. */
  470. if($dispMap)
  471. {
  472. $this->dmap = $dispMap;
  473. if($serviceNow)
  474. {
  475. $this->service();
  476. }
  477. }
  478. }
  479. /**
  480. * Set debug level of server.
  481. * @param integer $in debug lvl: determines info added to xmlrpc responses (as xml comments)
  482. * 0 = no debug info,
  483. * 1 = msgs set from user with debugmsg(),
  484. * 2 = add complete xmlrpc request (headers and body),
  485. * 3 = add also all processing warnings happened during method processing
  486. * (NB: this involves setting a custom error handler, and might interfere
  487. * with the standard processing of the php function exposed as method. In
  488. * particular, triggering an USER_ERROR level error will not halt script
  489. * execution anymore, but just end up logged in the xmlrpc response)
  490. * Note that info added at elevel 2 and 3 will be base64 encoded
  491. * @access public
  492. */
  493. function setDebug($in)
  494. {
  495. $this->debug=$in;
  496. }
  497. /**
  498. * Return a string with the serialized representation of all debug info
  499. * @param string $charset_encoding the target charset encoding for the serialization
  500. * @return string an XML comment (or two)
  501. */
  502. function serializeDebug($charset_encoding='')
  503. {
  504. // Tough encoding problem: which internal charset should we assume for debug info?
  505. // It might contain a copy of raw data received from client, ie with unknown encoding,
  506. // intermixed with php generated data and user generated data...
  507. // so we split it: system debug is base 64 encoded,
  508. // user debug info should be encoded by the end user using the INTERNAL_ENCODING
  509. $out = '';
  510. if ($this->debug_info != '')
  511. {
  512. $out .= "<!-- SERVER DEBUG INFO (BASE64 ENCODED):\n".base64_encode($this->debug_info)."\n-->\n";
  513. }
  514. if($GLOBALS['_xmlrpc_debuginfo']!='')
  515. {
  516. $out .= "<!-- DEBUG INFO:\n" . xmlrpc_encode_entitites(str_replace('--', '_-', $GLOBALS['_xmlrpc_debuginfo']), $GLOBALS['xmlrpc_internalencoding'], $charset_encoding) . "\n-->\n";
  517. // NB: a better solution MIGHT be to use CDATA, but we need to insert it
  518. // into return payload AFTER the beginning tag
  519. //$out .= "<![CDATA[ DEBUG INFO:\n\n" . str_replace(']]>', ']_]_>', $GLOBALS['_xmlrpc_debuginfo']) . "\n]]>\n";
  520. }
  521. return $out;
  522. }
  523. /**
  524. * Execute the xmlrpc request, printing the response
  525. * @param string $data the request body. If null, the http POST request will be examined
  526. * @return xmlrpcresp the response object (usually not used by caller...)
  527. * @access public
  528. */
  529. function service($data=null, $return_payload=false)
  530. {
  531. if ($data === null)
  532. {
  533. // workaround for a known bug in php ver. 5.2.2 that broke $HTTP_RAW_POST_DATA
  534. $ver = phpversion();
  535. if ($ver[0] >= 5)
  536. {
  537. $data = file_get_contents('php://input');
  538. }
  539. else
  540. {
  541. // Check if it has a value, if it doesn't have a value try and read php://input but supress the error
  542. // this will mimic returning an empty string, without a "cant find wrapper error" and allow backwards compat
  543. // php docs are unclear as to when this was added, works on php 4.4 at least, and probably 4.3
  544. $data = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : @file_get_contents('php://input');
  545. }
  546. }
  547. $raw_data = $data;
  548. // reset internal debug info
  549. $this->debug_info = '';
  550. // Echo back what we received, before parsing it
  551. if($this->debug > 1)
  552. {
  553. $this->debugmsg("+++GOT+++\n" . $data . "\n+++END+++");
  554. }
  555. $r = $this->parseRequestHeaders($data, $req_charset, $resp_charset, $resp_encoding);
  556. if (!$r)
  557. {
  558. $r=$this->parseRequest($data, $req_charset);
  559. }
  560. // save full body of request into response, for more debugging usages
  561. $r->raw_data = $raw_data;
  562. if($this->debug > 2 && $GLOBALS['_xmlrpcs_occurred_errors'])
  563. {
  564. $this->debugmsg("+++PROCESSING ERRORS AND WARNINGS+++\n" .
  565. $GLOBALS['_xmlrpcs_occurred_errors'] . "+++END+++");
  566. }
  567. $payload=$this->xml_header($resp_charset);
  568. if($this->debug > 0)
  569. {
  570. $payload = $payload . $this->serializeDebug($resp_charset);
  571. }
  572. // G. Giunta 2006-01-27: do not create response serialization if it has
  573. // already happened. Helps building json magic
  574. if (empty($r->payload))
  575. {
  576. $r->serialize($resp_charset);
  577. }
  578. $payload = $payload . $r->payload;
  579. if ($return_payload)
  580. {
  581. return $payload;
  582. }
  583. // if we get a warning/error that has output some text before here, then we cannot
  584. // add a new header. We cannot say we are sending xml, either...
  585. if(!headers_sent())
  586. {
  587. header('Content-Type: '.$r->content_type);
  588. // we do not know if client actually told us an accepted charset, but if he did
  589. // we have to tell him what we did
  590. header("Vary: Accept-Charset");
  591. // http compression of output: only
  592. // if we can do it, and we want to do it, and client asked us to,
  593. // and php ini settings do not force it already
  594. $php_no_self_compress = ini_get('zlib.output_compression') == '' && (ini_get('output_handler') != 'ob_gzhandler');
  595. if($this->compress_response && function_exists('gzencode') && $resp_encoding != ''
  596. && $php_no_self_compress)
  597. {
  598. if(strpos($resp_encoding, 'gzip') !== false)
  599. {
  600. $payload = gzencode($payload);
  601. header("Content-Encoding: gzip");
  602. header("Vary: Accept-Encoding");
  603. }
  604. elseif (strpos($resp_encoding, 'deflate') !== false)
  605. {
  606. $payload = gzcompress($payload);
  607. header("Content-Encoding: deflate");
  608. header("Vary: Accept-Encoding");
  609. }
  610. }
  611. // do not ouput content-length header if php is compressing output for us:
  612. // it will mess up measurements
  613. if($php_no_self_compress)
  614. {
  615. header('Content-Length: ' . (int)strlen($payload));
  616. }
  617. }
  618. else
  619. {
  620. error_log('XML-RPC: xmlrpc_server::service: http headers already sent before response is fully generated. Check for php warning or error messages');
  621. }
  622. print $payload;
  623. // return request, in case subclasses want it
  624. return $r;
  625. }
  626. /**
  627. * Add a method to the dispatch map
  628. * @param string $methodname the name with which the method will be made available
  629. * @param string $function the php function that will get invoked
  630. * @param array $sig the array of valid method signatures
  631. * @param string $doc method documentation
  632. * @access public
  633. */
  634. function add_to_map($methodname,$function,$sig=null,$doc='')
  635. {
  636. $this->dmap[$methodname] = array(
  637. 'function' => $function,
  638. 'docstring' => $doc
  639. );
  640. if ($sig)
  641. {
  642. $this->dmap[$methodname]['signature'] = $sig;
  643. }
  644. }
  645. /**
  646. * Verify type and number of parameters received against a list of known signatures
  647. * @param array $in array of either xmlrpcval objects or xmlrpc type definitions
  648. * @param array $sig array of known signatures to match against
  649. * @access private
  650. */
  651. function verifySignature($in, $sig)
  652. {
  653. // check each possible signature in turn
  654. if (is_object($in))
  655. {
  656. $numParams = $in->getNumParams();
  657. }
  658. else
  659. {
  660. $numParams = count($in);
  661. }
  662. foreach($sig as $cursig)
  663. {
  664. if(count($cursig)==$numParams+1)
  665. {
  666. $itsOK=1;
  667. for($n=0; $n<$numParams; $n++)
  668. {
  669. if (is_object($in))
  670. {
  671. $p=$in->getParam($n);
  672. if($p->kindOf() == 'scalar')
  673. {
  674. $pt=$p->scalartyp();
  675. }
  676. else
  677. {
  678. $pt=$p->kindOf();
  679. }
  680. }
  681. else
  682. {
  683. $pt= $in[$n] == 'i4' ? 'int' : $in[$n]; // dispatch maps never use i4...
  684. }
  685. // param index is $n+1, as first member of sig is return type
  686. if($pt != $cursig[$n+1] && $cursig[$n+1] != $GLOBALS['xmlrpcValue'])
  687. {
  688. $itsOK=0;
  689. $pno=$n+1;
  690. $wanted=$cursig[$n+1];
  691. $got=$pt;
  692. break;
  693. }
  694. }
  695. if($itsOK)
  696. {
  697. return array(1,'');
  698. }
  699. }
  700. }
  701. if(isset($wanted))
  702. {
  703. return array(0, "Wanted ${wanted}, got ${got} at param ${pno}");
  704. }
  705. else
  706. {
  707. return array(0, "No method signature matches number of parameters");
  708. }
  709. }
  710. /**
  711. * Parse http headers received along with xmlrpc request. If needed, inflate request
  712. * @return null on success or an xmlrpcresp
  713. * @access private
  714. */
  715. function parseRequestHeaders(&$data, &$req_encoding, &$resp_encoding, &$resp_compression)
  716. {
  717. // Play nice to PHP 4.0.x: superglobals were not yet invented...
  718. if(!isset($_SERVER))
  719. {
  720. $_SERVER = $GLOBALS['HTTP_SERVER_VARS'];
  721. }
  722. if($this->debug > 1)
  723. {
  724. if(function_exists('getallheaders'))
  725. {
  726. $this->debugmsg(''); // empty line
  727. foreach(getallheaders() as $name => $val)
  728. {
  729. $this->debugmsg("HEADER: $name: $val");
  730. }
  731. }
  732. }
  733. if(isset($_SERVER['HTTP_CONTENT_ENCODING']))
  734. {
  735. $content_encoding = str_replace('x-', '', $_SERVER['HTTP_CONTENT_ENCODING']);
  736. }
  737. else
  738. {
  739. $content_encoding = '';
  740. }
  741. // check if request body has been compressed and decompress it
  742. if($content_encoding != '' && strlen($data))
  743. {
  744. if($content_encoding == 'deflate' || $content_encoding == 'gzip')
  745. {
  746. // if decoding works, use it. else assume data wasn't gzencoded
  747. if(function_exists('gzinflate') && in_array($content_encoding, $this->accepted_compression))
  748. {
  749. if($content_encoding == 'deflate' && $degzdata = @gzuncompress($data))
  750. {
  751. $data = $degzdata;
  752. if($this->debug > 1)
  753. {
  754. $this->debugmsg("\n+++INFLATED REQUEST+++[".strlen($data)." chars]+++\n" . $data . "\n+++END+++");
  755. }
  756. }
  757. elseif($content_encoding == 'gzip' && $degzdata = @gzinflate(substr($data, 10)))
  758. {
  759. $data = $degzdata;
  760. if($this->debug > 1)
  761. $this->debugmsg("+++INFLATED REQUEST+++[".strlen($data)." chars]+++\n" . $data . "\n+++END+++");
  762. }
  763. else
  764. {
  765. $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['server_decompress_fail'], $GLOBALS['xmlrpcstr']['server_decompress_fail']);
  766. return $r;
  767. }
  768. }
  769. else
  770. {
  771. //error_log('The server sent deflated data. Your php install must have the Zlib extension compiled in to support this.');
  772. $r =& new xmlrpcresp(0, $GLOBALS['xmlrpcerr']['server_cannot_decompress'], $GLOBALS['xmlrpcstr']['server_cannot_decompress']);
  773. return $r;
  774. }
  775. }
  776. }
  777. // check if client specified accepted charsets, and if we know how to fulfill
  778. // the request
  779. if ($this->response_charset_encoding == 'auto')
  780. {
  781. $resp_encoding = '';
  782. if (isset($_SERVER['HTTP_ACCEPT_CHARSET']))
  783. {
  784. // here we should check if we can match the client-requested encoding
  785. // with the encodings we know we can generate.
  786. /// @todo we should parse q=0.x preferences instead of getting first charset specified...
  787. $client_accepted_charsets = explode(',', strtoupper($_SERVER['HTTP_ACCEPT_CHARSET']));
  788. // Give preference to internal encoding
  789. $known_charsets = array($this->internal_encoding, 'UTF-8', 'ISO-8859-1', 'US-ASCII');
  790. foreach ($known_charsets as $charset)
  791. {
  792. foreach ($client_accepted_charsets as $accepted)
  793. if (strpos($accepted, $charset) === 0)
  794. {
  795. $resp_encoding = $charset;
  796. break;
  797. }
  798. if ($resp_encoding)
  799. break;
  800. }
  801. }
  802. }
  803. else
  804. {
  805. $resp_encoding = $this->response_charset_encoding;
  806. }
  807. if (isset($_SERVER['HTTP_ACCEPT_ENCODING']))
  808. {
  809. $resp_compression = $_SERVER['HTTP_ACCEPT_ENCODING'];
  810. }
  811. else
  812. {
  813. $resp_compression = '';
  814. }
  815. // 'guestimate' request encoding
  816. /// @todo check if mbstring is enabled and automagic input conversion is on: it might mingle with this check???
  817. $req_encoding = guess_encoding(isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : '',
  818. $data);
  819. return null;
  820. }
  821. /**
  822. * Parse an xml chunk containing an xmlrpc request and execute the corresponding
  823. * php function registered with the server
  824. * @param string $data the xml request
  825. * @param string $req_encoding (optional) the charset encoding of the xml request
  826. * @return xmlrpcresp
  827. * @access private
  828. */
  829. function parseRequest($data, $req_encoding='')
  830. {
  831. // 2005/05/07 commented and moved into caller function code
  832. //if($data=='')
  833. //{
  834. // $data=$GLOBALS['HTTP_RAW_POST_DATA'];
  835. //}
  836. // G. Giunta 2005/02/13: we do NOT expect to receive html entities
  837. // so we do not try to convert them into xml character entities
  838. //$data = xmlrpc_html_entity_xlate($data);
  839. $GLOBALS['_xh']=array();
  840. $GLOBALS['_xh']['ac']='';
  841. $GLOBALS['_xh']['stack']=array();
  842. $GLOBALS['_xh']['valuestack'] = array();
  843. $GLOBALS['_xh']['params']=array();
  844. $GLOBALS['_xh']['pt']=array();
  845. $GLOBALS['_xh']['isf']=0;
  846. $GLOBALS['_xh']['isf_reason']='';
  847. $GLOBALS['_xh']['method']=false; // so we can check later if we got a methodname or not
  848. $GLOBALS['_xh']['rt']='';
  849. // decompose incoming XML into request structure
  850. if ($req_encoding != '')
  851. {
  852. if (!in_array($req_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
  853. // the following code might be better for mb_string enabled installs, but
  854. // makes the lib about 200% slower...
  855. //if (!is_valid_charset($req_encoding, array('UTF-8', 'ISO-8859-1', 'US-ASCII')))
  856. {
  857. error_log('XML-RPC: xmlrpc_server::parseRequest: invalid charset encoding of received request: '.$req_encoding);
  858. $req_encoding = $GLOBALS['xmlrpc_defencoding'];
  859. }
  860. /// @BUG this will fail on PHP 5 if charset is not specified in the xml prologue,
  861. // the encoding is not UTF8 and there are non-ascii chars in the text...
  862. $parser = xml_parser_create($req_encoding);
  863. }
  864. else
  865. {
  866. $parser = xml_parser_create();
  867. }
  868. xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, true);
  869. // G. Giunta 2005/02/13: PHP internally uses ISO-8859-1, so we have to tell
  870. // the xml parser to give us back data in the expected charset
  871. xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, $GLOBALS['xmlrpc_internalencoding']);
  872. if ($this->functions_parameters_type != 'xmlrpcvals')
  873. xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee_fast');
  874. else
  875. xml_set_element_handler($parser, 'xmlrpc_se', 'xmlrpc_ee');
  876. xml_set_character_data_handler($parser, 'xmlrpc_cd');
  877. xml_set_default_handler($parser, 'xmlrpc_dh');
  878. if(!xml_parse($parser, $data, 1))
  879. {
  880. // return XML error as a faultCode
  881. $r=&new xmlrpcresp(0,
  882. $GLOBALS['xmlrpcerrxml']+xml_get_error_code($parser),
  883. sprintf('XML error: %s at line %d, column %d',
  884. xml_error_string(xml_get_error_code($parser)),
  885. xml_get_current_line_number($parser), xml_get_current_column_number($parser)));
  886. xml_parser_free($parser);
  887. }
  888. elseif ($GLOBALS['_xh']['isf'])
  889. {
  890. xml_parser_free($parser);
  891. $r=&new xmlrpcresp(0,
  892. $GLOBALS['xmlrpcerr']['invalid_request'],
  893. $GLOBALS['xmlrpcstr']['invalid_request'] . ' ' . $GLOBALS['_xh']['isf_reason']);
  894. }
  895. else
  896. {
  897. xml_parser_free($parser);
  898. if ($this->functions_parameters_type != 'xmlrpcvals')
  899. {
  900. if($this->debug > 1)
  901. {
  902. $this->debugmsg("\n+++PARSED+++\n".var_export($GLOBALS['_xh']['params'], true)."\n+++END+++");
  903. }
  904. $r = $this->execute($GLOBALS['_xh']['method'], $GLOBALS['_xh']['params'], $GLOBALS['_xh']['pt']);
  905. }
  906. else
  907. {
  908. // build an xmlrpcmsg object with data parsed from xml
  909. $m=&new xmlrpcmsg($GLOBALS['_xh']['method']);
  910. // now add parameters in
  911. for($i=0; $i<count($GLOBALS['_xh']['params']); $i++)
  912. {
  913. $m->addParam($GLOBALS['_xh']['params'][$i]);
  914. }
  915. if($this->debug > 1)
  916. {
  917. $this->debugmsg("\n+++PARSED+++\n".var_export($m, true)."\n+++END+++");
  918. }
  919. $r = $this->execute($m);
  920. }
  921. }
  922. return $r;
  923. }
  924. /**
  925. * Execute a method invoked by the client, checking parameters used
  926. * @param mixed $m either an xmlrpcmsg obj or a method name
  927. * @param array $params array with method parameters as php types (if m is method name only)
  928. * @param array $paramtypes array with xmlrpc types of method parameters (if m is method name only)
  929. * @return xmlrpcresp
  930. * @access private
  931. */
  932. function execute($m, $params=null, $paramtypes=null)
  933. {
  934. if (is_object($m))
  935. {
  936. $methName = $m->method();
  937. }
  938. else
  939. {
  940. $methName = $m;
  941. }
  942. $sysCall = $this->allow_system_funcs && (strpos($methName, "system.") === 0);
  943. $dmap = $sysCall ? $GLOBALS['_xmlrpcs_dmap'] : $this->dmap;
  944. if(!isset($dmap[$methName]['function']))
  945. {
  946. // No such method
  947. return new xmlrpcresp(0,
  948. $GLOBALS['xmlrpcerr']['unknown_method'],
  949. $GLOBALS['xmlrpcstr']['unknown_method']);
  950. }
  951. // Check signature
  952. if(isset($dmap[$methName]['signature']))
  953. {
  954. $sig = $dmap[$methName]['signature'];
  955. if (is_object($m))
  956. {
  957. list($ok, $errstr) = $this->verifySignature($m, $sig);
  958. }
  959. else
  960. {
  961. list($ok, $errstr) = $this->verifySignature($paramtypes, $sig);
  962. }
  963. if(!$ok)
  964. {
  965. // Didn't match.
  966. return new xmlrpcresp(
  967. 0,
  968. $GLOBALS['xmlrpcerr']['incorrect_params'],
  969. $GLOBALS['xmlrpcstr']['incorrect_params'] . ": ${errstr}"
  970. );
  971. }
  972. }
  973. $func = $dmap[$methName]['function'];
  974. // let the 'class::function' syntax be accepted in dispatch maps
  975. if(is_string($func) && strpos($func, '::'))
  976. {
  977. $func = explode('::', $func);
  978. }
  979. // verify that function to be invoked is in fact callable
  980. if(!is_callable($func))
  981. {
  982. error_log("XML-RPC: xmlrpc_server::execute: function $func registered as method handler is not callable");
  983. return new xmlrpcresp(
  984. 0,
  985. $GLOBALS['xmlrpcerr']['server_error'],
  986. $GLOBALS['xmlrpcstr']['server_error'] . ": no function matches method"
  987. );
  988. }
  989. // If debug level is 3, we should catch all errors generated during
  990. // processing of user function, and log them as part of response
  991. if($this->debug > 2)
  992. {
  993. $GLOBALS['_xmlrpcs_prev_ehandler'] = set_error_handler('_xmlrpcs_errorHandler');
  994. }
  995. if (is_object($m))
  996. {
  997. if($sysCall)
  998. {
  999. $r = call_user_func($func, $this, $m);
  1000. }
  1001. else
  1002. {
  1003. $r = call_user_func($func, $m);
  1004. }
  1005. if (!is_a($r, 'xmlrpcresp'))
  1006. {
  1007. error_log("XML-RPC: xmlrpc_server::execute: function $func registered as method handler does not return an xmlrpcresp object");
  1008. if (is_a($r, 'xmlrpcval'))
  1009. {
  1010. $r =& new xmlrpcresp($r);
  1011. }
  1012. else
  1013. {
  1014. $r =& new xmlrpcresp(
  1015. 0,
  1016. $GLOBALS['xmlrpcerr']['server_error'],
  1017. $GLOBALS['xmlrpcstr']['server_error'] . ": function does not return xmlrpcresp object"
  1018. );
  1019. }
  1020. }
  1021. }
  1022. else
  1023. {
  1024. // call a 'plain php' function
  1025. if($sysCall)
  1026. {
  1027. array_unshift($params, $this);
  1028. $r = call_user_func_array($func, $params);
  1029. }
  1030. else
  1031. {
  1032. // 3rd API convention for method-handling functions: EPI-style
  1033. if ($this->functions_parameters_type == 'epivals')
  1034. {
  1035. $r = call_user_func_array($func, array($methName, $params, $this->user_data));
  1036. // mimic EPI behaviour: if we get an array that looks like an error, make it
  1037. // an eror response
  1038. if (is_array($r) && array_key_exists('faultCode', $r) && array_key_exists('faultString', $r))
  1039. {
  1040. $r =& new xmlrpcresp(0, (integer)$r['faultCode'], (string)$r['faultString']);
  1041. }
  1042. else
  1043. {
  1044. // functions using EPI api should NOT return resp objects,
  1045. // so make sure we encode the return type correctly
  1046. $r =& new xmlrpcresp(php_xmlrpc_encode($r, array('extension_api')));
  1047. }
  1048. }
  1049. else
  1050. {
  1051. $r = call_user_func_array($func, $params);
  1052. }
  1053. }
  1054. // the return type can be either an xmlrpcresp object or a plain php value...
  1055. if (!is_a($r, 'xmlrpcresp'))
  1056. {
  1057. // what should we assume here about automatic encoding of datetimes
  1058. // and php classes instances???
  1059. $r =& new xmlrpcresp(php_xmlrpc_encode($r, array('auto_dates')));
  1060. }
  1061. }
  1062. if($this->debug > 2)
  1063. {
  1064. // note: restore the error handler we found before calling the
  1065. // user func, even if it has been changed inside the func itself
  1066. if($GLOBALS['_xmlrpcs_prev_ehandler'])
  1067. {
  1068. set_error_handler($GLOBALS['_xmlrpcs_prev_ehandler']);
  1069. }
  1070. else
  1071. {
  1072. restore_error_handler();
  1073. }
  1074. }
  1075. return $r;
  1076. }
  1077. /**
  1078. * add a string to the 'internal debug message' (separate from 'user debug message')
  1079. * @param string $strings
  1080. * @access private
  1081. */
  1082. function debugmsg($string)
  1083. {
  1084. $this->debug_info .= $string."\n";
  1085. }
  1086. /**
  1087. * @access private
  1088. */
  1089. function xml_header($charset_encoding='')
  1090. {
  1091. if ($charset_encoding != '')
  1092. {
  1093. return "<?xml version=\"1.0\" encoding=\"$charset_encoding\"?" . ">\n";
  1094. }
  1095. else
  1096. {
  1097. return "<?xml version=\"1.0\"?" . ">\n";
  1098. }
  1099. }
  1100. /**
  1101. * A debugging routine: just echoes back the input packet as a string value
  1102. * DEPRECATED!
  1103. */
  1104. function echoInput()
  1105. {
  1106. $r=&new xmlrpcresp(new xmlrpcval( "'Aha said I: '" . $GLOBALS['HTTP_RAW_POST_DATA'], 'string'));
  1107. print $r->serialize();
  1108. }
  1109. }
  1110. ?>