xmlrpcs.inc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. <?php
  2. /**
  3. * @file
  4. * Provides API for defining and handling XML-RPC requests.
  5. */
  6. /**
  7. * Invokes XML-RPC methods on this server.
  8. *
  9. * @param array $callbacks
  10. * Array of external XML-RPC method names with the callbacks they map to.
  11. */
  12. function xmlrpc_server($callbacks) {
  13. $xmlrpc_server = new stdClass();
  14. // Define built-in XML-RPC method names
  15. $defaults = array(
  16. 'system.multicall' => 'xmlrpc_server_multicall',
  17. array(
  18. 'system.methodSignature',
  19. 'xmlrpc_server_method_signature',
  20. array('array', 'string'),
  21. 'Returns an array describing the return type and required parameters of a method.',
  22. ),
  23. array(
  24. 'system.getCapabilities',
  25. 'xmlrpc_server_get_capabilities',
  26. array('struct'),
  27. 'Returns a struct describing the XML-RPC specifications supported by this server.',
  28. ),
  29. array(
  30. 'system.listMethods',
  31. 'xmlrpc_server_list_methods',
  32. array('array'),
  33. 'Returns an array of available methods on this server.',
  34. ),
  35. array(
  36. 'system.methodHelp',
  37. 'xmlrpc_server_method_help',
  38. array('string', 'string'),
  39. 'Returns a documentation string for the specified method.',
  40. ),
  41. );
  42. // We build an array of all method names by combining the built-ins
  43. // with those defined by modules implementing the _xmlrpc hook.
  44. // Built-in methods are overridable.
  45. $callbacks = array_merge($defaults, (array) $callbacks);
  46. drupal_alter('xmlrpc', $callbacks);
  47. foreach ($callbacks as $key => $callback) {
  48. // we could check for is_array($callback)
  49. if (is_int($key)) {
  50. $method = $callback[0];
  51. $xmlrpc_server->callbacks[$method] = $callback[1];
  52. $xmlrpc_server->signatures[$method] = $callback[2];
  53. $xmlrpc_server->help[$method] = $callback[3];
  54. }
  55. else {
  56. $xmlrpc_server->callbacks[$key] = $callback;
  57. $xmlrpc_server->signatures[$key] = '';
  58. $xmlrpc_server->help[$key] = '';
  59. }
  60. }
  61. $data = file_get_contents('php://input');
  62. if (!$data) {
  63. print 'XML-RPC server accepts POST requests only.';
  64. drupal_exit();
  65. }
  66. $xmlrpc_server->message = xmlrpc_message($data);
  67. if (!xmlrpc_message_parse($xmlrpc_server->message)) {
  68. xmlrpc_server_error(-32700, t('Parse error. Request not well formed.'));
  69. }
  70. if ($xmlrpc_server->message->messagetype != 'methodCall') {
  71. xmlrpc_server_error(-32600, t('Server error. Invalid XML-RPC. Request must be a methodCall.'));
  72. }
  73. if (!isset($xmlrpc_server->message->params)) {
  74. $xmlrpc_server->message->params = array();
  75. }
  76. xmlrpc_server_set($xmlrpc_server);
  77. $result = xmlrpc_server_call($xmlrpc_server, $xmlrpc_server->message->methodname, $xmlrpc_server->message->params);
  78. if (is_object($result) && !empty($result->is_error)) {
  79. xmlrpc_server_error($result);
  80. }
  81. // Encode the result
  82. $r = xmlrpc_value($result);
  83. // Create the XML
  84. $xml = '
  85. <methodResponse>
  86. <params>
  87. <param>
  88. <value>' . xmlrpc_value_get_xml($r) . '</value>
  89. </param>
  90. </params>
  91. </methodResponse>
  92. ';
  93. // Send it
  94. xmlrpc_server_output($xml);
  95. }
  96. /**
  97. * Throws an XML-RPC error.
  98. *
  99. * @param $error
  100. * An error object or integer error code.
  101. * @param $message
  102. * (optional) The description of the error. Used only if an integer error
  103. * code was passed in.
  104. */
  105. function xmlrpc_server_error($error, $message = FALSE) {
  106. if ($message && !is_object($error)) {
  107. $error = xmlrpc_error($error, $message);
  108. }
  109. xmlrpc_server_output(xmlrpc_error_get_xml($error));
  110. }
  111. /**
  112. * Sends XML-RPC output to the browser.
  113. *
  114. * @param string $xml
  115. * XML to send to the browser.
  116. */
  117. function xmlrpc_server_output($xml) {
  118. $xml = '<?xml version="1.0"?>' . "\n" . $xml;
  119. drupal_add_http_header('Content-Length', strlen($xml));
  120. drupal_add_http_header('Content-Type', 'text/xml');
  121. echo $xml;
  122. drupal_exit();
  123. }
  124. /**
  125. * Stores a copy of an XML-RPC request temporarily.
  126. *
  127. * @param object $xmlrpc_server
  128. * (optional) Request object created by xmlrpc_server(). Omit to leave the
  129. * previous server object saved.
  130. *
  131. * @return
  132. * The latest stored request.
  133. *
  134. * @see xmlrpc_server_get()
  135. */
  136. function xmlrpc_server_set($xmlrpc_server = NULL) {
  137. static $server;
  138. if (!isset($server)) {
  139. $server = $xmlrpc_server;
  140. }
  141. return $server;
  142. }
  143. /**
  144. * Retrieves the latest stored XML-RPC request.
  145. *
  146. * @return object
  147. * The stored request.
  148. *
  149. * @see xmlrpc_server_set()
  150. */
  151. function xmlrpc_server_get() {
  152. return xmlrpc_server_set();
  153. }
  154. /**
  155. * Dispatches an XML-RPC request and any parameters to the appropriate handler.
  156. *
  157. * @param object $xmlrpc_server
  158. * Object containing information about this XML-RPC server, the methods it
  159. * provides, their signatures, etc.
  160. * @param string $methodname
  161. * The external XML-RPC method name; e.g., 'system.methodHelp'.
  162. * @param array $args
  163. * Array containing any parameters that are to be sent along with the request.
  164. *
  165. * @return
  166. * The results of the call.
  167. */
  168. function xmlrpc_server_call($xmlrpc_server, $methodname, $args) {
  169. // Make sure parameters are in an array
  170. if ($args && !is_array($args)) {
  171. $args = array($args);
  172. }
  173. // Has this method been mapped to a Drupal function by us or by modules?
  174. if (!isset($xmlrpc_server->callbacks[$methodname])) {
  175. return xmlrpc_error(-32601, t('Server error. Requested method @methodname not specified.', array("@methodname" => $xmlrpc_server->message->methodname)));
  176. }
  177. $method = $xmlrpc_server->callbacks[$methodname];
  178. $signature = $xmlrpc_server->signatures[$methodname];
  179. // If the method has a signature, validate the request against the signature
  180. if (is_array($signature)) {
  181. $ok = TRUE;
  182. $return_type = array_shift($signature);
  183. // Check the number of arguments
  184. if (count($args) != count($signature)) {
  185. return xmlrpc_error(-32602, t('Server error. Wrong number of method parameters.'));
  186. }
  187. // Check the argument types
  188. foreach ($signature as $key => $type) {
  189. $arg = $args[$key];
  190. switch ($type) {
  191. case 'int':
  192. case 'i4':
  193. if (is_array($arg) || !is_int($arg)) {
  194. $ok = FALSE;
  195. }
  196. break;
  197. case 'base64':
  198. case 'string':
  199. if (!is_string($arg)) {
  200. $ok = FALSE;
  201. }
  202. break;
  203. case 'boolean':
  204. if ($arg !== FALSE && $arg !== TRUE) {
  205. $ok = FALSE;
  206. }
  207. break;
  208. case 'float':
  209. case 'double':
  210. if (!is_float($arg)) {
  211. $ok = FALSE;
  212. }
  213. break;
  214. case 'date':
  215. case 'dateTime.iso8601':
  216. if (!$arg->is_date) {
  217. $ok = FALSE;
  218. }
  219. break;
  220. }
  221. if (!$ok) {
  222. return xmlrpc_error(-32602, t('Server error. Invalid method parameters.'));
  223. }
  224. }
  225. }
  226. if (!function_exists($method)) {
  227. return xmlrpc_error(-32601, t('Server error. Requested function @method does not exist.', array("@method" => $method)));
  228. }
  229. // Call the mapped function
  230. return call_user_func_array($method, $args);
  231. }
  232. /**
  233. * Dispatches multiple XML-RPC requests.
  234. *
  235. * @param array $methodcalls
  236. * An array of XML-RPC requests to make. Each request is an array with the
  237. * following elements:
  238. * - methodName: Name of the method to invoke.
  239. * - params: Parameters to pass to the method.
  240. *
  241. * @return
  242. * An array of the results of each request.
  243. *
  244. * @see xmlrpc_server_call()
  245. */
  246. function xmlrpc_server_multicall($methodcalls) {
  247. // See http://www.xmlrpc.com/discuss/msgReader$1208
  248. $return = array();
  249. $xmlrpc_server = xmlrpc_server_get();
  250. foreach ($methodcalls as $call) {
  251. $ok = TRUE;
  252. if (!isset($call['methodName']) || !isset($call['params'])) {
  253. $result = xmlrpc_error(3, t('Invalid syntax for system.multicall.'));
  254. $ok = FALSE;
  255. }
  256. $method = $call['methodName'];
  257. $params = $call['params'];
  258. if ($method == 'system.multicall') {
  259. $result = xmlrpc_error(-32600, t('Recursive calls to system.multicall are forbidden.'));
  260. }
  261. elseif ($ok) {
  262. $result = xmlrpc_server_call($xmlrpc_server, $method, $params);
  263. }
  264. if (is_object($result) && !empty($result->is_error)) {
  265. $return[] = array(
  266. 'faultCode' => $result->code,
  267. 'faultString' => $result->message,
  268. );
  269. }
  270. else {
  271. $return[] = array($result);
  272. }
  273. }
  274. return $return;
  275. }
  276. /**
  277. * Lists the methods available on this XML-RPC server.
  278. *
  279. * XML-RPC method system.listMethods maps to this function.
  280. *
  281. * @return array
  282. * Array of the names of methods available on this server.
  283. */
  284. function xmlrpc_server_list_methods() {
  285. $xmlrpc_server = xmlrpc_server_get();
  286. return array_keys($xmlrpc_server->callbacks);
  287. }
  288. /**
  289. * Returns a list of the capabilities of this server.
  290. *
  291. * XML-RPC method system.getCapabilities maps to this function.
  292. *
  293. * @return array
  294. * Array of server capabilities.
  295. *
  296. * @see http://groups.yahoo.com/group/xml-rpc/message/2897
  297. */
  298. function xmlrpc_server_get_capabilities() {
  299. return array(
  300. 'xmlrpc' => array(
  301. 'specUrl' => 'http://www.xmlrpc.com/spec',
  302. 'specVersion' => 1,
  303. ),
  304. 'faults_interop' => array(
  305. 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
  306. 'specVersion' => 20010516,
  307. ),
  308. 'system.multicall' => array(
  309. 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
  310. 'specVersion' => 1,
  311. ),
  312. 'introspection' => array(
  313. 'specUrl' => 'http://scripts.incutio.com/xmlrpc/introspection.html',
  314. 'specVersion' => 1,
  315. ),
  316. );
  317. }
  318. /**
  319. * Returns one method signature for a function.
  320. *
  321. * This is the function mapped to the XML-RPC method system.methodSignature.
  322. *
  323. * A method signature is an array of the input and output types of a method. For
  324. * instance, the method signature of this function is array('array', 'string'),
  325. * because it takes an array and returns a string.
  326. *
  327. * @param string $methodname
  328. * Name of method to return a method signature for.
  329. *
  330. * @return array
  331. * An array of arrays of types, each of the arrays representing one method
  332. * signature of the function that $methodname maps to.
  333. */
  334. function xmlrpc_server_method_signature($methodname) {
  335. $xmlrpc_server = xmlrpc_server_get();
  336. if (!isset($xmlrpc_server->callbacks[$methodname])) {
  337. return xmlrpc_error(-32601, t('Server error. Requested method @methodname not specified.', array("@methodname" => $methodname)));
  338. }
  339. if (!is_array($xmlrpc_server->signatures[$methodname])) {
  340. return xmlrpc_error(-32601, t('Server error. Requested method @methodname signature not specified.', array("@methodname" => $methodname)));
  341. }
  342. // We array of types
  343. $return = array();
  344. foreach ($xmlrpc_server->signatures[$methodname] as $type) {
  345. $return[] = $type;
  346. }
  347. return array($return);
  348. }
  349. /**
  350. * Returns the help for an XML-RPC method.
  351. *
  352. * XML-RPC method system.methodHelp maps to this function.
  353. *
  354. * @param string $method
  355. * Name of method for which we return a help string.
  356. *
  357. * @return string
  358. * Help text for $method.
  359. */
  360. function xmlrpc_server_method_help($method) {
  361. $xmlrpc_server = xmlrpc_server_get();
  362. return $xmlrpc_server->help[$method];
  363. }