Project

General

Profile

Paste
Download (11.6 KB) Statistics
| Branch: | Revision:

root / drupal7 / includes / xmlrpcs.inc @ 27e02aed

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
}