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