diff --git a/composer.json b/composer.json index 417e793..11f1920 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ }, "require": { "fguillot/simple-validator": "v1.0.0", - "fguillot/json-rpc": "v1.0.2", + "fguillot/json-rpc": "v1.1.0", "fguillot/picodb": "v1.0.2", "fguillot/picofeed": "v0.1.23", "pda/pheanstalk": "v3.1.0" diff --git a/jsonrpc.php b/jsonrpc.php index a6c4530..585652f 100644 --- a/jsonrpc.php +++ b/jsonrpc.php @@ -3,27 +3,26 @@ require __DIR__.'/common.php'; use JsonRPC\Server; -use PicoFeed\PicoFeedException; use Model\Config; -$server = new Server; +$server = new Server(); + $server->authentication(array( Config\get('username') => Config\get('api_token') )); // Get version $server->register('app.version', function () { - return array('version' => APP_VERSION); }); // Get all feeds $server->register('feed.list', function () { - $feeds = Model\Feed\get_all(); - if (!$feeds) { - return $feeds; + if (empty($feeds)) { + return array(); } + $groups = Model\Group\get_feeds_map(); foreach ($feeds as &$feed) { $feed_id = $feed['id']; @@ -38,23 +37,16 @@ $server->register('feed.list', function () { // Get one feed $server->register('feed.info', function ($feed_id) { - $result = Model\Feed\get($feed_id); $result['feed_group_ids'] = Model\Group\get_feed_group_ids($feed_id); - return $result; }); // Add a new feed $server->register('feed.create', function($url) { - try { $result = Model\Feed\create($url); - } - catch (PicoFeedException $e) { - $result = false; - } - catch (UnexpectedValueException $e) { + } catch (Exception $e) { $result = false; } @@ -65,157 +57,131 @@ $server->register('feed.create', function($url) { // Delete a feed $server->register('feed.delete', function($feed_id) { - return Model\Feed\remove($feed_id); }); // Delete all feeds $server->register('feed.delete_all', function() { - return Model\Feed\remove_all(); }); // Enable a feed $server->register('feed.enable', function($feed_id) { - return Model\Feed\enable($feed_id); }); // Disable a feed $server->register('feed.disable', function($feed_id) { - return Model\Feed\disable($feed_id); }); // Update a feed $server->register('feed.update', function($feed_id) { - return Model\Feed\refresh($feed_id); }); // Get all groups $server->register('group.list', function () { - return Model\Group\get_all(); }); // Get all items for a specific feed $server->register('item.feed.list', function ($feed_id, $offset = null, $limit = null) { - return Model\Item\get_all_by_feed($feed_id, $offset, $limit); }); // Count all feed items $server->register('item.feed.count', function ($feed_id) { - return Model\Item\count_by_feed($feed_id); }); // Get all bookmark items $server->register('item.bookmark.list', function ($offset = null, $limit = null) { - return Model\Item\get_bookmarks($offset, $limit); }); // Count bookmarks $server->register('item.bookmark.count', function () { - return Model\Item\count_bookmarks(); }); // Add a bookmark $server->register('item.bookmark.create', function ($item_id) { - return Model\Item\set_bookmark_value($item_id, 1); }); // Remove a bookmark $server->register('item.bookmark.delete', function ($item_id) { - return Model\Item\set_bookmark_value($item_id, 0); }); // Get all unread items $server->register('item.list_unread', function ($offset = null, $limit = null) { - return Model\Item\get_all_by_status('unread', array(), $offset, $limit); }); // Count all unread items $server->register('item.count_unread', function () { - return Model\Item\count_by_status('unread'); }); // Get all read items $server->register('item.list_read', function ($offset = null, $limit = null) { - return Model\Item\get_all_by_status('read', array(), $offset, $limit); }); // Count all read items $server->register('item.count_read', function () { - return Model\Item\count_by_status('read'); }); // Get one item $server->register('item.info', function ($item_id) { - return Model\Item\get($item_id); }); // Delete an item $server->register('item.delete', function($item_id) { - return Model\Item\set_removed($item_id); }); // Mark item as read $server->register('item.mark_as_read', function($item_id) { - return Model\Item\set_read($item_id); }); // Mark item as unread $server->register('item.mark_as_unread', function($item_id) { - return Model\Item\set_unread($item_id); }); // Change the status of list of items $server->register('item.set_list_status', function($status, array $items) { - return Model\Item\set_status($status, $items); }); // Flush all read items $server->register('item.flush', function() { - return Model\Item\mark_all_as_removed(); }); // Mark all unread items as read $server->register('item.mark_all_as_read', function() { - return Model\Item\mark_all_as_read(); }); // Get all items with the content $server->register('item.get_all', function() { - return Model\Item\get_all(); }); // Get all items since a date $server->register('item.get_all_since', function($timestamp) { - return Model\Item\get_all_since($timestamp); }); // Get all items id and status $server->register('item.get_all_status', function() { - return Model\Item\get_all_status(); }); diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index abefef8..d9cd020 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -6,15 +6,28 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( - 'JsonRPC\\AccessDeniedException' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/AccessDeniedException.php', - 'JsonRPC\\AuthenticationFailure' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Server.php', 'JsonRPC\\Client' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Client.php', - 'JsonRPC\\ConnectionFailureException' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Client.php', - 'JsonRPC\\InvalidJsonFormat' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Server.php', - 'JsonRPC\\InvalidJsonRpcFormat' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Server.php', - 'JsonRPC\\ResponseException' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/ResponseException.php', + 'JsonRPC\\Exception\\AccessDeniedException' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Exception/AccessDeniedException.php', + 'JsonRPC\\Exception\\AuthenticationFailureException' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Exception/AuthenticationFailureException.php', + 'JsonRPC\\Exception\\ConnectionFailureException' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Exception/ConnectionFailureException.php', + 'JsonRPC\\Exception\\InvalidJsonFormatException' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Exception/InvalidJsonFormatException.php', + 'JsonRPC\\Exception\\InvalidJsonRpcFormatException' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Exception/InvalidJsonRpcFormatException.php', + 'JsonRPC\\Exception\\ResponseEncodingFailureException' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Exception/ResponseEncodingFailureException.php', + 'JsonRPC\\Exception\\ResponseException' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Exception/ResponseException.php', + 'JsonRPC\\Exception\\ServerErrorException' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Exception/ServerErrorException.php', + 'JsonRPC\\HttpClient' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/HttpClient.php', + 'JsonRPC\\ProcedureHandler' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/ProcedureHandler.php', + 'JsonRPC\\Request\\BatchRequestParser' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Request/BatchRequestParser.php', + 'JsonRPC\\Request\\RequestBuilder' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Request/RequestBuilder.php', + 'JsonRPC\\Request\\RequestParser' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Request/RequestParser.php', + 'JsonRPC\\Response\\ResponseBuilder' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Response/ResponseBuilder.php', + 'JsonRPC\\Response\\ResponseParser' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Response/ResponseParser.php', 'JsonRPC\\Server' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Server.php', - 'JsonRPC\\ServerErrorException' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Client.php', + 'JsonRPC\\Validator\\HostValidator' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Validator/HostValidator.php', + 'JsonRPC\\Validator\\JsonEncodingValidator' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Validator/JsonEncodingValidator.php', + 'JsonRPC\\Validator\\JsonFormatValidator' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Validator/JsonFormatValidator.php', + 'JsonRPC\\Validator\\RpcFormatValidator' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Validator/RpcFormatValidator.php', + 'JsonRPC\\Validator\\UserValidator' => $vendorDir . '/fguillot/json-rpc/src/JsonRPC/Validator/UserValidator.php', 'Pheanstalk\\Command' => $vendorDir . '/pda/pheanstalk/src/Command.php', 'Pheanstalk\\Command\\AbstractCommand' => $vendorDir . '/pda/pheanstalk/src/Command/AbstractCommand.php', 'Pheanstalk\\Command\\BuryCommand' => $vendorDir . '/pda/pheanstalk/src/Command/BuryCommand.php', diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index 204683f..3c707a0 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -37,44 +37,6 @@ "description": "Simple validator library", "homepage": "https://github.com/fguillot/simpleValidator" }, - { - "name": "fguillot/json-rpc", - "version": "v1.0.2", - "version_normalized": "1.0.2.0", - "source": { - "type": "git", - "url": "https://github.com/fguillot/JsonRPC.git", - "reference": "265cf039c2823f684349de78c0c03a597992bea9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fguillot/JsonRPC/zipball/265cf039c2823f684349de78c0c03a597992bea9", - "reference": "265cf039c2823f684349de78c0c03a597992bea9", - "shasum": "" - }, - "require": { - "php": ">=5.3.4" - }, - "time": "2015-09-12 16:27:13", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-0": { - "JsonRPC": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frédéric Guillot" - } - ], - "description": "Simple Json-RPC client/server library that just works", - "homepage": "https://github.com/fguillot/JsonRPC" - }, { "name": "fguillot/picodb", "version": "v1.0.2", @@ -262,5 +224,43 @@ "keywords": [ "beanstalkd" ] + }, + { + "name": "fguillot/json-rpc", + "version": "v1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/fguillot/JsonRPC.git", + "reference": "e915dab71940e7ac251955c785570048f460d332" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fguillot/JsonRPC/zipball/e915dab71940e7ac251955c785570048f460d332", + "reference": "e915dab71940e7ac251955c785570048f460d332", + "shasum": "" + }, + "require": { + "php": ">=5.3.4" + }, + "time": "2016-04-27 02:48:10", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "JsonRPC": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frédéric Guillot" + } + ], + "description": "Simple Json-RPC client/server library that just works", + "homepage": "https://github.com/fguillot/JsonRPC" } ] diff --git a/vendor/fguillot/json-rpc/src/JsonRPC/AccessDeniedException.php b/vendor/fguillot/json-rpc/src/JsonRPC/AccessDeniedException.php deleted file mode 100644 index ad92746..0000000 --- a/vendor/fguillot/json-rpc/src/JsonRPC/AccessDeniedException.php +++ /dev/null @@ -1,7 +0,0 @@ -', - 'Content-Type: application/json', - 'Accept: application/json', - 'Connection: close', - ); + private $batch = array(); /** - * SSL certificates verification + * Http Client * - * @access public - * @var boolean + * @access private + * @var HttpClient */ - public $ssl_verify_peer = true; + private $httpClient; /** * Constructor * * @access public - * @param string $url Server URL - * @param integer $timeout HTTP timeout - * @param array $headers Custom HTTP headers + * @param string $url Server URL + * @param bool $returnException Return exceptions + * @param HttpClient $httpClient HTTP client object */ - public function __construct($url, $timeout = 3, $headers = array()) + public function __construct($url = '', $returnException = false, HttpClient $httpClient = null) { - $this->url = $url; - $this->timeout = $timeout; - $this->headers = array_merge($this->headers, $headers); + $this->httpClient = $httpClient ?: new HttpClient($url); + $this->returnException = $returnException; + } + + /** + * Arguments passed are always positional + * + * @access public + * @return $this + */ + public function withPositionalArguments() + { + $this->isNamedArguments = false; + return $this; + } + + /** + * Get HTTP Client + * + * @access public + * @return HttpClient + */ + public function getHttpClient() + { + return $this->httpClient; + } + + /** + * Set username and password + * + * @access public + * @param string $username + * @param string $password + * @return $this + */ + public function authentication($username, $password) + { + $this->httpClient + ->withUsername($username) + ->withPassword($password); + + return $this; } /** @@ -129,30 +119,13 @@ class Client */ public function __call($method, array $params) { - // Allow to pass an array and use named arguments - if ($this->named_arguments && count($params) === 1 && is_array($params[0])) { + if ($this->isNamedArguments && count($params) === 1 && is_array($params[0])) { $params = $params[0]; } return $this->execute($method, $params); } - /** - * Set authentication parameters - * - * @access public - * @param string $username Username - * @param string $password Password - * @return Client - */ - public function authentication($username, $password) - { - $this->username = $username; - $this->password = $password; - - return $this; - } - /** * Start a batch request * @@ -161,9 +134,8 @@ class Client */ public function batch() { - $this->is_batch = true; + $this->isBatch = true; $this->batch = array(); - return $this; } @@ -175,11 +147,8 @@ class Client */ public function send() { - $this->is_batch = false; - - return $this->parseResponse( - $this->doRequest($this->batch) - ); + $this->isBatch = false; + return $this->sendPayload('['.implode(', ', $this->batch).']'); } /** @@ -192,198 +161,41 @@ class Client */ public function execute($procedure, array $params = array()) { - if ($this->is_batch) { - $this->batch[] = $this->prepareRequest($procedure, $params); + $payload = RequestBuilder::create() + ->withProcedure($procedure) + ->withParams($params) + ->build(); + + if ($this->isBatch) { + $this->batch[] = $payload; return $this; } - return $this->parseResponse( - $this->doRequest($this->prepareRequest($procedure, $params)) - ); + return $this->sendPayload($payload); } /** - * Prepare the payload + * Send payload * - * @access public - * @param string $procedure Procedure name - * @param array $params Procedure arguments - * @return array + * @access private + * @throws Exception + * @param string $payload + * @return Exception|Client */ - public function prepareRequest($procedure, array $params = array()) + private function sendPayload($payload) { - $payload = array( - 'jsonrpc' => '2.0', - 'method' => $procedure, - 'id' => mt_rand() - ); + try { - if (! empty($params)) { - $payload['params'] = $params; - } + return ResponseParser::create() + ->withPayload($this->httpClient->execute($payload)) + ->parse(); - return $payload; - } - - /** - * Parse the response and return the procedure result - * - * @access public - * @param array $payload - * @return mixed - */ - public function parseResponse(array $payload) - { - if ($this->isBatchResponse($payload)) { - $results = array(); - - foreach ($payload as $response) { - $results[] = $this->getResult($response); + } catch (Exception $e) { + if ($this->returnException) { + return $e; } - return $results; + throw $e; } - - return $this->getResult($payload); - } - - /** - * Throw an exception according the RPC error - * - * @access public - * @param array $error - * @throws BadFunctionCallException - * @throws InvalidArgumentException - * @throws RuntimeException - */ - public function handleRpcErrors(array $error) - { - switch ($error['code']) { - case -32601: - throw new BadFunctionCallException('Procedure not found: '. $error['message']); - case -32602: - throw new ResponseException( - 'Invalid arguments: '. $error['message'], - $error['code'], - null, - isset($error['data']) ? $error['data'] : null - ); - default: - throw new RuntimeException('Invalid request/response: '. $error['message'], $error['code']); - } - } - - /** - * Throw an exception according the HTTP response - * - * @access public - * @param array $headers - * @throws AccessDeniedException - * @throws ServerErrorException - */ - public function handleHttpErrors(array $headers) - { - $exceptions = array( - '401' => 'JsonRPC\AccessDeniedException', - '403' => 'JsonRPC\AccessDeniedException', - '404' => 'JsonRPC\ConnectionFailureException', - '500' => 'JsonRPC\ServerErrorException', - ); - - foreach ($headers as $header) { - foreach ($exceptions as $code => $exception) { - if (strpos($header, 'HTTP/1.0 '.$code) !== false || strpos($header, 'HTTP/1.1 '.$code) !== false) { - throw new $exception('Response: '.$header); - } - } - } - } - - /** - * Do the HTTP request - * - * @access private - * @param array $payload - * @return array - */ - private function doRequest(array $payload) - { - $stream = @fopen(trim($this->url), 'r', false, $this->getContext($payload)); - - if (! is_resource($stream)) { - throw new ConnectionFailureException('Unable to establish a connection'); - } - - $metadata = stream_get_meta_data($stream); - $this->handleHttpErrors($metadata['wrapper_data']); - - $response = json_decode(stream_get_contents($stream), true); - - if ($this->debug) { - error_log('==> Request: '.PHP_EOL.json_encode($payload, JSON_PRETTY_PRINT)); - error_log('==> Response: '.PHP_EOL.json_encode($response, JSON_PRETTY_PRINT)); - } - - return is_array($response) ? $response : array(); - } - - /** - * Prepare stream context - * - * @access private - * @param array $payload - * @return resource - */ - private function getContext(array $payload) - { - $headers = $this->headers; - - if (! empty($this->username) && ! empty($this->password)) { - $headers[] = 'Authorization: Basic '.base64_encode($this->username.':'.$this->password); - } - - return stream_context_create(array( - 'http' => array( - 'method' => 'POST', - 'protocol_version' => 1.1, - 'timeout' => $this->timeout, - 'max_redirects' => 2, - 'header' => implode("\r\n", $headers), - 'content' => json_encode($payload), - 'ignore_errors' => true, - ), - "ssl" => array( - "verify_peer" => $this->ssl_verify_peer, - "verify_peer_name" => $this->ssl_verify_peer, - ) - )); - } - - /** - * Return true if we have a batch response - * - * @access public - * @param array $payload - * @return boolean - */ - private function isBatchResponse(array $payload) - { - return array_keys($payload) === range(0, count($payload) - 1); - } - - /** - * Get a RPC call result - * - * @access private - * @param array $payload - * @return mixed - */ - private function getResult(array $payload) - { - if (isset($payload['error']['code'])) { - $this->handleRpcErrors($payload['error']); - } - - return isset($payload['result']) ? $payload['result'] : null; } } diff --git a/vendor/fguillot/json-rpc/src/JsonRPC/Exception/AccessDeniedException.php b/vendor/fguillot/json-rpc/src/JsonRPC/Exception/AccessDeniedException.php new file mode 100644 index 0000000..8cb9bc2 --- /dev/null +++ b/vendor/fguillot/json-rpc/src/JsonRPC/Exception/AccessDeniedException.php @@ -0,0 +1,15 @@ +', + 'Content-Type: application/json', + 'Accept: application/json', + 'Connection: close', + ); + + /** + * Username for authentication + * + * @access private + * @var string + */ + private $username; + + /** + * Password for authentication + * + * @access private + * @var string + */ + private $password; + + /** + * Enable debug output to the php error log + * + * @access private + * @var boolean + */ + private $debug = false; + + /** + * Cookies + * + * @access private + * @var array + */ + private $cookies = array(); + + /** + * SSL certificates verification + * + * @access private + * @var boolean + */ + private $verifySslCertificate = true; + + /** + * Callback called before the doing the request + * + * @access private + * @var Closure + */ + private $beforeRequest; + + /** + * HttpClient constructor + * + * @access public + * @param string $url + */ + public function __construct($url = '') + { + $this->url = $url; + } + + /** + * Set URL + * + * @access public + * @param string $url + * @return $this + */ + public function withUrl($url) + { + $this->url = $url; + return $this; + } + + /** + * Set username + * + * @access public + * @param string $username + * @return $this + */ + public function withUsername($username) + { + $this->username = $username; + return $this; + } + + /** + * Set password + * + * @access public + * @param string $password + * @return $this + */ + public function withPassword($password) + { + $this->password = $password; + return $this; + } + + /** + * Set timeout + * + * @access public + * @param integer $timeout + * @return $this + */ + public function withTimeout($timeout) + { + $this->timeout = $timeout; + return $this; + } + + /** + * Set timeout + * + * @access public + * @param array $headers + * @return $this + */ + public function withHeaders(array $headers) + { + $this->headers = array_merge($this->headers, $headers); + return $this; + } + + /** + * Set cookies + * + * @access public + * @param array $cookies + * @param boolean $replace + */ + public function withCookies(array $cookies, $replace = false) + { + if ($replace) { + $this->cookies = $cookies; + } else { + $this->cookies = array_merge($this->cookies, $cookies); + } + } + + /** + * Enable debug mode + * + * @access public + * @return $this + */ + public function withDebug() + { + $this->debug = true; + return $this; + } + + /** + * Disable SSL verification + * + * @access public + * @return $this + */ + public function withoutSslVerification() + { + $this->verifySslCertificate = false; + return $this; + } + + /** + * Assign a callback before the request + * + * @access public + * @param Closure $closure + * @return $this + */ + public function withBeforeRequestCallback(Closure $closure) + { + $this->beforeRequest = $closure; + return $this; + } + + /** + * Get cookies + * + * @access public + * @return array + */ + public function getCookies() + { + return $this->cookies; + } + + /** + * Do the HTTP request + * + * @access public + * @throws ConnectionFailureException + * @param string $payload + * @return array + */ + public function execute($payload) + { + if (is_callable($this->beforeRequest)) { + call_user_func_array($this->beforeRequest, array($this, $payload)); + } + + $stream = fopen(trim($this->url), 'r', false, $this->buildContext($payload)); + + if (! is_resource($stream)) { + throw new ConnectionFailureException('Unable to establish a connection'); + } + + $metadata = stream_get_meta_data($stream); + $headers = $metadata['wrapper_data']; + $response = json_decode(stream_get_contents($stream), true); + + if ($this->debug) { + error_log('==> Request: '.PHP_EOL.json_encode($payload, JSON_PRETTY_PRINT)); + error_log('==> Headers: '.PHP_EOL.var_export($headers, true)); + error_log('==> Response: '.PHP_EOL.json_encode($response, JSON_PRETTY_PRINT)); + } + + $this->handleExceptions($headers); + $this->parseCookies($headers); + + return $response; + } + + /** + * Prepare stream context + * + * @access private + * @param string $payload + * @return resource + */ + private function buildContext($payload) + { + $headers = $this->headers; + + if (! empty($this->username) && ! empty($this->password)) { + $headers[] = 'Authorization: Basic '.base64_encode($this->username.':'.$this->password); + } + + if (! empty($this->cookies)){ + $cookies = array(); + + foreach ($this->cookies as $key => $value) { + $cookies[] = $key.'='.$value; + } + + $headers[] = 'Cookie: '.implode('; ', $cookies); + } + + return stream_context_create(array( + 'http' => array( + 'method' => 'POST', + 'protocol_version' => 1.1, + 'timeout' => $this->timeout, + 'max_redirects' => 2, + 'header' => implode("\r\n", $headers), + 'content' => $payload, + 'ignore_errors' => true, + ), + 'ssl' => array( + 'verify_peer' => $this->verifySslCertificate, + 'verify_peer_name' => $this->verifySslCertificate, + ) + )); + } + + /** + * Parse cookies from response + * + * @access private + * @param array $headers + */ + private function parseCookies(array $headers) + { + foreach ($headers as $header) { + $pos = stripos($header, 'Set-Cookie:'); + + if ($pos !== false) { + $cookies = explode(';', substr($header, $pos + 11)); + + foreach ($cookies as $cookie) { + $item = explode('=', $cookie); + + if (count($item) === 2) { + $name = trim($item[0]); + $value = $item[1]; + $this->cookies[$name] = $value; + } + } + } + } + } + + /** + * Throw an exception according the HTTP response + * + * @access public + * @param array $headers + * @throws AccessDeniedException + * @throws ServerErrorException + */ + public function handleExceptions(array $headers) + { + $exceptions = array( + '401' => '\JsonRPC\Exception\AccessDeniedException', + '403' => '\JsonRPC\Exception\AccessDeniedException', + '404' => '\JsonRPC\Exception\ConnectionFailureException', + '500' => '\JsonRPC\Exception\ServerErrorException', + ); + + foreach ($headers as $header) { + foreach ($exceptions as $code => $exception) { + if (strpos($header, 'HTTP/1.0 '.$code) !== false || strpos($header, 'HTTP/1.1 '.$code) !== false) { + throw new $exception('Response: '.$header); + } + } + } + } +} diff --git a/vendor/fguillot/json-rpc/src/JsonRPC/ProcedureHandler.php b/vendor/fguillot/json-rpc/src/JsonRPC/ProcedureHandler.php new file mode 100644 index 0000000..b95e83b --- /dev/null +++ b/vendor/fguillot/json-rpc/src/JsonRPC/ProcedureHandler.php @@ -0,0 +1,304 @@ +username = $username; + return $this; + } + + /** + * Set password + * + * @access public + * @param string $password + * @return $this + */ + public function withPassword($password) + { + $this->password = $password; + return $this; + } + + /** + * Register a new procedure + * + * @access public + * @param string $procedure Procedure name + * @param closure $callback Callback + * @return Server + */ + public function withCallback($procedure, Closure $callback) + { + $this->callbacks[$procedure] = $callback; + return $this; + } + + /** + * Bind a procedure to a class + * + * @access public + * @param string $procedure Procedure name + * @param mixed $class Class name or instance + * @param string $method Procedure name + * @return Server + */ + public function withClassAndMethod($procedure, $class, $method = '') + { + if ($method === '') { + $method = $procedure; + } + + $this->classes[$procedure] = array($class, $method); + return $this; + } + + /** + * Bind a class instance + * + * @access public + * @param mixed $instance + * @return Server + */ + public function withObject($instance) + { + $this->instances[] = $instance; + return $this; + } + + /** + * Attach a method that will be called before the procedure + * + * @access public + * @param string $before + * @return Server + */ + public function withBeforeMethod($before) + { + $this->before = $before; + return $this; + } + + /** + * Execute the procedure + * + * @access public + * @param string $procedure Procedure name + * @param array $params Procedure params + * @return mixed + */ + public function executeProcedure($procedure, array $params = array()) + { + if (isset($this->callbacks[$procedure])) { + return $this->executeCallback($this->callbacks[$procedure], $params); + } + else if (isset($this->classes[$procedure]) && method_exists($this->classes[$procedure][0], $this->classes[$procedure][1])) { + return $this->executeMethod($this->classes[$procedure][0], $this->classes[$procedure][1], $params); + } + + foreach ($this->instances as $instance) { + if (method_exists($instance, $procedure)) { + return $this->executeMethod($instance, $procedure, $params); + } + } + + throw new BadFunctionCallException('Unable to find the procedure'); + } + + /** + * Execute a callback + * + * @access public + * @param Closure $callback Callback + * @param array $params Procedure params + * @return mixed + */ + public function executeCallback(Closure $callback, $params) + { + $reflection = new ReflectionFunction($callback); + + $arguments = $this->getArguments( + $params, + $reflection->getParameters(), + $reflection->getNumberOfRequiredParameters(), + $reflection->getNumberOfParameters() + ); + + return $reflection->invokeArgs($arguments); + } + + /** + * Execute a method + * + * @access public + * @param mixed $class Class name or instance + * @param string $method Method name + * @param array $params Procedure params + * @return mixed + */ + public function executeMethod($class, $method, $params) + { + $instance = is_string($class) ? new $class : $class; + + // Execute before action + if (! empty($this->before)) { + if (is_callable($this->before)) { + call_user_func_array($this->before, array($this->username, $this->password, get_class($class), $method)); + } + else if (method_exists($instance, $this->before)) { + $instance->{$this->before}($this->username, $this->password, get_class($class), $method); + } + } + + $reflection = new ReflectionMethod($class, $method); + + $arguments = $this->getArguments( + $params, + $reflection->getParameters(), + $reflection->getNumberOfRequiredParameters(), + $reflection->getNumberOfParameters() + ); + + return $reflection->invokeArgs($instance, $arguments); + } + + /** + * Get procedure arguments + * + * @access public + * @param array $request_params Incoming arguments + * @param array $method_params Procedure arguments + * @param integer $nb_required_params Number of required parameters + * @param integer $nb_max_params Maximum number of parameters + * @return array + */ + public function getArguments(array $request_params, array $method_params, $nb_required_params, $nb_max_params) + { + $nb_params = count($request_params); + + if ($nb_params < $nb_required_params) { + throw new InvalidArgumentException('Wrong number of arguments'); + } + + if ($nb_params > $nb_max_params) { + throw new InvalidArgumentException('Too many arguments'); + } + + if ($this->isPositionalArguments($request_params)) { + return $request_params; + } + + return $this->getNamedArguments($request_params, $method_params); + } + + /** + * Return true if we have positional parameters + * + * @access public + * @param array $request_params Incoming arguments + * @return bool + */ + public function isPositionalArguments(array $request_params) + { + return array_keys($request_params) === range(0, count($request_params) - 1); + } + + /** + * Get named arguments + * + * @access public + * @param array $request_params Incoming arguments + * @param array $method_params Procedure arguments + * @return array + */ + public function getNamedArguments(array $request_params, array $method_params) + { + $params = array(); + + foreach ($method_params as $p) { + $name = $p->getName(); + + if (isset($request_params[$name])) { + $params[$name] = $request_params[$name]; + } + else if ($p->isDefaultValueAvailable()) { + $params[$name] = $p->getDefaultValue(); + } + else { + throw new InvalidArgumentException('Missing argument: '.$name); + } + } + + return $params; + } +} diff --git a/vendor/fguillot/json-rpc/src/JsonRPC/Request/BatchRequestParser.php b/vendor/fguillot/json-rpc/src/JsonRPC/Request/BatchRequestParser.php new file mode 100644 index 0000000..208d167 --- /dev/null +++ b/vendor/fguillot/json-rpc/src/JsonRPC/Request/BatchRequestParser.php @@ -0,0 +1,46 @@ +payload as $payload) { + $responses[] = RequestParser::create() + ->withPayload($payload) + ->withProcedureHandler($this->procedureHandler) + ->parse(); + } + + $responses = array_filter($responses); + return empty($responses) ? '' : '['.implode(',', $responses).']'; + } + + /** + * Return true if we have a batch request + * + * @static + * @access public + * @param array $payload + * @return bool + */ + public static function isBatchRequest(array $payload) + { + return is_array($payload) && array_keys($payload) === range(0, count($payload) - 1); + } +} diff --git a/vendor/fguillot/json-rpc/src/JsonRPC/Request/RequestBuilder.php b/vendor/fguillot/json-rpc/src/JsonRPC/Request/RequestBuilder.php new file mode 100644 index 0000000..95ff661 --- /dev/null +++ b/vendor/fguillot/json-rpc/src/JsonRPC/Request/RequestBuilder.php @@ -0,0 +1,108 @@ +id = $id; + return $this; + } + + /** + * Set method + * + * @access public + * @param string $procedure + * @return RequestBuilder + */ + public function withProcedure($procedure) + { + $this->procedure = $procedure; + return $this; + } + + /** + * Set parameters + * + * @access public + * @param array $params + * @return RequestBuilder + */ + public function withParams(array $params) + { + $this->params = $params; + return $this; + } + + /** + * Build the payload + * + * @access public + * @return string + */ + public function build() + { + $payload = array( + 'jsonrpc' => '2.0', + 'method' => $this->procedure, + 'id' => $this->id ?: mt_rand(), + ); + + if (! empty($this->params)) { + $payload['params'] = $this->params; + } + + return json_encode($payload); + } +} diff --git a/vendor/fguillot/json-rpc/src/JsonRPC/Request/RequestParser.php b/vendor/fguillot/json-rpc/src/JsonRPC/Request/RequestParser.php new file mode 100644 index 0000000..36db1de --- /dev/null +++ b/vendor/fguillot/json-rpc/src/JsonRPC/Request/RequestParser.php @@ -0,0 +1,129 @@ +payload = $payload; + return $this; + } + + /** + * Set procedure handler + * + * @access public + * @param ProcedureHandler $procedureHandler + * @return $this + */ + public function withProcedureHandler(ProcedureHandler $procedureHandler) + { + $this->procedureHandler = $procedureHandler; + return $this; + } + + /** + * Parse incoming request + * + * @access public + * @return string + * @throws AccessDeniedException + * @throws AuthenticationFailureException + */ + public function parse() + { + try { + + JsonFormatValidator::validate($this->payload); + RpcFormatValidator::validate($this->payload); + + $result = $this->procedureHandler->executeProcedure( + $this->payload['method'], + empty($this->payload['params']) ? array() : $this->payload['params'] + ); + + if (! $this->isNotification()) { + return ResponseBuilder::create() + ->withId($this->payload['id']) + ->withResult($result) + ->build(); + } + } catch (Exception $e) { + + if ($e instanceof AccessDeniedException || $e instanceof AuthenticationFailureException) { + throw $e; + } + + if ($e instanceof InvalidJsonRpcFormatException || ! $this->isNotification()) { + return ResponseBuilder::create() + ->withId(isset($this->payload['id']) ? $this->payload['id'] : null) + ->withException($e) + ->build(); + } + } + + return ''; + } + + /** + * Return true if the message is a notification + * + * @access private + * @return bool + */ + private function isNotification() + { + return is_array($this->payload) && !isset($this->payload['id']); + } +} diff --git a/vendor/fguillot/json-rpc/src/JsonRPC/Response/ResponseBuilder.php b/vendor/fguillot/json-rpc/src/JsonRPC/Response/ResponseBuilder.php new file mode 100644 index 0000000..c1caff9 --- /dev/null +++ b/vendor/fguillot/json-rpc/src/JsonRPC/Response/ResponseBuilder.php @@ -0,0 +1,324 @@ + 'application/json', + ); + + /** + * HTTP status + * + * @access private + * @var string + */ + private $status; + + /** + * Exception + * + * @access private + * @var ResponseException + */ + private $exception; + + /** + * Get new object instance + * + * @static + * @access public + * @return ResponseBuilder + */ + public static function create() + { + return new static(); + } + + /** + * Set id + * + * @access public + * @param mixed $id + * @return $this + */ + public function withId($id) + { + $this->id = $id; + return $this; + } + + /** + * Set result + * + * @access public + * @param mixed $result + * @return $this + */ + public function withResult($result) + { + $this->result = $result; + return $this; + } + + /** + * Set error + * + * @access public + * @param integer $code + * @param string $message + * @param string $data + * @return $this + */ + public function withError($code, $message, $data = '') + { + $this->errorCode = $code; + $this->errorMessage = $message; + $this->errorData = $data; + return $this; + } + + /** + * Set exception + * + * @access public + * @param Exception $exception + * @return $this + */ + public function withException(Exception $exception) + { + $this->exception = $exception; + return $this; + } + + /** + * Add HTTP header + * + * @access public + * @param string $name + * @param string $value + * @return $this + */ + public function withHeader($name, $value) + { + $this->headers[$name] = $value; + return $this; + } + + /** + * Add HTTP Status + * + * @access public + * @param string $status + * @return $this + */ + public function withStatus($status) + { + $this->status = $status; + return $this; + } + + /** + * Get status + * + * @access public + * @return string + */ + public function getStatus() + { + return $this->status; + } + + /** + * Get headers + * + * @access public + * @return string[] + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Build response + * + * @access public + * @return string + */ + public function build() + { + $encodedResponse = json_encode($this->buildResponse()); + JsonEncodingValidator::validate(); + + return $encodedResponse; + } + + /** + * Send HTTP headers + * + * @access public + * @return $this + */ + public function sendHeaders() + { + if (! empty($this->status)) { + header($this->status); + } + + foreach ($this->headers as $name => $value) { + header($name.': '.$value); + } + + return $this; + } + + /** + * Build response payload + * + * @access private + * @return array + */ + private function buildResponse() + { + $response = array('jsonrpc' => '2.0'); + $this->handleExceptions(); + + if (! empty($this->errorMessage)) { + $response['error'] = $this->buildErrorResponse(); + } else { + $response['result'] = $this->result; + } + + $response['id'] = $this->id; + return $response; + } + + /** + * Build response error payload + * + * @access private + * @return array + */ + private function buildErrorResponse() + { + $response = array( + 'code' => $this->errorCode, + 'message' => $this->errorMessage, + ); + + if (! empty($this->errorData)) { + $response['data'] = $this->errorData; + } + + return $response; + } + + /** + * Transform exceptions to JSON-RPC errors + * + * @access private + */ + private function handleExceptions() + { + if ($this->exception instanceof InvalidJsonFormatException) { + $this->errorCode = -32700; + $this->errorMessage = 'Parse error'; + $this->id = null; + } elseif ($this->exception instanceof InvalidJsonRpcFormatException) { + $this->errorCode = -32600; + $this->errorMessage = 'Invalid Request'; + $this->id = null; + } elseif ($this->exception instanceof BadFunctionCallException) { + $this->errorCode = -32601; + $this->errorMessage = 'Method not found'; + } elseif ($this->exception instanceof InvalidArgumentException) { + $this->errorCode = -32602; + $this->errorMessage = 'Invalid params'; + } elseif ($this->exception instanceof ResponseEncodingFailureException) { + $this->errorCode = -32603; + $this->errorMessage = 'Internal error'; + $this->errorData = $this->exception->getMessage(); + } elseif ($this->exception instanceof AuthenticationFailureException) { + $this->errorCode = 401; + $this->errorMessage = 'Unauthorized'; + $this->status = 'HTTP/1.0 401 Unauthorized'; + $this->withHeader('WWW-Authenticate', 'Basic realm="JsonRPC"'); + } elseif ($this->exception instanceof AccessDeniedException) { + $this->errorCode = 403; + $this->errorMessage = 'Forbidden'; + $this->status = 'HTTP/1.0 403 Forbidden'; + } elseif ($this->exception instanceof ResponseException) { + $this->errorCode = $this->exception->getCode(); + $this->errorMessage = $this->exception->getMessage(); + $this->errorData = $this->exception->getData(); + } elseif ($this->exception instanceof Exception) { + $this->errorCode = $this->exception->getCode(); + $this->errorMessage = $this->exception->getMessage(); + } + } +} diff --git a/vendor/fguillot/json-rpc/src/JsonRPC/Response/ResponseParser.php b/vendor/fguillot/json-rpc/src/JsonRPC/Response/ResponseParser.php new file mode 100644 index 0000000..6763753 --- /dev/null +++ b/vendor/fguillot/json-rpc/src/JsonRPC/Response/ResponseParser.php @@ -0,0 +1,124 @@ +payload = $payload; + return $this; + } + + /** + * Parse response + * + * @access public + * @throws InvalidJsonFormatException + * @throws InvalidJsonRpcFormatException + * @throws ResponseException + * @return mixed + */ + public function parse() + { + JsonFormatValidator::validate($this->payload); + + if ($this->isBatchResponse()) { + $results = array(); + + foreach ($this->payload as $response) { + $results[] = self::create() + ->withPayload($response) + ->parse(); + } + + return $results; + } + + if (isset($this->payload['error']['code'])) { + $this->handleExceptions(); + } + + return isset($this->payload['result']) ? $this->payload['result'] : null; + } + + /** + * Handle exceptions + * + * @access private + * @throws InvalidJsonFormatException + * @throws InvalidJsonRpcFormatException + * @throws ResponseException + */ + private function handleExceptions() + { + switch ($this->payload['error']['code']) { + case -32700: + throw new InvalidJsonFormatException('Parse error: '.$this->payload['error']['message']); + case -32600: + throw new InvalidJsonRpcFormatException('Invalid Request: '.$this->payload['error']['message']); + case -32601: + throw new BadFunctionCallException('Procedure not found: '.$this->payload['error']['message']); + case -32602: + throw new InvalidArgumentException('Invalid arguments: '.$this->payload['error']['message']); + default: + throw new ResponseException( + $this->payload['error']['message'], + $this->payload['error']['code'], + null, + isset($this->payload['error']['data']) ? $this->payload['error']['data'] : null + ); + } + } + + /** + * Return true if we have a batch response + * + * @access private + * @return boolean + */ + private function isBatchResponse() + { + return array_keys($this->payload) === range(0, count($this->payload) - 1); + } +} diff --git a/vendor/fguillot/json-rpc/src/JsonRPC/Server.php b/vendor/fguillot/json-rpc/src/JsonRPC/Server.php index e0d6e57..13de294 100644 --- a/vendor/fguillot/json-rpc/src/JsonRPC/Server.php +++ b/vendor/fguillot/json-rpc/src/JsonRPC/Server.php @@ -3,16 +3,13 @@ namespace JsonRPC; use Closure; -use BadFunctionCallException; use Exception; -use InvalidArgumentException; -use LogicException; -use ReflectionFunction; -use ReflectionMethod; - -class InvalidJsonRpcFormat extends Exception {}; -class InvalidJsonFormat extends Exception {}; -class AuthenticationFailure extends Exception {}; +use JsonRPC\Request\BatchRequestParser; +use JsonRPC\Request\RequestParser; +use JsonRPC\Response\ResponseBuilder; +use JsonRPC\Validator\HostValidator; +use JsonRPC\Validator\JsonFormatValidator; +use JsonRPC\Validator\UserValidator; /** * JsonRPC server class @@ -22,6 +19,14 @@ class AuthenticationFailure extends Exception {}; */ class Server { + /** + * Allowed hosts + * + * @access private + * @var array + */ + private $hosts = array(); + /** * Data received from the client * @@ -30,30 +35,6 @@ class Server */ private $payload = array(); - /** - * List of procedures - * - * @access private - * @var array - */ - private $callbacks = array(); - - /** - * List of classes - * - * @access private - * @var array - */ - private $classes = array(); - - /** - * List of instances - * - * @access private - * @var array - */ - private $instances = array(); - /** * List of exception classes that should be relayed to client * @@ -62,14 +43,6 @@ class Server */ private $exceptions = array(); - /** - * Method name to execute before the procedure - * - * @access private - * @var string - */ - private $before = ''; - /** * Username * @@ -86,32 +59,47 @@ class Server */ private $password = ''; + /** + * Allowed users + * + * @access private + * @var array + */ + private $users = array(); + + /** + * $_SERVER + * + * @access private + * @var array + */ + private $serverVariable; + + /** + * ProcedureHandler object + * + * @access private + * @var ProcedureHandler + */ + private $procedureHandler; + /** * Constructor * * @access public - * @param string $request + * @param string $request + * @param array $server */ - public function __construct($request = '') + public function __construct($request = '', array $server = array()) { if ($request !== '') { $this->payload = json_decode($request, true); - } - else { + } else { $this->payload = json_decode(file_get_contents('php://input'), true); } - } - /** - * Set a payload - * - * @access public - * @param array $payload - * @return Server - */ - public function setPayload(array $payload) - { - $this->payload = $payload; + $this->serverVariable = $server ?: $_SERVER; + $this->procedureHandler = new ProcedureHandler(); } /** @@ -124,17 +112,28 @@ class Server public function setAuthenticationHeader($header) { if (! empty($header)) { - $header = 'HTTP_'.str_replace('-', '_', strtoupper($header)); + $value = $this->getServerVariable($header); - if (isset($_SERVER[$header])) { - list($this->username, $this->password) = explode(':', @base64_decode($_SERVER[$header])); + if (! empty($value)) { + list($this->username, $this->password) = explode(':', base64_decode($value)); } } return $this; } + /** + * Get ProcedureHandler + * + * @access public + * @return ProcedureHandler + */ + public function getProcedureHandler() + { + return $this->procedureHandler; + } + /** * Get username * @@ -143,7 +142,7 @@ class Server */ public function getUsername() { - return $this->username ?: @$_SERVER['PHP_AUTH_USER']; + return $this->username ?: $this->getServerVariable('PHP_AUTH_USER'); } /** @@ -154,66 +153,32 @@ class Server */ public function getPassword() { - return $this->password ?: @$_SERVER['PHP_AUTH_PW']; - } - - /** - * Send authentication failure response - * - * @access public - */ - public function sendAuthenticationFailureResponse() - { - header('WWW-Authenticate: Basic realm="JsonRPC"'); - header('Content-Type: application/json'); - header('HTTP/1.0 401 Unauthorized'); - echo '{"error": "Authentication failed"}'; - exit; - } - - /** - * Send forbidden response - * - * @access public - */ - public function sendForbiddenResponse() - { - header('Content-Type: application/json'); - header('HTTP/1.0 403 Forbidden'); - echo '{"error": "Access Forbidden"}'; - exit; + return $this->password ?: $this->getServerVariable('PHP_AUTH_PW'); } /** * IP based client restrictions * - * Return an HTTP error 403 if the client is not allowed - * * @access public * @param array $hosts List of hosts + * @return Server */ public function allowHosts(array $hosts) { - if (! in_array($_SERVER['REMOTE_ADDR'], $hosts)) { - $this->sendForbiddenResponse(); - } + $this->hosts = $hosts; + return $this; } /** * HTTP Basic authentication * - * Return an HTTP error 401 if the client is not allowed - * * @access public - * @param array $users Map of username/password + * @param array $users Dictionary of username/password * @return Server */ public function authentication(array $users) { - if (! isset($users[$this->getUsername()]) || $users[$this->getUsername()] !== $this->getPassword()) { - $this->sendAuthenticationFailureResponse(); - } - + $this->users = $users; return $this; } @@ -227,7 +192,7 @@ class Server */ public function register($procedure, Closure $callback) { - $this->callbacks[$procedure] = $callback; + $this->procedureHandler->withCallback($procedure, $callback); return $this; } @@ -242,11 +207,7 @@ class Server */ public function bind($procedure, $class, $method = '') { - if ($method === '') { - $method = $procedure; - } - - $this->classes[$procedure] = array($class, $method); + $this->procedureHandler->withClassAndMethod($procedure, $class, $method); return $this; } @@ -259,7 +220,7 @@ class Server */ public function attach($instance) { - $this->instances[] = $instance; + $this->procedureHandler->withObject($instance); return $this; } @@ -286,112 +247,10 @@ class Server */ public function before($before) { - $this->before = $before; + $this->procedureHandler->withBeforeMethod($before); return $this; } - /** - * Return the response to the client - * - * @access public - * @param array $data Data to send to the client - * @param array $payload Incoming data - * @return string - */ - public function getResponse(array $data, array $payload = array()) - { - if (! array_key_exists('id', $payload)) { - return ''; - } - - $response = array( - 'jsonrpc' => '2.0', - 'id' => $payload['id'] - ); - - $response = array_merge($response, $data); - - @header('Content-Type: application/json'); - return json_encode($response); - } - - /** - * Parse the payload and test if the parsed JSON is ok - * - * @access private - */ - private function checkJsonFormat() - { - if (! is_array($this->payload)) { - throw new InvalidJsonFormat('Malformed payload'); - } - } - - /** - * Test if all required JSON-RPC parameters are here - * - * @access private - */ - private function checkRpcFormat() - { - if (! isset($this->payload['jsonrpc']) || - ! isset($this->payload['method']) || - ! is_string($this->payload['method']) || - $this->payload['jsonrpc'] !== '2.0' || - (isset($this->payload['params']) && ! is_array($this->payload['params']))) { - - throw new InvalidJsonRpcFormat('Invalid JSON RPC payload'); - } - } - - /** - * Return true if we have a batch request - * - * @access public - * @return boolean - */ - private function isBatchRequest() - { - return array_keys($this->payload) === range(0, count($this->payload) - 1); - } - - /** - * Handle batch request - * - * @access private - * @return string - */ - private function handleBatchRequest() - { - $responses = array(); - - foreach ($this->payload as $payload) { - - if (! is_array($payload)) { - - $responses[] = $this->getResponse(array( - 'error' => array( - 'code' => -32600, - 'message' => 'Invalid Request' - )), - array('id' => null) - ); - } - else { - - $server = clone($this); - $server->setPayload($payload); - $response = $server->execute(); - - if (! empty($response)) { - $responses[] = $response; - } - } - } - - return empty($responses) ? '' : '['.implode(',', $responses).']'; - } - /** * Parse incoming requests * @@ -400,249 +259,57 @@ class Server */ public function execute() { + $responseBuilder = ResponseBuilder::create(); + try { + $this->procedureHandler + ->withUsername($this->getUsername()) + ->withPassword($this->getPassword()); - $this->checkJsonFormat(); + JsonFormatValidator::validate($this->payload); + HostValidator::validate($this->hosts, $this->getServerVariable('REMOTE_ADDR')); + UserValidator::validate($this->users, $this->getUsername(), $this->getPassword()); - if ($this->isBatchRequest()){ - return $this->handleBatchRequest(); - } + $response = $this->parseRequest(); - $this->checkRpcFormat(); - - $result = $this->executeProcedure( - $this->payload['method'], - empty($this->payload['params']) ? array() : $this->payload['params'] - ); - - return $this->getResponse(array('result' => $result), $this->payload); + } catch (Exception $e) { + $response = $responseBuilder->withException($e)->build(); } - catch (InvalidJsonFormat $e) { - return $this->getResponse(array( - 'error' => array( - 'code' => -32700, - 'message' => 'Parse error' - )), - array('id' => null) - ); - } - catch (InvalidJsonRpcFormat $e) { - - return $this->getResponse(array( - 'error' => array( - 'code' => -32600, - 'message' => 'Invalid Request' - )), - array('id' => null) - ); - } - catch (BadFunctionCallException $e) { - - return $this->getResponse(array( - 'error' => array( - 'code' => -32601, - 'message' => 'Method not found' - )), - $this->payload - ); - } - catch (InvalidArgumentException $e) { - - return $this->getResponse(array( - 'error' => array( - 'code' => -32602, - 'message' => 'Invalid params' - )), - $this->payload - ); - } - catch (AuthenticationFailure $e) { - $this->sendAuthenticationFailureResponse(); - } - catch (AccessDeniedException $e) { - $this->sendForbiddenResponse(); - } - catch (ResponseException $e) { - return $this->getResponse(array( - 'error' => array( - 'code' => $e->getCode(), - 'message' => $e->getMessage(), - 'data' => $e->getData(), - )), - $this->payload - ); - } - catch (Exception $e) { - - foreach ($this->exceptions as $class) { - if ($e instanceof $class) { - return $this->getResponse(array( - 'error' => array( - 'code' => $e->getCode(), - 'message' => $e->getMessage() - )), - $this->payload - ); - } - } - - throw $e; - } + $responseBuilder->sendHeaders(); + return $response; } /** - * Execute the procedure + * Parse incoming request * - * @access public - * @param string $procedure Procedure name - * @param array $params Procedure params - * @return mixed + * @access private + * @return string */ - public function executeProcedure($procedure, array $params = array()) + private function parseRequest() { - if (isset($this->callbacks[$procedure])) { - return $this->executeCallback($this->callbacks[$procedure], $params); - } - else if (isset($this->classes[$procedure]) && method_exists($this->classes[$procedure][0], $this->classes[$procedure][1])) { - return $this->executeMethod($this->classes[$procedure][0], $this->classes[$procedure][1], $params); + if (BatchRequestParser::isBatchRequest($this->payload)) { + return BatchRequestParser::create() + ->withPayload($this->payload) + ->withProcedureHandler($this->procedureHandler) + ->parse(); } - foreach ($this->instances as $instance) { - if (method_exists($instance, $procedure)) { - return $this->executeMethod($instance, $procedure, $params); - } - } - - throw new BadFunctionCallException('Unable to find the procedure'); + return RequestParser::create() + ->withPayload($this->payload) + ->withProcedureHandler($this->procedureHandler) + ->parse(); } /** - * Execute a callback + * Check existence and get value of server variable * - * @access public - * @param Closure $callback Callback - * @param array $params Procedure params - * @return mixed + * @access private + * @param string $variable + * @return string|null */ - public function executeCallback(Closure $callback, $params) + private function getServerVariable($variable) { - $reflection = new ReflectionFunction($callback); - - $arguments = $this->getArguments( - $params, - $reflection->getParameters(), - $reflection->getNumberOfRequiredParameters(), - $reflection->getNumberOfParameters() - ); - - return $reflection->invokeArgs($arguments); - } - - /** - * Execute a method - * - * @access public - * @param mixed $class Class name or instance - * @param string $method Method name - * @param array $params Procedure params - * @return mixed - */ - public function executeMethod($class, $method, $params) - { - $instance = is_string($class) ? new $class : $class; - - // Execute before action - if (! empty($this->before)) { - if (is_callable($this->before)) { - call_user_func_array($this->before, array($this->getUsername(), $this->getPassword(), get_class($class), $method)); - } - else if (method_exists($instance, $this->before)) { - $instance->{$this->before}($this->getUsername(), $this->getPassword(), get_class($class), $method); - } - } - - $reflection = new ReflectionMethod($class, $method); - - $arguments = $this->getArguments( - $params, - $reflection->getParameters(), - $reflection->getNumberOfRequiredParameters(), - $reflection->getNumberOfParameters() - ); - - return $reflection->invokeArgs($instance, $arguments); - } - - /** - * Get procedure arguments - * - * @access public - * @param array $request_params Incoming arguments - * @param array $method_params Procedure arguments - * @param integer $nb_required_params Number of required parameters - * @param integer $nb_max_params Maximum number of parameters - * @return array - */ - public function getArguments(array $request_params, array $method_params, $nb_required_params, $nb_max_params) - { - $nb_params = count($request_params); - - if ($nb_params < $nb_required_params) { - throw new InvalidArgumentException('Wrong number of arguments'); - } - - if ($nb_params > $nb_max_params) { - throw new InvalidArgumentException('Too many arguments'); - } - - if ($this->isPositionalArguments($request_params, $method_params)) { - return $request_params; - } - - return $this->getNamedArguments($request_params, $method_params); - } - - /** - * Return true if we have positional parametes - * - * @access public - * @param array $request_params Incoming arguments - * @param array $method_params Procedure arguments - * @return bool - */ - public function isPositionalArguments(array $request_params, array $method_params) - { - return array_keys($request_params) === range(0, count($request_params) - 1); - } - - /** - * Get named arguments - * - * @access public - * @param array $request_params Incoming arguments - * @param array $method_params Procedure arguments - * @return array - */ - public function getNamedArguments(array $request_params, array $method_params) - { - $params = array(); - - foreach ($method_params as $p) { - - $name = $p->getName(); - - if (isset($request_params[$name])) { - $params[$name] = $request_params[$name]; - } - else if ($p->isDefaultValueAvailable()) { - $params[$name] = $p->getDefaultValue(); - } - else { - throw new InvalidArgumentException('Missing argument: '.$name); - } - } - - return $params; + return isset($this->serverVariable[$variable]) ? $this->serverVariable[$variable] : null; } } diff --git a/vendor/fguillot/json-rpc/src/JsonRPC/Validator/HostValidator.php b/vendor/fguillot/json-rpc/src/JsonRPC/Validator/HostValidator.php new file mode 100644 index 0000000..7f8c0a0 --- /dev/null +++ b/vendor/fguillot/json-rpc/src/JsonRPC/Validator/HostValidator.php @@ -0,0 +1,30 @@ +