diff --git a/JsonRpcServer.php b/JsonRpcServer.php deleted file mode 100644 index 823c4b6..0000000 --- a/JsonRpcServer.php +++ /dev/null @@ -1,238 +0,0 @@ -in = $in; - $this->method_name = isset($in['method']) ? $in['method'] : NULL; - $this->id = isset($in['id']) ? $in['id'] : NULL; - $this->version = isset($in['jsonrpc']) ? $in['jsonrpc'] : '1.1'; - $this->major_version = intval(substr($this->version, 0, 1)); - $this->params = isset($in['params']) ? $in['params'] : NULL; - } - - public function handle() { - //A method is required, no matter what - if(empty($this->method_name)) { - $this->error(JSONRPC_ERROR_REQUEST, t("The received JSON not a valid JSON-RPC Request")); - } - - $endpoint = services_get_server_info('endpoint'); - - //Find the method - $this->method = services_controller_get($this->method_name, $endpoint); - $args = array(); - - if (!isset($this->method)) { // No method found is a fatal error - $this->error(JSONRPC_ERROR_PROCEDURE_NOT_FOUND, t("Invalid method @method", - array('@method' => $request))); - } - - //If needed, check if parameters can be omitted - $arg_count = count($this->method['args']); - if (!isset($this->params)) { - for ($i=0; $i<$arg_count; $i++) { - $arg = $this->method['#args'][$i]; - if (!$arg['optional']) { - if (empty($this->params)) { - // We have required parameter, but we don't have any. - if (is_array($this->params)) { - // The request has probably been parsed correctly if params is an array, - // just tell the client that we're missing parameters. - $this->error(JSONRPC_ERROR_PARAMS, t("No parameters received, the method '@method' has required parameters.", - array('@method'=>$this->method_name))); - } - else { - // If params isn't an array we probably have a syntax error in the json. - // Tell the client that there was a error while parsing the json. - // TODO: parse errors should be caught earlier - $this->error(JSONRPC_ERROR_PARSE, t("No parameters received, the likely reason is malformed json, the method '@method' has required parameters.", - array('@method'=>$this->method_name))); - } - } - } - } - } - - // Map parameters to arguments, the 1.1 draft is more generous than the 2.0 proposal when - // it comes to parameter passing. 1.1-d allows mixed positional and named parameters while - // 2.0-p forces the client to choose between the two. - // - // 2.0 proposal on parameters: http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal#parameters-positional-and-named - // 1.1 draft on parameters: http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html#NamedPositionalParameters - if($this->array_is_assoc($this->params)) - { - $this->args = array(); - - //Create a assoc array to look up indexes for parameter names - $arg_dict = array(); - for ($i=0; $i<$arg_count; $i++) { - $arg = $this->method['args'][$i]; - $arg_dict[$arg['name']] = $i; - } - - foreach ($this->params as $key => $value) { - if ($this->major_version==1 && preg_match('/^\d+$/',$key)) { //A positional argument (only allowed in v1.1 calls) - if ($key >= $arg_count) { //Index outside bounds - $this->error(JSONRPC_ERROR_PARAMS, t("Positional parameter with a position outside the bounds (index: @index) received", - array('@index'=>$key))); - } - else { - $this->args[intval($key)] = $value; - } - } - else { //Associative key - if (!isset($arg_dict[$key])) { //Unknown parameter - $this->error(JSONRPC_ERROR_PARAMS, t("Unknown named parameter '@name' received", - array('@name'=>$key))); - } - else { - $this->args[$arg_dict[$key]] = $value; - } - } - } - } - else { //Non associative arrays can be mapped directly - $param_count = count($this->params); - if ($param_count > $arg_count) { - $this->error(JSONRPC_ERROR_PARAMS, t("Too many arguments received, the method '@method' only takes '@num' argument(s)", - array('@method'=>$this->method_name, '@num'=> $arg_count ))); - } - $this->args = $this->params; - } - - //Validate arguments - for($i=0; $i<$arg_count; $i++) - { - $val = $this->args[$i]; - $arg = $this->method['args'][$i]; - - if (isset($val)) { //If we have data - if ($arg['type'] == 'struct' && is_array($val) && $this->array_is_assoc($val)) { - $this->args[$i] = $val = (object)$val; - } - - //Only array-type parameters accepts arrays - if (is_array($val) && $arg['type']!='array' && !($this->is_assoc($val) && $arg['type'] == 'struct')){ - $this->error_wrong_type($arg, 'array'); - } - //Check that int and float value type arguments get numeric values - else if(($arg['type']=='int' || $arg['type']=='float') && !is_numeric($val)) { - $this->error_wrong_type($arg,'string'); - } - } - else if (!$arg['optional']) { //Trigger error if a required parameter is missing - $this->error(JSONRPC_ERROR_PARAMS, t("Argument '@name' is required but was not received", array('@name'=>$arg['name']))); - } - } - - // We are returning JSON, so tell the browser. - drupal_set_header('Content-Type: application/json; charset=utf-8'); - - // Services assumes parameter positions to match the method callback's - // function signature so we need to sort arguments by position (key) - // before passing them to the method callback. The best solution here would - // be to pad optional parameters using a #default key in the hook_service - // method definitions instead of requiring all parameters to be present, as - // we do now. - // For reference: http://drupal.org/node/715044 - ksort($this->args); - - //Call service method - try { - $result = services_controller_execute($this->method, $this->args); - return $this->result($result); - } catch (ServicesException $e) { - $this->error(JSONRPC_ERROR_INTERNAL_ERROR, $e->getMessage(), $e->getData()); - } - catch (Exception $e) { - $this->error(JSONRPC_ERROR_INTERNAL_ERROR, $e->getMessage()); - } - } - - private function array_is_assoc(&$arr) { - $count = count($arr); - for ($i=0;$i<$count;$i++) { - if (!array_key_exists($i, $arr)) { - return true; - } - } - return false; - } - - private function response_version(&$response) { - switch ($this->major_version) { - case 2: - $response['jsonrpc'] = '2.0'; - break; - case 1: - $response['version'] = '1.1'; - break; - } - } - - private function response_id(&$response) { - if (!empty($this->id)) { - $response['id'] = $this->id; - } - } - - private function result($result) { - $response = array('result' => $result); - return $this->response($response); - } - - private function error($code, $message, $data = NULL) { - $response = array('error' => array('name' => 'JSONRPCError', 'code' => $code, 'message' => $message)); - if ($data) { - $response['data'] = $data; - } - throw new ServicesException($message, $code, $response); - } - - private function error_wrong_type(&$arg, $type){ - $this->error(JSONRPC_ERROR_PARAMS, t("The argument '@arg' should be a @type, not @used_type", - array( - '@arg' => $arg['name'], - '@type' => $arg['type'], - '@used_type' => $type, - ) - )); - } - - public function response($response) { - // Check if this is a 2.0 notification call - if($this->major_version==2 && empty($this->id)) - return; - - $this->response_version($response); - $this->response_id($response); - - //Using the current development version of Drupal 7:s drupal_to_js instead - return json_encode($response); - } - - private function is_assoc($array) { - return (is_array($array) && 0 !== count(array_diff_key($array, array_keys(array_keys($array))))); - } -} \ No newline at end of file diff --git a/jsonrpc_server.class.inc b/jsonrpc_server.class.inc new file mode 100644 index 0000000..f20a67e --- /dev/null +++ b/jsonrpc_server.class.inc @@ -0,0 +1,485 @@ +input = $input; + $this->httpMethod = $http_method; + $this->methodName = isset($input['method']) ? $input['method'] : NULL; + $this->id = isset($input['id']) ? $input['id'] : NULL; + $this->version = isset($input['jsonrpc']) ? $input['jsonrpc'] : '1.1'; + $this->majorVersion = intval(substr($this->version, 0, 1)); + $this->params = isset($input['params']) ? $input['params'] : array(); + $this->args = array(); + } + + /** + * Validate and prepare a JSON-RPC call according to specifications. + * + * Not all the validations a made here. + */ + protected function validate() { + + // A method is required. + if (empty($this->methodName)) { + $this->error(JSONRPC_ERROR_REQUEST, t('The received JSON not a valid JSON-RPC Request.')); + } + + // Check for if valid "params" were sent. + if (!is_array($this->params)) { + $this->error(JSONRPC_ERROR_PARSE, t('No valid parameters received. The "params" member of JSON must be an Array or Object.')); + } + + $endpoint = services_get_server_info('endpoint'); + + // Find the method definition as it was provided by the Services module. + $this->method = services_controller_get($this->methodName, $endpoint); + + // Check id this method has been defined in hook_services_resources(). + if (!isset($this->method)) { + $this->error(JSONRPC_ERROR_PROCEDURE_NOT_FOUND, t('Invalid method "@method".', array('@method' => $this->methodName))); + } + $this->method['args'] = isset($this->method['args']) ? $this->method['args'] : array(); + + $count_params = count($this->params); + $this->methodArgumentsCount = count($this->method['args']); + + // Too many parameters passed. + if ($count_params > $this->methodArgumentsCount) { + if ($this->methodArgumentsCount == 0) { + $this->error(JSONRPC_ERROR_PARAMS, t('@params parameters were passed but this method (@method) accepts none.', array('@params' => $count_params, '@method' => $this->methodName))); + } + elseif ($this->methodArgumentsCount == 1) { + $this->error(JSONRPC_ERROR_PARAMS, t('@params parameters were passed but this method (@method) accepts only one.', array('@params' => $count_params, '@method' => $this->methodName))); + } + else { + $this->error(JSONRPC_ERROR_PARAMS, t('@params parameters were passed but this method (@method) accepts only @args.', array('@params' => $count_params, '@args' => $this->methodArgumentsCount, '@method' => $this->methodName))); + } + } + + // JSON-RPC 2.0 doesn't allow mixture of named and positional parameters. Let's check that. + if ($this->majorVersion == 2) { + foreach ($this->params as $param_key => $param_value) { + + // Get only the first param type. + if (!isset($param_type)) { + $param_type = is_numeric($param_key) ? 0 : 1; + + // Just iterate after the first step. + continue; + } + + // Found parameter mixture? Break with an error. + if ((is_numeric($param_key) && $param_type == 1) || (is_string($param_key) && $param_type == 0)) { + $this->error(JSONRPC_ERROR_PARAMS, t("JSON-RPC version @ver doesn't alow mixture of named and positional parameters.", array('@ver' => $this->version))); + } + } + } + + // Build a list of argument names. + $args = array(); + foreach ($this->method['args'] as $arg) { + $args[] = $arg['name']; + } + + foreach ($this->params as $param_key => $param_value) { + // Check for positional parameters out-of-range. + if (is_numeric($param_key) && ($param_key >= $this->methodArgumentsCount)) { + $this->error(JSONRPC_ERROR_PARAMS, t('The index @position of the positional parameter @value is outside arguments range. Maximum allowed index @max.', array('@position' => $param_key, '@value' => drupal_json_encode($param_value), '@max' => ($this->methodArgumentsCount - 1)))); + } + // And for invalid names of named parameters. + elseif (is_string($param_key) && !in_array($param_key, $args)) { + $this->error(JSONRPC_ERROR_PARAMS, t('Unknown named parameter "@name" received.', array('@name' => $param_key))); + } + } + } + + /** + * Validate a single passed parameter against the method argument definition. + * + * @param Integer $delta + * The position in the method argument list. + * @param Array $arg + * The method argument definition. + * @param Arbitrary Variable $param + */ + protected function validateParam($delta, array $arg, &$param) { + + /** + * If "struct" is expected, convert associative array to object but only if + * the associative array has no numeric (positional) keys. Forcing those to + * object will remove them. + * + * Example: (object) array('somekey' => 'value', 2 => 'value2') results in + * {"somekey": "value"}. The numeric key item get lost. + */ + if (is_array($param) && ($arg['type'] == 'struct')) { + if (!((bool) count(array_filter(array_keys($param), 'is_numeric')))) { + $param = (object) $param; + } + + // We are leaving here. The parameter is either on Object, + // either an Array and a "struct" it's expected. + return; + } + + // Only array-type parameters accepts arrays. + if (is_array($param) && $arg['type'] != 'array') { + $this->errorWrongType($arg, 'array'); + } + + // Check that "int" or "float" value type arguments get numeric values. + if (in_array($arg['type'], array('int', 'float')) && !is_numeric($param)) { + $this->errorWrongType($arg, 'string'); + } + } + + /** + * Handle a JSON-RPC request and returns the response. + * + * @return + * A response or throw an ServicesException. + */ + public function handle() { + + // Validate & prepare. + $this->validate(); + + // Processing all sent parameters in the method argument order. + foreach ($this->method['args'] as $delta => $arg) { + + /** + * Check if there are 2 candidate parameters for the same argument. + * This may happen only in JSON-RPC 1.1 where a positional paramater may + * overlap a named parameter or viceversa. + */ + if (($this->majorVersion == 1) && isset($this->params[$delta]) && isset($this->params[$arg['name']])) { + $this->error(JSONRPC_ERROR_PARAMS, t('Two parameters "@delta": @positional and "@name": @named are disputing the same argument "@name".', array('@delta' => $delta, '@positional' => drupal_json_encode($this->params[$delta]), '@name' => $arg['name'], '@named' => drupal_json_encode($this->params[$arg['name']])))); + } + + // Find out the parameter for this argument. + $param = isset($this->params[$arg['name']]) ? $this->params[$arg['name']] : (isset($this->params[$delta]) ? $this->params[$delta] : NULL); + + // Check if the argument is mandatory but no parameter has been passed. + if (!$arg['optional'] && !isset($param)) { + $this->error(JSONRPC_ERROR_PARAMS, t('The argument "@arg" (index @delta) is required by the "@method" method but was not received.', array('@arg' => $arg['name'], '@delta' => $delta, '@method' => $this->methodName))); + } + + if (!is_null($param)) { + // Parameter level validation. + $this->validateParam($delta, $arg, $param); + } + + // Optional argument. + if ($arg['optional']) { + + // No parameter sent for this argument. + if (is_null($param)) { + $this->args[] = isset($arg['default value']) ? $arg['default value'] : NULL; + } + // It's optional but a parameter has been sent. Use it. + else { + $this->args[] = $param; + } + } + // Mandatory argument. + else { + $this->args[] = $param; + } + } + + // Call the service method. + try { + + // Using the Services controller. + $result = services_controller_execute($this->method, $this->args); + return $this->result($result); + } + + // On error, expect a ServicesException first. + catch (ServicesException $e) { + + // Check first if the application has sent an error code. If yes, use it. + $application_error_code = $e->getCode(); + $error_code = empty($application_error_code) ? JSONRPC_ERROR_INTERNAL_ERROR : $application_error_code; + + $this->error($error_code, $e->getMessage(), $e->getData()); + } + + // Fallback to a common Exception. + catch (Exception $e) { + $this->error(JSONRPC_ERROR_INTERNAL_ERROR, $e->getMessage()); + } + } + + /** + * Respond to the JSON_RPC Request. + * + * @param Arbitrary Variable $result. + * + * @return String + * JSON encoded string with the service response. + */ + protected function result($result) { + $response = array('result' => $result); + + // Inform the browser that we are returning JSON. + drupal_add_http_header('Content-Type', 'application/json; charset=utf-8'); + + // Add other JSON-RPC protocol informations to the response and sent it. + return $this->buildResponse($response); + } + + /** + * Add additional informations to the response. + * + * The response must be according to JSON-RPC specifications. The main result + * will be wrapped together with other information. + * + * @param array $response + * An Associative array containing the basic response of the service. + * + * @return + * JSON encoded string with the complete response for this request. + */ + public function buildResponse($response) { + + /* + * In JSON-RPC 2.0 requests made without sending the Request ID ("id") are + * notifications. So we are still let the consumer that his request is OK by + * sending 200 but will not send any response. + * + * From version 2.0 specs: + * + * "A Notification is a special Request, without "id" and without Response. + * The server MUST NOT reply to a Notification." + * + * @todo Such request must receive a 204 HTTP Code. + */ + if ($this->majorVersion == 2 && empty($this->id)) { + return; + } + + // Add the version to the response. + $this->responseAddVersion($response); + + // Add the Request ID ("id") to the response. + $this->responseAddId($response); + + // Encode the response. + return drupal_json_encode($response); + } + + /** + * Add JSON-RPC Version to a response. + * + * @param array $response + * An Associative array containing the basic response of the service. + */ + protected function responseAddVersion(&$response) { + if ($this->majorVersion == 1) { + $response['version'] = '1.1'; + } + elseif ($this->majorVersion == 1) { + $response['jsonrpc'] = '2.0'; + } + } + + /** + * Add the Request ID ("id") to a response. + * + * @param array $response + * An Associative array containing the basic response of the service. + */ + protected function responseAddId(&$response) { + if (!empty($this->id)) { + $response['id'] = $this->id; + } + } + + /** + * General error processing function. + * + * @param Integer $code + * The error code. + * @param String $message + * The error message. + * @param Array $data + * An associative array with additional data to be passed. + */ + protected function error($code, $message, $data = NULL) { + $response = array('error' => array('name' => 'JSONRPCError', 'code' => $code, 'message' => $message)); + if ($data) { + $response['data'] = $data; + } + throw new ServicesException($message, $code, $response); + } + + /** + * Throw an wrong type error. + * + * @param Array $arg + * The method argument definition array. + * @param String $type + * The variable type to which this error is referring. + */ + protected function errorWrongType(array $arg, $type) { + $this->error(JSONRPC_ERROR_PARAMS, t('The argument "@arg" should have "@type" type, not "@used_type".', array('@arg' => $arg['name'], '@type' => $arg['type'], '@used_type' => $type))); + } +} diff --git a/jsonrpc_server.inc b/jsonrpc_server.inc new file mode 100644 index 0000000..016d598 --- /dev/null +++ b/jsonrpc_server.inc @@ -0,0 +1,45 @@ + 'checkbox', + '#title' => t('Allow GET requests'), + '#description' => t('Allow consumers to send GET requests against this endpoint.'), + '#default_value' => isset($settings['allow_get_requests']) ? $settings['allow_get_requests'] : FALSE, + ); +} + +/** + * Submit handler for the services JSON-RPC Server settings form. + * + * @param object $endpoint + * The endpoint that's being configured. + * @param array $values + * The partial form-state from services. + * + * @return array + * The settings for the REST server in this endpoint. + */ +function _jsonrpc_server_settings_submit($endpoint, &$values) { + return $values; +} diff --git a/jsonrpc_server.info b/jsonrpc_server.info index f55fcd0..fd0c7cd 100644 --- a/jsonrpc_server.info +++ b/jsonrpc_server.info @@ -1,6 +1,8 @@ -; $Id$ name = "JSON-RPC Server" -description = "Provides an JSON-RPC server." +description = "Provides an JSON-RPC server for Services module." package = "Services - servers" dependencies[] = services -core = "6.x" +core = 7.x + +files[] = jsonrpc_server.module +files[] = jsonrpc_server.class.inc diff --git a/jsonrpc_server.js b/jsonrpc_server.js deleted file mode 100644 index 209c8db..0000000 --- a/jsonrpc_server.js +++ /dev/null @@ -1,18 +0,0 @@ -// $Id$ -Drupal.service = function(endpoint, method, parameters, callback, id) { - var call = {'method': method, 'params': JSON.stringify(parameters)}; - if (id != null) - call['id'] = id; - jQuery.ajax({ - 'url': Drupal.settings.basePath + "?q=" + endpoint, - 'type': "POST", - 'data': call, - 'success': function(data) { - parsed = Drupal.parseJson(data); - callback(parsed['result'], parsed['error'], parsed['id']); - } - }); -} - -//JSON parser and encoder -if(!this.JSON){JSON=function(){function f(n){return n<10?"0"+n:n}Date.prototype.toJSON=function(key){return this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z"};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()};var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapeable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;function quote(string){escapeable.lastIndex=0;return escapeable.test(string)?'"'+string.replace(escapeable,function(a){var c=meta[a];if(typeof c==="string"){return c}return"\\u"+("0000"+(+(a.charCodeAt(0))).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(typeof value.length==="number"&&!(value.propertyIsEnumerable("length"))){length=value.length;for(i=0;i 'JSON-RPC', - '#path' => 'json-rpc', + 'name' => 'JSON-RPC', + 'path' => 'json-rpc', + 'settings' => array( + 'file' => array('inc', 'jsonrpc_server'), + 'form' => '_jsonrpc_server_settings', + 'submit' => '_jsonrpc_server_settings_submit', + ), ); } +/** + * Implements hook_server(). + */ function jsonrpc_server_server() { - require_once('JsonRpcServer.php'); - $GLOBALS['devel_shutdown'] = false; - // TODO: Add settings page with options for disabling features that doesn't - // follow the JSON-RPC spec. One thing that definitely could be disabled is - // the GET request handling, regardless of conformance to spec. + // Remove Devel shutdown. + $GLOBALS['devel_shutdown'] = FALSE; + + // Get the endpoint. + $endpoint_name = services_get_server_info('endpoint'); + $endpoint = services_endpoint_load($endpoint_name); + $settings = isset($endpoint->server_settings[$endpoint->server]) ? $endpoint->server_settings[$endpoint->server] : NULL; $method = $_SERVER['REQUEST_METHOD']; - switch ($method) { - case 'POST': - // Omit charset parameter - list($content_type) = explode(';', $_SERVER['CONTENT_TYPE'], 2); - switch($content_type) { - // Handling of a standard JSON-RPC call - case 'application/json': - // We'll use the inputstream module if it's installed because - // otherwise it's only possible to read the input stream once. - // And other parts of services or drupal might want to access it. - if (module_exists('inputstream')) { - $body = file_get_contents('drupal://input'); - } - else { - $body = file_get_contents('php://input'); - } - $in = json_decode($body, TRUE); - break; - // It's not included in the JSON-RPC standard but we support - // sending only the params as JSON. - default: - $in = $_POST; - $in['params'] = json_decode($in['params'], TRUE); - break; + if ($method == 'POST') { + + // Strip out charset if is there. + list($content_type) = explode(';', $_SERVER['CONTENT_TYPE'], 2); + + if (in_array($content_type, array('application/json', 'application/json-rpc', 'application/jsonrequest'))) { + // We'll use the inputstream module if it's installed because + // otherwise it's only possible to read the input stream once. + // And other parts of services or drupal might want to access it. + if (module_exists('inputstream')) { + $body = file_get_contents('drupal://input'); + } + else { + $body = file_get_contents('php://input'); } - break; - case 'GET': - // This handling of get requests doesn't implement the JSON-RPC spec - // fully. Multiple parameters sharing names are not collapsed into - // arrays. See - // http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html#GetProcedureCall - // for details. - // TODO: Implement custom GET-parameter parsing - $in = array( - 'jsonrpc' => '1.1', - 'method' => basename($_GET['q']), - 'params' => $_GET, - ); - break; + + $input = drupal_json_decode($body); + } } - $in['method'] = trim($in['method'], ' "'); - $server = new JsonRpcServer($in); + // Allow GET only if was enabled. + elseif ($method == 'GET' && $settings['allow_get_requests']) { + /** + * Collapse multiple parameters with the same name in arrays. + * + * @see http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html#GetProcedureCall + */ + $params = array(); + foreach (explode('&', $_SERVER['QUERY_STRING']) as $pair) { + list($key, $value) = explode('=', $pair, 2); + $key = str_replace('[]', '', $key); + + if (isset($params[$key])) { + if (!is_array($params[$key])) { + $params[$key] = array($params[$key]); + } + $params[$key][] = html_entity_decode($value); + } + else { + $params[$key] = html_entity_decode($value); + } + } + + $input = array( + 'version' => '1.1', + 'method' => basename($_GET['q']), + 'params' => $params, + ); + } + + // Some cleanup on "method" may be necessary. + $input['method'] = trim($input['method'], ' "'); + + // Start a new server instance. + $server = new JsonRpcServer($input, $method); + try { + // Invoke the server handler. return $server->handle(); - } catch (ServicesException $e) { - return $server->response($e->getData()); + } + catch (ServicesException $e) { + return $server->buildResponse($e->getData()); } } - -function jsonrpc_server_add_javascript() { - $path = drupal_get_path("module", "jsonrpc_server"); - drupal_add_js($path ."/jsonrpc_server.js"); -} \ No newline at end of file