Vendoring deprecated composer libs

This commit is contained in:
Frédéric Guillot
2018-06-21 14:13:41 -07:00
parent c73ac5f1f8
commit a491348d44
515 changed files with 5376 additions and 693 deletions

21
libs/jsonrpc/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Frederic Guillot
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,412 @@
JsonRPC PHP Client and Server
=============================
A simple Json-RPC client/server that just works.
Features
--------
- JSON-RPC 2.0 only
- The server support batch requests and notifications
- Authentication and IP based client restrictions
- Custom Middleware
- Fully unit tested
- Requirements: PHP >= 5.3.4
- License: MIT
Author
------
Frédéric Guillot
Installation with Composer
--------------------------
```bash
composer require fguillot/json-rpc @stable
```
Examples
--------
### Server
Callback binding:
```php
<?php
use JsonRPC\Server;
$server = new Server();
$server->getProcedureHandler()
->withCallback('addition', function ($a, $b) {
return $a + $b;
})
->withCallback('random', function ($start, $end) {
return mt_rand($start, $end);
})
;
echo $server->execute();
```
Callback binding from array:
```php
<?php
use JsonRPC\Server;
$callbacks = array(
'getA' => function() { return 'A'; },
'getB' => function() { return 'B'; },
'getC' => function() { return 'C'; }
);
$server = new Server();
$server->getProcedureHandler()->withCallbackArray($callbacks);
echo $server->execute();
```
Class/Method binding:
```php
<?php
use JsonRPC\Server;
class Api
{
public function doSomething($arg1, $arg2 = 3)
{
return $arg1 + $arg2;
}
}
$server = new Server();
$procedureHandler = $server->getProcedureHandler();
// Bind the method Api::doSomething() to the procedure myProcedure
$procedureHandler->withClassAndMethod('myProcedure', 'Api', 'doSomething');
// Use a class instance instead of the class name
$procedureHandler->withClassAndMethod('mySecondProcedure', new Api, 'doSomething');
// The procedure and the method are the same
$procedureHandler->withClassAndMethod('doSomething', 'Api');
// Attach the class, the client will be able to call directly Api::doSomething()
$procedureHandler->withObject(new Api());
echo $server->execute();
```
Class/Method binding from array:
```php
<?php
use JsonRPC\Server;
class MathApi
{
public function addition($arg1, $arg2)
{
return $arg1 + $arg2;
}
public function subtraction($arg1, $arg2)
{
return $arg1 - $arg2;
}
public function multiplication($arg1, $arg2)
{
return $arg1 * $arg2;
}
public function division($arg1, $arg2)
{
return $arg1 / $arg2;
}
}
$callbacks = array(
'addition' => array( 'MathApi', addition ),
'subtraction' => array( 'MathApi', subtraction ),
'multiplication' => array( 'MathApi', multiplication ),
'division' => array( 'MathApi', division )
);
$server = new Server();
$server->getProcedureHandler()->withClassAndMethodArray($callbacks);
echo $server->execute();
```
Server Middleware:
Middleware might be used to authenticate and authorize the client.
They are executed before each procedure.
```php
<?php
use JsonRPC\Server;
use JsonRPC\MiddlewareInterface;
use JsonRPC\Exception\AuthenticationFailureException;
class Api
{
public function doSomething($arg1, $arg2 = 3)
{
return $arg1 + $arg2;
}
}
class MyMiddleware implements MiddlewareInterface
{
public function execute($username, $password, $procedureName)
{
if ($username !== 'foobar') {
throw new AuthenticationFailureException('Wrong credentials!');
}
}
}
$server = new Server();
$server->getMiddlewareHandler()->withMiddleware(new MyMiddleware());
$server->getProcedureHandler()->withObject(new Api());
echo $server->execute();
```
You can raise a `AuthenticationFailureException` when the API credentials are wrong or a `AccessDeniedException` when the user is not allowed to access to the procedure.
### Client
Example with positional parameters:
```php
<?php
use JsonRPC\Client;
$client = new Client('http://localhost/server.php');
$result = $client->execute('addition', [3, 5]);
```
Example with named arguments:
```php
<?php
use JsonRPC\Client;
$client = new Client('http://localhost/server.php');
$result = $client->execute('random', ['end' => 10, 'start' => 1]);
```
Arguments are called in the right order.
Examples with the magic method `__call()`:
```php
<?php
use JsonRPC\Client;
$client = new Client('http://localhost/server.php');
$result = $client->random(50, 100);
```
The example above use positional arguments for the request and this one use named arguments:
```php
$result = $client->random(['end' => 10, 'start' => 1]);
```
### Client batch requests
Call several procedures in a single HTTP request:
```php
<?php
use JsonRPC\Client;
$client = new Client('http://localhost/server.php');
$results = $client->batch()
->foo(['arg1' => 'bar'])
->random(1, 100)
->add(4, 3)
->execute('add', [2, 5])
->send();
print_r($results);
```
All results are stored at the same position of the call.
### Client exceptions
Client exceptions are normally thrown when an error is returned by the server. You can change this behaviour by
using the `$returnException` argument which causes exceptions to be returned. This can be extremely useful when
executing the batch request.
- `BadFunctionCallException`: Procedure not found on the server
- `InvalidArgumentException`: Wrong procedure arguments
- `JsonRPC\Exception\AccessDeniedException`: Access denied
- `JsonRPC\Exception\ConnectionFailureException`: Connection failure
- `JsonRPC\Exception\ServerErrorException`: Internal server error
### Enable client debugging
You can enable the debug mode to see the JSON request and response:
```php
<?php
use JsonRPC\Client;
$client = new Client('http://localhost/server.php');
$client->getHttpClient()->withDebug();
```
The debug output is sent to the PHP system logger.
You can configure the log destination in your `php.ini`.
Output example:
```json
==> Request:
{
"jsonrpc": "2.0",
"method": "removeCategory",
"id": 486782327,
"params": [
1
]
}
==> Response:
{
"jsonrpc": "2.0",
"id": 486782327,
"result": true
}
```
### IP based client restrictions
The server can allow only some IP addresses:
```php
<?php
use JsonRPC\Server;
$server = new Server;
// IP client restrictions
$server->allowHosts(['192.168.0.1', '127.0.0.1']);
[...]
// Return the response to the client
echo $server->execute();
```
If the client is blocked, you got a 403 Forbidden HTTP response.
### HTTP Basic Authentication
If you use HTTPS, you can allow client by using a username/password.
```php
<?php
use JsonRPC\Server;
$server = new Server;
// List of users to allow
$server->authentication(['user1' => 'password1', 'user2' => 'password2']);
[...]
// Return the response to the client
echo $server->execute();
```
On the client, set credentials like that:
```php
<?php
use JsonRPC\Client;
$client = new Client('http://localhost/server.php');
$client->getHttpClient()
->withUsername('Foo')
->withPassword('Bar');
```
If the authentication failed, the client throw a RuntimeException.
Using an alternative authentication header:
```php
use JsonRPC\Server;
$server = new Server();
$server->setAuthenticationHeader('X-Authentication');
$server->authentication(['myusername' => 'mypassword']);
```
The example above will use the HTTP header `X-Authentication` instead of the standard `Authorization: Basic [BASE64_CREDENTIALS]`.
The username/password values need be encoded in base64: `base64_encode('username:password')`.
### Local Exceptions
By default, the server will relay all exceptions to the client.
If you would like to relay only some of them, use the method `Server::withLocalException($exception)`:
```php
<?php
use JsonRPC\Server;
class MyException1 extends Exception {};
class MyException2 extends Exception {};
$server = new Server();
// Exceptions that should NOT be relayed to the client, if they occurs
$server
->withLocalException('MyException1')
->withLocalException('MyException2')
;
[...]
echo $server->execute();
```
### Callback before client request
You can use a callback to change the HTTP headers or the URL before to make the request to the server.
Example:
```php
<?php
$client = new Client();
$client->getHttpClient()->withBeforeRequestCallback(function(HttpClient $client, $payload) {
$client->withHeaders(array('Content-Length: '.strlen($payload)));
});
$client->myProcedure(123);
```

View File

@@ -0,0 +1,198 @@
<?php
namespace JsonRPC;
use Exception;
use JsonRPC\Request\RequestBuilder;
use JsonRPC\Response\ResponseParser;
/**
* JsonRPC client class
*
* @package JsonRPC
* @author Frederic Guillot
*/
class Client
{
/**
* If the only argument passed to a function is an array
* assume it contains named arguments
*
* @access private
* @var boolean
*/
private $isNamedArguments = true;
/**
* Do not immediately throw an exception on error. Return it instead.
*
* @access public
* @var boolean
*/
private $returnException = false;
/**
* True for a batch request
*
* @access private
* @var boolean
*/
private $isBatch = false;
/**
* Batch payload
*
* @access private
* @var array
*/
private $batch = array();
/**
* Http Client
*
* @access private
* @var HttpClient
*/
private $httpClient;
/**
* Constructor
*
* @access public
* @param string $url Server URL
* @param bool $returnException Return exceptions
* @param HttpClient $httpClient HTTP client object
*/
public function __construct($url = '', $returnException = false, HttpClient $httpClient = null)
{
$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;
}
/**
* Automatic mapping of procedures
*
* @access public
* @param string $method Procedure name
* @param array $params Procedure arguments
* @return mixed
*/
public function __call($method, array $params)
{
if ($this->isNamedArguments && count($params) === 1 && is_array($params[0])) {
$params = $params[0];
}
return $this->execute($method, $params);
}
/**
* Start a batch request
*
* @access public
* @return Client
*/
public function batch()
{
$this->isBatch = true;
$this->batch = array();
return $this;
}
/**
* Send a batch request
*
* @access public
* @return array
*/
public function send()
{
$this->isBatch = false;
return $this->sendPayload('['.implode(', ', $this->batch).']');
}
/**
* Execute a procedure
*
* @access public
* @param string $procedure Procedure name
* @param array $params Procedure arguments
* @param array $reqattrs
* @param string|null $requestId Request Id
* @param string[] $headers Headers for this request
* @return mixed
*/
public function execute($procedure, array $params = array(), array $reqattrs = array(), $requestId = null, array $headers = array())
{
$payload = RequestBuilder::create()
->withProcedure($procedure)
->withParams($params)
->withRequestAttributes($reqattrs)
->withId($requestId)
->build();
if ($this->isBatch) {
$this->batch[] = $payload;
return $this;
}
return $this->sendPayload($payload, $headers);
}
/**
* Send payload
*
* @access private
* @throws Exception
* @param string $payload
* @param string[] $headers
* @return Exception|Client
*/
private function sendPayload($payload, array $headers = array())
{
return ResponseParser::create()
->withReturnException($this->returnException)
->withPayload($this->httpClient->execute($payload, $headers))
->parse();
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace JsonRPC\Exception;
/**
* Class AccessDeniedException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class AccessDeniedException extends RpcCallFailedException
{
}

View File

@@ -0,0 +1,13 @@
<?php
namespace JsonRPC\Exception;
/**
* Class AuthenticationFailureException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class AuthenticationFailureException extends RpcCallFailedException
{
}

View File

@@ -0,0 +1,13 @@
<?php
namespace JsonRPC\Exception;
/**
* Class ConnectionFailureException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class ConnectionFailureException extends RpcCallFailedException
{
}

View File

@@ -0,0 +1,13 @@
<?php
namespace JsonRPC\Exception;
/**
* Class InvalidJsonFormatException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class InvalidJsonFormatException extends RpcCallFailedException
{
}

View File

@@ -0,0 +1,13 @@
<?php
namespace JsonRPC\Exception;
/**
* Class InvalidJsonRpcFormatException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class InvalidJsonRpcFormatException extends RpcCallFailedException
{
}

View File

@@ -0,0 +1,13 @@
<?php
namespace JsonRPC\Exception;
/**
* Class ResponseEncodingFailureException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class ResponseEncodingFailureException extends RpcCallFailedException
{
}

View File

@@ -0,0 +1,62 @@
<?php
namespace JsonRPC\Exception;
use Exception;
/**
* Class ResponseException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class ResponseException extends RpcCallFailedException
{
/**
* A value that contains additional information about the error.
*
* @access protected
* @link http://www.jsonrpc.org/specification#error_object
* @var mixed
*/
protected $data;
/**
* Constructor
*
* @access public
* @param string $message [optional] The Exception message to throw.
* @param int $code [optional] The Exception code.
* @param Exception $previous [optional] The previous exception used for the exception chaining. Since 5.3.0
* @param mixed $data [optional] A value that contains additional information about the error.
*/
public function __construct($message = '', $code = 0, Exception $previous = null, $data = null)
{
parent::__construct($message, $code, $previous);
$this->setData($data);
}
/**
* Attach additional information
*
* @access public
* @param mixed $data [optional] A value that contains additional information about the error.
* @return \JsonRPC\Exception\ResponseException
*/
public function setData($data = null)
{
$this->data = $data;
return $this;
}
/**
* Get additional information
*
* @access public
* @return mixed|null
*/
public function getData()
{
return $this->data;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace JsonRPC\Exception;
use Exception;
/**
* Class RpcCallFailedException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class RpcCallFailedException extends Exception
{
}

View File

@@ -0,0 +1,13 @@
<?php
namespace JsonRPC\Exception;
/**
* Class ServerErrorException
*
* @package JsonRPC\Exception
* @author Frederic Guillot
*/
class ServerErrorException extends RpcCallFailedException
{
}

View File

@@ -0,0 +1,449 @@
<?php
namespace JsonRPC;
use Closure;
use JsonRPC\Exception\AccessDeniedException;
use JsonRPC\Exception\ConnectionFailureException;
use JsonRPC\Exception\ServerErrorException;
/**
* Class HttpClient
*
* @package JsonRPC
* @author Frederic Guillot
*/
class HttpClient
{
/**
* URL of the server
*
* @access protected
* @var string
*/
protected $url;
/**
* HTTP client timeout
*
* @access protected
* @var integer
*/
protected $timeout = 5;
/**
* Default HTTP headers to send to the server
*
* @access protected
* @var array
*/
protected $headers = array(
'User-Agent: JSON-RPC PHP Client <https://github.com/fguillot/JsonRPC>',
'Content-Type: application/json',
'Accept: application/json',
'Connection: close',
);
/**
* Username for authentication
*
* @access protected
* @var string
*/
protected $username;
/**
* Password for authentication
*
* @access protected
* @var string
*/
protected $password;
/**
* Enable debug output to the php error log
*
* @access protected
* @var boolean
*/
protected $debug = false;
/**
* Cookies
*
* @access protected
* @var array
*/
protected $cookies = array();
/**
* SSL certificates verification
*
* @access protected
* @var boolean
*/
protected $verifySslCertificate = true;
/**
* SSL client certificate
*
* @access protected
* @var string
*/
protected $sslLocalCert;
/**
* Callback called before the doing the request
*
* @access protected
* @var Closure
*/
protected $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 headers
*
* @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 certificate to use TLS
*
* @access public
* @return $this
*/
public function withSslLocalCert($path)
{
$this->sslLocalCert = $path;
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
* @param string[] $headers Headers for this request
* @return array
*/
public function execute($payload, array $headers = array())
{
if (is_callable($this->beforeRequest)) {
call_user_func_array($this->beforeRequest, array($this, $payload, $headers));
}
if ($this->isCurlLoaded()) {
$ch = curl_init();
$requestHeaders = $this->buildHeaders($headers);
$headers = array();
curl_setopt_array($ch, array(
CURLOPT_URL => trim($this->url),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => $this->timeout,
CURLOPT_MAXREDIRS => 2,
CURLOPT_SSL_VERIFYPEER => $this->verifySslCertificate,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => $requestHeaders,
CURLOPT_HEADERFUNCTION => function ($curl, $header) use (&$headers) {
$headers[] = $header;
return strlen($header);
}
));
if ($this->sslLocalCert !== null) {
curl_setopt($ch, CURLOPT_CAINFO, $this->sslLocalCert);
}
$response = curl_exec($ch);
curl_close($ch);
if ($response !== false) {
$response = json_decode($response, true);
} else {
throw new ConnectionFailureException('Unable to establish a connection');
}
} else {
$stream = fopen(trim($this->url), 'r', false, $this->buildContext($payload, $headers));
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);
fclose($stream);
}
if ($this->debug) {
error_log('==> Request: '.PHP_EOL.(is_string($payload) ? $payload : 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 protected
* @param string $payload
* @param string[] $headers
* @return resource
*/
protected function buildContext($payload, array $headers = array())
{
$headers = $this->buildHeaders($headers);
$options = 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,
)
);
if ($this->sslLocalCert !== null) {
$options['ssl']['local_cert'] = $this->sslLocalCert;
}
return stream_context_create($options);
}
/**
* Parse cookies from response
*
* @access protected
* @param array $headers
*/
protected 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);
}
}
}
}
/**
* Tests if the curl extension is loaded
*
* @access protected
* @return bool
*/
protected function isCurlLoaded()
{
return extension_loaded('curl');
}
/**
* Prepare Headers
*
* @access protected
* @param array $headers
* @return array
*/
protected function buildHeaders(array $headers)
{
$headers = array_merge($this->headers, $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 $headers;
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace JsonRPC;
/**
* Class MiddlewareHandler
*
* @package JsonRPC
* @author Frederic Guillot
*/
class MiddlewareHandler
{
/**
* Procedure Name
*
* @access protected
* @var string
*/
protected $procedureName = '';
/**
* Username
*
* @access protected
* @var string
*/
protected $username = '';
/**
* Password
*
* @access protected
* @var string
*/
protected $password = '';
/**
* List of middleware to execute before to call the method
*
* @access protected
* @var MiddlewareInterface[]
*/
protected $middleware = array();
/**
* Set username
*
* @access public
* @param string $username
* @return $this
*/
public function withUsername($username)
{
if (! empty($username)) {
$this->username = $username;
}
return $this;
}
/**
* Set password
*
* @access public
* @param string $password
* @return $this
*/
public function withPassword($password)
{
if (! empty($password)) {
$this->password = $password;
}
return $this;
}
/**
* Set procedure name
*
* @access public
* @param string $procedureName
* @return $this
*/
public function withProcedure($procedureName)
{
$this->procedureName = $procedureName;
return $this;
}
/**
* Add a new middleware
*
* @access public
* @param MiddlewareInterface $middleware
* @return MiddlewareHandler
*/
public function withMiddleware(MiddlewareInterface $middleware)
{
$this->middleware[] = $middleware;
return $this;
}
/**
* Execute all middleware
*
* @access public
*/
public function execute()
{
foreach ($this->middleware as $middleware) {
$middleware->execute($this->username, $this->password, $this->procedureName);
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace JsonRPC;
use JsonRPC\Exception\AccessDeniedException;
use JsonRPC\Exception\AuthenticationFailureException;
/**
* Interface MiddlewareInterface
*
* @package JsonRPC
* @author Frederic Guillot
*/
interface MiddlewareInterface
{
/**
* Execute Middleware
*
* @access public
* @param string $username
* @param string $password
* @param string $procedureName
* @throws AccessDeniedException
* @throws AuthenticationFailureException
*/
public function execute($username, $password, $procedureName);
}

View File

@@ -0,0 +1,296 @@
<?php
namespace JsonRPC;
use BadFunctionCallException;
use Closure;
use InvalidArgumentException;
use ReflectionFunction;
use ReflectionMethod;
/**
* Class ProcedureHandler
*
* @package JsonRPC
* @author Frederic Guillot
*/
class ProcedureHandler
{
/**
* List of procedures
*
* @access protected
* @var array
*/
protected $callbacks = array();
/**
* List of classes
*
* @access protected
* @var array
*/
protected $classes = array();
/**
* List of instances
*
* @access protected
* @var array
*/
protected $instances = array();
/**
* Before method name to call
*
* @access protected
* @var string
*/
protected $beforeMethodName = '';
/**
* Register a new procedure
*
* @access public
* @param string $procedure Procedure name
* @param closure $callback Callback
* @return $this
*/
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 $this
*/
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 $this
*/
public function withObject($instance)
{
$this->instances[] = $instance;
return $this;
}
/**
* Set a before method to call
*
* @access public
* @param string $methodName
* @return $this
*/
public function withBeforeMethod($methodName)
{
$this->beforeMethodName = $methodName;
return $this;
}
/**
* Register multiple procedures from array
*
* @access public
* @param array $callbacks Array with procedure names (array keys) and callbacks (array values)
* @return $this
*/
public function withCallbackArray($callbacks)
{
foreach ($callbacks as $procedure => $callback) {
$this->withCallback($procedure, $callback);
}
return $this;
}
/**
* Bind multiple procedures to classes from array
*
* @access public
* @param array $callbacks Array with procedure names (array keys) and class and method names (array values)
* @return $this
*/
public function withClassAndMethodArray($callbacks)
{
foreach ($callbacks as $procedure => $callback) {
$this->withClassAndMethod($procedure, $callback[0], $callback[1]);
}
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);
} elseif (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;
$reflection = new ReflectionMethod($class, $method);
$this->executeBeforeMethod($instance, $method);
$arguments = $this->getArguments(
$params,
$reflection->getParameters(),
$reflection->getNumberOfRequiredParameters(),
$reflection->getNumberOfParameters()
);
return $reflection->invokeArgs($instance, $arguments);
}
/**
* Execute before method if defined
*
* @access public
* @param mixed $object
* @param string $method
*/
public function executeBeforeMethod($object, $method)
{
if ($this->beforeMethodName !== '' && method_exists($object, $this->beforeMethodName)) {
call_user_func_array(array($object, $this->beforeMethodName), array($method));
}
}
/**
* Get procedure arguments
*
* @access public
* @param array $requestParams Incoming arguments
* @param array $methodParams Procedure arguments
* @param integer $nbRequiredParams Number of required parameters
* @param integer $nbMaxParams Maximum number of parameters
* @return array
*/
public function getArguments(array $requestParams, array $methodParams, $nbRequiredParams, $nbMaxParams)
{
$nbParams = count($requestParams);
if ($nbParams < $nbRequiredParams) {
throw new InvalidArgumentException('Wrong number of arguments');
}
if ($nbParams > $nbMaxParams) {
throw new InvalidArgumentException('Too many arguments');
}
if ($this->isPositionalArguments($requestParams)) {
return $requestParams;
}
return $this->getNamedArguments($requestParams, $methodParams);
}
/**
* 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 $requestParams Incoming arguments
* @param array $methodParams Procedure arguments
* @return array
*/
public function getNamedArguments(array $requestParams, array $methodParams)
{
$params = array();
foreach ($methodParams as $p) {
$name = $p->getName();
if (array_key_exists($name, $requestParams)) {
$params[$name] = $requestParams[$name];
} elseif ($p->isDefaultValueAvailable()) {
$params[$name] = $p->getDefaultValue();
} else {
throw new InvalidArgumentException('Missing argument: '.$name);
}
}
return $params;
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace JsonRPC\Request;
/**
* Class BatchRequestParser
*
* @package JsonRPC\Request
* @author Frederic Guillot
*/
class BatchRequestParser extends RequestParser
{
/**
* Parse incoming request
*
* @access public
* @return string
*/
public function parse()
{
$responses = array();
foreach ($this->payload as $payload) {
$responses[] = RequestParser::create()
->withPayload($payload)
->withProcedureHandler($this->procedureHandler)
->withMiddlewareHandler($this->middlewareHandler)
->withLocalException($this->localExceptions)
->parse();
}
$responses = array_filter($responses);
return empty($responses) ? '' : '['.implode(',', $responses).']';
}
/**
* Return true if we have a batch request
*
* ex : [
* 0 => '...',
* 1 => '...',
* 2 => '...',
* 3 => '...',
* ]
*
* @static
* @access public
* @param array $payload
* @return bool
*/
public static function isBatchRequest(array $payload)
{
return array_keys($payload) === range(0, count($payload) - 1);
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace JsonRPC\Request;
/**
* Class RequestBuilder
*
* @package JsonRPC\Request
* @author Frederic Guillot
*/
class RequestBuilder
{
/**
* Request ID
*
* @access private
* @var mixed
*/
private $id = null;
/**
* Method name
*
* @access private
* @var string
*/
private $procedure = '';
/**
* Method arguments
*
* @access private
* @var array
*/
private $params = array();
/**
* Additional request attributes
*
* @access private
* @var array
*/
private $reqattrs = array();
/**
* Get new object instance
*
* @static
* @access public
* @return RequestBuilder
*/
public static function create()
{
return new static();
}
/**
* Set id
*
* @access public
* @param null $id
* @return RequestBuilder
*/
public function withId($id)
{
$this->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;
}
/**
* Set additional request attributes
*
* @access public
* @param array $reqattrs
* @return RequestBuilder
*/
public function withRequestAttributes(array $reqattrs)
{
$this->reqattrs = $reqattrs;
return $this;
}
/**
* Build the payload
*
* @access public
* @return string
*/
public function build()
{
$payload = array_merge_recursive($this->reqattrs, array(
'jsonrpc' => '2.0',
'method' => $this->procedure,
'id' => $this->id ?: mt_rand(),
));
if (! empty($this->params)) {
$payload['params'] = $this->params;
}
return json_encode($payload);
}
}

View File

@@ -0,0 +1,200 @@
<?php
namespace JsonRPC\Request;
use Exception;
use JsonRPC\Exception\AccessDeniedException;
use JsonRPC\Exception\AuthenticationFailureException;
use JsonRPC\Exception\InvalidJsonRpcFormatException;
use JsonRPC\MiddlewareHandler;
use JsonRPC\ProcedureHandler;
use JsonRPC\Response\ResponseBuilder;
use JsonRPC\Validator\JsonFormatValidator;
use JsonRPC\Validator\RpcFormatValidator;
/**
* Class RequestParser
*
* @package JsonRPC
* @author Frederic Guillot
*/
class RequestParser
{
/**
* Request payload
*
* @access protected
* @var mixed
*/
protected $payload;
/**
* List of exceptions that should not be relayed to the client
*
* @access protected
* @var array()
*/
protected $localExceptions = array(
'JsonRPC\Exception\AuthenticationFailureException',
'JsonRPC\Exception\AccessDeniedException',
);
/**
* ProcedureHandler
*
* @access protected
* @var ProcedureHandler
*/
protected $procedureHandler;
/**
* MiddlewareHandler
*
* @access protected
* @var MiddlewareHandler
*/
protected $middlewareHandler;
/**
* Get new object instance
*
* @static
* @access public
* @return RequestParser
*/
public static function create()
{
return new static();
}
/**
* Set payload
*
* @access public
* @param mixed $payload
* @return $this
*/
public function withPayload($payload)
{
$this->payload = $payload;
return $this;
}
/**
* Exception classes that should not be relayed to the client
*
* @access public
* @param mixed $exception
* @return $this
*/
public function withLocalException($exception)
{
if (is_array($exception)) {
$this->localExceptions = array_merge($this->localExceptions, $exception);
} else {
$this->localExceptions[] = $exception;
}
return $this;
}
/**
* Set procedure handler
*
* @access public
* @param ProcedureHandler $procedureHandler
* @return $this
*/
public function withProcedureHandler(ProcedureHandler $procedureHandler)
{
$this->procedureHandler = $procedureHandler;
return $this;
}
/**
* Set middleware handler
*
* @access public
* @param MiddlewareHandler $middlewareHandler
* @return $this
*/
public function withMiddlewareHandler(MiddlewareHandler $middlewareHandler)
{
$this->middlewareHandler = $middlewareHandler;
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);
$this->middlewareHandler
->withProcedure($this->payload['method'])
->execute();
$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) {
return $this->handleExceptions($e);
}
return '';
}
/**
* Handle exceptions
*
* @access protected
* @param Exception $e
* @return string
* @throws Exception
*/
protected function handleExceptions(Exception $e)
{
foreach ($this->localExceptions as $exception) {
if ($e instanceof $exception) {
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 protected
* @return bool
*/
protected function isNotification()
{
return is_array($this->payload) && !isset($this->payload['id']);
}
}

View File

@@ -0,0 +1,336 @@
<?php
namespace JsonRPC\Response;
use BadFunctionCallException;
use Exception;
use InvalidArgumentException;
use JsonRPC\Exception\AccessDeniedException;
use JsonRPC\Exception\AuthenticationFailureException;
use JsonRPC\Exception\InvalidJsonFormatException;
use JsonRPC\Exception\InvalidJsonRpcFormatException;
use JsonRPC\Exception\ResponseEncodingFailureException;
use JsonRPC\Exception\ResponseException;
use JsonRPC\Validator\JsonEncodingValidator;
/**
* Class ResponseBuilder
*
* @package JsonRPC
* @author Frederic Guillot
*/
class ResponseBuilder
{
/**
* Payload ID
*
* @access protected
* @var mixed
*/
protected $id;
/**
* Payload ID
*
* @access protected
* @var mixed
*/
protected $result;
/**
* Payload error code
*
* @access protected
* @var integer
*/
protected $errorCode;
/**
* Payload error message
*
* @access private
* @var string
*/
protected $errorMessage;
/**
* Payload error data
*
* @access protected
* @var mixed
*/
protected $errorData;
/**
* HTTP Headers
*
* @access protected
* @var array
*/
protected $headers = array(
'Content-Type' => 'application/json',
);
/**
* HTTP status
*
* @access protected
* @var string
*/
protected $status;
/**
* Exception
*
* @access protected
* @var ResponseException
*/
protected $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()
{
$options = 0;
if (defined('JSON_UNESCAPED_SLASHES')) {
$options |= JSON_UNESCAPED_SLASHES;
}
if (defined('JSON_UNESCAPED_UNICODE')) {
$options |= JSON_UNESCAPED_UNICODE;
}
$encodedResponse = json_encode($this->buildResponse(), $options);
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 protected
* @return array
*/
protected 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 protected
* @return array
*/
protected 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 protected
*/
protected function handleExceptions()
{
try {
if ($this->exception instanceof Exception) {
throw $this->exception;
}
} catch (InvalidJsonFormatException $e) {
$this->errorCode = -32700;
$this->errorMessage = 'Parse error';
$this->id = null;
} catch (InvalidJsonRpcFormatException $e) {
$this->errorCode = -32600;
$this->errorMessage = 'Invalid Request';
$this->id = null;
} catch (BadFunctionCallException $e) {
$this->errorCode = -32601;
$this->errorMessage = 'Method not found';
} catch (InvalidArgumentException $e) {
$this->errorCode = -32602;
$this->errorMessage = 'Invalid params';
$this->errorData = $this->exception->getMessage();
} catch (ResponseEncodingFailureException $e) {
$this->errorCode = -32603;
$this->errorMessage = 'Internal error';
$this->errorData = $this->exception->getMessage();
} catch (AuthenticationFailureException $e) {
$this->errorCode = 401;
$this->errorMessage = 'Unauthorized';
$this->status = 'HTTP/1.0 401 Unauthorized';
$this->withHeader('WWW-Authenticate', 'Basic realm="JsonRPC"');
} catch (AccessDeniedException $e) {
$this->errorCode = 403;
$this->errorMessage = 'Forbidden';
$this->status = 'HTTP/1.0 403 Forbidden';
} catch (ResponseException $e) {
$this->errorCode = $this->exception->getCode();
$this->errorMessage = $this->exception->getMessage();
$this->errorData = $this->exception->getData();
} catch (Exception $e) {
$this->errorCode = $this->exception->getCode();
$this->errorMessage = $this->exception->getMessage();
}
}
}

View File

@@ -0,0 +1,154 @@
<?php
namespace JsonRPC\Response;
use BadFunctionCallException;
use InvalidArgumentException;
use Exception;
use JsonRPC\Exception\InvalidJsonFormatException;
use JsonRPC\Exception\InvalidJsonRpcFormatException;
use JsonRPC\Exception\ResponseException;
use JsonRPC\Validator\JsonFormatValidator;
/**
* Class ResponseParser
*
* @package JsonRPC\Request
* @author Frederic Guillot
*/
class ResponseParser
{
/**
* Payload
*
* @access private
* @var mixed
*/
private $payload;
/**
* Do not immediately throw an exception on error. Return it instead.
*
* @var bool
*/
private $returnException = false;
/**
* Get new object instance
*
* @static
* @access public
* @return ResponseParser
*/
public static function create()
{
return new static();
}
/**
* Set Return Exception Or Throw It
*
* @param $returnException
* @return ResponseParser
*/
public function withReturnException($returnException)
{
$this->returnException = $returnException;
return $this;
}
/**
* Set payload
*
* @access public
* @param mixed $payload
* @return $this
*/
public function withPayload($payload)
{
$this->payload = $payload;
return $this;
}
/**
* Parse response
*
* @return array|Exception|null
* @throws InvalidJsonFormatException
* @throws BadFunctionCallException
* @throws InvalidJsonRpcFormatException
* @throws InvalidArgumentException
* @throws Exception
* @throws ResponseException
*/
public function parse()
{
JsonFormatValidator::validate($this->payload);
if ($this->isBatchResponse()) {
$results = array();
foreach ($this->payload as $response) {
$results[] = self::create()
->withReturnException($this->returnException)
->withPayload($response)
->parse();
}
return $results;
}
if (isset($this->payload['error']['code'])) {
try {
$this->handleExceptions();
} catch (Exception $e) {
if ($this->returnException) {
return $e;
}
throw $e;
}
}
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);
}
}

View File

@@ -0,0 +1,386 @@
<?php
namespace JsonRPC;
use Closure;
use 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
*
* @package JsonRPC
* @author Frederic Guillot
*/
class Server
{
/**
* Allowed hosts
*
* @access protected
* @var array
*/
protected $hosts = array();
/**
* Data received from the client
*
* @access protected
* @var array
*/
protected $payload = array();
/**
* List of exceptions that should not be relayed to the client
*
* @access protected
* @var array()
*/
protected $localExceptions = array();
/**
* Username
*
* @access protected
* @var string
*/
protected $username = '';
/**
* Password
*
* @access protected
* @var string
*/
protected $password = '';
/**
* Allowed users
*
* @access protected
* @var array
*/
protected $users = array();
/**
* $_SERVER
*
* @access protected
* @var array
*/
protected $serverVariable;
/**
* ProcedureHandler object
*
* @access protected
* @var ProcedureHandler
*/
protected $procedureHandler;
/**
* MiddlewareHandler object
*
* @access protected
* @var MiddlewareHandler
*/
protected $middlewareHandler;
/**
* Response builder
*
* @access protected
* @var ResponseBuilder
*/
protected $responseBuilder;
/**
* Response builder
*
* @access protected
* @var RequestParser
*/
protected $requestParser;
/**
*
* Batch request parser
*
* @access protected
* @var BatchRequestParser
*/
protected $batchRequestParser;
/**
* Constructor
*
* @access public
* @param string $request
* @param array $server
* @param ResponseBuilder $responseBuilder
* @param RequestParser $requestParser
* @param BatchRequestParser $batchRequestParser
* @param ProcedureHandler $procedureHandler
* @param MiddlewareHandler $middlewareHandler
*/
public function __construct(
$request = '',
array $server = array(),
ResponseBuilder $responseBuilder = null,
RequestParser $requestParser = null,
BatchRequestParser $batchRequestParser = null,
ProcedureHandler $procedureHandler = null,
MiddlewareHandler $middlewareHandler = null
) {
if ($request !== '') {
$this->payload = json_decode($request, true);
} else {
$this->payload = json_decode(file_get_contents('php://input'), true);
}
$this->serverVariable = $server ?: $_SERVER;
$this->responseBuilder = $responseBuilder ?: ResponseBuilder::create();
$this->requestParser = $requestParser ?: RequestParser::create();
$this->batchRequestParser = $batchRequestParser ?: BatchRequestParser::create();
$this->procedureHandler = $procedureHandler ?: new ProcedureHandler();
$this->middlewareHandler = $middlewareHandler ?: new MiddlewareHandler();
}
/**
* Define alternative authentication header
*
* @access public
* @param string $header Header name
* @return $this
*/
public function setAuthenticationHeader($header)
{
if (! empty($header)) {
$header = 'HTTP_'.str_replace('-', '_', strtoupper($header));
$value = $this->getServerVariable($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 MiddlewareHandler
*
* @access public
* @return MiddlewareHandler
*/
public function getMiddlewareHandler()
{
return $this->middlewareHandler;
}
/**
* Get username
*
* @access public
* @return string
*/
public function getUsername()
{
return $this->username ?: $this->getServerVariable('PHP_AUTH_USER');
}
/**
* Get password
*
* @access public
* @return string
*/
public function getPassword()
{
return $this->password ?: $this->getServerVariable('PHP_AUTH_PW');
}
/**
* IP based client restrictions
*
* @access public
* @param array $hosts List of hosts
* @return $this
*/
public function allowHosts(array $hosts)
{
$this->hosts = $hosts;
return $this;
}
/**
* HTTP Basic authentication
*
* @access public
* @param array $users Dictionary of username/password
* @return $this
*/
public function authentication(array $users)
{
$this->users = $users;
return $this;
}
/**
* Register a new procedure
*
* @access public
* @deprecated Use $server->getProcedureHandler()->withCallback($procedure, $callback)
* @param string $procedure Procedure name
* @param closure $callback Callback
* @return $this
*/
public function register($procedure, Closure $callback)
{
$this->procedureHandler->withCallback($procedure, $callback);
return $this;
}
/**
* Bind a procedure to a class
*
* @access public
* @deprecated Use $server->getProcedureHandler()->withClassAndMethod($procedure, $class, $method);
* @param string $procedure Procedure name
* @param mixed $class Class name or instance
* @param string $method Procedure name
* @return $this
*/
public function bind($procedure, $class, $method = '')
{
$this->procedureHandler->withClassAndMethod($procedure, $class, $method);
return $this;
}
/**
* Bind a class instance
*
* @access public
* @deprecated Use $server->getProcedureHandler()->withObject($instance);
* @param mixed $instance Instance name
* @return $this
*/
public function attach($instance)
{
$this->procedureHandler->withObject($instance);
return $this;
}
/**
* Exception classes that should not be relayed to the client
*
* @access public
* @param Exception|string $exception
* @return $this
*/
public function withLocalException($exception)
{
$this->localExceptions[] = $exception;
return $this;
}
/**
* Parse incoming requests
*
* @access public
* @return string
*/
public function execute()
{
try {
JsonFormatValidator::validate($this->payload);
HostValidator::validate($this->hosts, $this->getServerVariable('REMOTE_ADDR'));
UserValidator::validate($this->users, $this->getUsername(), $this->getPassword());
$this->middlewareHandler
->withUsername($this->getUsername())
->withPassword($this->getPassword())
;
$response = $this->parseRequest();
} catch (Exception $e) {
$response = $this->handleExceptions($e);
}
$this->responseBuilder->sendHeaders();
return $response;
}
/**
* Handle exceptions
*
* @access protected
* @param Exception $e
* @return string
* @throws Exception
*/
protected function handleExceptions(Exception $e)
{
foreach ($this->localExceptions as $exception) {
if ($e instanceof $exception) {
throw $e;
}
}
return $this->responseBuilder->withException($e)->build();
}
/**
* Parse incoming request
*
* @access protected
* @return string
*/
protected function parseRequest()
{
if (BatchRequestParser::isBatchRequest($this->payload)) {
return $this->batchRequestParser
->withPayload($this->payload)
->withProcedureHandler($this->procedureHandler)
->withMiddlewareHandler($this->middlewareHandler)
->withLocalException($this->localExceptions)
->parse();
}
return $this->requestParser
->withPayload($this->payload)
->withProcedureHandler($this->procedureHandler)
->withMiddlewareHandler($this->middlewareHandler)
->withLocalException($this->localExceptions)
->parse();
}
/**
* Check existence and get value of server variable
*
* @access protected
* @param string $variable
* @return string|null
*/
protected function getServerVariable($variable)
{
return isset($this->serverVariable[$variable]) ? $this->serverVariable[$variable] : null;
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace JsonRPC\Validator;
use JsonRPC\Exception\AccessDeniedException;
/**
* Class HostValidator
*
* @package JsonRPC\Validator
* @author Frederic Guillot
*/
class HostValidator
{
/**
* Validate
*
* @static
* @access public
* @param array $hosts
* @param string $remoteAddress
* @throws AccessDeniedException
*/
public static function validate(array $hosts, $remoteAddress)
{
if (!empty($hosts)) {
foreach ($hosts as $host) {
if (self::ipMatch($remoteAddress, $host)) {
return;
}
}
throw new AccessDeniedException('Access Forbidden');
}
}
/**
* Validate remoteAddress match host
* @param $remoteAddress
* @param $host
* @return bool
*/
public static function ipMatch($remoteAddress, $host)
{
$host = trim($host);
if (strpos($host, '/') !== false) {
list($network, $mask) = explode('/', $host);
if (self::netMatch($remoteAddress, $network, $mask)) {
return true;
}
}
if ($host === $remoteAddress) {
return true;
}
return false;
}
/**
* validate the ipAddress in network
* 192.168.1.1/24
* @param $clientIp
* @param $networkIp
* @param $mask
*
* @return bool
*/
public static function netMatch($clientIp, $networkIp, $mask)
{
$mask1 = 32 - $mask;
return ((ip2long($clientIp) >> $mask1) == (ip2long($networkIp) >> $mask1));
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace JsonRPC\Validator;
use JsonRPC\Exception\ResponseEncodingFailureException;
/**
* Class JsonEncodingValidator
*
* @package JsonRPC\Validator
* @author Frederic Guillot
*/
class JsonEncodingValidator
{
public static function validate()
{
$jsonError = json_last_error();
if ($jsonError !== JSON_ERROR_NONE) {
switch ($jsonError) {
case JSON_ERROR_DEPTH:
$errorMessage = 'Maximum stack depth exceeded';
break;
case JSON_ERROR_STATE_MISMATCH:
$errorMessage = 'Underflow or the modes mismatch';
break;
case JSON_ERROR_CTRL_CHAR:
$errorMessage = 'Unexpected control character found';
break;
case JSON_ERROR_SYNTAX:
$errorMessage = 'Syntax error, malformed JSON';
break;
case JSON_ERROR_UTF8:
$errorMessage = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
default:
$errorMessage = 'Unknown error';
break;
}
throw new ResponseEncodingFailureException($errorMessage, $jsonError);
}
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace JsonRPC\Validator;
use JsonRPC\Exception\InvalidJsonFormatException;
/**
* Class JsonFormatValidator
*
* @package JsonRPC\Validator
* @author Frederic Guillot
*/
class JsonFormatValidator
{
/**
* Validate
*
* @static
* @access public
* @param mixed $payload
* @throws InvalidJsonFormatException
*/
public static function validate($payload)
{
if (! is_array($payload)) {
throw new InvalidJsonFormatException('Malformed payload');
}
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace JsonRPC\Validator;
use JsonRPC\Exception\InvalidJsonRpcFormatException;
/**
* Class RpcFormatValidator
*
* @package JsonRPC\Validator
* @author Frederic Guillot
*/
class RpcFormatValidator
{
/**
* Validate
*
* @static
* @access public
* @param array $payload
* @throws InvalidJsonRpcFormatException
*/
public static function validate(array $payload)
{
if (! isset($payload['jsonrpc']) ||
! isset($payload['method']) ||
! is_string($payload['method']) ||
$payload['jsonrpc'] !== '2.0' ||
(isset($payload['params']) && ! is_array($payload['params']))) {
throw new InvalidJsonRpcFormatException('Invalid JSON RPC payload');
}
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace JsonRPC\Validator;
use JsonRPC\Exception\AuthenticationFailureException;
/**
* Class UserValidator
*
* @package JsonRPC\Validator
* @author Frederic Guillot
*/
class UserValidator
{
public static function validate(array $users, $username, $password)
{
if (! empty($users) && (! isset($users[$username]) || $users[$username] !== $password)) {
throw new AuthenticationFailureException('Access not allowed');
}
}
}

View File

@@ -0,0 +1,103 @@
<?php
use JsonRPC\Client;
require_once __DIR__.'/../../../vendor/autoload.php';
class ClientTest extends PHPUnit_Framework_TestCase
{
private $httpClient;
public function setUp()
{
$this->httpClient = $this
->getMockBuilder('\JsonRPC\HttpClient')
->setMethods(array('execute'))
->getMock();
}
public function testSendBatch()
{
$client = new Client('', false, $this->httpClient);
$response = array(
array(
'jsonrpc' => '2.0',
'result' => 'c',
'id' => 1,
),
array(
'jsonrpc' => '2.0',
'result' => 'd',
'id' => 2,
)
);
$this->httpClient
->expects($this->once())
->method('execute')
->with($this->stringContains('[{"jsonrpc":"2.0","method":"methodA","id":'))
->will($this->returnValue($response));
$result = $client->batch()
->execute('methodA', array('a' => 'b'))
->execute('methodB', array('a' => 'b'))
->send();
$this->assertEquals(array('c', 'd'), $result);
}
public function testSendRequest()
{
$client = new Client('', false, $this->httpClient);
$this->httpClient
->expects($this->once())
->method('execute')
->with($this->stringContains('{"jsonrpc":"2.0","method":"methodA","id":'))
->will($this->returnValue(array('jsonrpc' => '2.0', 'result' => 'foobar', 'id' => 1)));
$result = $client->execute('methodA', array('a' => 'b'));
$this->assertEquals($result, 'foobar');
}
public function testSendRequestWithError()
{
$client = new Client('', false, $this->httpClient);
$this->httpClient
->expects($this->once())
->method('execute')
->with($this->stringContains('{"jsonrpc":"2.0","method":"methodA","id":'))
->will($this->returnValue(array(
'jsonrpc' => '2.0',
'error' => array(
'code' => -32601,
'message' => 'Method not found',
),
)));
$this->setExpectedException('BadFunctionCallException');
$client->execute('methodA', array('a' => 'b'));
}
public function testSendRequestWithErrorAndReturnExceptionEnabled()
{
$client = new Client('', true, $this->httpClient);
$this->httpClient
->expects($this->once())
->method('execute')
->with($this->stringContains('{"jsonrpc":"2.0","method":"methodA","id":'))
->will($this->returnValue(array(
'jsonrpc' => '2.0',
'error' => array(
'code' => -32601,
'message' => 'Method not found',
),
)));
$result = $client->execute('methodA', array('a' => 'b'));
$this->assertInstanceOf('BadFunctionCallException', $result);
}
}

View File

@@ -0,0 +1,220 @@
<?php
namespace JsonRPC;
require_once __DIR__.'/../../../vendor/autoload.php';
defined('CURLOPT_URL') or define('CURLOPT_URL', 10002);
defined('CURLOPT_RETURNTRANSFER') or define('CURLOPT_RETURNTRANSFER', 19913);
defined('CURLOPT_CONNECTTIMEOUT') or define('CURLOPT_CONNECTTIMEOUT', 78);
defined('CURLOPT_MAXREDIRS') or define('CURLOPT_MAXREDIRS', 68);
defined('CURLOPT_SSL_VERIFYPEER') or define('CURLOPT_SSL_VERIFYPEER', 64);
defined('CURLOPT_POST') or define('CURLOPT_POST', 47);
defined('CURLOPT_POSTFIELDS') or define('CURLOPT_POSTFIELDS', 10015);
defined('CURLOPT_HTTPHEADER') or define('CURLOPT_HTTPHEADER', 10023);
defined('CURLOPT_HEADERFUNCTION') or define('CURLOPT_HEADERFUNCTION', 20079);
defined('CURLOPT_CAINFO') or define('CURLOPT_CAINFO', 10065);
function extension_loaded($extension) {
return HttpClientTest::$functions->extension_loaded($extension);
}
function fopen($url, $mode, $use_include_path, $context)
{
return HttpClientTest::$functions->fopen($url, $mode, $use_include_path, $context);
}
function stream_context_create(array $params)
{
return HttpClientTest::$functions->stream_context_create($params);
}
function curl_init() {
return HttpClientTest::$functions->curl_init();
}
function curl_setopt_array($ch, array $params) {
HttpClientTest::$functions->curl_setopt_array($ch, $params);
}
function curl_setopt($ch, $option, $value) {
HttpClientTest::$functions->curl_setopt($ch, $option, $value);
}
function curl_exec($ch) {
return HttpClientTest::$functions->curl_exec($ch);
}
function curl_close($ch) {
HttpClientTest::$functions->curl_close($ch);
}
class HttpClientTest extends \PHPUnit_Framework_TestCase
{
public static $functions;
public function setUp()
{
self::$functions = $this
->getMockBuilder('stdClass')
->setMethods(array('extension_loaded', 'fopen', 'stream_context_create',
'curl_init', 'curl_setopt_array', 'curl_setopt', 'curl_exec', 'curl_close'))
->getMock();
}
public function testWithServerError()
{
$this->setExpectedException('\JsonRPC\Exception\ServerErrorException');
$httpClient = new HttpClient();
$httpClient->handleExceptions(array(
'HTTP/1.0 301 Moved Permanently',
'Connection: close',
'HTTP/1.1 500 Internal Server Error',
));
}
public function testWithConnectionFailure()
{
$this->setExpectedException('\JsonRPC\Exception\ConnectionFailureException');
$httpClient = new HttpClient();
$httpClient->handleExceptions(array(
'HTTP/1.1 404 Not Found',
));
}
public function testWithAccessForbidden()
{
$this->setExpectedException('\JsonRPC\Exception\AccessDeniedException');
$httpClient = new HttpClient();
$httpClient->handleExceptions(array(
'HTTP/1.1 403 Forbidden',
));
}
public function testWithAccessNotAllowed()
{
$this->setExpectedException('\JsonRPC\Exception\AccessDeniedException');
$httpClient = new HttpClient();
$httpClient->handleExceptions(array(
'HTTP/1.0 401 Unauthorized',
));
}
public function testWithCallback()
{
self::$functions
->expects($this->at(0))
->method('extension_loaded')
->with('curl')
->will($this->returnValue(false));
self::$functions
->expects($this->at(1))
->method('stream_context_create')
->with(array(
'http' => array(
'method' => 'POST',
'protocol_version' => 1.1,
'timeout' => 5,
'max_redirects' => 2,
'header' => implode("\r\n", array(
'User-Agent: JSON-RPC PHP Client <https://github.com/fguillot/JsonRPC>',
'Content-Type: application/json',
'Accept: application/json',
'Connection: close',
'Content-Length: 4',
)),
'content' => 'test',
'ignore_errors' => true,
),
'ssl' => array(
'verify_peer' => true,
'verify_peer_name' => true,
)
))
->will($this->returnValue('context'));
self::$functions
->expects($this->at(2))
->method('fopen')
->with('url', 'r', false, 'context')
->will($this->returnValue(false));
$httpClient = new HttpClient('url');
$httpClient->withBeforeRequestCallback(function(HttpClient $client, $payload) {
$client->withHeaders(array('Content-Length: '.strlen($payload)));
});
$this->setExpectedException('\JsonRPC\Exception\ConnectionFailureException');
$httpClient->execute('test');
}
public function testWithCurl()
{
self::$functions
->expects($this->at(0))
->method('extension_loaded')
->with('curl')
->will($this->returnValue(true));
self::$functions
->expects($this->at(1))
->method('curl_init')
->will($this->returnValue('curl'));
self::$functions
->expects($this->at(2))
->method('curl_setopt_array')
->with('curl', array(
CURLOPT_URL => 'url',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_MAXREDIRS => 2,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => 'test',
CURLOPT_HTTPHEADER => array(
'User-Agent: JSON-RPC PHP Client <https://github.com/fguillot/JsonRPC>',
'Content-Type: application/json',
'Accept: application/json',
'Connection: close',
'Content-Length: 4',
),
CURLOPT_HEADERFUNCTION => function ($curl, $header) use (&$headers) {
$headers[] = $header;
return strlen($header);
}
));
self::$functions
->expects($this->at(3))
->method('curl_setopt')
->with('curl', CURLOPT_CAINFO, 'test.crt');
self::$functions
->expects($this->at(4))
->method('curl_exec')
->with('curl')
->will($this->returnValue(false));
self::$functions
->expects($this->at(5))
->method('curl_close')
->with('curl');
$httpClient = new HttpClient('url');
$httpClient
->withSslLocalCert('test.crt')
->withBeforeRequestCallback(function(HttpClient $client, $payload) {
$client->withHeaders(array('Content-Length: '.strlen($payload)));
});
$this->setExpectedException('\JsonRPC\Exception\ConnectionFailureException');
$httpClient->execute('test');
}
}

View File

@@ -0,0 +1,40 @@
<?php
use JsonRPC\Exception\AuthenticationFailureException;
use JsonRPC\MiddlewareHandler;
use JsonRPC\MiddlewareInterface;
require_once __DIR__.'/../../../vendor/autoload.php';
class FirstMiddleware implements MiddlewareInterface
{
public function execute($username, $password, $procedureName)
{
}
}
class SecondMiddleware implements MiddlewareInterface
{
public function execute($username, $password, $procedureName)
{
if ($username === 'myUsername' && $password === 'myPassword' && $procedureName === 'myProcedure') {
throw new AuthenticationFailureException('Bad user');
}
}
}
class MiddlewareHandlerTest extends PHPUnit_Framework_TestCase
{
public function testMiddlewareCanRaiseException()
{
$this->setExpectedException('JsonRpc\Exception\AuthenticationFailureException');
$middlewareHandler = new MiddlewareHandler();
$middlewareHandler->withUsername('myUsername');
$middlewareHandler->withPassword('myPassword');
$middlewareHandler->withProcedure('myProcedure');
$middlewareHandler->withMiddleware(new FirstMiddleware());
$middlewareHandler->withMiddleware(new SecondMiddleware());
$middlewareHandler->execute();
}
}

View File

@@ -0,0 +1,153 @@
<?php
use JsonRPC\ProcedureHandler;
require_once __DIR__.'/../../../vendor/autoload.php';
class A
{
public function getAll($p1, $p2, $p3 = 4)
{
return $p1 + $p2 + $p3;
}
}
class B
{
public function getAll($p1)
{
return $p1 + 2;
}
}
class ClassWithBeforeMethod
{
private $foobar = '';
public function before($procedure)
{
$this->foobar = $procedure;
}
public function myProcedure()
{
return $this->foobar;
}
}
class ProcedureHandlerTest extends PHPUnit_Framework_TestCase
{
public function testProcedureNotFound()
{
$this->setExpectedException('BadFunctionCallException');
$handler = new ProcedureHandler;
$handler->executeProcedure('a');
}
public function testCallbackNotFound()
{
$this->setExpectedException('BadFunctionCallException');
$handler = new ProcedureHandler;
$handler->withCallback('b', function() {});
$handler->executeProcedure('a');
}
public function testClassNotFound()
{
$this->setExpectedException('BadFunctionCallException');
$handler = new ProcedureHandler;
$handler->withClassAndMethod('getAllTasks', 'c', 'getAll');
$handler->executeProcedure('getAllTasks');
}
public function testMethodNotFound()
{
$this->setExpectedException('BadFunctionCallException');
$handler = new ProcedureHandler;
$handler->withClassAndMethod('getAllTasks', 'A', 'getNothing');
$handler->executeProcedure('getAllTasks');
}
public function testIsPositionalArguments()
{
$handler = new ProcedureHandler;
$this->assertFalse($handler->isPositionalArguments(
array('a' => 'b', 'c' => 'd')
));
$handler = new ProcedureHandler;
$this->assertTrue($handler->isPositionalArguments(
array('a', 'b', 'c')
));
}
public function testBindNamedArguments()
{
$handler = new ProcedureHandler;
$handler->withClassAndMethod('getAllA', 'A', 'getAll');
$handler->withClassAndMethod('getAllB', 'B', 'getAll');
$handler->withClassAndMethod('getAllC', new B, 'getAll');
$this->assertEquals(6, $handler->executeProcedure('getAllA', array('p2' => 4, 'p1' => -2)));
$this->assertEquals(10, $handler->executeProcedure('getAllA', array('p2' => 4, 'p3' => 8, 'p1' => -2)));
$this->assertEquals(6, $handler->executeProcedure('getAllB', array('p1' => 4)));
$this->assertEquals(5, $handler->executeProcedure('getAllC', array('p1' => 3)));
}
public function testBindPositionalArguments()
{
$handler = new ProcedureHandler;
$handler->withClassAndMethod('getAllA', 'A', 'getAll');
$handler->withClassAndMethod('getAllB', 'B', 'getAll');
$this->assertEquals(6, $handler->executeProcedure('getAllA', array(4, -2)));
$this->assertEquals(2, $handler->executeProcedure('getAllA', array(4, 0, -2)));
$this->assertEquals(4, $handler->executeProcedure('getAllB', array(2)));
}
public function testRegisterNamedArguments()
{
$handler = new ProcedureHandler;
$handler->withCallback('getAllA', function($p1, $p2, $p3 = 4) {
return $p1 + $p2 + $p3;
});
$this->assertEquals(6, $handler->executeProcedure('getAllA', array('p2' => 4, 'p1' => -2)));
$this->assertEquals(10, $handler->executeProcedure('getAllA', array('p2' => 4, 'p3' => 8, 'p1' => -2)));
}
public function testRegisterPositionalArguments()
{
$handler = new ProcedureHandler;
$handler->withCallback('getAllA', function($p1, $p2, $p3 = 4) {
return $p1 + $p2 + $p3;
});
$this->assertEquals(6, $handler->executeProcedure('getAllA', array(4, -2)));
$this->assertEquals(2, $handler->executeProcedure('getAllA', array(4, 0, -2)));
}
public function testTooManyArguments()
{
$this->setExpectedException('InvalidArgumentException');
$handler = new ProcedureHandler;
$handler->withClassAndMethod('getAllC', new B, 'getAll');
$handler->executeProcedure('getAllC', array('p1' => 3, 'p2' => 5));
}
public function testNotEnoughArguments()
{
$this->setExpectedException('InvalidArgumentException');
$handler = new ProcedureHandler;
$handler->withClassAndMethod('getAllC', new B, 'getAll');
$handler->executeProcedure('getAllC');
}
public function testBeforeMethod()
{
$handler = new ProcedureHandler;
$handler->withObject(new ClassWithBeforeMethod);
$handler->withBeforeMethod('before');
$this->assertEquals('myProcedure', $handler->executeProcedure('myProcedure'));
}
}

View File

@@ -0,0 +1,53 @@
<?php
use JsonRPC\Request\RequestBuilder;
require_once __DIR__.'/../../../../vendor/autoload.php';
class RequestBuilderTest extends PHPUnit_Framework_TestCase
{
public function testBuilder()
{
$payload = RequestBuilder::create()
->withId(123)
->withProcedure('foobar')
->withParams(array(1, 2, 3))
->build();
$this->assertEquals('{"jsonrpc":"2.0","method":"foobar","id":123,"params":[1,2,3]}', $payload);
}
public function testBuilderWithoutParams()
{
$payload = RequestBuilder::create()
->withId(123)
->withProcedure('foobar')
->build();
$this->assertEquals('{"jsonrpc":"2.0","method":"foobar","id":123}', $payload);
}
public function testBuilderWithoutId()
{
$payload = RequestBuilder::create()
->withProcedure('foobar')
->withParams(array(1, 2, 3))
->build();
$result = json_decode($payload, true);
$this->assertNotNull($result['id']);
}
public function testBuilderWithAdditionalRequestAttributes()
{
$payload = RequestBuilder::create()
->withProcedure('foobar')
->withParams(array(1, 2, 3))
->withRequestAttributes(array("some-attr" => 42))
->build();
$result = json_decode($payload, true);
$this->assertNotNull($result['some-attr']);
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace JsonRPC\Response;
use PHPUnit_Framework_TestCase;
require_once __DIR__.'/../../../../vendor/autoload.php';
function header($value)
{
HeaderMockTest::$functions->header($value);
}
abstract class HeaderMockTest extends PHPUnit_Framework_TestCase
{
public static $functions;
public function setUp()
{
self::$functions = $this
->getMockBuilder('stdClass')
->setMethods(array('header'))
->getMock();
}
}

View File

@@ -0,0 +1,151 @@
<?php
use JsonRPC\Exception\AccessDeniedException;
use JsonRPC\Exception\AuthenticationFailureException;
use JsonRPC\Exception\InvalidJsonFormatException;
use JsonRPC\Exception\InvalidJsonRpcFormatException;
use JsonRPC\Exception\ResponseEncodingFailureException;
use JsonRPC\Exception\ResponseException;
use JsonRPC\Response\ResponseBuilder;
require_once __DIR__.'/../../../../vendor/autoload.php';
class ResponseBuilderTest extends PHPUnit_Framework_TestCase
{
public function testBuildResponse()
{
$response = ResponseBuilder::create()
->withId(123)
->withResult('test')
->build();
$this->assertEquals('{"jsonrpc":"2.0","result":"test","id":123}', $response);
}
public function testBuildResponseWithError()
{
$response = ResponseBuilder::create()
->withId(123)
->withResult('test')
->withError(42, 'Test', 'More info')
->build();
$this->assertEquals('{"jsonrpc":"2.0","error":{"code":42,"message":"Test","data":"More info"},"id":123}', $response);
}
public function testBuildResponseWithException()
{
$response = ResponseBuilder::create()
->withId(123)
->withResult('test')
->withException(new Exception('Test'))
->build();
$this->assertEquals('{"jsonrpc":"2.0","error":{"code":0,"message":"Test"},"id":123}', $response);
}
public function testBuildResponseWithResponseException()
{
$exception = new ResponseException('Error', 42);
$exception->setData('Data');
$response = ResponseBuilder::create()
->withId(123)
->withResult('test')
->withException($exception)
->build();
$this->assertEquals('{"jsonrpc":"2.0","error":{"code":42,"message":"Error","data":"Data"},"id":123}', $response);
}
public function testBuildResponseWithAccessDeniedException()
{
$responseBuilder = ResponseBuilder::create();
$response = $responseBuilder
->withId(123)
->withResult('test')
->withException(new AccessDeniedException('Test'))
->build();
$this->assertEquals('{"jsonrpc":"2.0","error":{"code":403,"message":"Forbidden"},"id":123}', $response);
$this->assertEquals('HTTP/1.0 403 Forbidden', $responseBuilder->getStatus());
$this->assertEquals(
array('Content-Type' => 'application/json'),
$responseBuilder->getHeaders()
);
}
public function testBuildResponseWithAuthenticationFailureException()
{
$responseBuilder = ResponseBuilder::create();
$response = $responseBuilder
->withId(123)
->withResult('test')
->withException(new AuthenticationFailureException('Test'))
->build();
$this->assertEquals('{"jsonrpc":"2.0","error":{"code":401,"message":"Unauthorized"},"id":123}', $response);
$this->assertEquals('HTTP/1.0 401 Unauthorized', $responseBuilder->getStatus());
$this->assertEquals(
array('Content-Type' => 'application/json', 'WWW-Authenticate' => 'Basic realm="JsonRPC"'),
$responseBuilder->getHeaders()
);
}
public function testBuildResponseWithResponseEncodingFailureException()
{
$response = ResponseBuilder::create()
->withId(123)
->withResult('test')
->withException(new ResponseEncodingFailureException('Test'))
->build();
$this->assertEquals('{"jsonrpc":"2.0","error":{"code":-32603,"message":"Internal error","data":"Test"},"id":123}', $response);
}
public function testBuildResponseWithInvalidArgumentException()
{
$response = ResponseBuilder::create()
->withId(123)
->withResult('test')
->withException(new InvalidArgumentException('Test'))
->build();
$this->assertEquals('{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params","data":"Test"},"id":123}', $response);
}
public function testBuildResponseWithBadFunctionCallException()
{
$response = ResponseBuilder::create()
->withId(123)
->withResult('test')
->withException(new BadFunctionCallException('Test'))
->build();
$this->assertEquals('{"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found"},"id":123}', $response);
}
public function testBuildResponseWithInvalidJsonRpcFormatException()
{
$response = ResponseBuilder::create()
->withId(123)
->withResult('test')
->withException(new InvalidJsonRpcFormatException('Test'))
->build();
$this->assertEquals('{"jsonrpc":"2.0","error":{"code":-32600,"message":"Invalid Request"},"id":null}', $response);
}
public function testBuildResponseWithInvalidJsonFormatException()
{
$response = ResponseBuilder::create()
->withId(123)
->withResult('test')
->withException(new InvalidJsonFormatException('Test'))
->build();
$this->assertEquals('{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"},"id":null}', $response);
}
}

View File

@@ -0,0 +1,100 @@
<?php
use JsonRPC\Response\ResponseParser;
require_once __DIR__.'/../../../../vendor/autoload.php';
class ResponseParserTest extends PHPUnit_Framework_TestCase
{
public function testSingleRequest()
{
$result = ResponseParser::create()
->withPayload(json_decode('{"jsonrpc": "2.0", "result": "foobar", "id": "1"}', true))
->parse();
$this->assertEquals('foobar', $result);
}
public function testWithBadJsonFormat()
{
$this->setExpectedException('\JsonRPC\Exception\InvalidJsonFormatException');
ResponseParser::create()
->withPayload('foobar')
->parse();
}
public function testWithBadProcedure()
{
$this->setExpectedException('BadFunctionCallException');
ResponseParser::create()
->withPayload(json_decode('{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "1"}', true))
->parse();
}
public function testWithInvalidArgs()
{
$this->setExpectedException('InvalidArgumentException');
ResponseParser::create()
->withPayload(json_decode('{"jsonrpc": "2.0", "error": {"code": -32602, "message": "Invalid params"}, "id": "1"}', true))
->parse();
}
public function testWithInvalidRequest()
{
$this->setExpectedException('\JsonRPC\Exception\InvalidJsonRpcFormatException');
ResponseParser::create()
->withPayload(json_decode('{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}', true))
->parse();
}
public function testWithParseError()
{
$this->setExpectedException('\JsonRPC\Exception\InvalidJsonFormatException');
ResponseParser::create()
->withPayload(json_decode('{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}', true))
->parse();
}
public function testWithOtherError()
{
$this->setExpectedException('\JsonRPC\Exception\ResponseException');
ResponseParser::create()
->withPayload(json_decode('{"jsonrpc": "2.0", "error": {"code": 42, "message": "Something", "data": "foobar"}, "id": null}', true))
->parse();
}
public function testBatch()
{
$payload = '[
{"jsonrpc": "2.0", "result": 7, "id": "1"},
{"jsonrpc": "2.0", "result": 19, "id": "2"}
]';
$result = ResponseParser::create()
->withPayload(json_decode($payload, true))
->parse();
$this->assertEquals(array(7, 19), $result);
}
public function testBatchWithError()
{
$payload = '[
{"jsonrpc": "2.0", "result": 7, "id": "1"},
{"jsonrpc": "2.0", "result": 19, "id": "2"},
{"jsonrpc": "2.0", "error": {"code": -32602, "message": "Invalid params"}, "id": "1"}
]';
$this->setExpectedException('InvalidArgumentException');
ResponseParser::create()
->withPayload(json_decode($payload, true))
->parse();
}
}

View File

@@ -0,0 +1,237 @@
<?php
use JsonRPC\Server;
require_once __DIR__.'/../../../vendor/autoload.php';
require_once __DIR__.'/Response/HeaderMockTest.php';
class C
{
public function doSomething()
{
return 'something';
}
}
class ServerProtocolTest extends \JsonRPC\Response\HeaderMockTest
{
public function testPositionalParameters()
{
$subtract = function ($minuend, $subtrahend) {
return $minuend - $subtrahend;
};
$server = new Server('{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}');
$server->register('subtract', $subtract);
$this->assertEquals(
json_decode('{"jsonrpc": "2.0", "result": 19, "id": 1}', true),
json_decode($server->execute(), true)
);
$server = new Server('{"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 1}');
$server->register('subtract', $subtract);
$this->assertEquals(
json_decode('{"jsonrpc": "2.0", "result": -19, "id": 1}', true),
json_decode($server->execute(), true)
);
}
public function testNamedParameters()
{
$subtract = function ($minuend, $subtrahend) {
return $minuend - $subtrahend;
};
$server = new Server('{"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3}');
$server->register('subtract', $subtract);
$this->assertEquals(
json_decode('{"jsonrpc": "2.0", "result": 19, "id": 3}', true),
json_decode($server->execute(), true)
);
$server = new Server('{"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 4}');
$server->register('subtract', $subtract);
$this->assertEquals(
json_decode('{"jsonrpc": "2.0", "result": 19, "id": 4}', true),
json_decode($server->execute(), true)
);
}
public function testNotification()
{
$update = function($p1, $p2, $p3, $p4, $p5) {};
$foobar = function() {};
$server = new Server('{"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]}');
$server->register('update', $update);
$server->register('foobar', $foobar);
$this->assertEquals('', $server->execute());
$server = new Server('{"jsonrpc": "2.0", "method": "foobar"}');
$server->register('update', $update);
$server->register('foobar', $foobar);
$this->assertEquals('', $server->execute());
}
public function testNoMethod()
{
$server = new Server('{"jsonrpc": "2.0", "method": "foobar", "id": "1"}');
$this->assertEquals(
json_decode('{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "1"}', true),
json_decode($server->execute(), true)
);
}
public function testInvalidJson()
{
$server = new Server('{"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz]');
$this->assertEquals(
json_decode('{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}', true),
json_decode($server->execute(), true)
);
}
public function testInvalidRequest()
{
$server = new Server('{"jsonrpc": "2.0", "method": 1, "params": "bar", "id": 1}');
$this->assertEquals(
json_decode('{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}', true),
json_decode($server->execute(), true)
);
}
public function testInvalidResponse_MalformedCharacters()
{
$server = new Server('{"jsonrpc": "2.0", "method": "invalidresponse","id": 1}');
$invalidresponse = function() {
return pack("H*" ,'c32e');
};
$server->register('invalidresponse', $invalidresponse);
$this->assertEquals(
json_decode('{"jsonrpc": "2.0","id": 1, "error": {"code": -32603, "message": "Internal error","data": "Malformed UTF-8 characters, possibly incorrectly encoded"}}', true),
json_decode($server->execute(), true)
);
}
public function testBatchInvalidJson()
{
$server = new Server('[
{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
{"jsonrpc": "2.0", "method"
]');
$this->assertEquals(
json_decode('{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}', true),
json_decode($server->execute(), true)
);
}
public function testBatchEmptyArray()
{
$server = new Server('[]');
$this->assertEquals(
json_decode('{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}', true),
json_decode($server->execute(), true)
);
}
public function testBatchNotEmptyButInvalid()
{
$server = new Server('[1]');
$this->assertEquals(
json_decode('[{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}]', true),
json_decode($server->execute(), true)
);
}
public function testBatchInvalid()
{
$server = new Server('[1,2,3]');
$this->assertEquals(
json_decode('[
{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null},
{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null},
{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error"}, "id": null}
]', true),
json_decode($server->execute(), true)
);
}
public function testBatchOk()
{
$server = new Server('[
{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
{"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"},
{"foo": "boo"},
{"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"},
{"jsonrpc": "2.0", "method": "get_data", "id": "9"},
{"jsonrpc": "2.0", "method": "doSomething", "id": 10},
{"jsonrpc": "2.0", "method": "doStuff", "id": 15}
]');
$server->register('sum', function($a, $b, $c) {
return $a + $b + $c;
});
$server->register('subtract', function($minuend, $subtrahend) {
return $minuend - $subtrahend;
});
$server->register('get_data', function() {
return array('hello', 5);
});
$server->attach(new C);
$server->bind('doStuff', 'C', 'doSomething');
$response = $server->execute();
$this->assertEquals(
json_decode('[
{"jsonrpc": "2.0", "result": 7, "id": "1"},
{"jsonrpc": "2.0", "result": 19, "id": "2"},
{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null},
{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "5"},
{"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"},
{"jsonrpc": "2.0", "result": "something", "id": "10"},
{"jsonrpc": "2.0", "result": "something", "id": "15"}
]', true),
json_decode($response, true)
);
}
public function testBatchNotifications()
{
$server = new Server('[
{"jsonrpc": "2.0", "method": "notify_sum", "params": [1,2,4]},
{"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}
]');
$server->register('notify_sum', function($a, $b, $c) {
});
$server->register('notify_hello', function($id) {
});
$this->assertEquals('', $server->execute());
}
}

View File

@@ -0,0 +1,258 @@
<?php
use JsonRPC\Exception\AccessDeniedException;
use JsonRPC\Exception\AuthenticationFailureException;
use JsonRPC\Exception\ResponseException;
use JsonRPC\MiddlewareInterface;
use JsonRPC\Response\HeaderMockTest;
use JsonRPC\Server;
require_once __DIR__.'/../../../vendor/autoload.php';
require_once __DIR__.'/Response/HeaderMockTest.php';
class MyException extends Exception
{
}
class DummyMiddleware implements MiddlewareInterface
{
public function execute($username, $password, $procedureName)
{
throw new AuthenticationFailureException('Bad user');
}
}
class ServerTest extends HeaderMockTest
{
private $payload = '{"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"}';
public function testCustomAuthenticationHeader()
{
$env = array(
'HTTP_X_AUTH' => base64_encode('myuser:mypassword'),
);
$server = new Server($this->payload, $env);
$server->setAuthenticationHeader('X-Auth');
$this->assertEquals('myuser', $server->getUsername());
$this->assertEquals('mypassword', $server->getPassword());
}
public function testCustomAuthenticationHeaderWithEmptyValue()
{
$server = new Server($this->payload);
$server->setAuthenticationHeader('X-Auth');
$this->assertNull($server->getUsername());
$this->assertNull($server->getPassword());
}
public function testGetUsername()
{
$server = new Server($this->payload);
$this->assertNull($server->getUsername());
$server = new Server($this->payload, array('PHP_AUTH_USER' => 'username'));
$this->assertEquals('username', $server->getUsername());
}
public function testGetPassword()
{
$server = new Server($this->payload);
$this->assertNull($server->getPassword());
$server = new Server($this->payload, array('PHP_AUTH_PW' => 'password'));
$this->assertEquals('password', $server->getPassword());
}
public function testExecute()
{
$server = new Server($this->payload);
$server->getProcedureHandler()->withCallback('sum', function($a, $b, $c) {
return $a + $b + $c;
});
self::$functions
->expects($this->once())
->method('header')
->with('Content-Type: application/json');
$this->assertEquals('{"jsonrpc":"2.0","result":7,"id":"1"}', $server->execute());
}
public function testExecuteRequestParserOverride()
{
$requestParser = $this->getMockBuilder('JsonRPC\Request\RequestParser')
->getMock();
$requestParser->method('withPayload')->willReturn($requestParser);
$requestParser->method('withProcedureHandler')->willReturn($requestParser);
$requestParser->method('withMiddlewareHandler')->willReturn($requestParser);
$requestParser->method('withLocalException')->willReturn($requestParser);
$server = new Server($this->payload, array(), null, $requestParser);
$requestParser->expects($this->once())
->method('parse');
$server->execute();
}
public function testExecuteBatchRequestParserOverride()
{
$batchRequestParser = $this->getMockBuilder('JsonRPC\Request\BatchRequestParser')
->getMock();
$batchRequestParser->method('withPayload')->willReturn($batchRequestParser);
$batchRequestParser->method('withProcedureHandler')->willReturn($batchRequestParser);
$batchRequestParser->method('withMiddlewareHandler')->willReturn($batchRequestParser);
$batchRequestParser->method('withLocalException')->willReturn($batchRequestParser);
$server = new Server('["...", "..."]', array(), null, null, $batchRequestParser);
$batchRequestParser->expects($this->once())
->method('parse');
$server->execute();
}
public function testExecuteResponseBuilderOverride()
{
$responseBuilder = $this->getMockBuilder('JsonRPC\Response\ResponseBuilder')
->getMock();
$responseBuilder->expects($this->once())
->method('sendHeaders');
$server = new Server($this->payload, array(), $responseBuilder);
$server->execute();
}
public function testExecuteProcedureHandlerOverride()
{
$batchRequestParser = $this->getMockBuilder('JsonRPC\Request\BatchRequestParser')
->getMock();
$procedureHandler = $this->getMockBuilder('JsonRPC\ProcedureHandler')
->getMock();
$batchRequestParser->method('withPayload')->willReturn($batchRequestParser);
$batchRequestParser->method('withProcedureHandler')->willReturn($batchRequestParser);
$batchRequestParser->method('withMiddlewareHandler')->willReturn($batchRequestParser);
$batchRequestParser->method('withLocalException')->willReturn($batchRequestParser);
$server = new Server('["...", "..."]', array(), null, null, $batchRequestParser, $procedureHandler);
$batchRequestParser->expects($this->once())
->method('parse');
$batchRequestParser->expects($this->once())
->method('withProcedureHandler')
->with($this->identicalTo($procedureHandler));
$server->execute();
}
public function testWhenCallbackRaiseForbiddenException()
{
$server = new Server($this->payload);
$server->getProcedureHandler()->withCallback('sum', function($a, $b, $c) {
throw new AccessDeniedException();
});
self::$functions
->expects($this->at(0))
->method('header')
->with('HTTP/1.0 403 Forbidden');
self::$functions
->expects($this->at(1))
->method('header')
->with('Content-Type: application/json');
$this->assertEquals('{"jsonrpc":"2.0","error":{"code":403,"message":"Forbidden"},"id":null}', $server->execute());
}
public function testWhenCallbackRaiseUnauthorizedException()
{
$server = new Server($this->payload);
$server->getProcedureHandler()->withCallback('sum', function($a, $b, $c) {
throw new AuthenticationFailureException();
});
self::$functions
->expects($this->at(0))
->method('header')
->with('HTTP/1.0 401 Unauthorized');
self::$functions
->expects($this->at(1))
->method('header')
->with('Content-Type: application/json');
$this->assertEquals('{"jsonrpc":"2.0","error":{"code":401,"message":"Unauthorized"},"id":null}', $server->execute());
}
public function testWhenMiddlewareRaiseUnauthorizedException()
{
$server = new Server($this->payload);
$server->getMiddlewareHandler()->withMiddleware(new DummyMiddleware());
$server->getProcedureHandler()->withCallback('sum', function($a, $b) {
return $a + $b;
});
self::$functions
->expects($this->at(0))
->method('header')
->with('HTTP/1.0 401 Unauthorized');
self::$functions
->expects($this->at(1))
->method('header')
->with('Content-Type: application/json');
$this->assertEquals('{"jsonrpc":"2.0","error":{"code":401,"message":"Unauthorized"},"id":null}', $server->execute());
}
public function testFilterRelayExceptions()
{
$server = new Server($this->payload);
$server->withLocalException('MyException');
$server->getProcedureHandler()->withCallback('sum', function($a, $b, $c) {
throw new MyException('test');
});
$this->setExpectedException('MyException');
$server->execute();
}
public function testCustomExceptionAreRelayedToClient()
{
$server = new Server($this->payload);
$server->getProcedureHandler()->withCallback('sum', function($a, $b, $c) {
throw new MyException('test');
});
self::$functions
->expects($this->once())
->method('header')
->with('Content-Type: application/json');
$this->assertEquals('{"jsonrpc":"2.0","error":{"code":0,"message":"test"},"id":"1"}', $server->execute());
}
public function testCustomResponseException()
{
$server = new Server($this->payload);
$server->getProcedureHandler()->withCallback('sum', function($a, $b, $c) {
throw new ResponseException('test', 123, null, 'more info');
});
self::$functions
->expects($this->once())
->method('header')
->with('Content-Type: application/json');
$this->assertEquals('{"jsonrpc":"2.0","error":{"code":123,"message":"test","data":"more info"},"id":"1"}', $server->execute());
}
}

View File

@@ -0,0 +1,32 @@
<?php
use JsonRPC\Validator\HostValidator;
require_once __DIR__.'/../../../../vendor/autoload.php';
class HostValidatorTest extends PHPUnit_Framework_TestCase
{
public function testWithEmptyHosts()
{
$this->assertNull(HostValidator::validate(array(), '127.0.0.1', '127.0.0.1'));
}
public function testWithValidHosts()
{
$this->assertNull(HostValidator::validate(array('127.0.0.1'), '127.0.0.1', '127.0.0.1'));
}
public function testWithValidNetwork()
{
$this->assertNull(HostValidator::validate(array('192.168.10.1/24'), '192.168.10.1'),'test ip match');
$this->assertNull(HostValidator::validate(array('192.168.10.1/24'), '192.168.10.250'),'test ip match');
$this->setExpectedException('\JsonRPC\Exception\AccessDeniedException');
HostValidator::validate(array('192.168.10.1/24'), '192.168.11.1');
}
public function testWithNotAuthorizedHosts()
{
$this->setExpectedException('\JsonRPC\Exception\AccessDeniedException');
HostValidator::validate(array('192.168.1.1'), '127.0.0.1', '127.0.0.1');
}
}

View File

@@ -0,0 +1,22 @@
<?php
use JsonRPC\Validator\JsonEncodingValidator;
require_once __DIR__.'/../../../../vendor/autoload.php';
class JsonEncodingValidatorTest extends PHPUnit_Framework_TestCase
{
public function testWithValidJson()
{
json_encode('{"foo": "bar"}');
$this->assertNull(JsonEncodingValidator::validate());
}
public function testWithJsonError()
{
json_encode("\xB1\x31");
$this->setExpectedException('\JsonRPC\Exception\ResponseEncodingFailureException');
JsonEncodingValidator::validate();
}
}

View File

@@ -0,0 +1,19 @@
<?php
use JsonRPC\Validator\JsonFormatValidator;
require_once __DIR__.'/../../../../vendor/autoload.php';
class JsonFormatValidatorTest extends PHPUnit_Framework_TestCase
{
public function testJsonParsedCorrectly()
{
$this->assertNull(JsonFormatValidator::validate(array('foobar')));
}
public function testJsonNotParsedCorrectly()
{
$this->setExpectedException('\JsonRPC\Exception\InvalidJsonFormatException');
JsonFormatValidator::validate('');
}
}

View File

@@ -0,0 +1,48 @@
<?php
use JsonRPC\Validator\RpcFormatValidator;
require_once __DIR__.'/../../../../vendor/autoload.php';
class RpcFormatValidatorTest extends PHPUnit_Framework_TestCase
{
public function testWithMinimumRequirement()
{
$this->assertNull(RpcFormatValidator::validate(array('jsonrpc' => '2.0', 'method' => 'foobar')));
}
public function testWithNoVersion()
{
$this->setExpectedException('\JsonRPC\Exception\InvalidJsonRpcFormatException');
RpcFormatValidator::validate(array('method' => 'foobar'));
}
public function testWithNoMethod()
{
$this->setExpectedException('\JsonRPC\Exception\InvalidJsonRpcFormatException');
RpcFormatValidator::validate(array('jsonrpc' => '2.0'));
}
public function testWithMethodNotString()
{
$this->setExpectedException('\JsonRPC\Exception\InvalidJsonRpcFormatException');
RpcFormatValidator::validate(array('jsonrpc' => '2.0', 'method' => array()));
}
public function testWithBadVersion()
{
$this->setExpectedException('\JsonRPC\Exception\InvalidJsonRpcFormatException');
RpcFormatValidator::validate(array('jsonrpc' => '1.0', 'method' => 'abc'));
}
public function testWithBadParams()
{
$this->setExpectedException('\JsonRPC\Exception\InvalidJsonRpcFormatException');
RpcFormatValidator::validate(array('jsonrpc' => '2.0', 'method' => 'abc', 'params' => 'foobar'));
}
public function testWithParams()
{
$this->assertNull(RpcFormatValidator::validate(array('jsonrpc' => '2.0', 'method' => 'abc', 'params' => array(1, 2))));
}
}

View File

@@ -0,0 +1,24 @@
<?php
use JsonRPC\Validator\UserValidator;
require_once __DIR__.'/../../../../vendor/autoload.php';
class UserValidatorTest extends PHPUnit_Framework_TestCase
{
public function testWithEmptyHosts()
{
$this->assertNull(UserValidator::validate(array(), 'user', 'pass'));
}
public function testWithValidHosts()
{
$this->assertNull(UserValidator::validate(array('user' => 'pass'), 'user', 'pass'));
}
public function testWithNotAuthorizedHosts()
{
$this->setExpectedException('\JsonRPC\Exception\AuthenticationFailureException');
UserValidator::validate(array('user' => 'pass'), 'user', 'wrong password');
}
}

165
libs/phpqrcode/LICENSE Executable file
View File

@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

2
libs/phpqrcode/cache/frame_1.dat vendored Executable file
View File

@@ -0,0 +1,2 @@
<EFBFBD><EFBFBD>Á
À E9³u<06><>`³"PÅ„CÛ牗T!0$

BIN
libs/phpqrcode/cache/frame_1.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

BIN
libs/phpqrcode/cache/frame_10.dat vendored Executable file

Binary file not shown.

BIN
libs/phpqrcode/cache/frame_10.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

BIN
libs/phpqrcode/cache/frame_11.dat vendored Executable file

Binary file not shown.

BIN
libs/phpqrcode/cache/frame_11.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

BIN
libs/phpqrcode/cache/frame_12.dat vendored Executable file

Binary file not shown.

BIN
libs/phpqrcode/cache/frame_12.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

BIN
libs/phpqrcode/cache/frame_13.dat vendored Executable file

Binary file not shown.

BIN
libs/phpqrcode/cache/frame_13.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

BIN
libs/phpqrcode/cache/frame_14.dat vendored Executable file

Binary file not shown.

BIN
libs/phpqrcode/cache/frame_14.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

BIN
libs/phpqrcode/cache/frame_15.dat vendored Executable file

Binary file not shown.

BIN
libs/phpqrcode/cache/frame_15.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 B

1
libs/phpqrcode/cache/frame_16.dat vendored Executable file
View File

@@ -0,0 +1 @@
xÚí™A E]sëIX´;¸Ün6€È`q”êêW6ñ奚`Œ%A/3!¢°‚¢Š!gÈÌ¡1N) éE¢Ï|;®—>6â¸<C3A2>Þ97$ëÄôëc]kkö<6B>wé1Öü[·m­CÍœcÊRºÄê¹>¦èµ¾šE,•hʼnp„#áxF<1C>yWÏÇVWGçòÕ3¼Õ+шþàË“úSŽâ}Äž<C384>#áG8b^c^cÏÀŽp„c&3YQ"ñŽ÷çÌvµù…ñàÎþþ¼¹kÞ9ŠÜ‡÷}”¹³ï×ú ¢Ä¿<C384>QäÿL—/ÝÔÀÏ

BIN
libs/phpqrcode/cache/frame_16.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

BIN
libs/phpqrcode/cache/frame_17.dat vendored Executable file

Binary file not shown.

BIN
libs/phpqrcode/cache/frame_17.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

2
libs/phpqrcode/cache/frame_18.dat vendored Executable file
View File

@@ -0,0 +1,2 @@
xÚí™A
ƒ0E]çÖ…,2;sƒä&ÉÍšh¥ÛêO¡ôÝÈàã1&09OIv@DDÒ Ì&§Ù‰K<E280B0>XÈÕFv•<Ádqò9Ö<%h•¹ Yïs !(d¥²ës;~||b(ÏøYůg#µ`œK ±S¼Åô¹Ä¶˜ùsàidß<64>Lg:Ó™Îtþ/gmª<6D>™ƒkÅMâ3³{­4rTÈQýÿe¥·s·>ó<Ó™Ît¦3<C2A6>éÌ;ïH¼#Ñ™Ît¦3<C2A6>ÍYœ+og©hù¶óµÙ½¬lnðûF>Øi^»#awm;gè~pÛgìNs{6z»ãºïÞäp¾Ê'

BIN
libs/phpqrcode/cache/frame_18.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

3
libs/phpqrcode/cache/frame_19.dat vendored Executable file
View File

@@ -0,0 +1,3 @@
xÚíšA
Ä E»öÖ.ĚNo Ť¶iiRÚN2áW%đxÁ@ÚÚśę'­
u<EFBFBD>6×ę<EFBFBD>.ť*S;}<7D>«ŇĂ ĎT účĚztąď%ç,ŇĹÚâÎ}ç;“âç)ąź<C485>âÝZÚîLĺčą÷¬Pçç$Ż×÷ĎqËgśLÂôdJ‡;Üáw¸Ăý.]z#źľ«[Íť˝ďOg­Ćô"ĐË áBíî¦}Ç}‡;Üáw¸Ăî<>#1GbŽ„;Üáw¸Ăý_ÝC+w˘@Dfî÷ďç™uťř2™ĹÚÉNţű9R7|pWßkďű®ż“ßßkşöżşú»ĽÎÓ

BIN
libs/phpqrcode/cache/frame_19.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

1
libs/phpqrcode/cache/frame_2.dat vendored Executable file
View File

@@ -0,0 +1 @@
xÚÍÍ

BIN
libs/phpqrcode/cache/frame_2.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

BIN
libs/phpqrcode/cache/frame_20.dat vendored Executable file

Binary file not shown.

BIN
libs/phpqrcode/cache/frame_20.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

1
libs/phpqrcode/cache/frame_21.dat vendored Executable file
View File

@@ -0,0 +1 @@
xÚíšA E]sëIX´Ün6Up<13>“в™ÿ]Ù˜þ< i-eWö˜)×äÅ•¼ÉÂ…H\jvqÙHL\6šÝÐ…rI¢LܹÜÕ%ÅÓ@´þ±V—vÆÂúý¤(ÏP4|ÎXnÒgÉ<>ß¼~]D¾ÉÕ×u1Us S\À°€,ÿÅ2Þ¢N§Ã?DKºüF-:“eJ]p_À°€,˜a0Ã`†ÁÝ °`†Á ƒw,` X´]˜ˆ¹˜°5 ‰®Y4{屿ñ2íûåvçJs†±Ûí9±˜í)õu±Û¹êÏØ,«]¸“‹Ù^_§7$ƒ_Í

BIN
libs/phpqrcode/cache/frame_21.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

3
libs/phpqrcode/cache/frame_22.dat vendored Executable file
View File

@@ -0,0 +1,3 @@
xÚíšA
„0 E]{ë<>.]{{{³©Z¥BepÆÞwe@<1F>VERZ3»Á"*2o€4¦y‰)i#dÒbdFÒ…´ŒI"ú‘—4ž½W­IíuŠÓ45ßx«.Z­SÙ{ÁŸ¯8åËÿk={o.±qÊÙ£[œÍ:å¸q»õƒy
)t#á„N8ádCj<43>-O<>OG}¼:/Ÿ:s<>z!Å)^<ùe½·S·uâ{ 'œp 'ú=ú=ú=¾'œp 'œp¢ß£ß£ßã<1F>N8á„Óÿ9©ªˆôpQQõ]HÔpz¾<7A>ØGœ^æ½Qº˜I|¾ß³<C39F>u;9™ÎïÕëd;“X~$ËÙÑÉt¶ÊÛédy

BIN
libs/phpqrcode/cache/frame_22.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

3
libs/phpqrcode/cache/frame_23.dat vendored Executable file
View File

@@ -0,0 +1,3 @@
xÚíšA
à E³öÖfo U<E280BA>) %M!ΔÂûYu(<šð“sK²“TœÓ
É&§IÚ\i+¥Ðª™(m®´FQ¡¹¯h±æöüèv~n1„oÏ]sëçÖï¤_ÞŸÊ3`î_w2õȹ•lc[¼•;·Ûc֟ˤNóª4ÜpÃ

BIN
libs/phpqrcode/cache/frame_23.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

1
libs/phpqrcode/cache/frame_24.dat vendored Executable file
View File

@@ -0,0 +1 @@
νAƒ E»φΦMX0;Έ<>άnVP4ΪHSS»xίU3±/O΄ύ LiJ4<4A><34>±Vβ JC<4A>%ύ‰6VR&ΓήDB<E28098>HjDωJΟ??™―κBl­cΗ±ρ½§'σU­λXοUοή<CEBF>0ζΓywΝΔ―χj¬ιλ<CEB9>³€3ΕΎλ<CE8E>cj†ω£{¨¥½:GG<1C>έρψ<CF81>ϋΪ°N†v;Ή¶η¬“J ‡ΔΠ<ϋ‡Ι]<5D>κλΘσ<CE98>#<23><38>#<23>8βH'§“ΣΙωΝΑGGιδtr:9Ο#<23><38>#<23>8βΨ“h­<68>―NΤt”<74>΄Φ_έΨ>tΉeλμS­―¦ζ<C2A6>ω^<5E>\g―υΞQe?ωvΜoοΥ;<3B>ο<>*οwlςΧmΡ

BIN
libs/phpqrcode/cache/frame_24.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

3
libs/phpqrcode/cache/frame_25.dat vendored Executable file
View File

@@ -0,0 +1,3 @@
xÚíÛA
à …á¬së™]rƒx½Y51mMÈBG
ÿ¸*Sx|Ua5ƵZ—Š„-,Ž1ä²HÑPÒRjšX5§®i†©áG©>W¥ŽžRïöÕ/Ëâ+uT廯å Ïӯ嗴ªuæÏ¥Ú[Sía£[kví÷5•+5n§Á´JêÜ%+V¬X±bÅŠõ߬u'Á<07>±þÔû SRýå÷štzZ»ì+÷+V¬X±bÅŠ•ٟٟٟûŠ+V¬X±bÅÊìÏìÏìÏ}ÅŠ+V¬X±ö±ª¤¥ÖVI©¢ÖÖ+k«qÿ[úËtŽ·oVZÍþvoNV³wÇ}µ{³r<ýR­Þ"<22>RÍÞ]ê

BIN
libs/phpqrcode/cache/frame_25.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

2
libs/phpqrcode/cache/frame_26.dat vendored Executable file
View File

@@ -0,0 +1,2 @@
xÚíA
à E³öÖ…,t§7ˆU<E280BA> E)i7ï»*~cÃüÅÄXÖEBÆè°FC˜³6¡:&çL,å¬Mv.ŽÂÎæKgŸÕ¸ãYMç>ŸÎí>ûmÛš·?ª•vô¹¾mg?<3F>ßÒ±Îþ³æÎ·ªd˜¹U¦ÏIk•ÚÚE\ÕÙMs†f˜a†f˜a>œ[sÓˆ9쬩ެ8bö<kÕÙ7œ}ç†k³™§õ™ÿ3Ì0à 3Ì0à 3Ìä*r¹Š\Å7 f˜a†f˜a†fr¹Š\Å7 f˜a†f˜a†YÆÙ<18>Î æd4ƒ9kíÆÌÔÝyûX y‰gŒØÙ)«dw<64>nÌ¢ûU×>Ëî”]ßöLgÉÝÁ³è¾äEo w1

BIN
libs/phpqrcode/cache/frame_26.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

BIN
libs/phpqrcode/cache/frame_27.dat vendored Executable file

Binary file not shown.

BIN
libs/phpqrcode/cache/frame_27.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

BIN
libs/phpqrcode/cache/frame_28.dat vendored Executable file

Binary file not shown.

BIN
libs/phpqrcode/cache/frame_28.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

2
libs/phpqrcode/cache/frame_29.dat vendored Executable file
View File

@@ -0,0 +1,2 @@
xÚíÜA<0E> …a×Ţş ‹™ťÜ@n7+*¶šÖÚ4!Í?®Jšđň ł<>”抮«]Ş—ÉSźâTf)ŮsŠIÂ"…Č”bžÝ0…Š|•"Luٸî,Ž×EÇ1\6®*ĎuQŢ?Ľ>aĚĎ…ăţńŽÄRő-r­“÷n.ďꯋ\®Tżü:Ó*)|)°Ŕ ,°Ŕ ,ţŃâęóĺéx_ă¬}:^R„<52>Uoɢ‰uÁ~ÁމX`<60>XĐŹĐŹĐŹĐŹ°_`<60>X`<60>XĐŹĐŹĐŹ°_`<60>X`<60>XĐŹĐŹĐŹĐŹ°wb<77>X`<60>żĄPUőö)DÔŢ"cČ{zçÎő3ęé<}¸óˇ^?b÷m˙ÎÂěž<C49B>íş°»óaűŽ´Âę.<2E>]
ł{Q6uáT,9

BIN
libs/phpqrcode/cache/frame_29.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

1
libs/phpqrcode/cache/frame_3.dat vendored Executable file
View File

@@ -0,0 +1 @@
xÚí“Á

BIN
libs/phpqrcode/cache/frame_3.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

BIN
libs/phpqrcode/cache/frame_30.dat vendored Executable file

Binary file not shown.

BIN
libs/phpqrcode/cache/frame_30.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

1
libs/phpqrcode/cache/frame_31.dat vendored Executable file
View File

@@ -0,0 +1 @@
xÚíÜAƒ …a×Þº ØÉ

BIN
libs/phpqrcode/cache/frame_31.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

2
libs/phpqrcode/cache/frame_32.dat vendored Executable file
View File

@@ -0,0 +1,2 @@
xЪнЬБ
…бЦѕхЂ‹л.Я ЯDЯl¬, ¦љMz‰я6†Г‡ gcJЛD;ф'.®AIqћЮ‰ДI,IrўYЁ»ЛFk%‰DюOжy|EDЄDЧы(LУ_YЌК>*Яљ?aКїk±L_Ј<[c—с¶п>К<63>хuФLIдХ%В#Њ0В#Њ0В#ЊЮotСўљхµ}ЕЬ4Нfќv_)‰ВEўpъЏ¬h5R·Џ8Џ8і1В#Њ0В#Њ0ўУТiйґtZО#Њ0В#Њ0В#Њ0ўУТiйґtZО#Њ0В#Њ0В#Њ0ўУТiйґtZОlЊ0В#Њ0ВЈч9q"ўЙHЬњH™Qюќµп"ЫХL5}-ЭЬѕУкёkм`¤в>¶zйёі®юЦ4&Тб!‘Љы!«щ`ї:5

BIN
libs/phpqrcode/cache/frame_32.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

14
libs/phpqrcode/cache/frame_33.dat vendored Executable file
View File

@@ -0,0 +1,14 @@
xЪнЬAѓ …aЧЮє‰™ќЬ@n7+*L++Ужџ®утМbbЬ*LCп°‡‰c k™HҐrљ”j•ІђJ5Yнi~0•_«тЊыЧTКTх}е—e©>эц5b_еwРНџ?ї¤Ямж§ЦЬщ†\э­RaЖi+7хЯW©¦\гюwLUNеВ
+¬°В
+¬°Вкя­ТO·џkcлЮсфз\Л©|%•o<бk­Lо+О+Оv¬°В
+¬°В
+¬°ВЉ>}ъ ф8Ї°В
+¬°В
+¬°В
фи3РgајВ
+¬°В
+¬°В
+¬и3Рg П@џЃу
+¬°В
+¬°В
+¬°:R‰ЁЄXіЪB‰9«”IФ=зkЮЏ±o/SwзШ<D0B7>™ЩЇП`g¶бЕКМИr_Щ™™YѕѓVSY™ЕzIefnmQoz

BIN
libs/phpqrcode/cache/frame_33.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

BIN
libs/phpqrcode/cache/frame_34.dat vendored Executable file

Binary file not shown.

BIN
libs/phpqrcode/cache/frame_34.png vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

BIN
libs/phpqrcode/cache/frame_35.dat vendored Executable file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More