remote = new Api(); } /** * Serve the request * * @param string $body Should only be set for testing, otherwise the request body is read from php://input * @return mixed * @throws RemoteException */ public function serve($body = '') { global $conf; global $INPUT; if (!$conf['remote']) { http_status(404); throw new RemoteException("JSON-RPC server not enabled.", -32605); } if (!empty($conf['remotecors'])) { header('Access-Control-Allow-Origin: ' . $conf['remotecors']); } if ($INPUT->server->str('REQUEST_METHOD') !== 'POST') { http_status(405); header('Allow: POST'); throw new RemoteException("JSON-RPC server only accepts POST requests.", -32606); } [$contentType] = explode(';', $INPUT->server->str('CONTENT_TYPE'), 2); // ignore charset $contentType = strtolower($contentType); // mime types are case-insensitive if ($contentType !== 'application/json') { http_status(415); throw new RemoteException("JSON-RPC server only accepts application/json requests.", -32606); } try { if ($body === '') { $body = file_get_contents('php://input'); } if ($body !== '') { $data = json_decode($body, true, 512, JSON_THROW_ON_ERROR); } else { $data = []; } } catch (\Exception $e) { http_status(400); throw new RemoteException("JSON-RPC server only accepts valid JSON.", -32700); } return $this->createResponse($data); } /** * This executes the method and returns the result * * This should handle all JSON-RPC versions and our simplified version * * @link https://en.wikipedia.org/wiki/JSON-RPC * @link https://www.jsonrpc.org/specification * @param array $data * @return array * @throws RemoteException */ protected function createResponse($data) { global $INPUT; $return = []; if (isset($data['method'])) { // this is a standard conform request (at least version 1.0) $method = $data['method']; $params = $data['params'] ?? []; $this->version = 1; // always return the same ID if (isset($data['id'])) $return['id'] = $data['id']; // version 2.0 request if (isset($data['jsonrpc'])) { $return['jsonrpc'] = $data['jsonrpc']; $this->version = (float)$data['jsonrpc']; } // version 1.1 request if (isset($data['version'])) { $return['version'] = $data['version']; $this->version = (float)$data['version']; } } else { // this is a simplified request $method = $INPUT->server->str('PATH_INFO'); $method = trim($method, '/'); $params = $data; $this->version = 0; } // excute the method $return['result'] = $this->call($method, $params); $this->addErrorData($return); // handles non-error info return $return; } /** * Create an error response * * @param \Exception $exception * @return array */ public function returnError($exception) { $return = []; $this->addErrorData($return, $exception); return $return; } /** * Depending on the requested version, add error data to the response * * @param array $response * @param \Exception|null $e * @return void */ protected function addErrorData(&$response, $e = null) { if ($e !== null) { // error occured, add to response $response['error'] = [ 'code' => $e->getCode(), 'message' => $e->getMessage() ]; } else { // no error, act according to version if ($this->version > 0 && $this->version < 2) { // version 1.* wants null $response['error'] = null; } elseif ($this->version < 1) { // simplified version wants success $response['error'] = [ 'code' => 0, 'message' => 'success' ]; } // version 2 wants no error at all } } /** * Call an API method * * @param string $methodname * @param array $args * @return mixed * @throws RemoteException */ public function call($methodname, $args) { try { return $this->remote->call($methodname, $args); } catch (AccessDeniedException $e) { if (!isset($_SERVER['REMOTE_USER'])) { http_status(401); throw new RemoteException("server error. not authorized to call method $methodname", -32603); } else { http_status(403); throw new RemoteException("server error. forbidden to call the method $methodname", -32604); } } catch (RemoteException $e) { http_status(400); throw $e; } } }