[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/Remote/ -> JsonRpcServer.php (source)

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