cms/drupal/includes/xmlrpcs.inc
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     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 }