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