dokuwiki/inc/Remote/JsonRpcServer.php

191 lines
5.6 KiB
PHP

<?php
namespace dokuwiki\Remote;
/**
* Provide the Remote XMLRPC API as a JSON based API
*/
class JsonRpcServer
{
protected $remote;
/** @var float The XML-RPC Version. 0 is our own simplified variant */
protected $version = 0;
/**
* JsonRpcServer constructor.
*/
public function __construct()
{
$this->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;
}
}
}