191 lines
5.6 KiB
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;
|
|
}
|
|
}
|
|
}
|