1f657e5d0SAndreas Gohr<?php 2f657e5d0SAndreas Gohr 3f657e5d0SAndreas Gohrnamespace dokuwiki\Remote; 4f657e5d0SAndreas Gohr 5f657e5d0SAndreas Gohr/** 6f657e5d0SAndreas Gohr * Provide the Remote XMLRPC API as a JSON based API 7f657e5d0SAndreas Gohr */ 8f657e5d0SAndreas Gohrclass JsonRpcServer 9f657e5d0SAndreas Gohr{ 10f657e5d0SAndreas Gohr 11f657e5d0SAndreas Gohr protected $remote; 12f657e5d0SAndreas Gohr 13*6f8e03f5SAndreas Gohr /** @var float The XML-RPC Version. 0 is our own simplified variant */ 14*6f8e03f5SAndreas Gohr protected $version = 0; 15*6f8e03f5SAndreas Gohr 16f657e5d0SAndreas Gohr /** 17f657e5d0SAndreas Gohr * JsonRpcServer constructor. 18f657e5d0SAndreas Gohr */ 19f657e5d0SAndreas Gohr public function __construct() 20f657e5d0SAndreas Gohr { 21f657e5d0SAndreas Gohr $this->remote = new Api(); 22f657e5d0SAndreas Gohr $this->remote->setFileTransformation(array($this, 'toFile')); 23f657e5d0SAndreas Gohr } 24f657e5d0SAndreas Gohr 25f657e5d0SAndreas Gohr /** 26f657e5d0SAndreas Gohr * Serve the request 27f657e5d0SAndreas Gohr * 28f657e5d0SAndreas Gohr * @return mixed 29f657e5d0SAndreas Gohr * @throws RemoteException 30f657e5d0SAndreas Gohr */ 31f657e5d0SAndreas Gohr public function serve() 32f657e5d0SAndreas Gohr { 33f657e5d0SAndreas Gohr global $conf; 348fae2e99SAndreas Gohr global $INPUT; 358fae2e99SAndreas Gohr 36f657e5d0SAndreas Gohr if (!$conf['remote']) { 37f657e5d0SAndreas Gohr http_status(404); 38f657e5d0SAndreas Gohr throw new RemoteException("JSON-RPC server not enabled.", -32605); 39f657e5d0SAndreas Gohr } 40f657e5d0SAndreas Gohr if (!empty($conf['remotecors'])) { 41f657e5d0SAndreas Gohr header('Access-Control-Allow-Origin: ' . $conf['remotecors']); 42f657e5d0SAndreas Gohr } 438fae2e99SAndreas Gohr if ($INPUT->server->str('REQUEST_METHOD') !== 'POST') { 448fae2e99SAndreas Gohr http_status(405); 458fae2e99SAndreas Gohr header('Allow: POST'); 468fae2e99SAndreas Gohr throw new RemoteException("JSON-RPC server only accepts POST requests.", -32606); 478fae2e99SAndreas Gohr } 488fae2e99SAndreas Gohr if ($INPUT->server->str('CONTENT_TYPE') !== 'application/json') { 498fae2e99SAndreas Gohr http_status(415); 508fae2e99SAndreas Gohr throw new RemoteException("JSON-RPC server only accepts application/json requests.", -32606); 518fae2e99SAndreas Gohr } 52f657e5d0SAndreas Gohr 5387603a0aSAndreas Gohr try { 54*6f8e03f5SAndreas Gohr $data = json_decode(file_get_contents('php://input'), true, 512, JSON_THROW_ON_ERROR); 5587603a0aSAndreas Gohr } catch (\Exception $e) { 56*6f8e03f5SAndreas Gohr $data = []; 5787603a0aSAndreas Gohr } 58f657e5d0SAndreas Gohr 59*6f8e03f5SAndreas Gohr return $this->createResponse($data); 60*6f8e03f5SAndreas Gohr } 61*6f8e03f5SAndreas Gohr 62*6f8e03f5SAndreas Gohr /** 63*6f8e03f5SAndreas Gohr * This executes the method and returns the result 64*6f8e03f5SAndreas Gohr * 65*6f8e03f5SAndreas Gohr * This should handle all JSON-RPC versions and our simplified version 66*6f8e03f5SAndreas Gohr * 67*6f8e03f5SAndreas Gohr * @link https://en.wikipedia.org/wiki/JSON-RPC 68*6f8e03f5SAndreas Gohr * @link https://www.jsonrpc.org/specification 69*6f8e03f5SAndreas Gohr * @param array $data 70*6f8e03f5SAndreas Gohr * @return array 71*6f8e03f5SAndreas Gohr * @throws RemoteException 72*6f8e03f5SAndreas Gohr */ 73*6f8e03f5SAndreas Gohr protected function createResponse($data) 74*6f8e03f5SAndreas Gohr { 75*6f8e03f5SAndreas Gohr global $INPUT; 76*6f8e03f5SAndreas Gohr $return = []; 77*6f8e03f5SAndreas Gohr 78*6f8e03f5SAndreas Gohr if (isset($data['method'])) { 79*6f8e03f5SAndreas Gohr // this is a standard conform request (at least version 1.0) 80*6f8e03f5SAndreas Gohr $method = $data['method']; 81*6f8e03f5SAndreas Gohr $params = $data['params'] ?? []; 82*6f8e03f5SAndreas Gohr $this->version = 1; 83*6f8e03f5SAndreas Gohr 84*6f8e03f5SAndreas Gohr // always return the same ID 85*6f8e03f5SAndreas Gohr if (isset($data['id'])) $return['id'] = $data['id']; 86*6f8e03f5SAndreas Gohr 87*6f8e03f5SAndreas Gohr // version 2.0 request 88*6f8e03f5SAndreas Gohr if (isset($data['jsonrpc'])) { 89*6f8e03f5SAndreas Gohr $return['jsonrpc'] = $data['jsonrpc']; 90*6f8e03f5SAndreas Gohr $this->version = (float)$data['jsonrpc']; 91*6f8e03f5SAndreas Gohr } 92*6f8e03f5SAndreas Gohr 93*6f8e03f5SAndreas Gohr // version 1.1 request 94*6f8e03f5SAndreas Gohr if (isset($data['version'])) { 95*6f8e03f5SAndreas Gohr $return['version'] = $data['version']; 96*6f8e03f5SAndreas Gohr $this->version = (float)$data['version']; 97*6f8e03f5SAndreas Gohr } 98*6f8e03f5SAndreas Gohr } else { 99*6f8e03f5SAndreas Gohr // this is a simplified request 100*6f8e03f5SAndreas Gohr $method = $INPUT->server->str('PATH_INFO'); 101*6f8e03f5SAndreas Gohr $method = trim($method, '/'); 102*6f8e03f5SAndreas Gohr $params = $data; 103*6f8e03f5SAndreas Gohr $this->version = 0; 104*6f8e03f5SAndreas Gohr } 105*6f8e03f5SAndreas Gohr 106*6f8e03f5SAndreas Gohr // excute the method 107*6f8e03f5SAndreas Gohr $return['result'] = $this->call($method, $params); 108*6f8e03f5SAndreas Gohr $this->addErrorData($return); // handles non-error info 109*6f8e03f5SAndreas Gohr return $return; 110*6f8e03f5SAndreas Gohr } 111*6f8e03f5SAndreas Gohr 112*6f8e03f5SAndreas Gohr /** 113*6f8e03f5SAndreas Gohr * Create an error response 114*6f8e03f5SAndreas Gohr * 115*6f8e03f5SAndreas Gohr * @param \Exception $exception 116*6f8e03f5SAndreas Gohr * @return array 117*6f8e03f5SAndreas Gohr */ 118*6f8e03f5SAndreas Gohr public function returnError($exception) 119*6f8e03f5SAndreas Gohr { 120*6f8e03f5SAndreas Gohr $return = []; 121*6f8e03f5SAndreas Gohr $this->addErrorData($return, $exception); 122*6f8e03f5SAndreas Gohr return $return; 123*6f8e03f5SAndreas Gohr } 124*6f8e03f5SAndreas Gohr 125*6f8e03f5SAndreas Gohr /** 126*6f8e03f5SAndreas Gohr * Depending on the requested version, add error data to the response 127*6f8e03f5SAndreas Gohr * 128*6f8e03f5SAndreas Gohr * @param array $response 129*6f8e03f5SAndreas Gohr * @param \Exception|null $e 130*6f8e03f5SAndreas Gohr * @return void 131*6f8e03f5SAndreas Gohr */ 132*6f8e03f5SAndreas Gohr protected function addErrorData(&$response, $e = null) 133*6f8e03f5SAndreas Gohr { 134*6f8e03f5SAndreas Gohr if ($e !== null) { 135*6f8e03f5SAndreas Gohr // error occured, add to response 136*6f8e03f5SAndreas Gohr $response['error'] = [ 137*6f8e03f5SAndreas Gohr 'code' => $e->getCode(), 138*6f8e03f5SAndreas Gohr 'message' => $e->getMessage() 139*6f8e03f5SAndreas Gohr ]; 140*6f8e03f5SAndreas Gohr } else { 141*6f8e03f5SAndreas Gohr // no error, act according to version 142*6f8e03f5SAndreas Gohr if ($this->version > 0 && $this->version < 2) { 143*6f8e03f5SAndreas Gohr // version 1.* wants null 144*6f8e03f5SAndreas Gohr $response['error'] = null; 145*6f8e03f5SAndreas Gohr } elseif ($this->version < 1) { 146*6f8e03f5SAndreas Gohr // simplified version wants success 147*6f8e03f5SAndreas Gohr $response['error'] = [ 148*6f8e03f5SAndreas Gohr 'code' => 0, 149*6f8e03f5SAndreas Gohr 'message' => 'success' 150*6f8e03f5SAndreas Gohr ]; 151*6f8e03f5SAndreas Gohr } 152*6f8e03f5SAndreas Gohr // version 2 wants no error at all 153*6f8e03f5SAndreas Gohr } 154f657e5d0SAndreas Gohr } 155f657e5d0SAndreas Gohr 156f657e5d0SAndreas Gohr /** 157f657e5d0SAndreas Gohr * Call an API method 158f657e5d0SAndreas Gohr * 159f657e5d0SAndreas Gohr * @param string $methodname 160f657e5d0SAndreas Gohr * @param array $args 161f657e5d0SAndreas Gohr * @return mixed 162f657e5d0SAndreas Gohr * @throws RemoteException 163f657e5d0SAndreas Gohr */ 164f657e5d0SAndreas Gohr public function call($methodname, $args) 165f657e5d0SAndreas Gohr { 166f657e5d0SAndreas Gohr try { 167f657e5d0SAndreas Gohr $result = $this->remote->call($methodname, $args); 168f657e5d0SAndreas Gohr return $result; 169f657e5d0SAndreas Gohr } catch (AccessDeniedException $e) { 170f657e5d0SAndreas Gohr if (!isset($_SERVER['REMOTE_USER'])) { 171f657e5d0SAndreas Gohr http_status(401); 172f657e5d0SAndreas Gohr throw new RemoteException("server error. not authorized to call method $methodname", -32603); 173f657e5d0SAndreas Gohr } else { 174f657e5d0SAndreas Gohr http_status(403); 175f657e5d0SAndreas Gohr throw new RemoteException("server error. forbidden to call the method $methodname", -32604); 176f657e5d0SAndreas Gohr } 177f657e5d0SAndreas Gohr } catch (RemoteException $e) { 178f657e5d0SAndreas Gohr http_status(400); 179f657e5d0SAndreas Gohr throw $e; 180f657e5d0SAndreas Gohr } 181f657e5d0SAndreas Gohr } 182f657e5d0SAndreas Gohr 183f657e5d0SAndreas Gohr /** 184f657e5d0SAndreas Gohr * @param string $data 185f657e5d0SAndreas Gohr * @return string 186f657e5d0SAndreas Gohr */ 187f657e5d0SAndreas Gohr public function toFile($data) 188f657e5d0SAndreas Gohr { 189f657e5d0SAndreas Gohr return base64_encode($data); 190f657e5d0SAndreas Gohr } 191f657e5d0SAndreas Gohr 192f657e5d0SAndreas Gohr} 193