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