mirror of
https://github.com/Icinga/icingaweb2-module-reactbundle.git
synced 2025-07-21 12:54:27 +02:00
Version v0.7.0
This commit is contained in:
parent
342a1d15fc
commit
8b8c9689e5
1694
composer.lock
generated
Normal file
1694
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
Name: React Bundle
|
||||
Version: dev-master
|
||||
Version: 0.7.0
|
||||
Description: ReactPHP-based 3rd party libraries
|
||||
This repository is an attempt to ship 3rd party libraries that might be useful
|
||||
for asynchronous PHP-based Icinga Web 2 modules. Please download the latest
|
||||
|
19
vendor/autoload.php
vendored
19
vendor/autoload.php
vendored
@ -1,20 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Reactbundle {
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
use Icinga\Application\Hook\ApplicationStateHook;
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
class ApplicationState extends ApplicationStateHook
|
||||
{
|
||||
public function collectMessages()
|
||||
{
|
||||
$this->addError(
|
||||
'reactbundle.master',
|
||||
time(),
|
||||
'Please install a Release version of the Reactbundle module, not the GIT master'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->provideHook('ApplicationState', '\\Icinga\\Module\\Reactbundle\\ApplicationState');
|
||||
}
|
||||
return ComposerAutoloaderInit42ef24ab76510716787335cfbc6c878a::getLoader();
|
||||
|
21
vendor/clue/block-react/LICENSE
vendored
Normal file
21
vendor/clue/block-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Christian Lück
|
||||
|
||||
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.
|
25
vendor/clue/block-react/composer.json
vendored
Normal file
25
vendor/clue/block-react/composer.json
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "clue/block-react",
|
||||
"description": "Lightweight library that eases integrating async components built for ReactPHP in a traditional, blocking environment.",
|
||||
"keywords": ["blocking", "await", "sleep", "Event Loop", "synchronous", "Promise", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/clue/reactphp-block",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"files": [ "src/functions_include.php" ]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
|
||||
"react/promise": "^2.7 || ^1.2.1",
|
||||
"react/promise-timer": "^1.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
|
||||
}
|
||||
}
|
221
vendor/clue/block-react/src/functions.php
vendored
Normal file
221
vendor/clue/block-react/src/functions.php
vendored
Normal file
@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Block;
|
||||
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\CancellablePromiseInterface;
|
||||
use UnderflowException;
|
||||
use Exception;
|
||||
use React\Promise;
|
||||
use React\Promise\Timer;
|
||||
use React\Promise\Timer\TimeoutException;
|
||||
|
||||
/**
|
||||
* wait/sleep for $time seconds
|
||||
*
|
||||
* The $time value will be used as a timer for the loop so that it keeps running
|
||||
* until the timeout triggers.
|
||||
* This implies that if you pass a really small (or negative) value, it will still
|
||||
* start a timer and will thus trigger at the earliest possible time in the future.
|
||||
*
|
||||
* @param float $time
|
||||
* @param LoopInterface $loop
|
||||
*/
|
||||
function sleep($time, LoopInterface $loop)
|
||||
{
|
||||
await(Timer\resolve($time, $loop), $loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* block waiting for the given $promise to resolve
|
||||
*
|
||||
* Once the promise is resolved, this will return whatever the promise resolves to.
|
||||
*
|
||||
* Once the promise is rejected, this will throw whatever the promise rejected with.
|
||||
* If the promise did not reject with an `Exception`, then this function will
|
||||
* throw an `UnexpectedValueException` instead.
|
||||
*
|
||||
* If no $timeout is given and the promise stays pending, then this will
|
||||
* potentially wait/block forever until the promise is settled.
|
||||
*
|
||||
* If a $timeout is given and the promise is still pending once the timeout
|
||||
* triggers, this will cancel() the promise and throw a `TimeoutException`.
|
||||
* This implies that if you pass a really small (or negative) value, it will still
|
||||
* start a timer and will thus trigger at the earliest possible time in the future.
|
||||
*
|
||||
* @param PromiseInterface $promise
|
||||
* @param LoopInterface $loop
|
||||
* @param null|float $timeout (optional) maximum timeout in seconds or null=wait forever
|
||||
* @return mixed returns whatever the promise resolves to
|
||||
* @throws Exception when the promise is rejected
|
||||
* @throws TimeoutException if the $timeout is given and triggers
|
||||
*/
|
||||
function await(PromiseInterface $promise, LoopInterface $loop, $timeout = null)
|
||||
{
|
||||
$wait = true;
|
||||
$resolved = null;
|
||||
$exception = null;
|
||||
$rejected = false;
|
||||
|
||||
if ($timeout !== null) {
|
||||
$promise = Timer\timeout($promise, $timeout, $loop);
|
||||
}
|
||||
|
||||
$promise->then(
|
||||
function ($c) use (&$resolved, &$wait, $loop) {
|
||||
$resolved = $c;
|
||||
$wait = false;
|
||||
$loop->stop();
|
||||
},
|
||||
function ($error) use (&$exception, &$rejected, &$wait, $loop) {
|
||||
$exception = $error;
|
||||
$rejected = true;
|
||||
$wait = false;
|
||||
$loop->stop();
|
||||
}
|
||||
);
|
||||
|
||||
// Explicitly overwrite argument with null value. This ensure that this
|
||||
// argument does not show up in the stack trace in PHP 7+ only.
|
||||
$promise = null;
|
||||
|
||||
while ($wait) {
|
||||
$loop->run();
|
||||
}
|
||||
|
||||
if ($rejected) {
|
||||
if (!$exception instanceof \Exception) {
|
||||
$exception = new \UnexpectedValueException(
|
||||
'Promise rejected with unexpected value of type ' . (is_object($exception) ? get_class($exception) : gettype($exception)),
|
||||
0,
|
||||
$exception instanceof \Throwable ? $exception : null
|
||||
);
|
||||
}
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* wait for ANY of the given promises to resolve
|
||||
*
|
||||
* Once the first promise is resolved, this will try to cancel() all
|
||||
* remaining promises and return whatever the first promise resolves to.
|
||||
*
|
||||
* If ALL promises fail to resolve, this will fail and throw an Exception.
|
||||
*
|
||||
* If no $timeout is given and either promise stays pending, then this will
|
||||
* potentially wait/block forever until the first promise is settled.
|
||||
*
|
||||
* If a $timeout is given and either promise is still pending once the timeout
|
||||
* triggers, this will cancel() all pending promises and throw a `TimeoutException`.
|
||||
* This implies that if you pass a really small (or negative) value, it will still
|
||||
* start a timer and will thus trigger at the earliest possible time in the future.
|
||||
*
|
||||
* @param array $promises
|
||||
* @param LoopInterface $loop
|
||||
* @param null|float $timeout (optional) maximum timeout in seconds or null=wait forever
|
||||
* @return mixed returns whatever the first promise resolves to
|
||||
* @throws Exception if ALL promises are rejected
|
||||
* @throws TimeoutException if the $timeout is given and triggers
|
||||
*/
|
||||
function awaitAny(array $promises, LoopInterface $loop, $timeout = null)
|
||||
{
|
||||
// Explicitly overwrite argument with null value. This ensure that this
|
||||
// argument does not show up in the stack trace in PHP 7+ only.
|
||||
$all = $promises;
|
||||
$promises = null;
|
||||
|
||||
try {
|
||||
// Promise\any() does not cope with an empty input array, so reject this here
|
||||
if (!$all) {
|
||||
throw new UnderflowException('Empty input array');
|
||||
}
|
||||
|
||||
$ret = await(Promise\any($all)->then(null, function () {
|
||||
// rejects with an array of rejection reasons => reject with Exception instead
|
||||
throw new Exception('All promises rejected');
|
||||
}), $loop, $timeout);
|
||||
} catch (TimeoutException $e) {
|
||||
// the timeout fired
|
||||
// => try to cancel all promises (rejected ones will be ignored anyway)
|
||||
_cancelAllPromises($all);
|
||||
|
||||
throw $e;
|
||||
} catch (Exception $e) {
|
||||
// if the above throws, then ALL promises are already rejected
|
||||
// => try to cancel all promises (rejected ones will be ignored anyway)
|
||||
_cancelAllPromises($all);
|
||||
|
||||
throw new UnderflowException('No promise could resolve', 0, $e);
|
||||
}
|
||||
|
||||
// if we reach this, then ANY of the given promises resolved
|
||||
// => try to cancel all promises (settled ones will be ignored anyway)
|
||||
_cancelAllPromises($all);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* wait for ALL of the given promises to resolve
|
||||
*
|
||||
* Once the last promise resolves, this will return an array with whatever
|
||||
* each promise resolves to. Array keys will be left intact, i.e. they can
|
||||
* be used to correlate the return array to the promises passed.
|
||||
*
|
||||
* If ANY promise fails to resolve, this will try to cancel() all
|
||||
* remaining promises and throw an Exception.
|
||||
* If the promise did not reject with an `Exception`, then this function will
|
||||
* throw an `UnexpectedValueException` instead.
|
||||
*
|
||||
* If no $timeout is given and either promise stays pending, then this will
|
||||
* potentially wait/block forever until the last promise is settled.
|
||||
*
|
||||
* If a $timeout is given and either promise is still pending once the timeout
|
||||
* triggers, this will cancel() all pending promises and throw a `TimeoutException`.
|
||||
* This implies that if you pass a really small (or negative) value, it will still
|
||||
* start a timer and will thus trigger at the earliest possible time in the future.
|
||||
*
|
||||
* @param array $promises
|
||||
* @param LoopInterface $loop
|
||||
* @param null|float $timeout (optional) maximum timeout in seconds or null=wait forever
|
||||
* @return array returns an array with whatever each promise resolves to
|
||||
* @throws Exception when ANY promise is rejected
|
||||
* @throws TimeoutException if the $timeout is given and triggers
|
||||
*/
|
||||
function awaitAll(array $promises, LoopInterface $loop, $timeout = null)
|
||||
{
|
||||
// Explicitly overwrite argument with null value. This ensure that this
|
||||
// argument does not show up in the stack trace in PHP 7+ only.
|
||||
$all = $promises;
|
||||
$promises = null;
|
||||
|
||||
try {
|
||||
return await(Promise\all($all), $loop, $timeout);
|
||||
} catch (Exception $e) {
|
||||
// ANY of the given promises rejected or the timeout fired
|
||||
// => try to cancel all promises (rejected ones will be ignored anyway)
|
||||
_cancelAllPromises($all);
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* internal helper function used to iterate over an array of Promise instances and cancel() each
|
||||
*
|
||||
* @internal
|
||||
* @param array $promises
|
||||
*/
|
||||
function _cancelAllPromises(array $promises)
|
||||
{
|
||||
foreach ($promises as $promise) {
|
||||
if ($promise instanceof CancellablePromiseInterface) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
}
|
8
vendor/clue/block-react/src/functions_include.php
vendored
Normal file
8
vendor/clue/block-react/src/functions_include.php
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Block;
|
||||
|
||||
if (!function_exists('Clue\\React\\Block\\sleep')) {
|
||||
require __DIR__ . '/functions.php';
|
||||
}
|
||||
|
21
vendor/clue/buzz-react/LICENSE
vendored
Normal file
21
vendor/clue/buzz-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Christian Lück
|
||||
|
||||
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.
|
36
vendor/clue/buzz-react/composer.json
vendored
Normal file
36
vendor/clue/buzz-react/composer.json
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "clue/buzz-react",
|
||||
"description": "Simple, async PSR-7 HTTP client for concurrently processing any number of HTTP requests, built on top of ReactPHP",
|
||||
"keywords": ["HTTP client", "PSR-7", "HTTP", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/clue/reactphp-buzz",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Buzz\\": "src/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"psr/http-message": "^1.0",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
|
||||
"react/http-client": "^0.5.8",
|
||||
"react/promise": "^2.2.1 || ^1.2.1",
|
||||
"react/promise-stream": "^1.0 || ^0.1.1",
|
||||
"react/promise-timer": "^1.2",
|
||||
"react/socket": "^1.1",
|
||||
"react/stream": "^1.0 || ^0.7",
|
||||
"ringcentral/psr7": "^1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "^1.0",
|
||||
"clue/http-proxy-react": "^1.3",
|
||||
"clue/reactphp-ssh-proxy": "^1.0",
|
||||
"clue/socks-react": "^1.0",
|
||||
"phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35",
|
||||
"react/http": "^0.8"
|
||||
}
|
||||
}
|
338
vendor/clue/buzz-react/src/Browser.php
vendored
Normal file
338
vendor/clue/buzz-react/src/Browser.php
vendored
Normal file
@ -0,0 +1,338 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Buzz;
|
||||
|
||||
use Clue\React\Buzz\Io\Sender;
|
||||
use Clue\React\Buzz\Io\Transaction;
|
||||
use Clue\React\Buzz\Message\MessageFactory;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
class Browser
|
||||
{
|
||||
private $transaction;
|
||||
private $messageFactory;
|
||||
private $baseUri = null;
|
||||
|
||||
/** @var LoopInterface $loop */
|
||||
private $loop;
|
||||
|
||||
/**
|
||||
* The `Browser` is responsible for sending HTTP requests to your HTTP server
|
||||
* and keeps track of pending incoming HTTP responses.
|
||||
* It also registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage).
|
||||
*
|
||||
* ```php
|
||||
* $loop = React\EventLoop\Factory::create();
|
||||
*
|
||||
* $browser = new Browser($loop);
|
||||
* ```
|
||||
*
|
||||
* If you need custom connector settings (DNS resolution, TLS parameters, timeouts,
|
||||
* proxy servers etc.), you can explicitly pass a custom instance of the
|
||||
* [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface):
|
||||
*
|
||||
* ```php
|
||||
* $connector = new \React\Socket\Connector($loop, array(
|
||||
* 'dns' => '127.0.0.1',
|
||||
* 'tcp' => array(
|
||||
* 'bindto' => '192.168.10.1:0'
|
||||
* ),
|
||||
* 'tls' => array(
|
||||
* 'verify_peer' => false,
|
||||
* 'verify_peer_name' => false
|
||||
* )
|
||||
* ));
|
||||
*
|
||||
* $browser = new Browser($loop, $connector);
|
||||
* ```
|
||||
*
|
||||
* @param LoopInterface $loop
|
||||
* @param ConnectorInterface|null $connector [optional] Connector to use.
|
||||
* Should be `null` in order to use default Connector.
|
||||
*/
|
||||
public function __construct(LoopInterface $loop, ConnectorInterface $connector = null)
|
||||
{
|
||||
$this->messageFactory = new MessageFactory();
|
||||
$this->transaction = new Transaction(
|
||||
Sender::createFromLoop($loop, $connector, $this->messageFactory),
|
||||
$this->messageFactory,
|
||||
$loop
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|UriInterface $url URI for the request.
|
||||
* @param array $headers
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function get($url, array $headers = array())
|
||||
{
|
||||
return $this->send($this->messageFactory->request('GET', $url, $headers));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the outgoing request body is a `string`. If you're using a
|
||||
* streaming request body (`ReadableStreamInterface`), it will default to
|
||||
* using `Transfer-Encoding: chunked` or you have to explicitly pass in a
|
||||
* matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new ThroughStream();
|
||||
* $loop->addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->post($url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* @param string|UriInterface $url URI for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $contents
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function post($url, array $headers = array(), $contents = '')
|
||||
{
|
||||
return $this->send($this->messageFactory->request('POST', $url, $headers, $contents));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|UriInterface $url URI for the request.
|
||||
* @param array $headers
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function head($url, array $headers = array())
|
||||
{
|
||||
return $this->send($this->messageFactory->request('HEAD', $url, $headers));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the outgoing request body is a `string`. If you're using a
|
||||
* streaming request body (`ReadableStreamInterface`), it will default to
|
||||
* using `Transfer-Encoding: chunked` or you have to explicitly pass in a
|
||||
* matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new ThroughStream();
|
||||
* $loop->addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->patch($url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* @param string|UriInterface $url URI for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $contents
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function patch($url, array $headers = array(), $contents = '')
|
||||
{
|
||||
return $this->send($this->messageFactory->request('PATCH', $url , $headers, $contents));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the outgoing request body is a `string`. If you're using a
|
||||
* streaming request body (`ReadableStreamInterface`), it will default to
|
||||
* using `Transfer-Encoding: chunked` or you have to explicitly pass in a
|
||||
* matching `Content-Length` request header like so:
|
||||
*
|
||||
* ```php
|
||||
* $body = new ThroughStream();
|
||||
* $loop->addTimer(1.0, function () use ($body) {
|
||||
* $body->end("hello world");
|
||||
* });
|
||||
*
|
||||
* $browser->put($url, array('Content-Length' => '11'), $body);
|
||||
* ```
|
||||
*
|
||||
* @param string|UriInterface $url URI for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $contents
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function put($url, array $headers = array(), $contents = '')
|
||||
{
|
||||
return $this->send($this->messageFactory->request('PUT', $url, $headers, $contents));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|UriInterface $url URI for the request.
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $contents
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function delete($url, array $headers = array(), $contents = '')
|
||||
{
|
||||
return $this->send($this->messageFactory->request('DELETE', $url, $headers, $contents));
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits an array of field values similar to submitting a form (`application/x-www-form-urlencoded`).
|
||||
*
|
||||
* ```php
|
||||
* $browser->submit($url, array('user' => 'test', 'password' => 'secret'));
|
||||
* ```
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header for the encoded length of the given `$fields`.
|
||||
*
|
||||
* @param string|UriInterface $url URI for the request.
|
||||
* @param array $fields
|
||||
* @param array $headers
|
||||
* @param string $method
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function submit($url, array $fields, $headers = array(), $method = 'POST')
|
||||
{
|
||||
$headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
$contents = http_build_query($fields);
|
||||
|
||||
return $this->send($this->messageFactory->request($method, $url, $headers, $contents));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an arbitrary instance implementing the [`RequestInterface`](#requestinterface) (PSR-7).
|
||||
*
|
||||
* All the above [predefined methods](#methods) default to sending requests as HTTP/1.0.
|
||||
* If you need a custom HTTP protocol method or version, then you may want to use this
|
||||
* method:
|
||||
*
|
||||
* ```php
|
||||
* $request = new Request('OPTIONS', $url);
|
||||
* $request = $request->withProtocolVersion('1.1');
|
||||
*
|
||||
* $browser->send($request)->then(…);
|
||||
* ```
|
||||
*
|
||||
* This method will automatically add a matching `Content-Length` request
|
||||
* header if the size of the outgoing request body is known and non-empty.
|
||||
* For an empty request body, if will only include a `Content-Length: 0`
|
||||
* request header if the request method usually expects a request body (only
|
||||
* applies to `POST`, `PUT` and `PATCH`).
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function send(RequestInterface $request)
|
||||
{
|
||||
if ($this->baseUri !== null) {
|
||||
// ensure we're actually below the base URI
|
||||
$request = $request->withUri($this->messageFactory->expandBase($request->getUri(), $this->baseUri));
|
||||
}
|
||||
|
||||
return $this->transaction->send($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the base URI used to resolve relative URIs to.
|
||||
*
|
||||
* ```php
|
||||
* $newBrowser = $browser->withBase('http://api.example.com/v3');
|
||||
* ```
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withBase()` method
|
||||
* actually returns a *new* [`Browser`](#browser) instance with the given base URI applied.
|
||||
*
|
||||
* Any requests to relative URIs will then be processed by first prepending
|
||||
* the (absolute) base URI.
|
||||
* Please note that this merely prepends the base URI and does *not* resolve
|
||||
* any relative path references (like `../` etc.).
|
||||
* This is mostly useful for (RESTful) API calls where all endpoints (URIs)
|
||||
* are located under a common base URI scheme.
|
||||
*
|
||||
* ```php
|
||||
* // will request http://api.example.com/v3/example
|
||||
* $newBrowser->get('/example')->then(…);
|
||||
* ```
|
||||
*
|
||||
* By definition of this library, a given base URI MUST always absolute and
|
||||
* can not contain any placeholders.
|
||||
*
|
||||
* @param string|UriInterface $baseUri absolute base URI
|
||||
* @return self
|
||||
* @throws InvalidArgumentException if the given $baseUri is not a valid absolute URI
|
||||
* @see self::withoutBase()
|
||||
*/
|
||||
public function withBase($baseUri)
|
||||
{
|
||||
$browser = clone $this;
|
||||
$browser->baseUri = $this->messageFactory->uri($baseUri);
|
||||
|
||||
if ($browser->baseUri->getScheme() === '' || $browser->baseUri->getHost() === '') {
|
||||
throw new \InvalidArgumentException('Base URI must be absolute');
|
||||
}
|
||||
|
||||
return $browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the base URI.
|
||||
*
|
||||
* ```php
|
||||
* $newBrowser = $browser->withoutBase();
|
||||
* ```
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. the `withoutBase()` method
|
||||
* actually returns a *new* [`Browser`](#browser) instance without any base URI applied.
|
||||
*
|
||||
* See also [`withBase()`](#withbase).
|
||||
*
|
||||
* @return self
|
||||
* @see self::withBase()
|
||||
*/
|
||||
public function withoutBase()
|
||||
{
|
||||
$browser = clone $this;
|
||||
$browser->baseUri = null;
|
||||
|
||||
return $browser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the [options](#options) to use:
|
||||
*
|
||||
* The [`Browser`](#browser) class exposes several options for the handling of
|
||||
* HTTP transactions. These options resemble some of PHP's
|
||||
* [HTTP context options](http://php.net/manual/en/context.http.php) and
|
||||
* can be controlled via the following API (and their defaults):
|
||||
*
|
||||
* ```php
|
||||
* $newBrowser = $browser->withOptions(array(
|
||||
* 'timeout' => null,
|
||||
* 'followRedirects' => true,
|
||||
* 'maxRedirects' => 10,
|
||||
* 'obeySuccessCode' => true,
|
||||
* 'streaming' => false,
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* See also [timeouts](#timeouts), [redirects](#redirects) and
|
||||
* [streaming](#streaming) for more details.
|
||||
*
|
||||
* Notice that the [`Browser`](#browser) is an immutable object, i.e. this
|
||||
* method actually returns a *new* [`Browser`](#browser) instance with the
|
||||
* options applied.
|
||||
*
|
||||
* @param array $options
|
||||
* @return self
|
||||
*/
|
||||
public function withOptions(array $options)
|
||||
{
|
||||
$browser = clone $this;
|
||||
$browser->transaction = $this->transaction->withOptions($options);
|
||||
|
||||
return $browser;
|
||||
}
|
||||
}
|
151
vendor/clue/buzz-react/src/Io/Sender.php
vendored
Normal file
151
vendor/clue/buzz-react/src/Io/Sender.php
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Buzz\Io;
|
||||
|
||||
use Clue\React\Buzz\Message\MessageFactory;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\HttpClient\Client as HttpClient;
|
||||
use React\HttpClient\Response as ResponseStream;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* [Internal] Sends requests and receives responses
|
||||
*
|
||||
* The `Sender` is responsible for passing the [`RequestInterface`](#requestinterface) objects to
|
||||
* the underlying [`HttpClient`](https://github.com/reactphp/http-client) library
|
||||
* and keeps track of its transmission and converts its reponses back to [`ResponseInterface`](#responseinterface) objects.
|
||||
*
|
||||
* It also registers everything with the main [`EventLoop`](https://github.com/reactphp/event-loop#usage)
|
||||
* and the default [`Connector`](https://github.com/reactphp/socket-client) and [DNS `Resolver`](https://github.com/reactphp/dns).
|
||||
*
|
||||
* The `Sender` class mostly exists in order to abstract changes on the underlying
|
||||
* components away from this package in order to provide backwards and forwards
|
||||
* compatibility.
|
||||
*
|
||||
* @internal You SHOULD NOT rely on this API, it is subject to change without prior notice!
|
||||
* @see Browser
|
||||
*/
|
||||
class Sender
|
||||
{
|
||||
/**
|
||||
* create a new default sender attached to the given event loop
|
||||
*
|
||||
* This method is used internally to create the "default sender".
|
||||
*
|
||||
* You may also use this method if you need custom DNS or connector
|
||||
* settings. You can use this method manually like this:
|
||||
*
|
||||
* ```php
|
||||
* $connector = new \React\Socket\Connector($loop);
|
||||
* $sender = \Clue\React\Buzz\Io\Sender::createFromLoop($loop, $connector);
|
||||
* ```
|
||||
*
|
||||
* @param LoopInterface $loop
|
||||
* @param ConnectorInterface|null $connector
|
||||
* @return self
|
||||
*/
|
||||
public static function createFromLoop(LoopInterface $loop, ConnectorInterface $connector = null, MessageFactory $messageFactory)
|
||||
{
|
||||
return new self(new HttpClient($loop, $connector), $messageFactory);
|
||||
}
|
||||
|
||||
private $http;
|
||||
private $messageFactory;
|
||||
|
||||
/**
|
||||
* [internal] Instantiate Sender
|
||||
*
|
||||
* @param HttpClient $http
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(HttpClient $http, MessageFactory $messageFactory)
|
||||
{
|
||||
$this->http = $http;
|
||||
$this->messageFactory = $messageFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @internal
|
||||
* @param RequestInterface $request
|
||||
* @return PromiseInterface Promise<ResponseInterface, Exception>
|
||||
*/
|
||||
public function send(RequestInterface $request)
|
||||
{
|
||||
$body = $request->getBody();
|
||||
$size = $body->getSize();
|
||||
|
||||
if ($size !== null && $size !== 0) {
|
||||
// automatically assign a "Content-Length" request header if the body size is known and non-empty
|
||||
$request = $request->withHeader('Content-Length', (string)$size);
|
||||
} elseif ($size === 0 && \in_array($request->getMethod(), array('POST', 'PUT', 'PATCH'))) {
|
||||
// only assign a "Content-Length: 0" request header if the body is expected for certain methods
|
||||
$request = $request->withHeader('Content-Length', '0');
|
||||
} elseif ($body instanceof ReadableStreamInterface && $body->isReadable() && !$request->hasHeader('Content-Length')) {
|
||||
// use "Transfer-Encoding: chunked" when this is a streaming body and body size is unknown
|
||||
$request = $request->withHeader('Transfer-Encoding', 'chunked');
|
||||
} else {
|
||||
// do not use chunked encoding if size is known or if this is an empty request body
|
||||
$size = 0;
|
||||
}
|
||||
|
||||
$headers = array();
|
||||
foreach ($request->getHeaders() as $name => $values) {
|
||||
$headers[$name] = implode(', ', $values);
|
||||
}
|
||||
|
||||
$requestStream = $this->http->request($request->getMethod(), (string)$request->getUri(), $headers, $request->getProtocolVersion());
|
||||
|
||||
$deferred = new Deferred(function ($_, $reject) use ($requestStream) {
|
||||
// close request stream if request is cancelled
|
||||
$reject(new \RuntimeException('Request cancelled'));
|
||||
$requestStream->close();
|
||||
});
|
||||
|
||||
$requestStream->on('error', function($error) use ($deferred) {
|
||||
$deferred->reject($error);
|
||||
});
|
||||
|
||||
$messageFactory = $this->messageFactory;
|
||||
$requestStream->on('response', function (ResponseStream $responseStream) use ($deferred, $messageFactory) {
|
||||
// apply response header values from response stream
|
||||
$deferred->resolve($messageFactory->response(
|
||||
$responseStream->getVersion(),
|
||||
$responseStream->getCode(),
|
||||
$responseStream->getReasonPhrase(),
|
||||
$responseStream->getHeaders(),
|
||||
$responseStream
|
||||
));
|
||||
});
|
||||
|
||||
if ($body instanceof ReadableStreamInterface) {
|
||||
if ($body->isReadable()) {
|
||||
if ($size !== null) {
|
||||
// length is known => just write to request
|
||||
$body->pipe($requestStream);
|
||||
} else {
|
||||
// length unknown => apply chunked transfer-encoding
|
||||
// this should be moved somewhere else obviously
|
||||
$body->on('data', function ($data) use ($requestStream) {
|
||||
$requestStream->write(dechex(strlen($data)) . "\r\n" . $data . "\r\n");
|
||||
});
|
||||
$body->on('end', function() use ($requestStream) {
|
||||
$requestStream->end("0\r\n\r\n");
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// stream is not readable => end request without body
|
||||
$requestStream->end();
|
||||
}
|
||||
} else {
|
||||
// body is fully buffered => write as one chunk
|
||||
$requestStream->end((string)$body);
|
||||
}
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
247
vendor/clue/buzz-react/src/Io/Transaction.php
vendored
Normal file
247
vendor/clue/buzz-react/src/Io/Transaction.php
vendored
Normal file
@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Buzz\Io;
|
||||
|
||||
use Clue\React\Buzz\Message\ResponseException;
|
||||
use Clue\React\Buzz\Message\MessageFactory;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Timer\TimeoutException;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class Transaction
|
||||
{
|
||||
private $sender;
|
||||
private $messageFactory;
|
||||
private $loop;
|
||||
|
||||
// context: http.timeout (ini_get('default_socket_timeout'): 60)
|
||||
private $timeout;
|
||||
|
||||
// context: http.follow_location (true)
|
||||
private $followRedirects = true;
|
||||
|
||||
// context: http.max_redirects (10)
|
||||
private $maxRedirects = 10;
|
||||
|
||||
// context: http.ignore_errors (false)
|
||||
private $obeySuccessCode = true;
|
||||
|
||||
private $streaming = false;
|
||||
|
||||
public function __construct(Sender $sender, MessageFactory $messageFactory, LoopInterface $loop)
|
||||
{
|
||||
$this->sender = $sender;
|
||||
$this->messageFactory = $messageFactory;
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @return self returns new instance, without modifying existing instance
|
||||
*/
|
||||
public function withOptions(array $options)
|
||||
{
|
||||
$transaction = clone $this;
|
||||
foreach ($options as $name => $value) {
|
||||
if (property_exists($transaction, $name)) {
|
||||
// restore default value if null is given
|
||||
if ($value === null) {
|
||||
$default = new self($this->sender, $this->messageFactory, $this->loop);
|
||||
$value = $default->$name;
|
||||
}
|
||||
|
||||
$transaction->$name = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
public function send(RequestInterface $request)
|
||||
{
|
||||
$deferred = new Deferred(function () use (&$deferred) {
|
||||
if (isset($deferred->pending)) {
|
||||
$deferred->pending->cancel();
|
||||
unset($deferred->pending);
|
||||
}
|
||||
});
|
||||
|
||||
$deferred->numRequests = 0;
|
||||
|
||||
$this->next($request, $deferred)->then(
|
||||
array($deferred, 'resolve'),
|
||||
array($deferred, 'reject')
|
||||
);
|
||||
|
||||
// use timeout from options or default to PHP's default_socket_timeout (60)
|
||||
$timeout = (float)($this->timeout !== null ? $this->timeout : ini_get("default_socket_timeout"));
|
||||
if ($timeout < 0) {
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
return \React\Promise\Timer\timeout($deferred->promise(), $timeout, $this->loop)->then(null, function ($e) {
|
||||
if ($e instanceof TimeoutException) {
|
||||
throw new \RuntimeException(
|
||||
'Request timed out after ' . $e->getTimeout() . ' seconds'
|
||||
);
|
||||
}
|
||||
throw $e;
|
||||
});
|
||||
}
|
||||
|
||||
private function next(RequestInterface $request, Deferred $deferred)
|
||||
{
|
||||
$this->progress('request', array($request));
|
||||
|
||||
$that = $this;
|
||||
++$deferred->numRequests;
|
||||
|
||||
$promise = $this->sender->send($request);
|
||||
|
||||
if (!$this->streaming) {
|
||||
$promise = $promise->then(function ($response) use ($deferred, $that) {
|
||||
return $that->bufferResponse($response, $deferred);
|
||||
});
|
||||
}
|
||||
|
||||
$deferred->pending = $promise;
|
||||
|
||||
return $promise->then(
|
||||
function (ResponseInterface $response) use ($request, $that, $deferred) {
|
||||
return $that->onResponse($response, $request, $deferred);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param ResponseInterface $response
|
||||
* @return PromiseInterface Promise<ResponseInterface, Exception>
|
||||
*/
|
||||
public function bufferResponse(ResponseInterface $response, $deferred)
|
||||
{
|
||||
$stream = $response->getBody();
|
||||
|
||||
// body is not streaming => already buffered
|
||||
if (!$stream instanceof ReadableStreamInterface) {
|
||||
return \React\Promise\resolve($response);
|
||||
}
|
||||
|
||||
// buffer stream and resolve with buffered body
|
||||
$messageFactory = $this->messageFactory;
|
||||
$promise = \React\Promise\Stream\buffer($stream)->then(
|
||||
function ($body) use ($response, $messageFactory) {
|
||||
return $response->withBody($messageFactory->body($body));
|
||||
},
|
||||
function ($e) use ($stream) {
|
||||
// try to close stream if buffering fails (or is cancelled)
|
||||
$stream->close();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
);
|
||||
|
||||
$deferred->pending = $promise;
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param ResponseInterface $response
|
||||
* @param RequestInterface $request
|
||||
* @throws ResponseException
|
||||
* @return ResponseInterface|PromiseInterface
|
||||
*/
|
||||
public function onResponse(ResponseInterface $response, RequestInterface $request, $deferred)
|
||||
{
|
||||
$this->progress('response', array($response, $request));
|
||||
|
||||
// follow 3xx (Redirection) response status codes if Location header is present and not explicitly disabled
|
||||
// @link https://tools.ietf.org/html/rfc7231#section-6.4
|
||||
if ($this->followRedirects && ($response->getStatusCode() >= 300 && $response->getStatusCode() < 400) && $response->hasHeader('Location')) {
|
||||
return $this->onResponseRedirect($response, $request, $deferred);
|
||||
}
|
||||
|
||||
// only status codes 200-399 are considered to be valid, reject otherwise
|
||||
if ($this->obeySuccessCode && ($response->getStatusCode() < 200 || $response->getStatusCode() >= 400)) {
|
||||
throw new ResponseException($response);
|
||||
}
|
||||
|
||||
// resolve our initial promise
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
* @param RequestInterface $request
|
||||
* @return PromiseInterface
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
private function onResponseRedirect(ResponseInterface $response, RequestInterface $request, Deferred $deferred)
|
||||
{
|
||||
// resolve location relative to last request URI
|
||||
$location = $this->messageFactory->uriRelative($request->getUri(), $response->getHeaderLine('Location'));
|
||||
|
||||
$request = $this->makeRedirectRequest($request, $location);
|
||||
$this->progress('redirect', array($request));
|
||||
|
||||
if ($deferred->numRequests >= $this->maxRedirects) {
|
||||
throw new \RuntimeException('Maximum number of redirects (' . $this->maxRedirects . ') exceeded');
|
||||
}
|
||||
|
||||
return $this->next($request, $deferred);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
* @param UriInterface $location
|
||||
* @return RequestInterface
|
||||
*/
|
||||
private function makeRedirectRequest(RequestInterface $request, UriInterface $location)
|
||||
{
|
||||
$originalHost = $request->getUri()->getHost();
|
||||
$request = $request
|
||||
->withoutHeader('Host')
|
||||
->withoutHeader('Content-Type')
|
||||
->withoutHeader('Content-Length');
|
||||
|
||||
// Remove authorization if changing hostnames (but not if just changing ports or protocols).
|
||||
if ($location->getHost() !== $originalHost) {
|
||||
$request = $request->withoutHeader('Authorization');
|
||||
}
|
||||
|
||||
// naïve approach..
|
||||
$method = ($request->getMethod() === 'HEAD') ? 'HEAD' : 'GET';
|
||||
|
||||
return $this->messageFactory->request($method, $location, $request->getHeaders());
|
||||
}
|
||||
|
||||
private function progress($name, array $args = array())
|
||||
{
|
||||
return;
|
||||
|
||||
echo $name;
|
||||
|
||||
foreach ($args as $arg) {
|
||||
echo ' ';
|
||||
if ($arg instanceof ResponseInterface) {
|
||||
echo 'HTTP/' . $arg->getProtocolVersion() . ' ' . $arg->getStatusCode() . ' ' . $arg->getReasonPhrase();
|
||||
} elseif ($arg instanceof RequestInterface) {
|
||||
echo $arg->getMethod() . ' ' . $arg->getRequestTarget() . ' HTTP/' . $arg->getProtocolVersion();
|
||||
} else {
|
||||
echo $arg;
|
||||
}
|
||||
}
|
||||
|
||||
echo PHP_EOL;
|
||||
}
|
||||
}
|
126
vendor/clue/buzz-react/src/Message/MessageFactory.php
vendored
Normal file
126
vendor/clue/buzz-react/src/Message/MessageFactory.php
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Buzz\Message;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use RingCentral\Psr7\Request;
|
||||
use RingCentral\Psr7\Response;
|
||||
use RingCentral\Psr7\Uri;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class MessageFactory
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of RequestInterface for the given request parameters
|
||||
*
|
||||
* @param string $method
|
||||
* @param string|UriInterface $uri
|
||||
* @param array $headers
|
||||
* @param string|ReadableStreamInterface $content
|
||||
* @return Request
|
||||
*/
|
||||
public function request($method, $uri, $headers = array(), $content = '')
|
||||
{
|
||||
return new Request($method, $uri, $headers, $this->body($content), '1.0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of ResponseInterface for the given response parameters
|
||||
*
|
||||
* @param string $version
|
||||
* @param int $status
|
||||
* @param string $reason
|
||||
* @param array $headers
|
||||
* @param ReadableStreamInterface|string $body
|
||||
* @return Response
|
||||
* @uses self::body()
|
||||
*/
|
||||
public function response($version, $status, $reason, $headers = array(), $body = '')
|
||||
{
|
||||
return new Response($status, $headers, $this->body($body), $version, $reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of StreamInterface for the given body contents
|
||||
*
|
||||
* @param ReadableStreamInterface|string $body
|
||||
* @return StreamInterface
|
||||
*/
|
||||
public function body($body)
|
||||
{
|
||||
if ($body instanceof ReadableStreamInterface) {
|
||||
return new ReadableBodyStream($body);
|
||||
}
|
||||
|
||||
return \RingCentral\Psr7\stream_for($body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of UriInterface for the given URI string
|
||||
*
|
||||
* @param string $uri
|
||||
* @return UriInterface
|
||||
*/
|
||||
public function uri($uri)
|
||||
{
|
||||
return new Uri($uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of UriInterface for the given URI string relative to the given base URI
|
||||
*
|
||||
* @param UriInterface $base
|
||||
* @param string $uri
|
||||
* @return UriInterface
|
||||
*/
|
||||
public function uriRelative(UriInterface $base, $uri)
|
||||
{
|
||||
return Uri::resolve($base, $uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the given relative or absolute $uri by appending it behind $this base URI
|
||||
*
|
||||
* The given $uri parameter can be either a relative or absolute URI and
|
||||
* as such can not contain any URI template placeholders.
|
||||
*
|
||||
* As such, the outcome of this method represents a valid, absolute URI
|
||||
* which will be returned as an instance implementing `UriInterface`.
|
||||
*
|
||||
* If the given $uri is a relative URI, it will simply be appended behind $base URI.
|
||||
*
|
||||
* If the given $uri is an absolute URI, it will simply be verified to
|
||||
* be *below* the given $base URI.
|
||||
*
|
||||
* @param UriInterface $uri
|
||||
* @param UriInterface $base
|
||||
* @return UriInterface
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
public function expandBase(UriInterface $uri, UriInterface $base)
|
||||
{
|
||||
if ($uri->getScheme() !== '') {
|
||||
if (strpos((string)$uri, (string)$base) !== 0) {
|
||||
throw new \UnexpectedValueException('Invalid base, "' . $uri . '" does not appear to be below "' . $base . '"');
|
||||
}
|
||||
return $uri;
|
||||
}
|
||||
|
||||
$uri = (string)$uri;
|
||||
$base = (string)$base;
|
||||
|
||||
if ($uri !== '' && substr($base, -1) !== '/' && substr($uri, 0, 1) !== '?') {
|
||||
$base .= '/';
|
||||
}
|
||||
|
||||
if (isset($uri[0]) && $uri[0] === '/') {
|
||||
$uri = substr($uri, 1);
|
||||
}
|
||||
|
||||
return $this->uri($base . $uri);
|
||||
}
|
||||
}
|
135
vendor/clue/buzz-react/src/Message/ReadableBodyStream.php
vendored
Normal file
135
vendor/clue/buzz-react/src/Message/ReadableBodyStream.php
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Buzz\Message;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ReadableBodyStream extends EventEmitter implements ReadableStreamInterface, StreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $closed = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
$that = $this;
|
||||
$input->on('data', function ($data) use ($that) {
|
||||
$that->emit('data', array($data));
|
||||
});
|
||||
$input->on('error', function ($error) use ($that) {
|
||||
$that->emit('error', array($error));
|
||||
$that->close();
|
||||
});
|
||||
$input->on('end', function () use ($that) {
|
||||
$that->emit('end');
|
||||
$that->close();
|
||||
});
|
||||
$input->on('close', array($that, 'close'));
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
$this->closed = true;
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function eof()
|
||||
{
|
||||
return !$this->isReadable();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function detach()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function getSize()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function tell()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function isSeekable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function seek($offset, $whence = SEEK_SET)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function write($string)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function read($length)
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function getContents()
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
return ($key === null) ? array() : null;
|
||||
}
|
||||
}
|
43
vendor/clue/buzz-react/src/Message/ResponseException.php
vendored
Normal file
43
vendor/clue/buzz-react/src/Message/ResponseException.php
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Buzz\Message;
|
||||
|
||||
use RuntimeException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* The `ResponseException` is an `Exception` sub-class that will be used to reject
|
||||
* a request promise if the remote server returns a non-success status code
|
||||
* (anything but 2xx or 3xx).
|
||||
* You can control this behavior via the ["obeySuccessCode" option](#options).
|
||||
*
|
||||
* The `getCode(): int` method can be used to
|
||||
* return the HTTP response status code.
|
||||
*/
|
||||
class ResponseException extends RuntimeException
|
||||
{
|
||||
private $response;
|
||||
|
||||
public function __construct(ResponseInterface $response, $message = null, $code = null, $previous = null)
|
||||
{
|
||||
if ($message === null) {
|
||||
$message = 'HTTP status code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ')';
|
||||
}
|
||||
if ($code === null) {
|
||||
$code = $response->getStatusCode();
|
||||
}
|
||||
parent::__construct($message, $code, $previous);
|
||||
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access its underlying [`ResponseInterface`](#responseinterface) object.
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getResponse()
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
}
|
21
vendor/clue/connection-manager-extra/LICENSE
vendored
Normal file
21
vendor/clue/connection-manager-extra/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Christian Lück
|
||||
|
||||
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.
|
26
vendor/clue/connection-manager-extra/composer.json
vendored
Normal file
26
vendor/clue/connection-manager-extra/composer.json
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "clue/connection-manager-extra",
|
||||
"description": "Extra decorators for creating async TCP/IP connections, built on top of ReactPHP's Socket component",
|
||||
"keywords": ["Socket", "network", "connection", "timeout", "delay", "reject", "repeat", "retry", "random", "acl", "firewall", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/php-connection-manager-extra",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "ConnectionManager\\Extra\\": "src" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/socket": "^1.0 || ^0.8 || ^0.7",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5",
|
||||
"react/promise": "^2.1 || ^1.2.1",
|
||||
"react/promise-timer": "^1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.0 || ^4.8"
|
||||
}
|
||||
}
|
30
vendor/clue/connection-manager-extra/src/ConnectionManagerDelay.php
vendored
Normal file
30
vendor/clue/connection-manager-extra/src/ConnectionManagerDelay.php
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Timer;
|
||||
|
||||
class ConnectionManagerDelay implements ConnectorInterface
|
||||
{
|
||||
private $connectionManager;
|
||||
private $delay;
|
||||
private $loop;
|
||||
|
||||
public function __construct(ConnectorInterface $connectionManager, $delay, LoopInterface $loop)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
$this->delay = $delay;
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$connectionManager = $this->connectionManager;
|
||||
|
||||
return Timer\resolve($this->delay, $this->loop)->then(function () use ($connectionManager, $uri) {
|
||||
return $connectionManager->connect($uri);
|
||||
});
|
||||
}
|
||||
}
|
41
vendor/clue/connection-manager-extra/src/ConnectionManagerReject.php
vendored
Normal file
41
vendor/clue/connection-manager-extra/src/ConnectionManagerReject.php
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Promise;
|
||||
use Exception;
|
||||
|
||||
// a simple connection manager that rejects every single connection attempt
|
||||
class ConnectionManagerReject implements ConnectorInterface
|
||||
{
|
||||
private $reason = 'Connection rejected';
|
||||
|
||||
/**
|
||||
* @param null|string|callable $reason
|
||||
*/
|
||||
public function __construct($reason = null)
|
||||
{
|
||||
if ($reason !== null) {
|
||||
$this->reason = $reason;
|
||||
}
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$reason = $this->reason;
|
||||
if (!is_string($reason)) {
|
||||
try {
|
||||
$reason = $reason($uri);
|
||||
} catch (\Exception $e) {
|
||||
$reason = $e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$reason instanceof \Exception) {
|
||||
$reason = new Exception($reason);
|
||||
}
|
||||
|
||||
return Promise\reject($reason);
|
||||
}
|
||||
}
|
52
vendor/clue/connection-manager-extra/src/ConnectionManagerRepeat.php
vendored
Normal file
52
vendor/clue/connection-manager-extra/src/ConnectionManagerRepeat.php
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
use InvalidArgumentException;
|
||||
use Exception;
|
||||
use React\Promise\Promise;
|
||||
use React\Promise\CancellablePromiseInterface;
|
||||
|
||||
class ConnectionManagerRepeat implements ConnectorInterface
|
||||
{
|
||||
protected $connectionManager;
|
||||
protected $maximumTries;
|
||||
|
||||
public function __construct(ConnectorInterface $connectionManager, $maximumTries)
|
||||
{
|
||||
if ($maximumTries < 1) {
|
||||
throw new InvalidArgumentException('Maximum number of tries must be >= 1');
|
||||
}
|
||||
$this->connectionManager = $connectionManager;
|
||||
$this->maximumTries = $maximumTries;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$tries = $this->maximumTries;
|
||||
$connector = $this->connectionManager;
|
||||
|
||||
return new Promise(function ($resolve, $reject) use ($uri, &$pending, &$tries, $connector) {
|
||||
$try = function ($error = null) use (&$try, &$pending, &$tries, $uri, $connector, $resolve, $reject) {
|
||||
if ($tries > 0) {
|
||||
--$tries;
|
||||
$pending = $connector->connect($uri);
|
||||
$pending->then($resolve, $try);
|
||||
} else {
|
||||
$reject(new Exception('Connection still fails even after retrying', 0, $error));
|
||||
}
|
||||
};
|
||||
|
||||
$try();
|
||||
}, function ($_, $reject) use (&$pending, &$tries) {
|
||||
// stop retrying, reject results and cancel pending attempt
|
||||
$tries = 0;
|
||||
$reject(new \RuntimeException('Cancelled'));
|
||||
|
||||
if ($pending instanceof CancellablePromiseInterface) {
|
||||
$pending->cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
26
vendor/clue/connection-manager-extra/src/ConnectionManagerSwappable.php
vendored
Normal file
26
vendor/clue/connection-manager-extra/src/ConnectionManagerSwappable.php
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
|
||||
// connection manager decorator which simplifies exchanging the actual connection manager during runtime
|
||||
class ConnectionManagerSwappable implements ConnectorInterface
|
||||
{
|
||||
protected $connectionManager;
|
||||
|
||||
public function __construct(ConnectorInterface $connectionManager)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
return $this->connectionManager->connect($uri);
|
||||
}
|
||||
|
||||
public function setConnectionManager(ConnectorInterface $connectionManager)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
}
|
||||
}
|
35
vendor/clue/connection-manager-extra/src/ConnectionManagerTimeout.php
vendored
Normal file
35
vendor/clue/connection-manager-extra/src/ConnectionManagerTimeout.php
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Timer;
|
||||
|
||||
class ConnectionManagerTimeout implements ConnectorInterface
|
||||
{
|
||||
private $connectionManager;
|
||||
private $timeout;
|
||||
private $loop;
|
||||
|
||||
public function __construct(ConnectorInterface $connectionManager, $timeout, LoopInterface $loop)
|
||||
{
|
||||
$this->connectionManager = $connectionManager;
|
||||
$this->timeout = $timeout;
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$promise = $this->connectionManager->connect($uri);
|
||||
|
||||
return Timer\timeout($promise, $this->timeout, $this->loop)->then(null, function ($e) use ($promise) {
|
||||
// connection successfully established but timeout already expired => close successful connection
|
||||
$promise->then(function ($connection) {
|
||||
$connection->end();
|
||||
});
|
||||
|
||||
throw $e;
|
||||
});
|
||||
}
|
||||
}
|
36
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConcurrent.php
vendored
Normal file
36
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConcurrent.php
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra\Multiple;
|
||||
|
||||
use ConnectionManager\Extra\Multiple\ConnectionManagerConsecutive;
|
||||
use React\Promise;
|
||||
use React\Promise\CancellablePromiseInterface;
|
||||
|
||||
class ConnectionManagerConcurrent extends ConnectionManagerConsecutive
|
||||
{
|
||||
public function connect($uri)
|
||||
{
|
||||
$all = array();
|
||||
foreach ($this->managers as $connector) {
|
||||
/* @var $connection Connector */
|
||||
$all []= $connector->connect($uri);
|
||||
}
|
||||
return Promise\any($all)->then(function ($conn) use ($all) {
|
||||
// a connection attempt succeeded
|
||||
// => cancel all pending connection attempts
|
||||
foreach ($all as $promise) {
|
||||
if ($promise instanceof CancellablePromiseInterface) {
|
||||
$promise->cancel();
|
||||
}
|
||||
|
||||
// if promise resolves despite cancellation, immediately close stream
|
||||
$promise->then(function ($stream) use ($conn) {
|
||||
if ($stream !== $conn) {
|
||||
$stream->close();
|
||||
}
|
||||
});
|
||||
}
|
||||
return $conn;
|
||||
});
|
||||
}
|
||||
}
|
62
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConsecutive.php
vendored
Normal file
62
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConsecutive.php
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra\Multiple;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Promise;
|
||||
use UnderflowException;
|
||||
use React\Promise\CancellablePromiseInterface;
|
||||
|
||||
class ConnectionManagerConsecutive implements ConnectorInterface
|
||||
{
|
||||
protected $managers;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ConnectorInterface[] $managers
|
||||
*/
|
||||
public function __construct(array $managers)
|
||||
{
|
||||
if (!$managers) {
|
||||
throw new \InvalidArgumentException('List of connectors must not be empty');
|
||||
}
|
||||
$this->managers = $managers;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
return $this->tryConnection($this->managers, $uri);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ConnectorInterface[] $managers
|
||||
* @param string $uri
|
||||
* @return Promise
|
||||
* @internal
|
||||
*/
|
||||
public function tryConnection(array $managers, $uri)
|
||||
{
|
||||
return new Promise\Promise(function ($resolve, $reject) use (&$managers, &$pending, $uri) {
|
||||
$try = function () use (&$try, &$managers, $uri, $resolve, $reject, &$pending) {
|
||||
if (!$managers) {
|
||||
return $reject(new UnderflowException('No more managers to try to connect through'));
|
||||
}
|
||||
|
||||
$manager = array_shift($managers);
|
||||
$pending = $manager->connect($uri);
|
||||
$pending->then($resolve, $try);
|
||||
};
|
||||
|
||||
$try();
|
||||
}, function ($_, $reject) use (&$managers, &$pending) {
|
||||
// stop retrying, reject results and cancel pending attempt
|
||||
$managers = array();
|
||||
$reject(new \RuntimeException('Cancelled'));
|
||||
|
||||
if ($pending instanceof CancellablePromiseInterface) {
|
||||
$pending->cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
14
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerRandom.php
vendored
Normal file
14
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerRandom.php
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra\Multiple;
|
||||
|
||||
class ConnectionManagerRandom extends ConnectionManagerConsecutive
|
||||
{
|
||||
public function connect($uri)
|
||||
{
|
||||
$managers = $this->managers;
|
||||
shuffle($managers);
|
||||
|
||||
return $this->tryConnection($managers, $uri);
|
||||
}
|
||||
}
|
111
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerSelective.php
vendored
Normal file
111
vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerSelective.php
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace ConnectionManager\Extra\Multiple;
|
||||
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Promise;
|
||||
use UnderflowException;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class ConnectionManagerSelective implements ConnectorInterface
|
||||
{
|
||||
private $managers;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ConnectorInterface[] $managers
|
||||
*/
|
||||
public function __construct(array $managers)
|
||||
{
|
||||
foreach ($managers as $filter => $manager) {
|
||||
$host = $filter;
|
||||
$portMin = 0;
|
||||
$portMax = 65535;
|
||||
|
||||
// search colon (either single one OR preceded by "]" due to IPv6)
|
||||
$colon = strrpos($host, ':');
|
||||
if ($colon !== false && (strpos($host, ':') === $colon || substr($host, $colon - 1, 1) === ']' )) {
|
||||
if (!isset($host[$colon + 1])) {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" has no port after colon');
|
||||
}
|
||||
|
||||
$minus = strpos($host, '-', $colon);
|
||||
if ($minus === false) {
|
||||
$portMin = $portMax = (int)substr($host, $colon + 1);
|
||||
|
||||
if (substr($host, $colon + 1) !== (string)$portMin) {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" has no valid port after colon');
|
||||
}
|
||||
} else {
|
||||
$portMin = (int)substr($host, $colon + 1, ($minus - $colon));
|
||||
$portMax = (int)substr($host, $minus + 1);
|
||||
|
||||
if (substr($host, $colon + 1) !== ($portMin . '-' . $portMax)) {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" has no valid port range after colon');
|
||||
}
|
||||
|
||||
if ($portMin > $portMax) {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" has port range mixed up');
|
||||
}
|
||||
}
|
||||
$host = substr($host, 0, $colon);
|
||||
}
|
||||
|
||||
if ($host === '') {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" has an empty host');
|
||||
}
|
||||
|
||||
if (!$manager instanceof ConnectorInterface) {
|
||||
throw new InvalidArgumentException('Entry "' . $filter . '" is not a valid connector');
|
||||
}
|
||||
}
|
||||
|
||||
$this->managers = $managers;
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
$parts = parse_url((strpos($uri, '://') === false ? 'tcp://' : '') . $uri);
|
||||
if (!isset($parts) || !isset($parts['scheme'], $parts['host'], $parts['port'])) {
|
||||
return Promise\reject(new InvalidArgumentException('Invalid URI'));
|
||||
}
|
||||
|
||||
$connector = $this->getConnectorForTarget(
|
||||
trim($parts['host'], '[]'),
|
||||
$parts['port']
|
||||
);
|
||||
|
||||
if ($connector === null) {
|
||||
return Promise\reject(new UnderflowException('No connector for given target found'));
|
||||
}
|
||||
|
||||
return $connector->connect($uri);
|
||||
}
|
||||
|
||||
private function getConnectorForTarget($targetHost, $targetPort)
|
||||
{
|
||||
foreach ($this->managers as $host => $connector) {
|
||||
$portMin = 0;
|
||||
$portMax = 65535;
|
||||
|
||||
// search colon (either single one OR preceded by "]" due to IPv6)
|
||||
$colon = strrpos($host, ':');
|
||||
if ($colon !== false && (strpos($host, ':') === $colon || substr($host, $colon - 1, 1) === ']' )) {
|
||||
$minus = strpos($host, '-', $colon);
|
||||
if ($minus === false) {
|
||||
$portMin = $portMax = (int)substr($host, $colon + 1);
|
||||
} else {
|
||||
$portMin = (int)substr($host, $colon + 1, ($minus - $colon));
|
||||
$portMax = (int)substr($host, $minus + 1);
|
||||
}
|
||||
$host = trim(substr($host, 0, $colon), '[]');
|
||||
}
|
||||
|
||||
if ($targetPort >= $portMin && $targetPort <= $portMax && fnmatch($host, $targetHost)) {
|
||||
return $connector;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
21
vendor/clue/http-proxy-react/LICENSE
vendored
Normal file
21
vendor/clue/http-proxy-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Christian Lück
|
||||
|
||||
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.
|
30
vendor/clue/http-proxy-react/composer.json
vendored
Normal file
30
vendor/clue/http-proxy-react/composer.json
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "clue/http-proxy-react",
|
||||
"description": "Async HTTP proxy connector, use any TCP/IP-based protocol through an HTTP CONNECT proxy server, built on top of ReactPHP",
|
||||
"keywords": ["HTTP", "CONNECT", "proxy", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/clue/reactphp-http-proxy",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\HttpProxy\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Tests\\Clue\\React\\HttpProxy\\": "tests/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/promise": " ^2.1 || ^1.2.1",
|
||||
"react/socket": "^1.1",
|
||||
"ringcentral/psr7": "^1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.0 || ^4.8",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
|
||||
"clue/block-react": "^1.1"
|
||||
}
|
||||
}
|
268
vendor/clue/http-proxy-react/src/ProxyConnector.php
vendored
Normal file
268
vendor/clue/http-proxy-react/src/ProxyConnector.php
vendored
Normal file
@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\HttpProxy;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use RingCentral\Psr7;
|
||||
use React\Promise;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Socket\FixedUriConnector;
|
||||
|
||||
/**
|
||||
* A simple Connector that uses an HTTP CONNECT proxy to create plain TCP/IP connections to any destination
|
||||
*
|
||||
* [you] -> [proxy] -> [destination]
|
||||
*
|
||||
* This is most frequently used to issue HTTPS requests to your destination.
|
||||
* However, this is actually performed on a higher protocol layer and this
|
||||
* connector is actually inherently a general-purpose plain TCP/IP connector.
|
||||
*
|
||||
* Note that HTTP CONNECT proxies often restrict which ports one may connect to.
|
||||
* Many (public) proxy servers do in fact limit this to HTTPS (443) only.
|
||||
*
|
||||
* If you want to establish a TLS connection (such as HTTPS) between you and
|
||||
* your destination, you may want to wrap this connector in a SecureConnector
|
||||
* instance.
|
||||
*
|
||||
* Note that communication between the client and the proxy is usually via an
|
||||
* unencrypted, plain TCP/IP HTTP connection. Note that this is the most common
|
||||
* setup, because you can still establish a TLS connection between you and the
|
||||
* destination host as above.
|
||||
*
|
||||
* If you want to connect to a (rather rare) HTTPS proxy, you may want use its
|
||||
* HTTPS port (443) and use a SecureConnector instance to create a secure
|
||||
* connection to the proxy.
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc7231#section-4.3.6
|
||||
*/
|
||||
class ProxyConnector implements ConnectorInterface
|
||||
{
|
||||
private $connector;
|
||||
private $proxyUri;
|
||||
private $headers = '';
|
||||
|
||||
/**
|
||||
* Instantiate a new ProxyConnector which uses the given $proxyUrl
|
||||
*
|
||||
* @param string $proxyUrl The proxy URL may or may not contain a scheme and
|
||||
* port definition. The default port will be `80` for HTTP (or `443` for
|
||||
* HTTPS), but many common HTTP proxy servers use custom ports.
|
||||
* @param ConnectorInterface $connector In its most simple form, the given
|
||||
* connector will be a \React\Socket\Connector if you want to connect to
|
||||
* a given IP address.
|
||||
* @param array $httpHeaders Custom HTTP headers to be sent to the proxy.
|
||||
* @throws InvalidArgumentException if the proxy URL is invalid
|
||||
*/
|
||||
public function __construct($proxyUrl, ConnectorInterface $connector, array $httpHeaders = array())
|
||||
{
|
||||
// support `http+unix://` scheme for Unix domain socket (UDS) paths
|
||||
if (preg_match('/^http\+unix:\/\/(.*?@)?(.+?)$/', $proxyUrl, $match)) {
|
||||
// rewrite URI to parse authentication from dummy host
|
||||
$proxyUrl = 'http://' . $match[1] . 'localhost';
|
||||
|
||||
// connector uses Unix transport scheme and explicit path given
|
||||
$connector = new FixedUriConnector(
|
||||
'unix://' . $match[2],
|
||||
$connector
|
||||
);
|
||||
}
|
||||
|
||||
if (strpos($proxyUrl, '://') === false) {
|
||||
$proxyUrl = 'http://' . $proxyUrl;
|
||||
}
|
||||
|
||||
$parts = parse_url($proxyUrl);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host']) || ($parts['scheme'] !== 'http' && $parts['scheme'] !== 'https')) {
|
||||
throw new InvalidArgumentException('Invalid proxy URL "' . $proxyUrl . '"');
|
||||
}
|
||||
|
||||
// apply default port and TCP/TLS transport for given scheme
|
||||
if (!isset($parts['port'])) {
|
||||
$parts['port'] = $parts['scheme'] === 'https' ? 443 : 80;
|
||||
}
|
||||
$parts['scheme'] = $parts['scheme'] === 'https' ? 'tls' : 'tcp';
|
||||
|
||||
$this->connector = $connector;
|
||||
$this->proxyUri = $parts['scheme'] . '://' . $parts['host'] . ':' . $parts['port'];
|
||||
|
||||
// prepare Proxy-Authorization header if URI contains username/password
|
||||
if (isset($parts['user']) || isset($parts['pass'])) {
|
||||
$this->headers = 'Proxy-Authorization: Basic ' . base64_encode(
|
||||
rawurldecode($parts['user'] . ':' . (isset($parts['pass']) ? $parts['pass'] : ''))
|
||||
) . "\r\n";
|
||||
}
|
||||
|
||||
// append any additional custom request headers
|
||||
foreach ($httpHeaders as $name => $values) {
|
||||
foreach ((array)$values as $value) {
|
||||
$this->headers .= $name . ': ' . $value . "\r\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function connect($uri)
|
||||
{
|
||||
if (strpos($uri, '://') === false) {
|
||||
$uri = 'tcp://' . $uri;
|
||||
}
|
||||
|
||||
$parts = parse_url($uri);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
|
||||
return Promise\reject(new InvalidArgumentException('Invalid target URI specified'));
|
||||
}
|
||||
|
||||
$target = $parts['host'] . ':' . $parts['port'];
|
||||
|
||||
// construct URI to HTTP CONNECT proxy server to connect to
|
||||
$proxyUri = $this->proxyUri;
|
||||
|
||||
// append path from URI if given
|
||||
if (isset($parts['path'])) {
|
||||
$proxyUri .= $parts['path'];
|
||||
}
|
||||
|
||||
// parse query args
|
||||
$args = array();
|
||||
if (isset($parts['query'])) {
|
||||
parse_str($parts['query'], $args);
|
||||
}
|
||||
|
||||
// append hostname from URI to query string unless explicitly given
|
||||
if (!isset($args['hostname'])) {
|
||||
$args['hostname'] = trim($parts['host'], '[]');
|
||||
}
|
||||
|
||||
// append query string
|
||||
$proxyUri .= '?' . http_build_query($args, '', '&');
|
||||
|
||||
// append fragment from URI if given
|
||||
if (isset($parts['fragment'])) {
|
||||
$proxyUri .= '#' . $parts['fragment'];
|
||||
}
|
||||
|
||||
$connecting = $this->connector->connect($proxyUri);
|
||||
|
||||
$deferred = new Deferred(function ($_, $reject) use ($connecting, $uri) {
|
||||
$reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' cancelled while waiting for proxy (ECONNABORTED)',
|
||||
defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103
|
||||
));
|
||||
|
||||
// either close active connection or cancel pending connection attempt
|
||||
$connecting->then(function (ConnectionInterface $stream) {
|
||||
$stream->close();
|
||||
});
|
||||
$connecting->cancel();
|
||||
});
|
||||
|
||||
$headers = $this->headers;
|
||||
$connecting->then(function (ConnectionInterface $stream) use ($target, $headers, $deferred, $uri) {
|
||||
// keep buffering data until headers are complete
|
||||
$buffer = '';
|
||||
$stream->on('data', $fn = function ($chunk) use (&$buffer, $deferred, $stream, &$fn, $uri) {
|
||||
$buffer .= $chunk;
|
||||
|
||||
$pos = strpos($buffer, "\r\n\r\n");
|
||||
if ($pos !== false) {
|
||||
// end of headers received => stop buffering
|
||||
$stream->removeListener('data', $fn);
|
||||
$fn = null;
|
||||
|
||||
// try to parse headers as response message
|
||||
try {
|
||||
$response = Psr7\parse_response(substr($buffer, 0, $pos));
|
||||
} catch (Exception $e) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy returned invalid response (EBADMSG)',
|
||||
defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71,
|
||||
$e
|
||||
));
|
||||
$stream->close();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($response->getStatusCode() === 407) {
|
||||
// map status code 407 (Proxy Authentication Required) to EACCES
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy denied access with HTTP error code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (EACCES)',
|
||||
defined('SOCKET_EACCES') ? SOCKET_EACCES : 13
|
||||
));
|
||||
$stream->close();
|
||||
return;
|
||||
} elseif ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
|
||||
// map non-2xx status code to ECONNREFUSED
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy refused connection with HTTP error code ' . $response->getStatusCode() . ' (' . $response->getReasonPhrase() . ') (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
|
||||
));
|
||||
$stream->close();
|
||||
return;
|
||||
}
|
||||
|
||||
// all okay, resolve with stream instance
|
||||
$deferred->resolve($stream);
|
||||
|
||||
// emit remaining incoming as data event
|
||||
$buffer = (string)substr($buffer, $pos + 4);
|
||||
if ($buffer !== '') {
|
||||
$stream->emit('data', array($buffer));
|
||||
$buffer = '';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// stop buffering when 8 KiB have been read
|
||||
if (isset($buffer[8192])) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy response headers exceed maximum of 8 KiB (EMSGSIZE)',
|
||||
defined('SOCKET_EMSGSIZE') ? SOCKET_EMSGSIZE : 90
|
||||
));
|
||||
$stream->close();
|
||||
}
|
||||
});
|
||||
|
||||
$stream->on('error', function (Exception $e) use ($deferred, $uri) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because connection to proxy caused a stream error (EIO)',
|
||||
defined('SOCKET_EIO') ? SOCKET_EIO : 5,
|
||||
$e
|
||||
));
|
||||
});
|
||||
|
||||
$stream->on('close', function () use ($deferred, $uri) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because connection to proxy was lost while waiting for response (ECONNRESET)',
|
||||
defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104
|
||||
));
|
||||
});
|
||||
|
||||
$stream->write("CONNECT " . $target . " HTTP/1.1\r\nHost: " . $target . "\r\n" . $headers . "\r\n");
|
||||
}, function (Exception $e) use ($deferred, $uri) {
|
||||
$deferred->reject($e = new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because connection to proxy failed (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111,
|
||||
$e
|
||||
));
|
||||
|
||||
// avoid garbage references by replacing all closures in call stack.
|
||||
// what a lovely piece of code!
|
||||
$r = new \ReflectionProperty('Exception', 'trace');
|
||||
$r->setAccessible(true);
|
||||
$trace = $r->getValue($e);
|
||||
foreach ($trace as &$one) {
|
||||
foreach ($one['args'] as &$arg) {
|
||||
if ($arg instanceof \Closure) {
|
||||
$arg = 'Object(' . get_class($arg) . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
$r->setValue($e, $trace);
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
21
vendor/clue/mq-react/LICENSE
vendored
Normal file
21
vendor/clue/mq-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Christian Lück
|
||||
|
||||
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.
|
29
vendor/clue/mq-react/composer.json
vendored
Normal file
29
vendor/clue/mq-react/composer.json
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "clue/mq-react",
|
||||
"description": "Mini Queue, the lightweight in-memory message queue to concurrently do many (but not too many) things at once, built on top of ReactPHP",
|
||||
"keywords": ["Message Queue", "Mini Queue", "job", "message", "worker", "queue", "rate limit", "throttle", "concurrency", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/clue/php-mq-react",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Mq\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Clue\\Tests\\React\\Mq\\": "tests/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/promise": "^2.2.1 || ^1.2.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "^1.0",
|
||||
"clue/buzz-react": "^2.0",
|
||||
"phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3"
|
||||
}
|
||||
}
|
331
vendor/clue/mq-react/src/Queue.php
vendored
Normal file
331
vendor/clue/mq-react/src/Queue.php
vendored
Normal file
@ -0,0 +1,331 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Mq;
|
||||
|
||||
use React\Promise;
|
||||
use React\Promise\CancellablePromiseInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* The `Queue` is responsible for managing your operations and ensuring not too
|
||||
* many operations are executed at once. It's a very simple and lightweight
|
||||
* in-memory implementation of the
|
||||
* [leaky bucket](https://en.wikipedia.org/wiki/Leaky_bucket#As_a_queue) algorithm.
|
||||
*
|
||||
* This means that you control how many operations can be executed concurrently.
|
||||
* If you add a job to the queue and it still below the limit, it will be executed
|
||||
* immediately. If you keep adding new jobs to the queue and its concurrency limit
|
||||
* is reached, it will not start a new operation and instead queue this for future
|
||||
* execution. Once one of the pending operations complete, it will pick the next
|
||||
* job from the qeueue and execute this operation.
|
||||
*/
|
||||
class Queue implements \Countable
|
||||
{
|
||||
private $concurrency;
|
||||
private $limit;
|
||||
private $handler;
|
||||
|
||||
private $pending = 0;
|
||||
private $queue = array();
|
||||
|
||||
/**
|
||||
* Concurrently process all given jobs through the given `$handler`.
|
||||
*
|
||||
* This is a convenience method which uses the `Queue` internally to
|
||||
* schedule all jobs while limiting concurrency to ensure no more than
|
||||
* `$concurrency` jobs ever run at once. It will return a promise which
|
||||
* resolves with the results of all jobs on success.
|
||||
*
|
||||
* ```php
|
||||
* $loop = React\EventLoop\Factory::create();
|
||||
* $browser = new Clue\React\Buzz\Browser($loop);
|
||||
*
|
||||
* $promise = Queue:all(3, $urls, function ($url) use ($browser) {
|
||||
* return $browser->get($url);
|
||||
* });
|
||||
*
|
||||
* $promise->then(function (array $responses) {
|
||||
* echo 'All ' . count($responses) . ' successful!' . PHP_EOL;
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* If either of the jobs fail, it will reject the resulting promise and will
|
||||
* try to cancel all outstanding jobs. Similarly, calling `cancel()` on the
|
||||
* resulting promise will try to cancel all outstanding jobs. See
|
||||
* [promises](#promises) and [cancellation](#cancellation) for details.
|
||||
*
|
||||
* The `$concurrency` parameter sets a new soft limit for the maximum number
|
||||
* of jobs to handle concurrently. Finding a good concurrency limit depends
|
||||
* on your particular use case. It's common to limit concurrency to a rather
|
||||
* small value, as doing more than a dozen of things at once may easily
|
||||
* overwhelm the receiving side. Using a `1` value will ensure that all jobs
|
||||
* are processed one after another, effectively creating a "waterfall" of
|
||||
* jobs. Using a value less than 1 will reject with an
|
||||
* `InvalidArgumentException` without processing any jobs.
|
||||
*
|
||||
* ```php
|
||||
* // handle up to 10 jobs concurrently
|
||||
* $promise = Queue:all(10, $jobs, $handler);
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // handle each job after another without concurrency (waterfall)
|
||||
* $promise = Queue:all(1, $jobs, $handler);
|
||||
* ```
|
||||
*
|
||||
* The `$jobs` parameter must be an array with all jobs to process. Each
|
||||
* value in this array will be passed to the `$handler` to start one job.
|
||||
* The array keys will be preserved in the resulting array, while the array
|
||||
* values will be replaced with the job results as returned by the
|
||||
* `$handler`. If this array is empty, this method will resolve with an
|
||||
* empty array without processing any jobs.
|
||||
*
|
||||
* The `$handler` parameter must be a valid callable that accepts your job
|
||||
* parameters, invokes the appropriate operation and returns a Promise as a
|
||||
* placeholder for its future result. If the given argument is not a valid
|
||||
* callable, this method will reject with an `InvalidArgumentExceptionn`
|
||||
* without processing any jobs.
|
||||
*
|
||||
* ```php
|
||||
* // using a Closure as handler is usually recommended
|
||||
* $promise = Queue::all(10, $jobs, function ($url) use ($browser) {
|
||||
* return $browser->get($url);
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // accepts any callable, so PHP's array notation is also supported
|
||||
* $promise = Queue:all(10, $jobs, array($browser, 'get'));
|
||||
* ```
|
||||
*
|
||||
* > Keep in mind that returning an array of response messages means that
|
||||
* the whole response body has to be kept in memory.
|
||||
*
|
||||
* @param int $concurrency concurrency soft limit
|
||||
* @param array $jobs
|
||||
* @param callable $handler
|
||||
* @return PromiseInterface Returns a Promise<mixed[]> which resolves with an array of all resolution values
|
||||
* or rejects when any of the operations reject.
|
||||
*/
|
||||
public static function all($concurrency, array $jobs, $handler)
|
||||
{
|
||||
try {
|
||||
// limit number of concurrent operations
|
||||
$q = new self($concurrency, null, $handler);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// reject if $concurrency or $handler is invalid
|
||||
return Promise\reject($e);
|
||||
}
|
||||
|
||||
// try invoking all operations and automatically queue excessive ones
|
||||
$promises = array_map($q, $jobs);
|
||||
|
||||
return new Promise\Promise(function ($resolve, $reject) use ($promises) {
|
||||
Promise\all($promises)->then($resolve, function ($e) use ($promises, $reject) {
|
||||
// cancel all pending promises if a single promise fails
|
||||
foreach (array_reverse($promises) as $promise) {
|
||||
if ($promise instanceof CancellablePromiseInterface) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// reject with original rejection message
|
||||
$reject($e);
|
||||
});
|
||||
}, function () use ($promises) {
|
||||
// cancel all pending promises on cancellation
|
||||
foreach (array_reverse($promises) as $promise) {
|
||||
if ($promise instanceof CancellablePromiseInterface) {
|
||||
$promise->cancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new queue object.
|
||||
*
|
||||
* You can create any number of queues, for example when you want to apply
|
||||
* different limits to different kind of operations.
|
||||
*
|
||||
* The `$concurrency` parameter sets a new soft limit for the maximum number
|
||||
* of jobs to handle concurrently. Finding a good concurrency limit depends
|
||||
* on your particular use case. It's common to limit concurrency to a rather
|
||||
* small value, as doing more than a dozen of things at once may easily
|
||||
* overwhelm the receiving side.
|
||||
*
|
||||
* The `$limit` parameter sets a new hard limit on how many jobs may be
|
||||
* outstanding (kept in memory) at once. Depending on your particular use
|
||||
* case, it's usually safe to keep a few hundreds or thousands of jobs in
|
||||
* memory. If you do not want to apply an upper limit, you can pass a `null`
|
||||
* value which is semantically more meaningful than passing a big number.
|
||||
*
|
||||
* ```php
|
||||
* // handle up to 10 jobs concurrently, but keep no more than 1000 in memory
|
||||
* $q = new Queue(10, 1000, $handler);
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // handle up to 10 jobs concurrently, do not limit queue size
|
||||
* $q = new Queue(10, null, $handler);
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // handle up to 10 jobs concurrently, reject all further jobs
|
||||
* $q = new Queue(10, 10, $handler);
|
||||
* ```
|
||||
*
|
||||
* The `$handler` parameter must be a valid callable that accepts your job
|
||||
* parameters, invokes the appropriate operation and returns a Promise as a
|
||||
* placeholder for its future result.
|
||||
*
|
||||
* ```php
|
||||
* // using a Closure as handler is usually recommended
|
||||
* $q = new Queue(10, null, function ($url) use ($browser) {
|
||||
* return $browser->get($url);
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* ```php
|
||||
* // PHP's array callable as handler is also supported
|
||||
* $q = new Queue(10, null, array($browser, 'get'));
|
||||
* ```
|
||||
*
|
||||
* @param int $concurrency concurrency soft limit
|
||||
* @param int|null $limit queue hard limit or NULL=unlimited
|
||||
* @param callable $handler
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct($concurrency, $limit, $handler)
|
||||
{
|
||||
if ($concurrency < 1 || ($limit !== null && ($limit < 1 || $concurrency > $limit))) {
|
||||
throw new \InvalidArgumentException('Invalid limit given');
|
||||
}
|
||||
if (!is_callable($handler)) {
|
||||
throw new \InvalidArgumentException('Invalid handler given');
|
||||
}
|
||||
|
||||
$this->concurrency = $concurrency;
|
||||
$this->limit = $limit;
|
||||
$this->handler = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Queue instance is invokable, so that invoking `$q(...$args)` will
|
||||
* actually be forwarded as `$handler(...$args)` as given in the
|
||||
* `$handler` argument when concurrency is still below limits.
|
||||
*
|
||||
* Each operation may take some time to complete, but due to its async nature you
|
||||
* can actually start any number of (queued) operations. Once the concurrency limit
|
||||
* is reached, this invocation will simply be queued and this will return a pending
|
||||
* promise which will start the actual operation once another operation is
|
||||
* completed. This means that this is handled entirely transparently and you do not
|
||||
* need to worry about this concurrency limit yourself.
|
||||
*
|
||||
* @return \React\Promise\PromiseInterface
|
||||
*/
|
||||
public function __invoke()
|
||||
{
|
||||
// happy path: simply invoke handler if we're below concurrency limit
|
||||
if ($this->pending < $this->concurrency) {
|
||||
++$this->pending;
|
||||
|
||||
// invoke handler and await its resolution before invoking next queued job
|
||||
return $this->await(
|
||||
call_user_func_array($this->handler, func_get_args())
|
||||
);
|
||||
}
|
||||
|
||||
// we're currently above concurreny limit, make sure we do not exceed maximum queue limit
|
||||
if ($this->limit !== null && $this->count() >= $this->limit) {
|
||||
return Promise\reject(new \OverflowException('Maximum queue limit of ' . $this->limit . ' exceeded'));
|
||||
}
|
||||
|
||||
// if we reach this point, then this job will need to be queued
|
||||
// get next queue position
|
||||
$queue =& $this->queue;
|
||||
$queue[] = null;
|
||||
end($queue);
|
||||
$id = key($queue);
|
||||
|
||||
$deferred = new Deferred(function ($_, $reject) use (&$queue, $id, &$deferred) {
|
||||
// forward cancellation to pending operation if it is currently executing
|
||||
if (isset($deferred->pending) && $deferred->pending instanceof CancellablePromiseInterface) {
|
||||
$deferred->pending->cancel();
|
||||
}
|
||||
unset($deferred->pending);
|
||||
|
||||
if (isset($deferred->args)) {
|
||||
// queued promise cancelled before its handler is invoked
|
||||
// remove from queue and reject explicitly
|
||||
unset($queue[$id], $deferred->args);
|
||||
$reject(new \RuntimeException('Cancelled queued job before processing started'));
|
||||
}
|
||||
});
|
||||
|
||||
// queue job to process if number of pending jobs is below concurrency limit again
|
||||
$deferred->args = func_get_args();
|
||||
$queue[$id] = $deferred;
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
return $this->pending + count($this->queue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function await(PromiseInterface $promise)
|
||||
{
|
||||
$that = $this;
|
||||
|
||||
return $promise->then(function ($result) use ($that) {
|
||||
$that->processQueue();
|
||||
|
||||
return $result;
|
||||
}, function ($error) use ($that) {
|
||||
$that->processQueue();
|
||||
|
||||
return Promise\reject($error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function processQueue()
|
||||
{
|
||||
// skip if we're still above concurrency limit or there's no queued job waiting
|
||||
if (--$this->pending >= $this->concurrency || !$this->queue) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* @var $deferred Deferred */
|
||||
$deferred = reset($this->queue);
|
||||
unset($this->queue[key($this->queue)]);
|
||||
|
||||
// once number of pending jobs is below concurrency limit again:
|
||||
// await this situation, invoke handler and await its resolution before invoking next queued job
|
||||
++$this->pending;
|
||||
|
||||
$promise = call_user_func_array($this->handler, $deferred->args);
|
||||
$deferred->pending = $promise;
|
||||
unset($deferred->args);
|
||||
|
||||
// invoke handler and await its resolution before invoking next queued job
|
||||
$this->await($promise)->then(
|
||||
function ($result) use ($deferred) {
|
||||
unset($deferred->pending);
|
||||
$deferred->resolve($result);
|
||||
},
|
||||
function ($e) use ($deferred) {
|
||||
unset($deferred->pending);
|
||||
$deferred->reject($e);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
19
vendor/clue/redis-protocol/composer.json
vendored
Normal file
19
vendor/clue/redis-protocol/composer.json
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "clue/redis-protocol",
|
||||
"description": "A streaming redis wire protocol parser and serializer implementation in PHP",
|
||||
"keywords": ["streaming", "redis", "protocol", "parser", "serializer"],
|
||||
"homepage": "https://github.com/clue/php-redis-protocol",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "Clue\\Redis\\Protocol": "src" }
|
||||
}
|
||||
}
|
51
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Factory.php
vendored
Normal file
51
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Factory.php
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol;
|
||||
|
||||
use Clue\Redis\Protocol\Parser\ParserInterface;
|
||||
use Clue\Redis\Protocol\Parser\ResponseParser;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
use Clue\Redis\Protocol\Serializer\RecursiveSerializer;
|
||||
use Clue\Redis\Protocol\Parser\RequestParser;
|
||||
|
||||
/**
|
||||
* Provides factory methods used to instantiate the best available protocol implementation
|
||||
*/
|
||||
class Factory
|
||||
{
|
||||
/**
|
||||
* instantiate the best available protocol response parser implementation
|
||||
*
|
||||
* This is the parser every redis client implementation should use in order
|
||||
* to parse incoming response messages from a redis server.
|
||||
*
|
||||
* @return ParserInterface
|
||||
*/
|
||||
public function createResponseParser()
|
||||
{
|
||||
return new ResponseParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* instantiate the best available protocol request parser implementation
|
||||
*
|
||||
* This is most useful for a redis server implementation which needs to
|
||||
* process client requests.
|
||||
*
|
||||
* @return ParserInterface
|
||||
*/
|
||||
public function createRequestParser()
|
||||
{
|
||||
return new RequestParser();
|
||||
}
|
||||
|
||||
/**
|
||||
* instantiate the best available protocol serializer implementation
|
||||
*
|
||||
* @return SerializerInterface
|
||||
*/
|
||||
public function createSerializer()
|
||||
{
|
||||
return new RecursiveSerializer();
|
||||
}
|
||||
}
|
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/BulkReply.php
vendored
Normal file
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/BulkReply.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
class BulkReply implements ModelInterface
|
||||
{
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* create bulk reply (string reply)
|
||||
*
|
||||
* @param string|null $data
|
||||
*/
|
||||
public function __construct($value)
|
||||
{
|
||||
if ($value !== null) {
|
||||
$value = (string)$value;
|
||||
}
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getBulkMessage($this->value);
|
||||
}
|
||||
}
|
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ErrorReply.php
vendored
Normal file
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ErrorReply.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Exception;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
/**
|
||||
*
|
||||
* @link http://redis.io/topics/protocol#status-reply
|
||||
*/
|
||||
class ErrorReply extends Exception implements ModelInterface
|
||||
{
|
||||
/**
|
||||
* create error status reply (single line error message)
|
||||
*
|
||||
* @param string|ErrorReplyException $message
|
||||
* @return string
|
||||
*/
|
||||
public function __construct($message, $code = 0, $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
return $this->getMessage();
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getErrorMessage($this->getMessage());
|
||||
}
|
||||
}
|
31
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/IntegerReply.php
vendored
Normal file
31
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/IntegerReply.php
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
class IntegerReply implements ModelInterface
|
||||
{
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* create integer reply
|
||||
*
|
||||
* @param int $data
|
||||
*/
|
||||
public function __construct($value)
|
||||
{
|
||||
$this->value = (int)$value;
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getIntegerMessage($this->value);
|
||||
}
|
||||
}
|
23
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ModelInterface.php
vendored
Normal file
23
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ModelInterface.php
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
interface ModelInterface
|
||||
{
|
||||
/**
|
||||
* Returns value of this model as a native representation for PHP
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValueNative();
|
||||
|
||||
/**
|
||||
* Returns the serialized representation of this protocol message
|
||||
*
|
||||
* @param SerializerInterface $serializer;
|
||||
* @return string
|
||||
*/
|
||||
public function getMessageSerialized(SerializerInterface $serializer);
|
||||
}
|
100
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/MultiBulkReply.php
vendored
Normal file
100
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/MultiBulkReply.php
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use UnexpectedValueException;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
class MultiBulkReply implements ModelInterface
|
||||
{
|
||||
/**
|
||||
* @var array|null
|
||||
*/
|
||||
private $data;
|
||||
|
||||
/**
|
||||
* create multi bulk reply (an array of other replies, usually bulk replies)
|
||||
*
|
||||
* @param array|null $data
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $data = null)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
if ($this->data === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ret = array();
|
||||
foreach ($this->data as $one) {
|
||||
if ($one instanceof ModelInterface) {
|
||||
$ret []= $one->getValueNative();
|
||||
} else {
|
||||
$ret []= $one;
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getMultiBulkMessage($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this model represents a valid unified request protocol message
|
||||
*
|
||||
* The new unified protocol was introduced in Redis 1.2, but it became the
|
||||
* standard way for talking with the Redis server in Redis 2.0. The unified
|
||||
* request protocol is what Redis already uses in replies in order to send
|
||||
* list of items to clients, and is called a Multi Bulk Reply.
|
||||
*
|
||||
* @return boolean
|
||||
* @link http://redis.io/topics/protocol
|
||||
*/
|
||||
public function isRequest()
|
||||
{
|
||||
if (!$this->data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->data as $one) {
|
||||
if (!($one instanceof BulkReply) && !is_string($one)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getRequestModel()
|
||||
{
|
||||
if (!$this->data) {
|
||||
throw new UnexpectedValueException('Null-multi-bulk message can not be represented as a request, must contain string/bulk values');
|
||||
}
|
||||
|
||||
$command = null;
|
||||
$args = array();
|
||||
|
||||
foreach ($this->data as $one) {
|
||||
if ($one instanceof BulkReply) {
|
||||
$one = $one->getValueNative();
|
||||
} elseif (!is_string($one)) {
|
||||
throw new UnexpectedValueException('Message can not be represented as a request, must only contain string/bulk values');
|
||||
}
|
||||
|
||||
if ($command === null) {
|
||||
$command = $one;
|
||||
} else {
|
||||
$args []= $one;
|
||||
}
|
||||
}
|
||||
|
||||
return new Request($command, $args);
|
||||
}
|
||||
}
|
53
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/Request.php
vendored
Normal file
53
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/Request.php
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Model\BulkReply;
|
||||
use Clue\Redis\Protocol\Model\MultiBulkReply;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
|
||||
class Request implements ModelInterface
|
||||
{
|
||||
private $command;
|
||||
private $args;
|
||||
|
||||
public function __construct($command, array $args = array())
|
||||
{
|
||||
$this->command = $command;
|
||||
$this->args = $args;
|
||||
}
|
||||
|
||||
public function getCommand()
|
||||
{
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
public function getArgs()
|
||||
{
|
||||
return $this->args;
|
||||
}
|
||||
|
||||
public function getReplyModel()
|
||||
{
|
||||
$models = array(new BulkReply($this->command));
|
||||
foreach ($this->args as $arg) {
|
||||
$models []= new BulkReply($arg);
|
||||
}
|
||||
|
||||
return new MultiBulkReply($models);
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
$ret = $this->args;
|
||||
array_unshift($ret, $this->command);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getRequestMessage($this->command, $this->args);
|
||||
}
|
||||
}
|
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/StatusReply.php
vendored
Normal file
34
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/StatusReply.php
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Model;
|
||||
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
/**
|
||||
*
|
||||
* @link http://redis.io/topics/protocol#status-reply
|
||||
*/
|
||||
class StatusReply implements ModelInterface
|
||||
{
|
||||
private $message;
|
||||
|
||||
/**
|
||||
* create status reply (single line message)
|
||||
*
|
||||
* @param string|Status $message
|
||||
* @return string
|
||||
*/
|
||||
public function __construct($message)
|
||||
{
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
public function getValueNative()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function getMessageSerialized(SerializerInterface $serializer)
|
||||
{
|
||||
return $serializer->getStatusMessage($this->message);
|
||||
}
|
||||
}
|
40
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/MessageBuffer.php
vendored
Normal file
40
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/MessageBuffer.php
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use UnderflowException;
|
||||
|
||||
class MessageBuffer implements ParserInterface
|
||||
{
|
||||
private $parser;
|
||||
private $incomingQueue = array();
|
||||
|
||||
public function __construct(ParserInterface $parser)
|
||||
{
|
||||
$this->parser = $parser;
|
||||
}
|
||||
|
||||
public function popIncomingModel()
|
||||
{
|
||||
if (!$this->incomingQueue) {
|
||||
throw new UnderflowException('Incoming message queue is empty');
|
||||
}
|
||||
return array_shift($this->incomingQueue);
|
||||
}
|
||||
|
||||
public function hasIncomingModel()
|
||||
{
|
||||
return ($this->incomingQueue) ? true : false;
|
||||
}
|
||||
|
||||
public function pushIncoming($data)
|
||||
{
|
||||
$ret = $this->parser->pushIncoming($data);
|
||||
|
||||
foreach ($ret as $one) {
|
||||
$this->incomingQueue []= $one;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
10
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserException.php
vendored
Normal file
10
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserException.php
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use UnexpectedValueException;
|
||||
|
||||
class ParserException extends UnexpectedValueException
|
||||
{
|
||||
|
||||
}
|
28
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserInterface.php
vendored
Normal file
28
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserInterface.php
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Parser\ParserException;
|
||||
|
||||
interface ParserInterface
|
||||
{
|
||||
/**
|
||||
* push a chunk of the redis protocol message into the buffer and parse
|
||||
*
|
||||
* You can push any number of bytes of a redis protocol message into the
|
||||
* parser and it will try to parse messages from its data stream. So you can
|
||||
* pass data directly from your socket stream and the parser will return the
|
||||
* right amount of message model objects for you.
|
||||
*
|
||||
* If you pass an incomplete message, expect it to return an empty array. If
|
||||
* your incomplete message is split to across multiple chunks, the parsed
|
||||
* message model will be returned once the parser has sufficient data.
|
||||
*
|
||||
* @param string $dataChunk
|
||||
* @return ModelInterface[] 0+ message models
|
||||
* @throws ParserException if the message can not be parsed
|
||||
* @see self::popIncomingModel()
|
||||
*/
|
||||
public function pushIncoming($dataChunk);
|
||||
}
|
125
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/RequestParser.php
vendored
Normal file
125
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/RequestParser.php
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use Clue\Redis\Protocol\Parser\ParserException;
|
||||
use Clue\Redis\Protocol\Model\Request;
|
||||
|
||||
class RequestParser implements ParserInterface
|
||||
{
|
||||
const CRLF = "\r\n";
|
||||
|
||||
private $incomingBuffer = '';
|
||||
private $incomingOffset = 0;
|
||||
|
||||
public function pushIncoming($dataChunk)
|
||||
{
|
||||
$this->incomingBuffer .= $dataChunk;
|
||||
|
||||
$parsed = array();
|
||||
|
||||
do {
|
||||
$saved = $this->incomingOffset;
|
||||
$message = $this->readRequest();
|
||||
if ($message === null) {
|
||||
// restore previous position for next parsing attempt
|
||||
$this->incomingOffset = $saved;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($message !== false) {
|
||||
$parsed []= $message;
|
||||
}
|
||||
} while($this->incomingBuffer !== '');
|
||||
|
||||
if ($this->incomingOffset !== 0) {
|
||||
$this->incomingBuffer = (string)substr($this->incomingBuffer, $this->incomingOffset);
|
||||
$this->incomingOffset = 0;
|
||||
}
|
||||
|
||||
return $parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* try to parse request from incoming buffer
|
||||
*
|
||||
* @throws ParserException if the incoming buffer is invalid
|
||||
* @return Request|null
|
||||
*/
|
||||
private function readRequest()
|
||||
{
|
||||
$crlf = strpos($this->incomingBuffer, "\r\n", $this->incomingOffset);
|
||||
if ($crlf === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// line starts with a multi-bulk header "*"
|
||||
if (isset($this->incomingBuffer[$this->incomingOffset]) && $this->incomingBuffer[$this->incomingOffset] === '*') {
|
||||
$line = substr($this->incomingBuffer, $this->incomingOffset + 1, $crlf - $this->incomingOffset + 1);
|
||||
$this->incomingOffset = $crlf + 2;
|
||||
$count = (int)$line;
|
||||
|
||||
if ($count <= 0) {
|
||||
return false;
|
||||
}
|
||||
$command = null;
|
||||
$args = array();
|
||||
for ($i = 0; $i < $count; ++$i) {
|
||||
$sub = $this->readBulk();
|
||||
if ($sub === null) {
|
||||
return null;
|
||||
}
|
||||
if ($command === null) {
|
||||
$command = $sub;
|
||||
} else {
|
||||
$args []= $sub;
|
||||
}
|
||||
}
|
||||
return new Request($command, $args);
|
||||
}
|
||||
|
||||
// parse an old inline request instead
|
||||
$line = substr($this->incomingBuffer, $this->incomingOffset, $crlf - $this->incomingOffset);
|
||||
$this->incomingOffset = $crlf + 2;
|
||||
|
||||
$args = preg_split('/ +/', trim($line, ' '));
|
||||
$command = array_shift($args);
|
||||
|
||||
if ($command === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return new Request($command, $args);
|
||||
}
|
||||
|
||||
private function readBulk()
|
||||
{
|
||||
$crlf = strpos($this->incomingBuffer, "\r\n", $this->incomingOffset);
|
||||
if ($crlf === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// line has to start with a bulk header "$"
|
||||
if (!isset($this->incomingBuffer[$this->incomingOffset]) || $this->incomingBuffer[$this->incomingOffset] !== '$') {
|
||||
throw new ParserException('ERR Protocol error: expected \'$\', got \'' . substr($this->incomingBuffer, $this->incomingOffset, 1) . '\'');
|
||||
}
|
||||
|
||||
$line = substr($this->incomingBuffer, $this->incomingOffset + 1, $crlf - $this->incomingOffset + 1);
|
||||
$this->incomingOffset = $crlf + 2;
|
||||
$size = (int)$line;
|
||||
|
||||
if ($size < 0) {
|
||||
throw new ParserException('ERR Protocol error: invalid bulk length');
|
||||
}
|
||||
|
||||
if (!isset($this->incomingBuffer[$this->incomingOffset + $size + 1])) {
|
||||
// check enough bytes + crlf are buffered
|
||||
return null;
|
||||
}
|
||||
|
||||
$ret = substr($this->incomingBuffer, $this->incomingOffset, $size);
|
||||
$this->incomingOffset += $size + 2;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
151
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ResponseParser.php
vendored
Normal file
151
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ResponseParser.php
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Parser;
|
||||
|
||||
use Clue\Redis\Protocol\Parser\ParserInterface;
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Model\BulkReply;
|
||||
use Clue\Redis\Protocol\Model\ErrorReply;
|
||||
use Clue\Redis\Protocol\Model\IntegerReply;
|
||||
use Clue\Redis\Protocol\Model\MultiBulkReply;
|
||||
use Clue\Redis\Protocol\Model\StatusReply;
|
||||
use Clue\Redis\Protocol\Parser\ParserException;
|
||||
|
||||
/**
|
||||
* Simple recursive redis wire protocol parser
|
||||
*
|
||||
* Heavily influenced by blocking parser implementation from jpd/redisent.
|
||||
*
|
||||
* @link https://github.com/jdp/redisent
|
||||
* @link http://redis.io/topics/protocol
|
||||
*/
|
||||
class ResponseParser implements ParserInterface
|
||||
{
|
||||
const CRLF = "\r\n";
|
||||
|
||||
private $incomingBuffer = '';
|
||||
private $incomingOffset = 0;
|
||||
|
||||
public function pushIncoming($dataChunk)
|
||||
{
|
||||
$this->incomingBuffer .= $dataChunk;
|
||||
|
||||
return $this->tryParsingIncomingMessages();
|
||||
}
|
||||
|
||||
private function tryParsingIncomingMessages()
|
||||
{
|
||||
$messages = array();
|
||||
|
||||
do {
|
||||
$message = $this->readResponse();
|
||||
if ($message === null) {
|
||||
// restore previous position for next parsing attempt
|
||||
$this->incomingOffset = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
$messages []= $message;
|
||||
|
||||
$this->incomingBuffer = (string)substr($this->incomingBuffer, $this->incomingOffset);
|
||||
$this->incomingOffset = 0;
|
||||
} while($this->incomingBuffer !== '');
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
private function readLine()
|
||||
{
|
||||
$pos = strpos($this->incomingBuffer, "\r\n", $this->incomingOffset);
|
||||
|
||||
if ($pos === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$ret = (string)substr($this->incomingBuffer, $this->incomingOffset, $pos - $this->incomingOffset);
|
||||
$this->incomingOffset = $pos + 2;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
private function readLength($len)
|
||||
{
|
||||
$ret = substr($this->incomingBuffer, $this->incomingOffset, $len);
|
||||
if (strlen($ret) !== $len) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->incomingOffset += $len;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* try to parse response from incoming buffer
|
||||
*
|
||||
* ripped from jdp/redisent, with some minor modifications to read from
|
||||
* the incoming buffer instead of issuing a blocking fread on a stream
|
||||
*
|
||||
* @throws ParserException if the incoming buffer is invalid
|
||||
* @return ModelInterface|null
|
||||
* @link https://github.com/jdp/redisent
|
||||
*/
|
||||
private function readResponse()
|
||||
{
|
||||
/* Parse the response based on the reply identifier */
|
||||
$reply = $this->readLine();
|
||||
if ($reply === null) {
|
||||
return null;
|
||||
}
|
||||
switch (substr($reply, 0, 1)) {
|
||||
/* Error reply */
|
||||
case '-':
|
||||
$response = new ErrorReply(substr($reply, 1));
|
||||
break;
|
||||
/* Inline reply */
|
||||
case '+':
|
||||
$response = new StatusReply(substr($reply, 1));
|
||||
break;
|
||||
/* Bulk reply */
|
||||
case '$':
|
||||
$size = (int)substr($reply, 1);
|
||||
if ($size === -1) {
|
||||
return new BulkReply(null);
|
||||
}
|
||||
$data = $this->readLength($size);
|
||||
if ($data === null) {
|
||||
return null;
|
||||
}
|
||||
if ($this->readLength(2) === null) { /* discard crlf */
|
||||
return null;
|
||||
}
|
||||
$response = new BulkReply($data);
|
||||
break;
|
||||
/* Multi-bulk reply */
|
||||
case '*':
|
||||
$count = (int)substr($reply, 1);
|
||||
if ($count === -1) {
|
||||
return new MultiBulkReply(null);
|
||||
}
|
||||
$response = array();
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$sub = $this->readResponse();
|
||||
if ($sub === null) {
|
||||
return null;
|
||||
}
|
||||
$response []= $sub;
|
||||
}
|
||||
$response = new MultiBulkReply($response);
|
||||
break;
|
||||
/* Integer reply */
|
||||
case ':':
|
||||
$response = new IntegerReply(substr($reply, 1));
|
||||
break;
|
||||
default:
|
||||
throw new ParserException('Invalid message can not be parsed: "' . $reply . '"');
|
||||
break;
|
||||
}
|
||||
/* Party on */
|
||||
return $response;
|
||||
}
|
||||
}
|
111
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/RecursiveSerializer.php
vendored
Normal file
111
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/RecursiveSerializer.php
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Serializer;
|
||||
|
||||
use Clue\Redis\Protocol\Model\StatusReply;
|
||||
use InvalidArgumentException;
|
||||
use Exception;
|
||||
use Clue\Redis\Protocol\Model\BulkReply;
|
||||
use Clue\Redis\Protocol\Model\IntegerReply;
|
||||
use Clue\Redis\Protocol\Model\ErrorReply;
|
||||
use Clue\Redis\Protocol\Model\MultiBulkReply;
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Model\Request;
|
||||
|
||||
class RecursiveSerializer implements SerializerInterface
|
||||
{
|
||||
const CRLF = "\r\n";
|
||||
|
||||
public function getRequestMessage($command, array $args = array())
|
||||
{
|
||||
$data = '*' . (count($args) + 1) . "\r\n$" . strlen($command) . "\r\n" . $command . "\r\n";
|
||||
foreach ($args as $arg) {
|
||||
$data .= '$' . strlen($arg) . "\r\n" . $arg . "\r\n";
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function createRequestModel($command, array $args = array())
|
||||
{
|
||||
return new Request($command, $args);
|
||||
}
|
||||
|
||||
public function getReplyMessage($data)
|
||||
{
|
||||
if (is_string($data) || $data === null) {
|
||||
return $this->getBulkMessage($data);
|
||||
} else if (is_int($data) || is_float($data) || is_bool($data)) {
|
||||
return $this->getIntegerMessage($data);
|
||||
} else if ($data instanceof Exception) {
|
||||
return $this->getErrorMessage($data->getMessage());
|
||||
} else if (is_array($data)) {
|
||||
return $this->getMultiBulkMessage($data);
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid data type passed for serialization');
|
||||
}
|
||||
}
|
||||
|
||||
public function createReplyModel($data)
|
||||
{
|
||||
if (is_string($data) || $data === null) {
|
||||
return new BulkReply($data);
|
||||
} else if (is_int($data) || is_float($data) || is_bool($data)) {
|
||||
return new IntegerReply($data);
|
||||
} else if ($data instanceof Exception) {
|
||||
return new ErrorReply($data->getMessage());
|
||||
} else if (is_array($data)) {
|
||||
$models = array();
|
||||
foreach ($data as $one) {
|
||||
$models []= $this->createReplyModel($one);
|
||||
}
|
||||
return new MultiBulkReply($models);
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid data type passed for serialization');
|
||||
}
|
||||
}
|
||||
|
||||
public function getBulkMessage($data)
|
||||
{
|
||||
if ($data === null) {
|
||||
/* null bulk reply */
|
||||
return '$-1' . self::CRLF;
|
||||
}
|
||||
/* bulk reply */
|
||||
return '$' . strlen($data) . self::CRLF . $data . self::CRLF;
|
||||
}
|
||||
|
||||
public function getErrorMessage($data)
|
||||
{
|
||||
/* error status reply */
|
||||
return '-' . $data . self::CRLF;
|
||||
}
|
||||
|
||||
public function getIntegerMessage($data)
|
||||
{
|
||||
return ':' . (int)$data . self::CRLF;
|
||||
}
|
||||
|
||||
public function getMultiBulkMessage($data)
|
||||
{
|
||||
if ($data === null) {
|
||||
/* null multi bulk reply */
|
||||
return '*-1' . self::CRLF;
|
||||
}
|
||||
/* multi bulk reply */
|
||||
$ret = '*' . count($data) . self::CRLF;
|
||||
foreach ($data as $one) {
|
||||
if ($one instanceof ModelInterface) {
|
||||
$ret .= $one->getMessageSerialized($this);
|
||||
} else {
|
||||
$ret .= $this->getReplyMessage($one);
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function getStatusMessage($data)
|
||||
{
|
||||
/* status reply */
|
||||
return '+' . $data . self::CRLF;
|
||||
}
|
||||
}
|
83
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/SerializerInterface.php
vendored
Normal file
83
vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/SerializerInterface.php
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\Redis\Protocol\Serializer;
|
||||
|
||||
use Clue\Redis\Protocol\Model\ErrorReplyException;
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Model\MultiBulkReply;
|
||||
|
||||
interface SerializerInterface
|
||||
{
|
||||
/**
|
||||
* create a serialized unified request protocol message
|
||||
*
|
||||
* This is the *one* method most redis client libraries will likely want to
|
||||
* use in order to send a serialized message (a request) over the* wire to
|
||||
* your redis server instance.
|
||||
*
|
||||
* This method should be used in favor of constructing a request model and
|
||||
* then serializing it. While its effect might be equivalent, this method
|
||||
* is likely to (i.e. it /could/) provide a faster implementation.
|
||||
*
|
||||
* @param string $command
|
||||
* @param array $args
|
||||
* @return string
|
||||
* @see self::createRequestMessage()
|
||||
*/
|
||||
public function getRequestMessage($command, array $args = array());
|
||||
|
||||
/**
|
||||
* create a unified request protocol message model
|
||||
*
|
||||
* @param string $command
|
||||
* @param array $args
|
||||
* @return MultiBulkReply
|
||||
*/
|
||||
public function createRequestModel($command, array $args = array());
|
||||
|
||||
/**
|
||||
* create a serialized unified protocol reply message
|
||||
*
|
||||
* This is most useful for a redis server implementation which needs to
|
||||
* process client requests and send resulting reply messages.
|
||||
*
|
||||
* This method does its best to guess to right reply type and then returns
|
||||
* a serialized version of the message. It follows the "redis to lua
|
||||
* conversion table" (see link) which means most basic types can be mapped
|
||||
* as is.
|
||||
*
|
||||
* This method should be used in favor of constructing a reply model and
|
||||
* then serializing it. While its effect might be equivalent, this method
|
||||
* is likely to (i.e. it /could/) provide a faster implementation.
|
||||
*
|
||||
* Note however, you may still want to explicitly create a nested reply
|
||||
* model hierarchy if you need more control over the serialized message. For
|
||||
* instance, a null value will always be returned as a Null-Bulk-Reply, so
|
||||
* there's no way to express a Null-Multi-Bulk-Reply, unless you construct
|
||||
* it explicitly.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return string
|
||||
* @see self::createReplyModel()
|
||||
* @link http://redis.io/commands/eval
|
||||
*/
|
||||
public function getReplyMessage($data);
|
||||
|
||||
/**
|
||||
* create response message by determining datatype from given argument
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return ModelInterface
|
||||
*/
|
||||
public function createReplyModel($data);
|
||||
|
||||
public function getBulkMessage($data);
|
||||
|
||||
public function getErrorMessage($data);
|
||||
|
||||
public function getIntegerMessage($data);
|
||||
|
||||
public function getMultiBulkMessage($data);
|
||||
|
||||
public function getStatusMessage($data);
|
||||
}
|
21
vendor/clue/redis-react/LICENSE
vendored
Normal file
21
vendor/clue/redis-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Christian Lück
|
||||
|
||||
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.
|
32
vendor/clue/redis-react/composer.json
vendored
Normal file
32
vendor/clue/redis-react/composer.json
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "clue/redis-react",
|
||||
"description": "Async Redis client implementation, built on top of ReactPHP.",
|
||||
"keywords": ["Redis", "database", "client", "async", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/reactphp-redis",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"clue/redis-protocol": "0.3.*",
|
||||
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
|
||||
"react/event-loop": "^1.0 || ^0.5",
|
||||
"react/promise": "^2.0 || ^1.1",
|
||||
"react/promise-timer": "^1.5",
|
||||
"react/socket": "^1.1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Redis\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Clue\\Tests\\React\\Redis\\": "tests/" }
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "^1.1",
|
||||
"phpunit/phpunit": "^7.0 || ^6.0 || ^5.0 || ^4.8.35"
|
||||
}
|
||||
}
|
54
vendor/clue/redis-react/src/Client.php
vendored
Normal file
54
vendor/clue/redis-react/src/Client.php
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Redis;
|
||||
|
||||
use Evenement\EventEmitterInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* Simple interface for executing redis commands
|
||||
*
|
||||
* @event error(Exception $error)
|
||||
* @event close()
|
||||
*
|
||||
* @event message($channel, $message)
|
||||
* @event subscribe($channel, $numberOfChannels)
|
||||
* @event unsubscribe($channel, $numberOfChannels)
|
||||
*
|
||||
* @event pmessage($pattern, $channel, $message)
|
||||
* @event psubscribe($channel, $numberOfChannels)
|
||||
* @event punsubscribe($channel, $numberOfChannels)
|
||||
*/
|
||||
interface Client extends EventEmitterInterface
|
||||
{
|
||||
/**
|
||||
* Invoke the given command and return a Promise that will be resolved when the request has been replied to
|
||||
*
|
||||
* This is a magic method that will be invoked when calling any redis
|
||||
* command on this instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string[] $args
|
||||
* @return PromiseInterface Promise<mixed,Exception>
|
||||
*/
|
||||
public function __call($name, $args);
|
||||
|
||||
/**
|
||||
* end connection once all pending requests have been replied to
|
||||
*
|
||||
* @return void
|
||||
* @uses self::close() once all replies have been received
|
||||
* @see self::close() for closing the connection immediately
|
||||
*/
|
||||
public function end();
|
||||
|
||||
/**
|
||||
* close connection immediately
|
||||
*
|
||||
* This will emit the "close" event.
|
||||
*
|
||||
* @return void
|
||||
* @see self::end() for closing the connection once the client is idle
|
||||
*/
|
||||
public function close();
|
||||
}
|
203
vendor/clue/redis-react/src/Factory.php
vendored
Normal file
203
vendor/clue/redis-react/src/Factory.php
vendored
Normal file
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Redis;
|
||||
|
||||
use Clue\Redis\Protocol\Factory as ProtocolFactory;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\Timer\TimeoutException;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class Factory
|
||||
{
|
||||
private $loop;
|
||||
private $connector;
|
||||
private $protocol;
|
||||
|
||||
/**
|
||||
* @param LoopInterface $loop
|
||||
* @param ConnectorInterface|null $connector [optional] Connector to use.
|
||||
* Should be `null` in order to use default Connector.
|
||||
* @param ProtocolFactory|null $protocol
|
||||
*/
|
||||
public function __construct(LoopInterface $loop, ConnectorInterface $connector = null, ProtocolFactory $protocol = null)
|
||||
{
|
||||
if ($connector === null) {
|
||||
$connector = new Connector($loop);
|
||||
}
|
||||
|
||||
if ($protocol === null) {
|
||||
$protocol = new ProtocolFactory();
|
||||
}
|
||||
|
||||
$this->loop = $loop;
|
||||
$this->connector = $connector;
|
||||
$this->protocol = $protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Redis client connected to address of given redis instance
|
||||
*
|
||||
* @param string $target Redis server URI to connect to
|
||||
* @return \React\Promise\PromiseInterface<Client> resolves with Client or rejects with \Exception
|
||||
*/
|
||||
public function createClient($target)
|
||||
{
|
||||
try {
|
||||
$parts = $this->parseUrl($target);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return \React\Promise\reject($e);
|
||||
}
|
||||
|
||||
$connecting = $this->connector->connect($parts['authority']);
|
||||
$deferred = new Deferred(function ($_, $reject) use ($connecting) {
|
||||
// connection cancelled, start with rejecting attempt, then clean up
|
||||
$reject(new \RuntimeException('Connection to Redis server cancelled'));
|
||||
|
||||
// either close successful connection or cancel pending connection attempt
|
||||
$connecting->then(function (ConnectionInterface $connection) {
|
||||
$connection->close();
|
||||
});
|
||||
$connecting->cancel();
|
||||
});
|
||||
|
||||
$protocol = $this->protocol;
|
||||
$promise = $connecting->then(function (ConnectionInterface $stream) use ($protocol) {
|
||||
return new StreamingClient($stream, $protocol->createResponseParser(), $protocol->createSerializer());
|
||||
}, function (\Exception $e) {
|
||||
throw new \RuntimeException(
|
||||
'Connection to Redis server failed because underlying transport connection failed',
|
||||
0,
|
||||
$e
|
||||
);
|
||||
});
|
||||
|
||||
if (isset($parts['auth'])) {
|
||||
$promise = $promise->then(function (StreamingClient $client) use ($parts) {
|
||||
return $client->auth($parts['auth'])->then(
|
||||
function () use ($client) {
|
||||
return $client;
|
||||
},
|
||||
function ($error) use ($client) {
|
||||
$client->close();
|
||||
|
||||
throw new \RuntimeException(
|
||||
'Connection to Redis server failed because AUTH command failed',
|
||||
0,
|
||||
$error
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (isset($parts['db'])) {
|
||||
$promise = $promise->then(function (StreamingClient $client) use ($parts) {
|
||||
return $client->select($parts['db'])->then(
|
||||
function () use ($client) {
|
||||
return $client;
|
||||
},
|
||||
function ($error) use ($client) {
|
||||
$client->close();
|
||||
|
||||
throw new \RuntimeException(
|
||||
'Connection to Redis server failed because SELECT command failed',
|
||||
0,
|
||||
$error
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
$promise->then(array($deferred, 'resolve'), array($deferred, 'reject'));
|
||||
|
||||
// use timeout from explicit ?timeout=x parameter or default to PHP's default_socket_timeout (60)
|
||||
$timeout = (float) isset($parts['timeout']) ? $parts['timeout'] : ini_get("default_socket_timeout");
|
||||
if ($timeout < 0) {
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
return \React\Promise\Timer\timeout($deferred->promise(), $timeout, $this->loop)->then(null, function ($e) {
|
||||
if ($e instanceof TimeoutException) {
|
||||
throw new \RuntimeException(
|
||||
'Connection to Redis server timed out after ' . $e->getTimeout() . ' seconds'
|
||||
);
|
||||
}
|
||||
throw $e;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Redis client connected to address of given redis instance
|
||||
*
|
||||
* @param string $target
|
||||
* @return Client
|
||||
*/
|
||||
public function createLazyClient($target)
|
||||
{
|
||||
return new LazyClient($target, $this, $this->loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $target
|
||||
* @return array with keys authority, auth and db
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function parseUrl($target)
|
||||
{
|
||||
$ret = array();
|
||||
// support `redis+unix://` scheme for Unix domain socket (UDS) paths
|
||||
if (preg_match('/^redis\+unix:\/\/([^:]*:[^@]*@)?(.+?)(\?.*)?$/', $target, $match)) {
|
||||
$ret['authority'] = 'unix://' . $match[2];
|
||||
$target = 'redis://' . (isset($match[1]) ? $match[1] : '') . 'localhost' . (isset($match[3]) ? $match[3] : '');
|
||||
}
|
||||
|
||||
if (strpos($target, '://') === false) {
|
||||
$target = 'redis://' . $target;
|
||||
}
|
||||
|
||||
$parts = parse_url($target);
|
||||
if ($parts === false || !isset($parts['scheme'], $parts['host']) || !in_array($parts['scheme'], array('redis', 'rediss'))) {
|
||||
throw new InvalidArgumentException('Given URL can not be parsed');
|
||||
}
|
||||
|
||||
if (isset($parts['pass'])) {
|
||||
$ret['auth'] = rawurldecode($parts['pass']);
|
||||
}
|
||||
|
||||
if (isset($parts['path']) && $parts['path'] !== '') {
|
||||
// skip first slash
|
||||
$ret['db'] = substr($parts['path'], 1);
|
||||
}
|
||||
|
||||
if (!isset($ret['authority'])) {
|
||||
$ret['authority'] =
|
||||
($parts['scheme'] === 'rediss' ? 'tls://' : '') .
|
||||
$parts['host'] . ':' .
|
||||
(isset($parts['port']) ? $parts['port'] : 6379);
|
||||
}
|
||||
|
||||
if (isset($parts['query'])) {
|
||||
$args = array();
|
||||
parse_str($parts['query'], $args);
|
||||
|
||||
if (isset($args['password'])) {
|
||||
$ret['auth'] = $args['password'];
|
||||
}
|
||||
|
||||
if (isset($args['db'])) {
|
||||
$ret['db'] = $args['db'];
|
||||
}
|
||||
|
||||
if (isset($args['timeout'])) {
|
||||
$ret['timeout'] = $args['timeout'];
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
216
vendor/clue/redis-react/src/LazyClient.php
vendored
Normal file
216
vendor/clue/redis-react/src/LazyClient.php
vendored
Normal file
@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Redis;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\Util;
|
||||
use React\EventLoop\LoopInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class LazyClient extends EventEmitter implements Client
|
||||
{
|
||||
private $target;
|
||||
/** @var Factory */
|
||||
private $factory;
|
||||
private $closed = false;
|
||||
private $promise;
|
||||
|
||||
private $loop;
|
||||
private $idlePeriod = 60.0;
|
||||
private $idleTimer;
|
||||
private $pending = 0;
|
||||
|
||||
private $subscribed = array();
|
||||
private $psubscribed = array();
|
||||
|
||||
/**
|
||||
* @param $target
|
||||
*/
|
||||
public function __construct($target, Factory $factory, LoopInterface $loop)
|
||||
{
|
||||
$args = array();
|
||||
\parse_str(\parse_url($target, \PHP_URL_QUERY), $args);
|
||||
if (isset($args['idle'])) {
|
||||
$this->idlePeriod = (float)$args['idle'];
|
||||
}
|
||||
|
||||
$this->target = $target;
|
||||
$this->factory = $factory;
|
||||
$this->loop = $loop;
|
||||
}
|
||||
|
||||
private function client()
|
||||
{
|
||||
if ($this->promise !== null) {
|
||||
return $this->promise;
|
||||
}
|
||||
|
||||
$self = $this;
|
||||
$pending =& $this->promise;
|
||||
$idleTimer=& $this->idleTimer;
|
||||
$subscribed =& $this->subscribed;
|
||||
$psubscribed =& $this->psubscribed;
|
||||
$loop = $this->loop;
|
||||
return $pending = $this->factory->createClient($this->target)->then(function (Client $client) use ($self, &$pending, &$idleTimer, &$subscribed, &$psubscribed, $loop) {
|
||||
// connection completed => remember only until closed
|
||||
$client->on('close', function () use (&$pending, $self, &$subscribed, &$psubscribed, &$idleTimer, $loop) {
|
||||
$pending = null;
|
||||
|
||||
// foward unsubscribe/punsubscribe events when underlying connection closes
|
||||
$n = count($subscribed);
|
||||
foreach ($subscribed as $channel => $_) {
|
||||
$self->emit('unsubscribe', array($channel, --$n));
|
||||
}
|
||||
$n = count($psubscribed);
|
||||
foreach ($psubscribed as $pattern => $_) {
|
||||
$self->emit('punsubscribe', array($pattern, --$n));
|
||||
}
|
||||
$subscribed = array();
|
||||
$psubscribed = array();
|
||||
|
||||
if ($idleTimer !== null) {
|
||||
$loop->cancelTimer($idleTimer);
|
||||
$idleTimer = null;
|
||||
}
|
||||
});
|
||||
|
||||
// keep track of all channels and patterns this connection is subscribed to
|
||||
$client->on('subscribe', function ($channel) use (&$subscribed) {
|
||||
$subscribed[$channel] = true;
|
||||
});
|
||||
$client->on('psubscribe', function ($pattern) use (&$psubscribed) {
|
||||
$psubscribed[$pattern] = true;
|
||||
});
|
||||
$client->on('unsubscribe', function ($channel) use (&$subscribed) {
|
||||
unset($subscribed[$channel]);
|
||||
});
|
||||
$client->on('punsubscribe', function ($pattern) use (&$psubscribed) {
|
||||
unset($psubscribed[$pattern]);
|
||||
});
|
||||
|
||||
Util::forwardEvents(
|
||||
$client,
|
||||
$self,
|
||||
array(
|
||||
'message',
|
||||
'subscribe',
|
||||
'unsubscribe',
|
||||
'pmessage',
|
||||
'psubscribe',
|
||||
'punsubscribe',
|
||||
)
|
||||
);
|
||||
|
||||
return $client;
|
||||
}, function (\Exception $e) use (&$pending) {
|
||||
// connection failed => discard connection attempt
|
||||
$pending = null;
|
||||
|
||||
throw $e;
|
||||
});
|
||||
}
|
||||
|
||||
public function __call($name, $args)
|
||||
{
|
||||
if ($this->closed) {
|
||||
return \React\Promise\reject(new \RuntimeException('Connection closed'));
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
return $this->client()->then(function (Client $client) use ($name, $args, $that) {
|
||||
$that->awake();
|
||||
return \call_user_func_array(array($client, $name), $args)->then(
|
||||
function ($result) use ($that) {
|
||||
$that->idle();
|
||||
return $result;
|
||||
},
|
||||
function ($error) use ($that) {
|
||||
$that->idle();
|
||||
throw $error;
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
if ($this->promise === null) {
|
||||
$this->close();
|
||||
}
|
||||
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
return $this->client()->then(function (Client $client) use ($that) {
|
||||
$client->on('close', function () use ($that) {
|
||||
$that->close();
|
||||
});
|
||||
$client->end();
|
||||
});
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
|
||||
// either close active connection or cancel pending connection attempt
|
||||
if ($this->promise !== null) {
|
||||
$this->promise->then(function (Client $client) {
|
||||
$client->close();
|
||||
});
|
||||
if ($this->promise !== null) {
|
||||
$this->promise->cancel();
|
||||
$this->promise = null;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->idleTimer !== null) {
|
||||
$this->loop->cancelTimer($this->idleTimer);
|
||||
$this->idleTimer = null;
|
||||
}
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function awake()
|
||||
{
|
||||
++$this->pending;
|
||||
|
||||
if ($this->idleTimer !== null) {
|
||||
$this->loop->cancelTimer($this->idleTimer);
|
||||
$this->idleTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function idle()
|
||||
{
|
||||
--$this->pending;
|
||||
|
||||
if ($this->pending < 1 && $this->idlePeriod >= 0 && !$this->subscribed && !$this->psubscribed) {
|
||||
$idleTimer =& $this->idleTimer;
|
||||
$promise =& $this->promise;
|
||||
$idleTimer = $this->loop->addTimer($this->idlePeriod, function () use (&$idleTimer, &$promise) {
|
||||
$promise->then(function (Client $client) {
|
||||
$client->close();
|
||||
});
|
||||
$promise = null;
|
||||
$idleTimer = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
180
vendor/clue/redis-react/src/StreamingClient.php
vendored
Normal file
180
vendor/clue/redis-react/src/StreamingClient.php
vendored
Normal file
@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Redis;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use Clue\Redis\Protocol\Parser\ParserInterface;
|
||||
use Clue\Redis\Protocol\Parser\ParserException;
|
||||
use Clue\Redis\Protocol\Serializer\SerializerInterface;
|
||||
use Clue\Redis\Protocol\Factory as ProtocolFactory;
|
||||
use UnderflowException;
|
||||
use RuntimeException;
|
||||
use InvalidArgumentException;
|
||||
use React\Promise\Deferred;
|
||||
use Clue\Redis\Protocol\Model\ErrorReply;
|
||||
use Clue\Redis\Protocol\Model\ModelInterface;
|
||||
use Clue\Redis\Protocol\Model\MultiBulkReply;
|
||||
use React\Stream\DuplexStreamInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class StreamingClient extends EventEmitter implements Client
|
||||
{
|
||||
private $stream;
|
||||
private $parser;
|
||||
private $serializer;
|
||||
private $requests = array();
|
||||
private $ending = false;
|
||||
private $closed = false;
|
||||
|
||||
private $subscribed = 0;
|
||||
private $psubscribed = 0;
|
||||
|
||||
public function __construct(DuplexStreamInterface $stream, ParserInterface $parser = null, SerializerInterface $serializer = null)
|
||||
{
|
||||
if ($parser === null || $serializer === null) {
|
||||
$factory = new ProtocolFactory();
|
||||
if ($parser === null) {
|
||||
$parser = $factory->createResponseParser();
|
||||
}
|
||||
if ($serializer === null) {
|
||||
$serializer = $factory->createSerializer();
|
||||
}
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$stream->on('data', function($chunk) use ($parser, $that) {
|
||||
try {
|
||||
$models = $parser->pushIncoming($chunk);
|
||||
}
|
||||
catch (ParserException $error) {
|
||||
$that->emit('error', array($error));
|
||||
$that->close();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($models as $data) {
|
||||
try {
|
||||
$that->handleMessage($data);
|
||||
}
|
||||
catch (UnderflowException $error) {
|
||||
$that->emit('error', array($error));
|
||||
$that->close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$stream->on('close', array($this, 'close'));
|
||||
|
||||
$this->stream = $stream;
|
||||
$this->parser = $parser;
|
||||
$this->serializer = $serializer;
|
||||
}
|
||||
|
||||
public function __call($name, $args)
|
||||
{
|
||||
$request = new Deferred();
|
||||
$promise = $request->promise();
|
||||
|
||||
$name = strtolower($name);
|
||||
|
||||
// special (p)(un)subscribe commands only accept a single parameter and have custom response logic applied
|
||||
static $pubsubs = array('subscribe', 'unsubscribe', 'psubscribe', 'punsubscribe');
|
||||
|
||||
if ($this->ending) {
|
||||
$request->reject(new RuntimeException('Connection closed'));
|
||||
} elseif (count($args) !== 1 && in_array($name, $pubsubs)) {
|
||||
$request->reject(new InvalidArgumentException('PubSub commands limited to single argument'));
|
||||
} elseif ($name === 'monitor') {
|
||||
$request->reject(new \BadMethodCallException('MONITOR command explicitly not supported'));
|
||||
} else {
|
||||
$this->stream->write($this->serializer->getRequestMessage($name, $args));
|
||||
$this->requests []= $request;
|
||||
}
|
||||
|
||||
if (in_array($name, $pubsubs)) {
|
||||
$that = $this;
|
||||
$subscribed =& $this->subscribed;
|
||||
$psubscribed =& $this->psubscribed;
|
||||
|
||||
$promise->then(function ($array) use ($that, &$subscribed, &$psubscribed) {
|
||||
$first = array_shift($array);
|
||||
|
||||
// (p)(un)subscribe messages are to be forwarded
|
||||
$that->emit($first, $array);
|
||||
|
||||
// remember number of (p)subscribe topics
|
||||
if ($first === 'subscribe' || $first === 'unsubscribe') {
|
||||
$subscribed = $array[1];
|
||||
} else {
|
||||
$psubscribed = $array[1];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return $promise;
|
||||
}
|
||||
|
||||
public function handleMessage(ModelInterface $message)
|
||||
{
|
||||
if (($this->subscribed !== 0 || $this->psubscribed !== 0) && $message instanceof MultiBulkReply) {
|
||||
$array = $message->getValueNative();
|
||||
$first = array_shift($array);
|
||||
|
||||
// pub/sub messages are to be forwarded and should not be processed as request responses
|
||||
if (in_array($first, array('message', 'pmessage'))) {
|
||||
$this->emit($first, $array);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->requests) {
|
||||
throw new UnderflowException('Unexpected reply received, no matching request found');
|
||||
}
|
||||
|
||||
$request = array_shift($this->requests);
|
||||
/* @var $request Deferred */
|
||||
|
||||
if ($message instanceof ErrorReply) {
|
||||
$request->reject($message);
|
||||
} else {
|
||||
$request->resolve($message->getValueNative());
|
||||
}
|
||||
|
||||
if ($this->ending && !$this->requests) {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
public function end()
|
||||
{
|
||||
$this->ending = true;
|
||||
|
||||
if (!$this->requests) {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->ending = true;
|
||||
$this->closed = true;
|
||||
|
||||
$this->stream->close();
|
||||
|
||||
$this->emit('close');
|
||||
|
||||
// reject all remaining requests in the queue
|
||||
while($this->requests) {
|
||||
$request = array_shift($this->requests);
|
||||
/* @var $request Request */
|
||||
$request->reject(new RuntimeException('Connection closing'));
|
||||
}
|
||||
}
|
||||
}
|
21
vendor/clue/soap-react/LICENSE
vendored
Normal file
21
vendor/clue/soap-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Christian Lück
|
||||
|
||||
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.
|
27
vendor/clue/soap-react/composer.json
vendored
Normal file
27
vendor/clue/soap-react/composer.json
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "clue/soap-react",
|
||||
"description": "Simple, async SOAP webservice client library, built on top of ReactPHP",
|
||||
"keywords": ["SOAP", "SoapClient", "WebService", "WSDL", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/reactphp-soap",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Soap\\": "src/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"clue/buzz-react": "^2.5",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
|
||||
"react/promise": "^2.1 || ^1.2",
|
||||
"ext-soap": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/block-react": "^1.0",
|
||||
"phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35"
|
||||
}
|
||||
}
|
323
vendor/clue/soap-react/src/Client.php
vendored
Normal file
323
vendor/clue/soap-react/src/Client.php
vendored
Normal file
@ -0,0 +1,323 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Soap;
|
||||
|
||||
use Clue\React\Buzz\Browser;
|
||||
use Clue\React\Soap\Protocol\ClientDecoder;
|
||||
use Clue\React\Soap\Protocol\ClientEncoder;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* The `Client` class is responsible for communication with the remote SOAP
|
||||
* WebService server.
|
||||
*
|
||||
* It requires a [`Browser`](https://github.com/clue/reactphp-buzz#browser) object
|
||||
* bound to the main [`EventLoop`](https://github.com/reactphp/event-loop#usage)
|
||||
* in order to handle async requests, the WSDL file contents and an optional
|
||||
* array of SOAP options:
|
||||
*
|
||||
* ```php
|
||||
* $loop = React\EventLoop\Factory::create();
|
||||
* $browser = new Clue\React\Buzz\Browser($loop);
|
||||
*
|
||||
* $wsdl = '<?xml …';
|
||||
* $options = array();
|
||||
*
|
||||
* $client = new Client($browser, $wsdl, $options);
|
||||
* ```
|
||||
*
|
||||
* If you need custom connector settings (DNS resolution, TLS parameters, timeouts,
|
||||
* proxy servers etc.), you can explicitly pass a custom instance of the
|
||||
* [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface)
|
||||
* to the [`Browser`](https://github.com/clue/reactphp-buzz#browser) instance:
|
||||
*
|
||||
* ```php
|
||||
* $connector = new \React\Socket\Connector($loop, array(
|
||||
* 'dns' => '127.0.0.1',
|
||||
* 'tcp' => array(
|
||||
* 'bindto' => '192.168.10.1:0'
|
||||
* ),
|
||||
* 'tls' => array(
|
||||
* 'verify_peer' => false,
|
||||
* 'verify_peer_name' => false
|
||||
* )
|
||||
* ));
|
||||
*
|
||||
* $browser = new Browser($loop, $connector);
|
||||
* $client = new Client($browser, $wsdl);
|
||||
* ```
|
||||
*
|
||||
* The `Client` works similar to PHP's `SoapClient` (which it uses under the
|
||||
* hood), but leaves you the responsibility to load the WSDL file. This allows
|
||||
* you to use local WSDL files, WSDL files from a cache or the most common form,
|
||||
* downloading the WSDL file contents from an URL through the `Browser`:
|
||||
*
|
||||
* ```php
|
||||
* $browser = new Browser($loop);
|
||||
*
|
||||
* $browser->get($url)->then(
|
||||
* function (ResponseInterface $response) use ($browser) {
|
||||
* // WSDL file is ready, create client
|
||||
* $client = new Client($browser, (string)$response->getBody());
|
||||
*
|
||||
* // do something…
|
||||
* },
|
||||
* function (Exception $e) {
|
||||
* // an error occured while trying to download the WSDL
|
||||
* }
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* The `Client` constructor loads the given WSDL file contents into memory and
|
||||
* parses its definition. If the given WSDL file is invalid and can not be
|
||||
* parsed, this will throw a `SoapFault`:
|
||||
*
|
||||
* ```php
|
||||
* try {
|
||||
* $client = new Client($browser, $wsdl);
|
||||
* } catch (SoapFault $e) {
|
||||
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* > Note that if you have `ext-xdebug` loaded, this may halt with a fatal
|
||||
* error instead of throwing a `SoapFault`. It is not recommended to use this
|
||||
* extension in production, so this should only ever affect test environments.
|
||||
*
|
||||
* The `Client` constructor accepts an array of options. All given options will
|
||||
* be passed through to the underlying `SoapClient`. However, not all options
|
||||
* make sense in this async implementation and as such may not have the desired
|
||||
* effect. See also [`SoapClient`](http://php.net/manual/en/soapclient.soapclient.php)
|
||||
* documentation for more details.
|
||||
*
|
||||
* If working in WSDL mode, the `$options` parameter is optional. If working in
|
||||
* non-WSDL mode, the WSDL parameter must be set to `null` and the options
|
||||
* parameter must contain the `location` and `uri` options, where `location` is
|
||||
* the URL of the SOAP server to send the request to, and `uri` is the target
|
||||
* namespace of the SOAP service:
|
||||
*
|
||||
* ```php
|
||||
* $client = new Client($browser, null, array(
|
||||
* 'location' => 'http://example.com',
|
||||
* 'uri' => 'http://ping.example.com',
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* Similarly, if working in WSDL mode, the `location` option can be used to
|
||||
* explicitly overwrite the URL of the SOAP server to send the request to:
|
||||
*
|
||||
* ```php
|
||||
* $client = new Client($browser, $wsdl, array(
|
||||
* 'location' => 'http://example.com'
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* You can use the `soap_version` option to change from the default SOAP 1.1 to
|
||||
* use SOAP 1.2 instead:
|
||||
*
|
||||
* ```php
|
||||
* $client = new Client($browser, $wsdl, array(
|
||||
* 'soap_version' => SOAP_1_2
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* You can use the `classmap` option to map certain WSDL types to PHP classes
|
||||
* like this:
|
||||
*
|
||||
* ```php
|
||||
* $client = new Client($browser, $wsdl, array(
|
||||
* 'classmap' => array(
|
||||
* 'getBankResponseType' => BankResponse::class
|
||||
* )
|
||||
* ));
|
||||
* ```
|
||||
*
|
||||
* The `proxy_host` option (and family) is not supported by this library. As an
|
||||
* alternative, you can configure the given `$browser` instance to use an
|
||||
* [HTTP proxy server](https://github.com/clue/reactphp-buzz#http-proxy).
|
||||
* If you find any other option is missing or not supported here, PRs are much
|
||||
* appreciated!
|
||||
*
|
||||
* All public methods of the `Client` are considered *advanced usage*.
|
||||
* If you want to call RPC functions, see below for the [`Proxy`](#proxy) class.
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
private $browser;
|
||||
private $encoder;
|
||||
private $decoder;
|
||||
|
||||
/**
|
||||
* Instantiate a new SOAP client for the given WSDL contents.
|
||||
*
|
||||
* @param Browser $browser
|
||||
* @param string|null $wsdlContents
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(Browser $browser, $wsdlContents, array $options = array())
|
||||
{
|
||||
$wsdl = $wsdlContents !== null ? 'data://text/plain;base64,' . base64_encode($wsdlContents) : null;
|
||||
|
||||
// Accept HTTP responses with error status codes as valid responses.
|
||||
// This is done in order to process these error responses through the normal SOAP decoder.
|
||||
// Additionally, we explicitly limit number of redirects to zero because following redirects makes little sense
|
||||
// because it transforms the POST request to a GET one and hence loses the SOAP request body.
|
||||
$browser = $browser->withOptions(array(
|
||||
'obeySuccessCode' => false,
|
||||
'followRedirects' => true,
|
||||
'maxRedirects' => 0
|
||||
));
|
||||
|
||||
$this->browser = $browser;
|
||||
$this->encoder = new ClientEncoder($wsdl, $options);
|
||||
$this->decoder = new ClientDecoder($wsdl, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue the given function to be sent via SOAP and wait for a response from the remote web service.
|
||||
*
|
||||
* ```php
|
||||
* // advanced usage, see Proxy for recommended alternative
|
||||
* $promise = $client->soapCall('ping', array('hello', 42));
|
||||
* ```
|
||||
*
|
||||
* Note: This is considered *advanced usage*, you may want to look into using the [`Proxy`](#proxy) instead.
|
||||
*
|
||||
* ```php
|
||||
* $proxy = new Proxy($client);
|
||||
* $promise = $proxy->ping('hello', 42);
|
||||
* ```
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed[] $args
|
||||
* @return PromiseInterface Returns a Promise<mixed, Exception>
|
||||
*/
|
||||
public function soapCall($name, $args)
|
||||
{
|
||||
try {
|
||||
$request = $this->encoder->encode($name, $args);
|
||||
} catch (\Exception $e) {
|
||||
$deferred = new Deferred();
|
||||
$deferred->reject($e);
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
$decoder = $this->decoder;
|
||||
|
||||
return $this->browser->send($request)->then(
|
||||
function (ResponseInterface $response) use ($decoder, $name) {
|
||||
// HTTP response received => decode results for this function call
|
||||
return $decoder->decode($name, (string)$response->getBody());
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of functions defined in the WSDL.
|
||||
*
|
||||
* It returns the equivalent of PHP's
|
||||
* [`SoapClient::__getFunctions()`](http://php.net/manual/en/soapclient.getfunctions.php).
|
||||
* In non-WSDL mode, this method returns `null`.
|
||||
*
|
||||
* @return string[]|null
|
||||
*/
|
||||
public function getFunctions()
|
||||
{
|
||||
return $this->encoder->__getFunctions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of types defined in the WSDL.
|
||||
*
|
||||
* It returns the equivalent of PHP's
|
||||
* [`SoapClient::__getTypes()`](http://php.net/manual/en/soapclient.gettypes.php).
|
||||
* In non-WSDL mode, this method returns `null`.
|
||||
*
|
||||
* @return string[]|null
|
||||
*/
|
||||
public function getTypes()
|
||||
{
|
||||
return $this->encoder->__getTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the location (URI) of the given webservice `$function`.
|
||||
*
|
||||
* Note that this is not to be confused with the WSDL file location.
|
||||
* A WSDL file can contain any number of function definitions.
|
||||
* It's very common that all of these functions use the same location definition.
|
||||
* However, technically each function can potentially use a different location.
|
||||
*
|
||||
* The `$function` parameter should be a string with the the SOAP function name.
|
||||
* See also [`getFunctions()`](#getfunctions) for a list of all available functions.
|
||||
*
|
||||
* ```php
|
||||
* assert('http://example.com/soap/service' === $client->getLocation('echo'));
|
||||
* ```
|
||||
*
|
||||
* For easier access, this function also accepts a numeric function index.
|
||||
* It then uses [`getFunctions()`](#getfunctions) internally to get the function
|
||||
* name for the given index.
|
||||
* This is particularly useful for the very common case where all functions use the
|
||||
* same location and accessing the first location is sufficient.
|
||||
*
|
||||
* ```php
|
||||
* assert('http://example.com/soap/service' === $client->getLocation(0));
|
||||
* ```
|
||||
*
|
||||
* When the `location` option has been set in the `Client` constructor
|
||||
* (such as when in non-WSDL mode) or via the `withLocation()` method, this
|
||||
* method returns the value of the given location.
|
||||
*
|
||||
* Passing a `$function` not defined in the WSDL file will throw a `SoapFault`.
|
||||
*
|
||||
* @param string|int $function
|
||||
* @return string
|
||||
* @throws \SoapFault if given function does not exist
|
||||
* @see self::getFunctions()
|
||||
*/
|
||||
public function getLocation($function)
|
||||
{
|
||||
if (is_int($function)) {
|
||||
$functions = $this->getFunctions();
|
||||
if (isset($functions[$function]) && preg_match('/^\w+ (\w+)\(/', $functions[$function], $match)) {
|
||||
$function = $match[1];
|
||||
}
|
||||
}
|
||||
|
||||
// encode request for given $function
|
||||
return (string)$this->encoder->encode($function, array())->getUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new `Client` with the updated location (URI) for all functions.
|
||||
*
|
||||
* Note that this is not to be confused with the WSDL file location.
|
||||
* A WSDL file can contain any number of function definitions.
|
||||
* It's very common that all of these functions use the same location definition.
|
||||
* However, technically each function can potentially use a different location.
|
||||
*
|
||||
* ```php
|
||||
* $client = $client->withLocation('http://example.com/soap');
|
||||
*
|
||||
* assert('http://example.com/soap' === $client->getLocation('echo'));
|
||||
* ```
|
||||
*
|
||||
* As an alternative to this method, you can also set the `location` option
|
||||
* in the `Client` constructor (such as when in non-WSDL mode).
|
||||
*
|
||||
* @param string $location
|
||||
* @return self
|
||||
* @see self::getLocation()
|
||||
*/
|
||||
public function withLocation($location)
|
||||
{
|
||||
$client = clone $this;
|
||||
$client->encoder = clone $this->encoder;
|
||||
$client->encoder->__setLocation($location);
|
||||
|
||||
return $client;
|
||||
}
|
||||
}
|
53
vendor/clue/soap-react/src/Protocol/ClientDecoder.php
vendored
Normal file
53
vendor/clue/soap-react/src/Protocol/ClientDecoder.php
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Soap\Protocol;
|
||||
|
||||
use \SoapClient;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ClientDecoder extends SoapClient
|
||||
{
|
||||
private $response = null;
|
||||
|
||||
/**
|
||||
* Decodes the SOAP response / return value from the given SOAP envelope (HTTP response body)
|
||||
*
|
||||
* @param string $function
|
||||
* @param string $response
|
||||
* @return mixed
|
||||
* @throws \SoapFault if response indicates a fault (error condition) or is invalid
|
||||
*/
|
||||
public function decode($function, $response)
|
||||
{
|
||||
// Temporarily save response internally for further processing
|
||||
$this->response = $response;
|
||||
|
||||
// Let's pretend we just invoked the given SOAP function.
|
||||
// This won't actually invoke anything (see `__doRequest()`), but this
|
||||
// requires a valid function name to match its definition in the WSDL.
|
||||
// Internally, simply use the injected response to parse its results.
|
||||
$ret = $this->__soapCall($function, array());
|
||||
$this->response = null;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites the internal request logic to parse the response
|
||||
*
|
||||
* By overwriting this method, we can skip the actual request sending logic
|
||||
* and still use the internal parsing logic by injecting the response as
|
||||
* the return code in this method. This will implicitly be invoked by the
|
||||
* call to `pseudoCall()` in the above `decode()` method.
|
||||
*
|
||||
* @see SoapClient::__doRequest()
|
||||
*/
|
||||
public function __doRequest($request, $location, $action, $version, $one_way = 0)
|
||||
{
|
||||
// the actual result doesn't actually matter, just return the given result
|
||||
// this will be processed internally and will return the parsed result
|
||||
return $this->response;
|
||||
}
|
||||
}
|
69
vendor/clue/soap-react/src/Protocol/ClientEncoder.php
vendored
Normal file
69
vendor/clue/soap-react/src/Protocol/ClientEncoder.php
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Soap\Protocol;
|
||||
|
||||
use \SoapClient;
|
||||
use RingCentral\Psr7\Request;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ClientEncoder extends SoapClient
|
||||
{
|
||||
private $request = null;
|
||||
|
||||
/**
|
||||
* Encodes the given RPC function name and arguments as a SOAP request
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $args
|
||||
* @return Request
|
||||
* @throws \SoapFault if request is invalid according to WSDL
|
||||
*/
|
||||
public function encode($name, $args)
|
||||
{
|
||||
$this->__soapCall($name, $args);
|
||||
|
||||
$request = $this->request;
|
||||
$this->request = null;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites the internal request logic to build the request message
|
||||
*
|
||||
* By overwriting this method, we can skip the actual request sending logic
|
||||
* and still use the internal request serializing logic by accessing the
|
||||
* given `$request` parameter and building our custom request object from
|
||||
* it. We skip/ignore its parsing logic by returing an empty response here.
|
||||
* This will implicitly be invoked by the call to `__soapCall()` in the
|
||||
* above `encode()` method.
|
||||
*
|
||||
* @see SoapClient::__doRequest()
|
||||
*/
|
||||
public function __doRequest($request, $location, $action, $version, $one_way = 0)
|
||||
{
|
||||
$headers = array();
|
||||
if ($version === SOAP_1_1) {
|
||||
$headers = array(
|
||||
'SOAPAction' => $action,
|
||||
'Content-Type' => 'text/xml; charset=utf-8'
|
||||
);
|
||||
} elseif ($version === SOAP_1_2) {
|
||||
$headers = array(
|
||||
'Content-Type' => 'application/soap+xml; charset=utf-8; action=' . $action
|
||||
);
|
||||
}
|
||||
|
||||
$this->request = new Request(
|
||||
'POST',
|
||||
(string)$location,
|
||||
$headers,
|
||||
(string)$request
|
||||
);
|
||||
|
||||
// do not actually block here, just pretend we're done...
|
||||
return '';
|
||||
}
|
||||
}
|
50
vendor/clue/soap-react/src/Proxy.php
vendored
Normal file
50
vendor/clue/soap-react/src/Proxy.php
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Soap;
|
||||
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
/**
|
||||
* The `Proxy` class wraps an existing [`Client`](#client) instance in order to ease calling
|
||||
* SOAP functions.
|
||||
*
|
||||
* ```php
|
||||
* $proxy = new Proxy($client);
|
||||
* ```
|
||||
*
|
||||
* Each and every method call to the `Proxy` class will be sent via SOAP.
|
||||
*
|
||||
* ```php
|
||||
* $proxy->myMethod($myArg1, $myArg2)->then(function ($response) {
|
||||
* // result received
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Please refer to your WSDL or its accompanying documentation for details
|
||||
* on which functions and arguments are supported.
|
||||
*
|
||||
* > Note that this class is called "Proxy" because it will forward (proxy) all
|
||||
* method calls to the actual SOAP service via the underlying
|
||||
* [`Client::soapCall()`](#soapcall) method. This is not to be confused with
|
||||
* using a proxy server. See [`Client`](#client) documentation for more
|
||||
* details on how to use an HTTP proxy server.
|
||||
*/
|
||||
final class Proxy
|
||||
{
|
||||
private $client;
|
||||
|
||||
public function __construct(Client $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed[] $args
|
||||
* @return PromiseInterface
|
||||
*/
|
||||
public function __call($name, $args)
|
||||
{
|
||||
return $this->client->soapCall($name, $args);
|
||||
}
|
||||
}
|
21
vendor/clue/socket-raw/LICENSE
vendored
Normal file
21
vendor/clue/socket-raw/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Christian Lück
|
||||
|
||||
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.
|
23
vendor/clue/socket-raw/composer.json
vendored
Normal file
23
vendor/clue/socket-raw/composer.json
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "clue/socket-raw",
|
||||
"description": "Simple and lightweight OOP wrapper for PHP's low level sockets extension (ext-sockets)",
|
||||
"keywords": ["socket", "stream", "datagram", "dgram", "client", "server", "ipv6", "tcp", "udp", "icmp", "unix", "udg"],
|
||||
"homepage": "https://github.com/clue/php-socket-raw",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {"Socket\\Raw\\": "src"}
|
||||
},
|
||||
"require": {
|
||||
"ext-sockets": "*",
|
||||
"php": ">=5.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.0 || ^6.0 || ^5.2 || ^4.8.35"
|
||||
}
|
||||
}
|
80
vendor/clue/socket-raw/src/Exception.php
vendored
Normal file
80
vendor/clue/socket-raw/src/Exception.php
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Socket\Raw;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class Exception extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* Create an Exception after a socket operation on the given $resource failed
|
||||
*
|
||||
* @param resource $resource
|
||||
* @param string $messagePrefix
|
||||
* @return self
|
||||
* @uses socket_last_error() to get last socket error code
|
||||
* @uses socket_clear_error() to clear socket error code
|
||||
* @uses self::createFromCode() to automatically construct exception with full error message
|
||||
*/
|
||||
public static function createFromSocketResource($resource, $messagePrefix = 'Socket operation failed')
|
||||
{
|
||||
$code = socket_last_error($resource);
|
||||
socket_clear_error($resource);
|
||||
|
||||
return self::createFromCode($code, $messagePrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Exception after a global socket operation failed (like socket creation)
|
||||
*
|
||||
* @param string $messagePrefix
|
||||
* @return self
|
||||
* @uses socket_last_error() to get last global error code
|
||||
* @uses socket_clear_error() to clear global error code
|
||||
* @uses self::createFromCode() to automatically construct exception with full error message
|
||||
*/
|
||||
public static function createFromGlobalSocketOperation($messagePrefix = 'Socket operation failed')
|
||||
{
|
||||
$code = socket_last_error();
|
||||
socket_clear_error();
|
||||
|
||||
return self::createFromCode($code, $messagePrefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Exception for given error $code
|
||||
*
|
||||
* @param int $code
|
||||
* @param string $messagePrefix
|
||||
* @return self
|
||||
* @throws Exception if given $val is boolean false
|
||||
* @uses self::getErrorMessage() to translate error code to error message
|
||||
*/
|
||||
public static function createFromCode($code, $messagePrefix = 'Socket error')
|
||||
{
|
||||
return new self($messagePrefix . ': ' . self::getErrorMessage($code), $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* get error message for given error code
|
||||
*
|
||||
* @param int $code error code
|
||||
* @return string
|
||||
* @uses socket_strerror() to translate error code to error message
|
||||
* @uses get_defined_constants() to check for related error constant
|
||||
*/
|
||||
protected static function getErrorMessage($code)
|
||||
{
|
||||
$string = socket_strerror($code);
|
||||
|
||||
// search constant starting with SOCKET_ for this error code
|
||||
foreach (get_defined_constants() as $key => $value) {
|
||||
if($value === $code && strpos($key, 'SOCKET_') === 0) {
|
||||
$string .= ' (' . $key . ')';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
279
vendor/clue/socket-raw/src/Factory.php
vendored
Normal file
279
vendor/clue/socket-raw/src/Factory.php
vendored
Normal file
@ -0,0 +1,279 @@
|
||||
<?php
|
||||
|
||||
namespace Socket\Raw;
|
||||
|
||||
use \InvalidArgumentException;
|
||||
|
||||
class Factory
|
||||
{
|
||||
/**
|
||||
* create client socket connected to given target address
|
||||
*
|
||||
* @param string $address target address to connect to
|
||||
* @param null|float $timeout connection timeout (in seconds), default null = no limit
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws InvalidArgumentException if given address is invalid
|
||||
* @throws Exception on error
|
||||
* @uses self::createFromString()
|
||||
* @uses Socket::connect()
|
||||
* @uses Socket::connectTimeout()
|
||||
*/
|
||||
public function createClient($address, $timeout = null)
|
||||
{
|
||||
$socket = $this->createFromString($address, $scheme);
|
||||
|
||||
try {
|
||||
if ($timeout === null) {
|
||||
$socket->connect($address);
|
||||
} else {
|
||||
// connectTimeout enables non-blocking mode, so turn blocking on again
|
||||
$socket->connectTimeout($address, $timeout);
|
||||
$socket->setBlocking(true);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$socket->close();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* create server socket bound to given address (and start listening for streaming clients to connect to this stream socket)
|
||||
*
|
||||
* @param string $address address to bind socket to
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::createFromString()
|
||||
* @uses Socket::bind()
|
||||
* @uses Socket::listen() only for stream sockets (TCP/UNIX)
|
||||
*/
|
||||
public function createServer($address)
|
||||
{
|
||||
$socket = $this->createFromString($address, $scheme);
|
||||
|
||||
try {
|
||||
$socket->bind($address);
|
||||
|
||||
if ($socket->getType() === SOCK_STREAM) {
|
||||
$socket->listen();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$socket->close();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* create TCP/IPv4 stream socket
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createTcp4()
|
||||
{
|
||||
return $this->create(AF_INET, SOCK_STREAM, SOL_TCP);
|
||||
}
|
||||
|
||||
/**
|
||||
* create TCP/IPv6 stream socket
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createTcp6()
|
||||
{
|
||||
return $this->create(AF_INET6, SOCK_STREAM, SOL_TCP);
|
||||
}
|
||||
|
||||
/**
|
||||
* create UDP/IPv4 datagram socket
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createUdp4()
|
||||
{
|
||||
return $this->create(AF_INET, SOCK_DGRAM, SOL_UDP);
|
||||
}
|
||||
|
||||
/**
|
||||
* create UDP/IPv6 datagram socket
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createUdp6()
|
||||
{
|
||||
return $this->create(AF_INET6, SOCK_DGRAM, SOL_UDP);
|
||||
}
|
||||
|
||||
/**
|
||||
* create local UNIX stream socket
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createUnix()
|
||||
{
|
||||
return $this->create(AF_UNIX, SOCK_STREAM, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* create local UNIX datagram socket (UDG)
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createUdg()
|
||||
{
|
||||
return $this->create(AF_UNIX, SOCK_DGRAM, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* create raw ICMP/IPv4 datagram socket (requires root!)
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createIcmp4()
|
||||
{
|
||||
return $this->create(AF_INET, SOCK_RAW, getprotobyname('icmp'));
|
||||
}
|
||||
|
||||
/**
|
||||
* create raw ICMPv6 (IPv6) datagram socket (requires root!)
|
||||
*
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception on error
|
||||
* @uses self::create()
|
||||
*/
|
||||
public function createIcmp6()
|
||||
{
|
||||
return $this->create(AF_INET6, SOCK_RAW, 58 /*getprotobyname('icmp')*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* create low level socket with given arguments
|
||||
*
|
||||
* @param int $domain
|
||||
* @param int $type
|
||||
* @param int $protocol
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception if creating socket fails
|
||||
* @uses socket_create()
|
||||
*/
|
||||
public function create($domain, $type, $protocol)
|
||||
{
|
||||
$sock = @socket_create($domain, $type, $protocol);
|
||||
if ($sock === false) {
|
||||
throw Exception::createFromGlobalSocketOperation('Unable to create socket');
|
||||
}
|
||||
return new Socket($sock);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a pair of indistinguishable sockets (commonly used in IPC)
|
||||
*
|
||||
* @param int $domain
|
||||
* @param int $type
|
||||
* @param int $protocol
|
||||
* @return \Socket\Raw\Socket[]
|
||||
* @throws Exception if creating pair of sockets fails
|
||||
* @uses socket_create_pair()
|
||||
*/
|
||||
public function createPair($domain, $type, $protocol)
|
||||
{
|
||||
$ret = @socket_create_pair($domain, $type, $protocol, $pair);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromGlobalSocketOperation('Unable to create pair of sockets');
|
||||
}
|
||||
return array(new Socket($pair[0]), new Socket($pair[1]));
|
||||
}
|
||||
|
||||
/**
|
||||
* create TCP/IPv4 stream socket and listen for new connections
|
||||
*
|
||||
* @param int $port
|
||||
* @param int $backlog
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws Exception if creating listening socket fails
|
||||
* @uses socket_create_listen()
|
||||
* @see self::createServer() as an alternative to bind to specific IP, IPv6, UDP, UNIX, UGP
|
||||
*/
|
||||
public function createListen($port, $backlog = 128)
|
||||
{
|
||||
$sock = @socket_create_listen($port, $backlog);
|
||||
if ($sock === false) {
|
||||
throw Exception::createFromGlobalSocketOperation('Unable to create listening socket');
|
||||
}
|
||||
return new Socket($sock);
|
||||
}
|
||||
|
||||
/**
|
||||
* create socket for given address
|
||||
*
|
||||
* @param string $address (passed by reference in order to remove scheme, if present)
|
||||
* @param string $scheme default scheme to use, defaults to TCP (passed by reference in order to update with actual scheme used)
|
||||
* @return \Socket\Raw\Socket
|
||||
* @throws InvalidArgumentException if given address is invalid
|
||||
* @throws Exception in case creating socket failed
|
||||
* @uses self::createTcp4() etc.
|
||||
*/
|
||||
public function createFromString(&$address, &$scheme)
|
||||
{
|
||||
if ($scheme === null) {
|
||||
$scheme = 'tcp';
|
||||
}
|
||||
|
||||
$hasScheme = false;
|
||||
|
||||
$pos = strpos($address, '://');
|
||||
if ($pos !== false) {
|
||||
$scheme = substr($address, 0, $pos);
|
||||
$address = substr($address, $pos + 3);
|
||||
$hasScheme = true;
|
||||
}
|
||||
|
||||
if (strpos($address, ':') !== strrpos($address, ':') && in_array($scheme, array('tcp', 'udp', 'icmp'))) {
|
||||
// TCP/UDP/ICMP address with several colons => must be IPv6
|
||||
$scheme .= '6';
|
||||
}
|
||||
|
||||
if ($scheme === 'tcp') {
|
||||
$socket = $this->createTcp4();
|
||||
} elseif ($scheme === 'udp') {
|
||||
$socket = $this->createUdp4();
|
||||
} elseif ($scheme === 'tcp6') {
|
||||
$socket = $this->createTcp6();
|
||||
} elseif ($scheme === 'udp6') {
|
||||
$socket = $this->createUdp6();
|
||||
} elseif ($scheme === 'unix') {
|
||||
$socket = $this->createUnix();
|
||||
} elseif ($scheme === 'udg') {
|
||||
$socket = $this->createUdg();
|
||||
} elseif ($scheme === 'icmp') {
|
||||
$socket = $this->createIcmp4();
|
||||
} elseif ($scheme === 'icmp6') {
|
||||
$socket = $this->createIcmp6();
|
||||
if ($hasScheme) {
|
||||
// scheme was stripped from address, resulting IPv6 must not
|
||||
// have a port (due to ICMP) and thus must not be enclosed in
|
||||
// square brackets
|
||||
$address = trim($address, '[]');
|
||||
}
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid address scheme given');
|
||||
}
|
||||
return $socket;
|
||||
}
|
||||
}
|
530
vendor/clue/socket-raw/src/Socket.php
vendored
Normal file
530
vendor/clue/socket-raw/src/Socket.php
vendored
Normal file
@ -0,0 +1,530 @@
|
||||
<?php
|
||||
|
||||
namespace Socket\Raw;
|
||||
|
||||
/**
|
||||
* simple and lightweight OOP wrapper for the low level sockets extension (ext-sockets)
|
||||
*
|
||||
* @author clue
|
||||
* @link https://github.com/clue/socket-raw
|
||||
*/
|
||||
class Socket
|
||||
{
|
||||
/**
|
||||
* reference to actual socket resource
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
private $resource;
|
||||
|
||||
/**
|
||||
* instanciate socket wrapper for given socket resource
|
||||
*
|
||||
* should usually not be called manually, see Factory
|
||||
*
|
||||
* @param resource $resource
|
||||
* @see Factory as the preferred (and simplest) way to construct socket instances
|
||||
*/
|
||||
public function __construct($resource)
|
||||
{
|
||||
$this->resource = $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* get actual socket resource
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function getResource()
|
||||
{
|
||||
return $this->resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* accept an incomming connection on this listening socket
|
||||
*
|
||||
* @return \Socket\Raw\Socket new connected socket used for communication
|
||||
* @throws Exception on error, if this is not a listening socket or there's no connection pending
|
||||
* @see self::selectRead() to check if this listening socket can accept()
|
||||
* @see Factory::createServer() to create a listening socket
|
||||
* @see self::listen() has to be called first
|
||||
* @uses socket_accept()
|
||||
*/
|
||||
public function accept()
|
||||
{
|
||||
$resource = @socket_accept($this->resource);
|
||||
if ($resource === false) {
|
||||
throw Exception::createFromGlobalSocketOperation();
|
||||
}
|
||||
return new Socket($resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* binds a name/address/path to this socket
|
||||
*
|
||||
* has to be called before issuing connect() or listen()
|
||||
*
|
||||
* @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception on error
|
||||
* @uses socket_bind()
|
||||
*/
|
||||
public function bind($address)
|
||||
{
|
||||
$ret = @socket_bind($this->resource, $this->unformatAddress($address, $port), $port);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* close this socket
|
||||
*
|
||||
* ATTENTION: make sure to NOT re-use this socket instance after closing it!
|
||||
* its socket resource remains closed and most further operations will fail!
|
||||
*
|
||||
* @return self $this (chainable)
|
||||
* @see self::shutdown() should be called before closing socket
|
||||
* @uses socket_close()
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
socket_close($this->resource);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* initiate a connection to given address
|
||||
*
|
||||
* @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception on error
|
||||
* @uses socket_connect()
|
||||
*/
|
||||
public function connect($address)
|
||||
{
|
||||
$ret = @socket_connect($this->resource, $this->unformatAddress($address, $port), $port);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a new connection to given address, wait for up to $timeout seconds
|
||||
*
|
||||
* The given $timeout parameter is an upper bound, a maximum time to wait
|
||||
* for the connection to be either accepted or rejected.
|
||||
*
|
||||
* The resulting socket resource will be set to non-blocking mode,
|
||||
* regardless of its previous state and whether this method succedes or
|
||||
* if it fails. Make sure to reset with `setBlocking(true)` if you want to
|
||||
* continue using blocking calls.
|
||||
*
|
||||
* @param string $address either of IPv4:port, hostname:port, [IPv6]:port, unix-path
|
||||
* @param float $timeout maximum time to wait (in seconds)
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception on error
|
||||
* @uses self::setBlocking() to enable non-blocking mode
|
||||
* @uses self::connect() to initiate the connection
|
||||
* @uses self::selectWrite() to wait for the connection to complete
|
||||
* @uses self::assertAlive() to check connection state
|
||||
*/
|
||||
public function connectTimeout($address, $timeout)
|
||||
{
|
||||
$this->setBlocking(false);
|
||||
|
||||
try {
|
||||
// socket is non-blocking, so connect should emit EINPROGRESS
|
||||
$this->connect($address);
|
||||
|
||||
// socket is already connected immediately?
|
||||
return $this;
|
||||
} catch (Exception $e) {
|
||||
// non-blocking connect() should be EINPROGRESS (or EWOULDBLOCK on Windows) => otherwise re-throw
|
||||
if ($e->getCode() !== SOCKET_EINPROGRESS && $e->getCode() !== SOCKET_EWOULDBLOCK) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// connection should be completed (or rejected) within timeout
|
||||
if ($this->selectWrite($timeout) === false) {
|
||||
throw new Exception('Timed out while waiting for connection', SOCKET_ETIMEDOUT);
|
||||
}
|
||||
|
||||
// confirm connection success (or fail if connected has been rejected)
|
||||
$this->assertAlive();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get socket option
|
||||
*
|
||||
* @param int $level
|
||||
* @param int $optname
|
||||
* @return mixed
|
||||
* @throws Exception on error
|
||||
* @uses socket_get_option()
|
||||
*/
|
||||
public function getOption($level, $optname)
|
||||
{
|
||||
$value = @socket_get_option($this->resource, $level, $optname);
|
||||
if ($value === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* get remote side's address/path
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception on error
|
||||
* @uses socket_getpeername()
|
||||
*/
|
||||
public function getPeerName()
|
||||
{
|
||||
$ret = @socket_getpeername($this->resource, $address, $port);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this->formatAddress($address, $port);
|
||||
}
|
||||
|
||||
/**
|
||||
* get local side's address/path
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception on error
|
||||
* @uses socket_getsockname()
|
||||
*/
|
||||
public function getSockName()
|
||||
{
|
||||
$ret = @socket_getsockname($this->resource, $address, $port);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this->formatAddress($address, $port);
|
||||
}
|
||||
|
||||
/**
|
||||
* start listen for incoming connections
|
||||
*
|
||||
* @param int $backlog maximum number of incoming connections to be queued
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception on error
|
||||
* @see self::bind() has to be called first to bind name to socket
|
||||
* @uses socket_listen()
|
||||
*/
|
||||
public function listen($backlog = 0)
|
||||
{
|
||||
$ret = @socket_listen($this->resource, $backlog);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* read up to $length bytes from connect()ed / accept()ed socket
|
||||
*
|
||||
* The $type parameter specifies if this should use either binary safe reading
|
||||
* (PHP_BINARY_READ, the default) or stop at CR or LF characters (PHP_NORMAL_READ)
|
||||
*
|
||||
* @param int $length maximum length to read
|
||||
* @param int $type either of PHP_BINARY_READ (the default) or PHP_NORMAL_READ
|
||||
* @return string
|
||||
* @throws Exception on error
|
||||
* @see self::recv() if you need to pass flags
|
||||
* @uses socket_read()
|
||||
*/
|
||||
public function read($length, $type = PHP_BINARY_READ)
|
||||
{
|
||||
$data = @socket_read($this->resource, $length, $type);
|
||||
if ($data === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* receive up to $length bytes from connect()ed / accept()ed socket
|
||||
*
|
||||
* @param int $length maximum length to read
|
||||
* @param int $flags
|
||||
* @return string
|
||||
* @throws Exception on error
|
||||
* @see self::read() if you do not need to pass $flags
|
||||
* @see self::recvFrom() if your socket is not connect()ed
|
||||
* @uses socket_recv()
|
||||
*/
|
||||
public function recv($length, $flags)
|
||||
{
|
||||
$ret = @socket_recv($this->resource, $buffer, $length, $flags);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* receive up to $length bytes from socket
|
||||
*
|
||||
* @param int $length maximum length to read
|
||||
* @param int $flags
|
||||
* @param string $remote reference will be filled with remote/peer address/path
|
||||
* @return string
|
||||
* @throws Exception on error
|
||||
* @see self::recv() if your socket is connect()ed
|
||||
* @uses socket_recvfrom()
|
||||
*/
|
||||
public function recvFrom($length, $flags, &$remote)
|
||||
{
|
||||
$ret = @socket_recvfrom($this->resource, $buffer, $length, $flags, $address, $port);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
$remote = $this->formatAddress($address, $port);
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* check socket to see if a read/recv/revFrom will not block
|
||||
*
|
||||
* @param float|NULL $sec maximum time to wait (in seconds), 0 = immediate polling, null = no limit
|
||||
* @return boolean true = socket ready (read will not block), false = timeout expired, socket is not ready
|
||||
* @throws Exception on error
|
||||
* @uses socket_select()
|
||||
*/
|
||||
public function selectRead($sec = 0)
|
||||
{
|
||||
$usec = $sec === null ? null : (($sec - floor($sec)) * 1000000);
|
||||
$r = array($this->resource);
|
||||
$ret = @socket_select($r, $x, $x, $sec, $usec);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromGlobalSocketOperation('Failed to select socket for reading');
|
||||
}
|
||||
return !!$ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* check socket to see if a write/send/sendTo will not block
|
||||
*
|
||||
* @param float|NULL $sec maximum time to wait (in seconds), 0 = immediate polling, null = no limit
|
||||
* @return boolean true = socket ready (write will not block), false = timeout expired, socket is not ready
|
||||
* @throws Exception on error
|
||||
* @uses socket_select()
|
||||
*/
|
||||
public function selectWrite($sec = 0)
|
||||
{
|
||||
$usec = $sec === null ? null : (($sec - floor($sec)) * 1000000);
|
||||
$w = array($this->resource);
|
||||
$ret = @socket_select($x, $w, $x, $sec, $usec);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromGlobalSocketOperation('Failed to select socket for writing');
|
||||
}
|
||||
return !!$ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* send given $buffer to connect()ed / accept()ed socket
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param int $flags
|
||||
* @return int number of bytes actually written (make sure to check against given buffer length!)
|
||||
* @throws Exception on error
|
||||
* @see self::write() if you do not need to pass $flags
|
||||
* @see self::sendTo() if your socket is not connect()ed
|
||||
* @uses socket_send()
|
||||
*/
|
||||
public function send($buffer, $flags)
|
||||
{
|
||||
$ret = @socket_send($this->resource, $buffer, strlen($buffer), $flags);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* send given $buffer to socket
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param int $flags
|
||||
* @param string $remote remote/peer address/path
|
||||
* @return int number of bytes actually written
|
||||
* @throws Exception on error
|
||||
* @see self::send() if your socket is connect()ed
|
||||
* @uses socket_sendto()
|
||||
*/
|
||||
public function sendTo($buffer, $flags, $remote)
|
||||
{
|
||||
$ret = @socket_sendto($this->resource, $buffer, strlen($buffer), $flags, $this->unformatAddress($remote, $port), $port);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* enable/disable blocking/nonblocking mode (O_NONBLOCK flag)
|
||||
*
|
||||
* @param boolean $toggle
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception on error
|
||||
* @uses socket_set_block()
|
||||
* @uses socket_set_nonblock()
|
||||
*/
|
||||
public function setBlocking($toggle = true)
|
||||
{
|
||||
$ret = $toggle ? @socket_set_block($this->resource) : @socket_set_nonblock($this->resource);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* set socket option
|
||||
*
|
||||
* @param int $level
|
||||
* @param int $optname
|
||||
* @param mixed $optval
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception on error
|
||||
* @see self::getOption()
|
||||
* @uses socket_set_option()
|
||||
*/
|
||||
public function setOption($level, $optname, $optval)
|
||||
{
|
||||
$ret = @socket_set_option($this->resource, $level, $optname, $optval);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* shuts down socket for receiving, sending or both
|
||||
*
|
||||
* @param int $how 0 = shutdown reading, 1 = shutdown writing, 2 = shutdown reading and writing
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception on error
|
||||
* @see self::close()
|
||||
* @uses socket_shutdown()
|
||||
*/
|
||||
public function shutdown($how = 2)
|
||||
{
|
||||
$ret = @socket_shutdown($this->resource, $how);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* write $buffer to connect()ed / accept()ed socket
|
||||
*
|
||||
* @param string $buffer
|
||||
* @return int number of bytes actually written
|
||||
* @throws Exception on error
|
||||
* @see self::send() if you need to pass flags
|
||||
* @uses socket_write()
|
||||
*/
|
||||
public function write($buffer)
|
||||
{
|
||||
$ret = @socket_write($this->resource, $buffer);
|
||||
if ($ret === false) {
|
||||
throw Exception::createFromSocketResource($this->resource);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* get socket type as passed to socket_create()
|
||||
*
|
||||
* @return int usually either SOCK_STREAM or SOCK_DGRAM
|
||||
* @throws Exception on error
|
||||
* @uses self::getOption()
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->getOption(SOL_SOCKET, SO_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* assert that this socket is alive and its error code is 0
|
||||
*
|
||||
* This will fetch and reset the current socket error code from the
|
||||
* socket and options and will throw an Exception along with error
|
||||
* message and code if the code is not 0, i.e. if it does indicate
|
||||
* an error situation.
|
||||
*
|
||||
* Calling this method should not be needed in most cases and is
|
||||
* likely to not throw an Exception. Each socket operation like
|
||||
* connect(), send(), etc. will throw a dedicated Exception in case
|
||||
* of an error anyway.
|
||||
*
|
||||
* @return self $this (chainable)
|
||||
* @throws Exception if error code is not 0
|
||||
* @uses self::getOption() to retrieve and clear current error code
|
||||
* @uses self::getErrorMessage() to translate error code to
|
||||
*/
|
||||
public function assertAlive()
|
||||
{
|
||||
$code = $this->getOption(SOL_SOCKET, SO_ERROR);
|
||||
if ($code !== 0) {
|
||||
throw Exception::createFromCode($code, 'Socket error');
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* format given address/host/path and port
|
||||
*
|
||||
* @param string $address
|
||||
* @param int $port
|
||||
* @return string
|
||||
*/
|
||||
protected function formatAddress($address, $port)
|
||||
{
|
||||
if ($port !== 0) {
|
||||
if (strpos($address, ':') !== false) {
|
||||
$address = '[' . $address . ']';
|
||||
}
|
||||
$address .= ':' . $port;
|
||||
}
|
||||
return $address;
|
||||
}
|
||||
|
||||
/**
|
||||
* format given address by splitting it into returned address and port set by reference
|
||||
*
|
||||
* @param string $address
|
||||
* @param int $port
|
||||
* @return string address with port removed
|
||||
*/
|
||||
protected function unformatAddress($address, &$port)
|
||||
{
|
||||
// [::1]:2 => ::1 2
|
||||
// test:2 => test 2
|
||||
// ::1 => ::1
|
||||
// test => test
|
||||
|
||||
$colon = strrpos($address, ':');
|
||||
|
||||
// there is a colon and this is the only colon or there's a closing IPv6 bracket right before it
|
||||
if ($colon !== false && (strpos($address, ':') === $colon || strpos($address, ']') === ($colon - 1))) {
|
||||
$port = (int)substr($address, $colon + 1);
|
||||
$address = substr($address, 0, $colon);
|
||||
|
||||
// remove IPv6 square brackets
|
||||
if (substr($address, 0, 1) === '[') {
|
||||
$address = substr($address, 1, -1);
|
||||
}
|
||||
}
|
||||
return $address;
|
||||
}
|
||||
}
|
21
vendor/clue/socks-react/LICENSE
vendored
Normal file
21
vendor/clue/socks-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011 Christian Lück
|
||||
|
||||
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.
|
27
vendor/clue/socks-react/composer.json
vendored
Normal file
27
vendor/clue/socks-react/composer.json
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "clue/socks-react",
|
||||
"description": "Async SOCKS proxy connector client and server implementation, tunnel any TCP/IP-based protocol through a SOCKS5 or SOCKS4(a) proxy server, built on top of ReactPHP.",
|
||||
"keywords": ["socks client", "socks server", "socks5", "socks4a", "proxy server", "tcp tunnel", "async", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/reactphp-socks",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {"Clue\\React\\Socks\\": "src/"}
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/promise": "^2.1 || ^1.2",
|
||||
"react/socket": "^1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.0 || ^5.7 || ^4.8.35",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
|
||||
"clue/connection-manager-extra": "^1.0 || ^0.7",
|
||||
"clue/block-react": "^1.1"
|
||||
}
|
||||
}
|
441
vendor/clue/socks-react/src/Client.php
vendored
Normal file
441
vendor/clue/socks-react/src/Client.php
vendored
Normal file
@ -0,0 +1,441 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Socks;
|
||||
|
||||
use React\Promise;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Deferred;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Socket\FixedUriConnector;
|
||||
use \Exception;
|
||||
use \InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
final class Client implements ConnectorInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @var ConnectorInterface
|
||||
*/
|
||||
private $connector;
|
||||
|
||||
private $socksUri;
|
||||
|
||||
private $protocolVersion = 5;
|
||||
|
||||
private $auth = null;
|
||||
|
||||
public function __construct($socksUri, ConnectorInterface $connector)
|
||||
{
|
||||
// support `sockss://` scheme for SOCKS over TLS
|
||||
// support `socks+unix://` scheme for Unix domain socket (UDS) paths
|
||||
if (preg_match('/^(socks(?:5|4)?)(s|\+unix):\/\/(.*?@)?(.+?)$/', $socksUri, $match)) {
|
||||
// rewrite URI to parse SOCKS scheme, authentication and dummy host
|
||||
$socksUri = $match[1] . '://' . $match[3] . 'localhost';
|
||||
|
||||
// connector uses appropriate transport scheme and explicit host given
|
||||
$connector = new FixedUriConnector(
|
||||
($match[2] === 's' ? 'tls://' : 'unix://') . $match[4],
|
||||
$connector
|
||||
);
|
||||
}
|
||||
|
||||
// assume default scheme if none is given
|
||||
if (strpos($socksUri, '://') === false) {
|
||||
$socksUri = 'socks://' . $socksUri;
|
||||
}
|
||||
|
||||
// parse URI into individual parts
|
||||
$parts = parse_url($socksUri);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'])) {
|
||||
throw new \InvalidArgumentException('Invalid SOCKS server URI "' . $socksUri . '"');
|
||||
}
|
||||
|
||||
// assume default port
|
||||
if (!isset($parts['port'])) {
|
||||
$parts['port'] = 1080;
|
||||
}
|
||||
|
||||
// user or password in URI => SOCKS5 authentication
|
||||
if (isset($parts['user']) || isset($parts['pass'])) {
|
||||
if ($parts['scheme'] !== 'socks' && $parts['scheme'] !== 'socks5') {
|
||||
// fail if any other protocol version given explicitly
|
||||
throw new InvalidArgumentException('Authentication requires SOCKS5. Consider using protocol version 5 or waive authentication');
|
||||
}
|
||||
$parts += array('user' => '', 'pass' => '');
|
||||
$this->setAuth(rawurldecode($parts['user']), rawurldecode($parts['pass']));
|
||||
}
|
||||
|
||||
// check for valid protocol version from URI scheme
|
||||
$this->setProtocolVersionFromScheme($parts['scheme']);
|
||||
|
||||
$this->socksUri = $parts['host'] . ':' . $parts['port'];
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
||||
private function setProtocolVersionFromScheme($scheme)
|
||||
{
|
||||
if ($scheme === 'socks' || $scheme === 'socks5') {
|
||||
$this->protocolVersion = 5;
|
||||
} elseif ($scheme === 'socks4') {
|
||||
$this->protocolVersion = 4;
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid protocol version given "' . $scheme . '://"');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set login data for username/password authentication method (RFC1929)
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @link http://tools.ietf.org/html/rfc1929
|
||||
*/
|
||||
private function setAuth($username, $password)
|
||||
{
|
||||
if (strlen($username) > 255 || strlen($password) > 255) {
|
||||
throw new InvalidArgumentException('Both username and password MUST NOT exceed a length of 255 bytes each');
|
||||
}
|
||||
$this->auth = pack('C2', 0x01, strlen($username)) . $username . pack('C', strlen($password)) . $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish a TCP/IP connection to the given target URI through the SOCKS server
|
||||
*
|
||||
* Many higher-level networking protocols build on top of TCP. It you're dealing
|
||||
* with one such client implementation, it probably uses/accepts an instance
|
||||
* implementing ReactPHP's `ConnectorInterface` (and usually its default `Connector`
|
||||
* instance). In this case you can also pass this `Connector` instance instead
|
||||
* to make this client implementation SOCKS-aware. That's it.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return PromiseInterface Promise<ConnectionInterface,Exception>
|
||||
*/
|
||||
public function connect($uri)
|
||||
{
|
||||
if (strpos($uri, '://') === false) {
|
||||
$uri = 'tcp://' . $uri;
|
||||
}
|
||||
|
||||
$parts = parse_url($uri);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
|
||||
return Promise\reject(new InvalidArgumentException('Invalid target URI specified'));
|
||||
}
|
||||
|
||||
$host = trim($parts['host'], '[]');
|
||||
$port = $parts['port'];
|
||||
|
||||
if (strlen($host) > 255 || $port > 65535 || $port < 0 || (string)$port !== (string)(int)$port) {
|
||||
return Promise\reject(new InvalidArgumentException('Invalid target specified'));
|
||||
}
|
||||
|
||||
// construct URI to SOCKS server to connect to
|
||||
$socksUri = $this->socksUri;
|
||||
|
||||
// append path from URI if given
|
||||
if (isset($parts['path'])) {
|
||||
$socksUri .= $parts['path'];
|
||||
}
|
||||
|
||||
// parse query args
|
||||
$args = array();
|
||||
if (isset($parts['query'])) {
|
||||
parse_str($parts['query'], $args);
|
||||
}
|
||||
|
||||
// append hostname from URI to query string unless explicitly given
|
||||
if (!isset($args['hostname'])) {
|
||||
$args['hostname'] = $host;
|
||||
}
|
||||
|
||||
// append query string
|
||||
$socksUri .= '?' . http_build_query($args, '', '&');
|
||||
|
||||
// append fragment from URI if given
|
||||
if (isset($parts['fragment'])) {
|
||||
$socksUri .= '#' . $parts['fragment'];
|
||||
}
|
||||
|
||||
// start TCP/IP connection to SOCKS server
|
||||
$connecting = $this->connector->connect($socksUri);
|
||||
|
||||
$deferred = new Deferred(function ($_, $reject) use ($uri, $connecting) {
|
||||
$reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' cancelled while waiting for proxy (ECONNABORTED)',
|
||||
defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103
|
||||
));
|
||||
|
||||
// either close active connection or cancel pending connection attempt
|
||||
$connecting->then(function (ConnectionInterface $stream) {
|
||||
$stream->close();
|
||||
});
|
||||
$connecting->cancel();
|
||||
});
|
||||
|
||||
// handle SOCKS protocol once connection is ready
|
||||
// resolve plain connection once SOCKS protocol is completed
|
||||
$that = $this;
|
||||
$connecting->then(
|
||||
function (ConnectionInterface $stream) use ($that, $host, $port, $deferred, $uri) {
|
||||
$that->handleConnectedSocks($stream, $host, $port, $deferred, $uri);
|
||||
},
|
||||
function (Exception $e) use ($uri, $deferred) {
|
||||
$deferred->reject($e = new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because connection to proxy failed (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111,
|
||||
$e
|
||||
));
|
||||
|
||||
// avoid garbage references by replacing all closures in call stack.
|
||||
// what a lovely piece of code!
|
||||
$r = new \ReflectionProperty('Exception', 'trace');
|
||||
$r->setAccessible(true);
|
||||
$trace = $r->getValue($e);
|
||||
foreach ($trace as &$one) {
|
||||
foreach ($one['args'] as &$arg) {
|
||||
if ($arg instanceof \Closure) {
|
||||
$arg = 'Object(' . get_class($arg) . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
$r->setValue($e, $trace);
|
||||
}
|
||||
);
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper used to handle the communication with the SOCKS server
|
||||
*
|
||||
* @param ConnectionInterface $stream
|
||||
* @param string $host
|
||||
* @param int $port
|
||||
* @param Deferred $deferred
|
||||
* @param string $uri
|
||||
* @return void
|
||||
* @internal
|
||||
*/
|
||||
public function handleConnectedSocks(ConnectionInterface $stream, $host, $port, Deferred $deferred, $uri)
|
||||
{
|
||||
$reader = new StreamReader();
|
||||
$stream->on('data', array($reader, 'write'));
|
||||
|
||||
$stream->on('error', $onError = function (Exception $e) use ($deferred, $uri) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because connection to proxy caused a stream error (EIO)',
|
||||
defined('SOCKET_EIO') ? SOCKET_EIO : 5, $e)
|
||||
);
|
||||
});
|
||||
|
||||
$stream->on('close', $onClose = function () use ($deferred, $uri) {
|
||||
$deferred->reject(new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because connection to proxy was lost while waiting for response from proxy (ECONNRESET)',
|
||||
defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104)
|
||||
);
|
||||
});
|
||||
|
||||
if ($this->protocolVersion === 5) {
|
||||
$promise = $this->handleSocks5($stream, $host, $port, $reader, $uri);
|
||||
} else {
|
||||
$promise = $this->handleSocks4($stream, $host, $port, $reader, $uri);
|
||||
}
|
||||
|
||||
$promise->then(function () use ($deferred, $stream, $reader, $onError, $onClose) {
|
||||
$stream->removeListener('data', array($reader, 'write'));
|
||||
$stream->removeListener('error', $onError);
|
||||
$stream->removeListener('close', $onClose);
|
||||
|
||||
$deferred->resolve($stream);
|
||||
}, function (Exception $error) use ($deferred, $stream, $uri) {
|
||||
// pass custom RuntimeException through as-is, otherwise wrap in protocol error
|
||||
if (!$error instanceof RuntimeException) {
|
||||
$error = new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy returned invalid response (EBADMSG)',
|
||||
defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG: 71,
|
||||
$error
|
||||
);
|
||||
}
|
||||
|
||||
$deferred->reject($error);
|
||||
$stream->close();
|
||||
});
|
||||
}
|
||||
|
||||
private function handleSocks4(ConnectionInterface $stream, $host, $port, StreamReader $reader, $uri)
|
||||
{
|
||||
// do not resolve hostname. only try to convert to IP
|
||||
$ip = ip2long($host);
|
||||
|
||||
// send IP or (0.0.0.1) if invalid
|
||||
$data = pack('C2nNC', 0x04, 0x01, $port, $ip === false ? 1 : $ip, 0x00);
|
||||
|
||||
if ($ip === false) {
|
||||
// host is not a valid IP => send along hostname (SOCKS4a)
|
||||
$data .= $host . pack('C', 0x00);
|
||||
}
|
||||
|
||||
$stream->write($data);
|
||||
|
||||
return $reader->readBinary(array(
|
||||
'null' => 'C',
|
||||
'status' => 'C',
|
||||
'port' => 'n',
|
||||
'ip' => 'N'
|
||||
))->then(function ($data) use ($uri) {
|
||||
if ($data['null'] !== 0x00) {
|
||||
throw new Exception('Invalid SOCKS response');
|
||||
}
|
||||
if ($data['status'] !== 0x5a) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy refused connection with error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function handleSocks5(ConnectionInterface $stream, $host, $port, StreamReader $reader, $uri)
|
||||
{
|
||||
// protocol version 5
|
||||
$data = pack('C', 0x05);
|
||||
|
||||
$auth = $this->auth;
|
||||
if ($auth === null) {
|
||||
// one method, no authentication
|
||||
$data .= pack('C2', 0x01, 0x00);
|
||||
} else {
|
||||
// two methods, username/password and no authentication
|
||||
$data .= pack('C3', 0x02, 0x02, 0x00);
|
||||
}
|
||||
$stream->write($data);
|
||||
|
||||
$that = $this;
|
||||
|
||||
return $reader->readBinary(array(
|
||||
'version' => 'C',
|
||||
'method' => 'C'
|
||||
))->then(function ($data) use ($auth, $stream, $reader, $uri) {
|
||||
if ($data['version'] !== 0x05) {
|
||||
throw new Exception('Version/Protocol mismatch');
|
||||
}
|
||||
|
||||
if ($data['method'] === 0x02 && $auth !== null) {
|
||||
// username/password authentication requested and provided
|
||||
$stream->write($auth);
|
||||
|
||||
return $reader->readBinary(array(
|
||||
'version' => 'C',
|
||||
'status' => 'C'
|
||||
))->then(function ($data) use ($uri) {
|
||||
if ($data['version'] !== 0x01 || $data['status'] !== 0x00) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy denied access with given authentication details (EACCES)',
|
||||
defined('SOCKET_EACCES') ? SOCKET_EACCES : 13
|
||||
);
|
||||
}
|
||||
});
|
||||
} else if ($data['method'] !== 0x00) {
|
||||
// any other method than "no authentication"
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy denied access due to unsupported authentication method (EACCES)',
|
||||
defined('SOCKET_EACCES') ? SOCKET_EACCES : 13
|
||||
);
|
||||
}
|
||||
})->then(function () use ($stream, $reader, $host, $port) {
|
||||
// do not resolve hostname. only try to convert to (binary/packed) IP
|
||||
$ip = @inet_pton($host);
|
||||
|
||||
$data = pack('C3', 0x05, 0x01, 0x00);
|
||||
if ($ip === false) {
|
||||
// not an IP, send as hostname
|
||||
$data .= pack('C2', 0x03, strlen($host)) . $host;
|
||||
} else {
|
||||
// send as IPv4 / IPv6
|
||||
$data .= pack('C', (strpos($host, ':') === false) ? 0x01 : 0x04) . $ip;
|
||||
}
|
||||
$data .= pack('n', $port);
|
||||
|
||||
$stream->write($data);
|
||||
|
||||
return $reader->readBinary(array(
|
||||
'version' => 'C',
|
||||
'status' => 'C',
|
||||
'null' => 'C',
|
||||
'type' => 'C'
|
||||
));
|
||||
})->then(function ($data) use ($reader, $uri) {
|
||||
if ($data['version'] !== 0x05 || $data['null'] !== 0x00) {
|
||||
throw new Exception('Invalid SOCKS response');
|
||||
}
|
||||
if ($data['status'] !== 0x00) {
|
||||
// map limited list of SOCKS error codes to common socket error conditions
|
||||
// @link https://tools.ietf.org/html/rfc1928#section-6
|
||||
if ($data['status'] === Server::ERROR_GENERAL) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy refused connection with general server failure (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
|
||||
);
|
||||
} elseif ($data['status'] === Server::ERROR_NOT_ALLOWED_BY_RULESET) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy denied access due to ruleset (EACCES)',
|
||||
defined('SOCKET_EACCES') ? SOCKET_EACCES : 13
|
||||
);
|
||||
} elseif ($data['status'] === Server::ERROR_NETWORK_UNREACHABLE) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy reported network unreachable (ENETUNREACH)',
|
||||
defined('SOCKET_ENETUNREACH') ? SOCKET_ENETUNREACH : 101
|
||||
);
|
||||
} elseif ($data['status'] === Server::ERROR_HOST_UNREACHABLE) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy reported host unreachable (EHOSTUNREACH)',
|
||||
defined('SOCKET_EHOSTUNREACH') ? SOCKET_EHOSTUNREACH : 113
|
||||
);
|
||||
} elseif ($data['status'] === Server::ERROR_CONNECTION_REFUSED) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy reported connection refused (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
|
||||
);
|
||||
} elseif ($data['status'] === Server::ERROR_TTL) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy reported TTL/timeout expired (ETIMEDOUT)',
|
||||
defined('SOCKET_ETIMEDOUT') ? SOCKET_ETIMEDOUT : 110
|
||||
);
|
||||
} elseif ($data['status'] === Server::ERROR_COMMAND_UNSUPPORTED) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy does not support the CONNECT command (EPROTO)',
|
||||
defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71
|
||||
);
|
||||
} elseif ($data['status'] === Server::ERROR_ADDRESS_UNSUPPORTED) {
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy does not support this address type (EPROTO)',
|
||||
defined('SOCKET_EPROTO') ? SOCKET_EPROTO : 71
|
||||
);
|
||||
}
|
||||
|
||||
throw new RuntimeException(
|
||||
'Connection to ' . $uri . ' failed because proxy server refused connection with unknown error code ' . sprintf('0x%02X', $data['status']) . ' (ECONNREFUSED)',
|
||||
defined('SOCKET_ECONNREFUSED') ? SOCKET_ECONNREFUSED : 111
|
||||
);
|
||||
}
|
||||
if ($data['type'] === 0x01) {
|
||||
// IPv4 address => skip IP and port
|
||||
return $reader->readLength(6);
|
||||
} elseif ($data['type'] === 0x03) {
|
||||
// domain name => read domain name length
|
||||
return $reader->readBinary(array(
|
||||
'length' => 'C'
|
||||
))->then(function ($data) use ($reader) {
|
||||
// skip domain name and port
|
||||
return $reader->readLength($data['length'] + 2);
|
||||
});
|
||||
} elseif ($data['type'] === 0x04) {
|
||||
// IPv6 address => skip IP and port
|
||||
return $reader->readLength(18);
|
||||
} else {
|
||||
throw new Exception('Invalid SOCKS reponse: Invalid address type');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
393
vendor/clue/socks-react/src/Server.php
vendored
Normal file
393
vendor/clue/socks-react/src/Server.php
vendored
Normal file
@ -0,0 +1,393 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Socks;
|
||||
|
||||
use React\Socket\ServerInterface;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Socket\ConnectorInterface;
|
||||
use React\Socket\Connector;
|
||||
use React\Socket\ConnectionInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use \UnexpectedValueException;
|
||||
use \InvalidArgumentException;
|
||||
use \Exception;
|
||||
use React\Promise\Timer\TimeoutException;
|
||||
|
||||
final class Server
|
||||
{
|
||||
// the following error codes are only used for SOCKS5 only
|
||||
/** @internal */
|
||||
const ERROR_GENERAL = 0x01;
|
||||
/** @internal */
|
||||
const ERROR_NOT_ALLOWED_BY_RULESET = 0x02;
|
||||
/** @internal */
|
||||
const ERROR_NETWORK_UNREACHABLE = 0x03;
|
||||
/** @internal */
|
||||
const ERROR_HOST_UNREACHABLE = 0x04;
|
||||
/** @internal */
|
||||
const ERROR_CONNECTION_REFUSED = 0x05;
|
||||
/** @internal */
|
||||
const ERROR_TTL = 0x06;
|
||||
/** @internal */
|
||||
const ERROR_COMMAND_UNSUPPORTED = 0x07;
|
||||
/** @internal */
|
||||
const ERROR_ADDRESS_UNSUPPORTED = 0x08;
|
||||
|
||||
private $loop;
|
||||
|
||||
private $connector;
|
||||
|
||||
/**
|
||||
* @var null|callable
|
||||
*/
|
||||
private $auth;
|
||||
|
||||
/**
|
||||
* @param LoopInterface $loop
|
||||
* @param null|ConnectorInterface $connector
|
||||
* @param null|array|callable $auth
|
||||
*/
|
||||
public function __construct(LoopInterface $loop, ConnectorInterface $connector = null, $auth = null)
|
||||
{
|
||||
if ($connector === null) {
|
||||
$connector = new Connector($loop);
|
||||
}
|
||||
|
||||
if (\is_array($auth)) {
|
||||
// wrap authentication array in authentication callback
|
||||
$this->auth = function ($username, $password) use ($auth) {
|
||||
return \React\Promise\resolve(
|
||||
isset($auth[$username]) && (string)$auth[$username] === $password
|
||||
);
|
||||
};
|
||||
} elseif (\is_callable($auth)) {
|
||||
// wrap authentication callback in order to cast its return value to a promise
|
||||
$this->auth = function($username, $password, $remote) use ($auth) {
|
||||
return \React\Promise\resolve(
|
||||
\call_user_func($auth, $username, $password, $remote)
|
||||
);
|
||||
};
|
||||
} elseif ($auth !== null) {
|
||||
throw new \InvalidArgumentException('Invalid authenticator given');
|
||||
}
|
||||
|
||||
$this->loop = $loop;
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerInterface $socket
|
||||
* @return void
|
||||
*/
|
||||
public function listen(ServerInterface $socket)
|
||||
{
|
||||
$that = $this;
|
||||
$socket->on('connection', function ($connection) use ($that) {
|
||||
$that->onConnection($connection);
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function onConnection(ConnectionInterface $connection)
|
||||
{
|
||||
$that = $this;
|
||||
$handling = $this->handleSocks($connection)->then(null, function () use ($connection, $that) {
|
||||
// SOCKS failed => close connection
|
||||
$that->endConnection($connection);
|
||||
});
|
||||
|
||||
$connection->on('close', function () use ($handling) {
|
||||
$handling->cancel();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* [internal] gracefully shutdown connection by flushing all remaining data and closing stream
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function endConnection(ConnectionInterface $stream)
|
||||
{
|
||||
$tid = true;
|
||||
$loop = $this->loop;
|
||||
|
||||
// cancel below timer in case connection is closed in time
|
||||
$stream->once('close', function () use (&$tid, $loop) {
|
||||
// close event called before the timer was set up, so everything is okay
|
||||
if ($tid === true) {
|
||||
// make sure to not start a useless timer
|
||||
$tid = false;
|
||||
} else {
|
||||
$loop->cancelTimer($tid);
|
||||
}
|
||||
});
|
||||
|
||||
// shut down connection by pausing input data, flushing outgoing buffer and then exit
|
||||
$stream->pause();
|
||||
$stream->end();
|
||||
|
||||
// check if connection is not already closed
|
||||
if ($tid === true) {
|
||||
// fall back to forcefully close connection in 3 seconds if buffer can not be flushed
|
||||
$tid = $loop->addTimer(3.0, array($stream,'close'));
|
||||
}
|
||||
}
|
||||
|
||||
private function handleSocks(ConnectionInterface $stream)
|
||||
{
|
||||
$reader = new StreamReader();
|
||||
$stream->on('data', array($reader, 'write'));
|
||||
|
||||
$that = $this;
|
||||
$auth = $this->auth;
|
||||
|
||||
return $reader->readByte()->then(function ($version) use ($stream, $that, $auth, $reader){
|
||||
if ($version === 0x04) {
|
||||
if ($auth !== null) {
|
||||
throw new UnexpectedValueException('SOCKS4 not allowed because authentication is required');
|
||||
}
|
||||
return $that->handleSocks4($stream, $reader);
|
||||
} else if ($version === 0x05) {
|
||||
return $that->handleSocks5($stream, $auth, $reader);
|
||||
}
|
||||
throw new UnexpectedValueException('Unexpected/unknown version number');
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleSocks4(ConnectionInterface $stream, StreamReader $reader)
|
||||
{
|
||||
$remote = $stream->getRemoteAddress();
|
||||
if ($remote !== null) {
|
||||
// remove transport scheme and prefix socks4:// instead
|
||||
$secure = strpos($remote, 'tls://') === 0;
|
||||
if (($pos = strpos($remote, '://')) !== false) {
|
||||
$remote = substr($remote, $pos + 3);
|
||||
}
|
||||
$remote = 'socks4' . ($secure ? 's' : '') . '://' . $remote;
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
return $reader->readByteAssert(0x01)->then(function () use ($reader) {
|
||||
return $reader->readBinary(array(
|
||||
'port' => 'n',
|
||||
'ipLong' => 'N',
|
||||
'null' => 'C'
|
||||
));
|
||||
})->then(function ($data) use ($reader, $remote) {
|
||||
if ($data['null'] !== 0x00) {
|
||||
throw new Exception('Not a null byte');
|
||||
}
|
||||
if ($data['ipLong'] === 0) {
|
||||
throw new Exception('Invalid IP');
|
||||
}
|
||||
if ($data['port'] === 0) {
|
||||
throw new Exception('Invalid port');
|
||||
}
|
||||
if ($data['ipLong'] < 256) {
|
||||
// invalid IP => probably a SOCKS4a request which appends the hostname
|
||||
return $reader->readStringNull()->then(function ($string) use ($data, $remote){
|
||||
return array($string, $data['port'], $remote);
|
||||
});
|
||||
} else {
|
||||
$ip = long2ip($data['ipLong']);
|
||||
return array($ip, $data['port'], $remote);
|
||||
}
|
||||
})->then(function ($target) use ($stream, $that) {
|
||||
return $that->connectTarget($stream, $target)->then(function (ConnectionInterface $remote) use ($stream){
|
||||
$stream->write(pack('C8', 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
|
||||
|
||||
return $remote;
|
||||
}, function($error) use ($stream){
|
||||
$stream->end(pack('C8', 0x00, 0x5b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00));
|
||||
|
||||
throw $error;
|
||||
});
|
||||
}, function($error) {
|
||||
throw new UnexpectedValueException('SOCKS4 protocol error',0,$error);
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleSocks5(ConnectionInterface $stream, $auth, StreamReader $reader)
|
||||
{
|
||||
$remote = $stream->getRemoteAddress();
|
||||
if ($remote !== null) {
|
||||
// remove transport scheme and prefix socks5:// instead
|
||||
$secure = strpos($remote, 'tls://') === 0;
|
||||
if (($pos = strpos($remote, '://')) !== false) {
|
||||
$remote = substr($remote, $pos + 3);
|
||||
}
|
||||
$remote = 'socks' . ($secure ? 's' : '') . '://' . $remote;
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
return $reader->readByte()->then(function ($num) use ($reader) {
|
||||
// $num different authentication mechanisms offered
|
||||
return $reader->readLength($num);
|
||||
})->then(function ($methods) use ($reader, $stream, $auth, &$remote) {
|
||||
if ($auth === null && strpos($methods,"\x00") !== false) {
|
||||
// accept "no authentication"
|
||||
$stream->write(pack('C2', 0x05, 0x00));
|
||||
|
||||
return 0x00;
|
||||
} else if ($auth !== null && strpos($methods,"\x02") !== false) {
|
||||
// username/password authentication (RFC 1929) sub negotiation
|
||||
$stream->write(pack('C2', 0x05, 0x02));
|
||||
return $reader->readByteAssert(0x01)->then(function () use ($reader) {
|
||||
return $reader->readByte();
|
||||
})->then(function ($length) use ($reader) {
|
||||
return $reader->readLength($length);
|
||||
})->then(function ($username) use ($reader, $auth, $stream, &$remote) {
|
||||
return $reader->readByte()->then(function ($length) use ($reader) {
|
||||
return $reader->readLength($length);
|
||||
})->then(function ($password) use ($username, $auth, $stream, &$remote) {
|
||||
// username and password given => authenticate
|
||||
|
||||
// prefix username/password to remote URI
|
||||
if ($remote !== null) {
|
||||
$remote = str_replace('://', '://' . rawurlencode($username) . ':' . rawurlencode($password) . '@', $remote);
|
||||
}
|
||||
|
||||
return $auth($username, $password, $remote)->then(function ($authenticated) use ($stream) {
|
||||
if ($authenticated) {
|
||||
// accept auth
|
||||
$stream->write(pack('C2', 0x01, 0x00));
|
||||
} else {
|
||||
// reject auth => send any code but 0x00
|
||||
$stream->end(pack('C2', 0x01, 0xFF));
|
||||
throw new UnexpectedValueException('Authentication denied');
|
||||
}
|
||||
}, function ($e) use ($stream) {
|
||||
// reject failed authentication => send any code but 0x00
|
||||
$stream->end(pack('C2', 0x01, 0xFF));
|
||||
throw new UnexpectedValueException('Authentication error', 0, $e);
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// reject all offered authentication methods
|
||||
$stream->write(pack('C2', 0x05, 0xFF));
|
||||
throw new UnexpectedValueException('No acceptable authentication mechanism found');
|
||||
}
|
||||
})->then(function ($method) use ($reader) {
|
||||
return $reader->readBinary(array(
|
||||
'version' => 'C',
|
||||
'command' => 'C',
|
||||
'null' => 'C',
|
||||
'type' => 'C'
|
||||
));
|
||||
})->then(function ($data) use ($reader) {
|
||||
if ($data['version'] !== 0x05) {
|
||||
throw new UnexpectedValueException('Invalid SOCKS version');
|
||||
}
|
||||
if ($data['command'] !== 0x01) {
|
||||
throw new UnexpectedValueException('Only CONNECT requests supported', Server::ERROR_COMMAND_UNSUPPORTED);
|
||||
}
|
||||
// if ($data['null'] !== 0x00) {
|
||||
// throw new UnexpectedValueException('Reserved byte has to be NULL');
|
||||
// }
|
||||
if ($data['type'] === 0x03) {
|
||||
// target hostname string
|
||||
return $reader->readByte()->then(function ($len) use ($reader) {
|
||||
return $reader->readLength($len);
|
||||
});
|
||||
} else if ($data['type'] === 0x01) {
|
||||
// target IPv4
|
||||
return $reader->readLength(4)->then(function ($addr) {
|
||||
return inet_ntop($addr);
|
||||
});
|
||||
} else if ($data['type'] === 0x04) {
|
||||
// target IPv6
|
||||
return $reader->readLength(16)->then(function ($addr) {
|
||||
return inet_ntop($addr);
|
||||
});
|
||||
} else {
|
||||
throw new UnexpectedValueException('Invalid address type', Server::ERROR_ADDRESS_UNSUPPORTED);
|
||||
}
|
||||
})->then(function ($host) use ($reader, &$remote) {
|
||||
return $reader->readBinary(array('port'=>'n'))->then(function ($data) use ($host, &$remote) {
|
||||
return array($host, $data['port'], $remote);
|
||||
});
|
||||
})->then(function ($target) use ($that, $stream) {
|
||||
return $that->connectTarget($stream, $target);
|
||||
}, function($error) use ($stream) {
|
||||
throw new UnexpectedValueException('SOCKS5 protocol error', $error->getCode(), $error);
|
||||
})->then(function (ConnectionInterface $remote) use ($stream) {
|
||||
$stream->write(pack('C4Nn', 0x05, 0x00, 0x00, 0x01, 0, 0));
|
||||
|
||||
return $remote;
|
||||
}, function(Exception $error) use ($stream){
|
||||
$stream->write(pack('C4Nn', 0x05, $error->getCode() === 0 ? Server::ERROR_GENERAL : $error->getCode(), 0x00, 0x01, 0, 0));
|
||||
|
||||
throw $error;
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function connectTarget(ConnectionInterface $stream, array $target)
|
||||
{
|
||||
$uri = $target[0];
|
||||
if (strpos($uri, ':') !== false) {
|
||||
$uri = '[' . $uri . ']';
|
||||
}
|
||||
$uri .= ':' . $target[1];
|
||||
|
||||
// validate URI so a string hostname can not pass excessive URI parts
|
||||
$parts = parse_url('tcp://' . $uri);
|
||||
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || count($parts) !== 3) {
|
||||
return \React\Promise\reject(new InvalidArgumentException('Invalid target URI given'));
|
||||
}
|
||||
|
||||
if (isset($target[2])) {
|
||||
$uri .= '?source=' . rawurlencode($target[2]);
|
||||
}
|
||||
|
||||
$that = $this;
|
||||
$connecting = $this->connector->connect($uri);
|
||||
|
||||
$stream->on('close', function () use ($connecting) {
|
||||
$connecting->cancel();
|
||||
});
|
||||
|
||||
return $connecting->then(function (ConnectionInterface $remote) use ($stream, $that) {
|
||||
$stream->pipe($remote, array('end'=>false));
|
||||
$remote->pipe($stream, array('end'=>false));
|
||||
|
||||
// remote end closes connection => stop reading from local end, try to flush buffer to local and disconnect local
|
||||
$remote->on('end', function() use ($stream, $that) {
|
||||
$that->endConnection($stream);
|
||||
});
|
||||
|
||||
// local end closes connection => stop reading from remote end, try to flush buffer to remote and disconnect remote
|
||||
$stream->on('end', function() use ($remote, $that) {
|
||||
$that->endConnection($remote);
|
||||
});
|
||||
|
||||
// set bigger buffer size of 100k to improve performance
|
||||
$stream->bufferSize = $remote->bufferSize = 100 * 1024 * 1024;
|
||||
|
||||
return $remote;
|
||||
}, function(Exception $error) {
|
||||
// default to general/unknown error
|
||||
$code = Server::ERROR_GENERAL;
|
||||
|
||||
// map common socket error conditions to limited list of SOCKS error codes
|
||||
if ((defined('SOCKET_EACCES') && $error->getCode() === SOCKET_EACCES) || $error->getCode() === 13) {
|
||||
$code = Server::ERROR_NOT_ALLOWED_BY_RULESET;
|
||||
} elseif ((defined('SOCKET_EHOSTUNREACH') && $error->getCode() === SOCKET_EHOSTUNREACH) || $error->getCode() === 113) {
|
||||
$code = Server::ERROR_HOST_UNREACHABLE;
|
||||
} elseif ((defined('SOCKET_ENETUNREACH') && $error->getCode() === SOCKET_ENETUNREACH) || $error->getCode() === 101) {
|
||||
$code = Server::ERROR_NETWORK_UNREACHABLE;
|
||||
} elseif ((defined('SOCKET_ECONNREFUSED') && $error->getCode() === SOCKET_ECONNREFUSED) || $error->getCode() === 111 || $error->getMessage() === 'Connection refused') {
|
||||
// Socket component does not currently assign an error code for this, so we have to resort to checking the exception message
|
||||
$code = Server::ERROR_CONNECTION_REFUSED;
|
||||
} elseif ((defined('SOCKET_ETIMEDOUT') && $error->getCode() === SOCKET_ETIMEDOUT) || $error->getCode() === 110 || $error instanceof TimeoutException) {
|
||||
// Socket component does not currently assign an error code for this, but we can rely on the TimeoutException
|
||||
$code = Server::ERROR_TTL;
|
||||
}
|
||||
|
||||
throw new UnexpectedValueException('Unable to connect to remote target', $code, $error);
|
||||
});
|
||||
}
|
||||
}
|
149
vendor/clue/socks-react/src/StreamReader.php
vendored
Normal file
149
vendor/clue/socks-react/src/StreamReader.php
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Socks;
|
||||
|
||||
use React\Promise\Deferred;
|
||||
use \InvalidArgumentException;
|
||||
use \UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class StreamReader
|
||||
{
|
||||
const RET_DONE = true;
|
||||
const RET_INCOMPLETE = null;
|
||||
|
||||
private $buffer = '';
|
||||
private $queue = array();
|
||||
|
||||
public function write($data)
|
||||
{
|
||||
$this->buffer .= $data;
|
||||
|
||||
do {
|
||||
$current = reset($this->queue);
|
||||
|
||||
if ($current === false) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* @var $current Closure */
|
||||
|
||||
$ret = $current($this->buffer);
|
||||
|
||||
if ($ret === self::RET_INCOMPLETE) {
|
||||
// current is incomplete, so wait for further data to arrive
|
||||
break;
|
||||
} else {
|
||||
// current is done, remove from list and continue with next
|
||||
array_shift($this->queue);
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
public function readBinary($structure)
|
||||
{
|
||||
$length = 0;
|
||||
$unpack = '';
|
||||
foreach ($structure as $name=>$format) {
|
||||
if ($length !== 0) {
|
||||
$unpack .= '/';
|
||||
}
|
||||
$unpack .= $format . $name;
|
||||
|
||||
if ($format === 'C') {
|
||||
++$length;
|
||||
} else if ($format === 'n') {
|
||||
$length += 2;
|
||||
} else if ($format === 'N') {
|
||||
$length += 4;
|
||||
} else {
|
||||
throw new InvalidArgumentException('Invalid format given');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->readLength($length)->then(function ($response) use ($unpack) {
|
||||
return unpack($unpack, $response);
|
||||
});
|
||||
}
|
||||
|
||||
public function readLength($bytes)
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
|
||||
$this->readBufferCallback(function (&$buffer) use ($bytes, $deferred) {
|
||||
if (strlen($buffer) >= $bytes) {
|
||||
$deferred->resolve((string)substr($buffer, 0, $bytes));
|
||||
$buffer = (string)substr($buffer, $bytes);
|
||||
|
||||
return StreamReader::RET_DONE;
|
||||
}
|
||||
});
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
public function readByte()
|
||||
{
|
||||
return $this->readBinary(array(
|
||||
'byte' => 'C'
|
||||
))->then(function ($data) {
|
||||
return $data['byte'];
|
||||
});
|
||||
}
|
||||
|
||||
public function readByteAssert($expect)
|
||||
{
|
||||
return $this->readByte()->then(function ($byte) use ($expect) {
|
||||
if ($byte !== $expect) {
|
||||
throw new UnexpectedValueException('Unexpected byte encountered');
|
||||
}
|
||||
return $byte;
|
||||
});
|
||||
}
|
||||
|
||||
public function readStringNull()
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
$string = '';
|
||||
|
||||
$that = $this;
|
||||
$readOne = function () use (&$readOne, $that, $deferred, &$string) {
|
||||
$that->readByte()->then(function ($byte) use ($deferred, &$string, $readOne) {
|
||||
if ($byte === 0x00) {
|
||||
$deferred->resolve($string);
|
||||
} else {
|
||||
$string .= chr($byte);
|
||||
$readOne();
|
||||
}
|
||||
});
|
||||
};
|
||||
$readOne();
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
public function readBufferCallback(/* callable */ $callable)
|
||||
{
|
||||
if (!is_callable($callable)) {
|
||||
throw new InvalidArgumentException('Given function must be callable');
|
||||
}
|
||||
|
||||
if ($this->queue) {
|
||||
$this->queue []= $callable;
|
||||
} else {
|
||||
$this->queue = array($callable);
|
||||
|
||||
if ($this->buffer !== '') {
|
||||
// this is the first element in the queue and the buffer is filled => trigger write procedure
|
||||
$this->write('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getBuffer()
|
||||
{
|
||||
return $this->buffer;
|
||||
}
|
||||
}
|
21
vendor/clue/stdio-react/LICENSE
vendored
Normal file
21
vendor/clue/stdio-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Christian Lück
|
||||
|
||||
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.
|
34
vendor/clue/stdio-react/composer.json
vendored
Normal file
34
vendor/clue/stdio-react/composer.json
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "clue/stdio-react",
|
||||
"description": "Async, event-driven console input & output (STDIN, STDOUT) for truly interactive CLI applications, built on top of ReactPHP",
|
||||
"keywords": ["stdio", "stdin", "stdout", "interactive", "CLI", "readline", "autocomplete", "autocompletion", "history", "ReactPHP", "async"],
|
||||
"homepage": "https://github.com/clue/reactphp-stdio",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"clue/term-react": "^1.0 || ^0.1.1",
|
||||
"clue/utf8-react": "^1.0 || ^0.1",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3",
|
||||
"react/stream": "^1.0 || ^0.7 || ^0.6"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "Using ext-mbstring should provide slightly better performance for handling I/O"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Stdio\\": "src/" }
|
||||
},
|
||||
"require-dev": {
|
||||
"clue/arguments": "^2.0",
|
||||
"clue/commander": "^1.2",
|
||||
"phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
}
|
||||
}
|
1017
vendor/clue/stdio-react/src/Readline.php
vendored
Normal file
1017
vendor/clue/stdio-react/src/Readline.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
617
vendor/clue/stdio-react/src/Stdio.php
vendored
Normal file
617
vendor/clue/stdio-react/src/Stdio.php
vendored
Normal file
@ -0,0 +1,617 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Stdio;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Stream\DuplexStreamInterface;
|
||||
use React\Stream\ReadableResourceStream;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
use React\Stream\WritableResourceStream;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
|
||||
class Stdio extends EventEmitter implements DuplexStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $output;
|
||||
private $readline;
|
||||
|
||||
private $ending = false;
|
||||
private $closed = false;
|
||||
private $incompleteLine = '';
|
||||
private $originalTtyMode = null;
|
||||
|
||||
public function __construct(LoopInterface $loop, ReadableStreamInterface $input = null, WritableStreamInterface $output = null, Readline $readline = null)
|
||||
{
|
||||
if ($input === null) {
|
||||
$input = $this->createStdin($loop); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
if ($output === null) {
|
||||
$output = $this->createStdout($loop); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
if ($readline === null) {
|
||||
$readline = new Readline($input, $output, $this);
|
||||
}
|
||||
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
$this->readline = $readline;
|
||||
|
||||
$that = $this;
|
||||
|
||||
// readline data emits a new line
|
||||
$incomplete =& $this->incompleteLine;
|
||||
$this->readline->on('data', function($line) use ($that, &$incomplete) {
|
||||
// readline emits a new line on enter, so start with a blank line
|
||||
$incomplete = '';
|
||||
$that->emit('data', array($line));
|
||||
});
|
||||
|
||||
// handle all input events (readline forwards all input events)
|
||||
$this->readline->on('error', array($this, 'handleError'));
|
||||
$this->readline->on('end', array($this, 'handleEnd'));
|
||||
$this->readline->on('close', array($this, 'handleCloseInput'));
|
||||
|
||||
// handle all output events
|
||||
$this->output->on('error', array($this, 'handleError'));
|
||||
$this->output->on('close', array($this, 'handleCloseOutput'));
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->restoreTtyMode();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function isWritable()
|
||||
{
|
||||
return $this->output->isWritable();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function write($data)
|
||||
{
|
||||
// return false if already ended, return true if writing empty string
|
||||
if ($this->ending || $data === '') {
|
||||
return !$this->ending;
|
||||
}
|
||||
|
||||
$out = $data;
|
||||
|
||||
$lastNewline = strrpos($data, "\n");
|
||||
|
||||
$restoreReadline = false;
|
||||
|
||||
if ($this->incompleteLine !== '') {
|
||||
// the last write did not end with a newline => append to existing row
|
||||
|
||||
// move one line up and move cursor to last position before writing data
|
||||
$out = "\033[A" . "\r\033[" . $this->width($this->incompleteLine) . "C" . $out;
|
||||
|
||||
// data contains a newline, so this will overwrite the readline prompt
|
||||
if ($lastNewline !== false) {
|
||||
// move cursor to beginning of readline prompt and clear line
|
||||
// clearing is important because $data may not overwrite the whole line
|
||||
$out = "\r\033[K" . $out;
|
||||
|
||||
// make sure to restore readline after this output
|
||||
$restoreReadline = true;
|
||||
}
|
||||
} else {
|
||||
// here, we're writing to a new line => overwrite readline prompt
|
||||
|
||||
// move cursor to beginning of readline prompt and clear line
|
||||
$out = "\r\033[K" . $out;
|
||||
|
||||
// we always overwrite the readline prompt, so restore it on next line
|
||||
$restoreReadline = true;
|
||||
}
|
||||
|
||||
// following write will have have to append to this line if it does not end with a newline
|
||||
$endsWithNewline = substr($data, -1) === "\n";
|
||||
|
||||
if ($endsWithNewline) {
|
||||
// line ends with newline, so this is line is considered complete
|
||||
$this->incompleteLine = '';
|
||||
} else {
|
||||
// always end data with newline in order to append readline on next line
|
||||
$out .= "\n";
|
||||
|
||||
if ($lastNewline === false) {
|
||||
// contains no newline at all, everything is incomplete
|
||||
$this->incompleteLine .= $data;
|
||||
} else {
|
||||
// contains a newline, everything behind it is incomplete
|
||||
$this->incompleteLine = (string)substr($data, $lastNewline + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if ($restoreReadline) {
|
||||
// write output and restore original readline prompt and line buffer
|
||||
return $this->output->write($out . $this->readline->getDrawString());
|
||||
} else {
|
||||
// restore original cursor position in readline prompt
|
||||
$pos = $this->width($this->readline->getPrompt()) + $this->readline->getCursorCell();
|
||||
if ($pos !== 0) {
|
||||
// we always start at beginning of line, move right by X
|
||||
$out .= "\033[" . $pos . "C";
|
||||
}
|
||||
|
||||
// write to actual output stream
|
||||
return $this->output->write($out);
|
||||
}
|
||||
}
|
||||
|
||||
public function end($data = null)
|
||||
{
|
||||
if ($this->ending) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($data !== null) {
|
||||
$this->write($data);
|
||||
}
|
||||
|
||||
$this->ending = true;
|
||||
|
||||
// clear readline output, close input and end output
|
||||
$this->readline->setInput('')->setPrompt('');
|
||||
$this->input->close();
|
||||
$this->output->end();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->ending = true;
|
||||
$this->closed = true;
|
||||
|
||||
$this->input->close();
|
||||
$this->output->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @return Readline
|
||||
*/
|
||||
public function getReadline()
|
||||
{
|
||||
return $this->readline;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* prompt to prepend to input line
|
||||
*
|
||||
* Will redraw the current input prompt with the current input buffer.
|
||||
*
|
||||
* @param string $prompt
|
||||
* @return void
|
||||
*/
|
||||
public function setPrompt($prompt)
|
||||
{
|
||||
$this->readline->setPrompt($prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the prompt to prepend to input line
|
||||
*
|
||||
* @return string
|
||||
* @see self::setPrompt()
|
||||
*/
|
||||
public function getPrompt()
|
||||
{
|
||||
return $this->readline->getPrompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* sets whether/how to echo text input
|
||||
*
|
||||
* The default setting is `true`, which means that every character will be
|
||||
* echo'ed as-is, i.e. you can see what you're typing.
|
||||
* For example: Typing "test" shows "test".
|
||||
*
|
||||
* You can turn this off by supplying `false`, which means that *nothing*
|
||||
* will be echo'ed while you're typing. This could be a good idea for
|
||||
* password prompts. Note that this could be confusing for users, so using
|
||||
* a character replacement as following is often preferred.
|
||||
* For example: Typing "test" shows "" (nothing).
|
||||
*
|
||||
* Alternative, you can supply a single character replacement character
|
||||
* that will be echo'ed for each character in the text input. This could
|
||||
* be a good idea for password prompts, where an asterisk character ("*")
|
||||
* is often used to indicate typing activity and password length.
|
||||
* For example: Typing "test" shows "****" (with asterisk replacement)
|
||||
*
|
||||
* Changing this setting will redraw the current prompt and echo the current
|
||||
* input buffer according to the new setting.
|
||||
*
|
||||
* @param boolean|string $echo echo can be turned on (boolean true) or off (boolean true), or you can supply a single character replacement string
|
||||
* @return void
|
||||
*/
|
||||
public function setEcho($echo)
|
||||
{
|
||||
$this->readline->setEcho($echo);
|
||||
}
|
||||
|
||||
/**
|
||||
* whether or not to support moving cursor left and right
|
||||
*
|
||||
* switching cursor support moves the cursor to the end of the current
|
||||
* input buffer (if any).
|
||||
*
|
||||
* @param boolean $move
|
||||
* @return void
|
||||
*/
|
||||
public function setMove($move)
|
||||
{
|
||||
$this->readline->setMove($move);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current cursor position measured in number of text characters.
|
||||
*
|
||||
* Note that the number of text characters doesn't necessarily reflect the
|
||||
* number of monospace cells occupied by the text characters. If you want
|
||||
* to know the latter, use `self::getCursorCell()` instead.
|
||||
*
|
||||
* @return int
|
||||
* @see self::getCursorCell() to get the position measured in monospace cells
|
||||
* @see self::moveCursorTo() to move the cursor to a given character position
|
||||
* @see self::moveCursorBy() to move the cursor by given number of characters
|
||||
* @see self::setMove() to toggle whether the user can move the cursor position
|
||||
*/
|
||||
public function getCursorPosition()
|
||||
{
|
||||
return $this->readline->getCursorPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current cursor position measured in monospace cells.
|
||||
*
|
||||
* Note that the cell position doesn't necessarily reflect the number of
|
||||
* text characters. If you want to know the latter, use
|
||||
* `self::getCursorPosition()` instead.
|
||||
*
|
||||
* Most "normal" characters occupy a single monospace cell, i.e. the ASCII
|
||||
* sequence for "A" requires a single cell, as do most UTF-8 sequences
|
||||
* like "Ä".
|
||||
*
|
||||
* However, there are a number of code points that do not require a cell
|
||||
* (i.e. invisible surrogates) or require two cells (e.g. some asian glyphs).
|
||||
*
|
||||
* Also note that this takes the echo mode into account, i.e. the cursor is
|
||||
* always at position zero if echo is off. If using a custom echo character
|
||||
* (like asterisk), it will take its width into account instead of the actual
|
||||
* input characters.
|
||||
*
|
||||
* @return int
|
||||
* @see self::getCursorPosition() to get current cursor position measured in characters
|
||||
* @see self::moveCursorTo() to move the cursor to a given character position
|
||||
* @see self::moveCursorBy() to move the cursor by given number of characters
|
||||
* @see self::setMove() to toggle whether the user can move the cursor position
|
||||
* @see self::setEcho()
|
||||
*/
|
||||
public function getCursorCell()
|
||||
{
|
||||
return $this->readline->getCursorCell();
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves cursor to right by $n chars (or left if $n is negative).
|
||||
*
|
||||
* Zero value or values out of range (exceeding current input buffer) are
|
||||
* simply ignored.
|
||||
*
|
||||
* Will redraw() the readline only if the visible cell position changes,
|
||||
* see `self::getCursorCell()` for more details.
|
||||
*
|
||||
* @param int $n
|
||||
* @return void
|
||||
*/
|
||||
public function moveCursorBy($n)
|
||||
{
|
||||
$this->readline->moveCursorBy($n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves cursor to given position in current line buffer.
|
||||
*
|
||||
* Values out of range (exceeding current input buffer) are simply ignored.
|
||||
*
|
||||
* Will redraw() the readline only if the visible cell position changes,
|
||||
* see `self::getCursorCell()` for more details.
|
||||
*
|
||||
* @param int $n
|
||||
* @return void
|
||||
*/
|
||||
public function moveCursorTo($n)
|
||||
{
|
||||
$this->readline->moveCursorTo($n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given input to the current text input buffer at the current position
|
||||
*
|
||||
* This moves the cursor accordingly to the number of characters added.
|
||||
*
|
||||
* @param string $input
|
||||
* @return void
|
||||
*/
|
||||
public function addInput($input)
|
||||
{
|
||||
$this->readline->addInput($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* set current text input buffer
|
||||
*
|
||||
* this moves the cursor to the end of the current
|
||||
* input buffer (if any).
|
||||
*
|
||||
* @param string $input
|
||||
* @return void
|
||||
*/
|
||||
public function setInput($input)
|
||||
{
|
||||
$this->readline->setInput($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* get current text input buffer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getInput()
|
||||
{
|
||||
return $this->readline->getInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new line to the (bottom position of the) history list
|
||||
*
|
||||
* @param string $line
|
||||
* @return void
|
||||
*/
|
||||
public function addHistory($line)
|
||||
{
|
||||
$this->readline->addHistory($line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the complete history list
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearHistory()
|
||||
{
|
||||
$this->readline->clearHistory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with all lines in the history
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function listHistory()
|
||||
{
|
||||
return $this->readline->listHistory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Limits the history to a maximum of N entries and truncates the current history list accordingly
|
||||
*
|
||||
* @param int|null $limit
|
||||
* @return void
|
||||
*/
|
||||
public function limitHistory($limit)
|
||||
{
|
||||
$this->readline->limitHistory($limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* set autocompletion handler to use
|
||||
*
|
||||
* The autocomplete handler will be called whenever the user hits the TAB
|
||||
* key.
|
||||
*
|
||||
* @param callable|null $autocomplete
|
||||
* @return void
|
||||
* @throws \InvalidArgumentException if the given callable is invalid
|
||||
*/
|
||||
public function setAutocomplete($autocomplete)
|
||||
{
|
||||
$this->readline->setAutocomplete($autocomplete);
|
||||
}
|
||||
|
||||
/**
|
||||
* whether or not to emit a audible/visible BELL signal when using a disabled function
|
||||
*
|
||||
* By default, this class will emit a BELL signal when using a disable function,
|
||||
* such as using the <kbd>left</kbd> or <kbd>backspace</kbd> keys when
|
||||
* already at the beginning of the line.
|
||||
*
|
||||
* Whether or not the BELL is audible/visible depends on the termin and its
|
||||
* settings, i.e. some terminals may "beep" or flash the screen or emit a
|
||||
* short vibration.
|
||||
*
|
||||
* @param bool $bell
|
||||
* @return void
|
||||
*/
|
||||
public function setBell($bell)
|
||||
{
|
||||
$this->readline->setBell($bell);
|
||||
}
|
||||
|
||||
private function width($str)
|
||||
{
|
||||
return $this->readline->strwidth($str) - 2 * substr_count($str, "\x08");
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
$this->emit('end');
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleCloseInput()
|
||||
{
|
||||
$this->restoreTtyMode();
|
||||
|
||||
if (!$this->output->isWritable()) {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleCloseOutput()
|
||||
{
|
||||
if (!$this->input->isReadable()) {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore this is covered by functional tests with/without ext-readline
|
||||
*/
|
||||
private function restoreTtyMode()
|
||||
{
|
||||
if (function_exists('readline_callback_handler_remove')) {
|
||||
// remove dummy readline handler to turn to default input mode
|
||||
readline_callback_handler_remove();
|
||||
} elseif ($this->originalTtyMode !== null && is_resource(STDIN) && $this->isTty()) {
|
||||
// Reset stty so it behaves normally again
|
||||
shell_exec('stty ' . escapeshellarg($this->originalTtyMode));
|
||||
$this->originalTtyMode = null;
|
||||
}
|
||||
|
||||
// restore blocking mode so following programs behave normally
|
||||
if (defined('STDIN') && is_resource(STDIN)) {
|
||||
stream_set_blocking(STDIN, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LoopInterface $loop
|
||||
* @return ReadableStreamInterface
|
||||
* @codeCoverageIgnore this is covered by functional tests with/without ext-readline
|
||||
*/
|
||||
private function createStdin(LoopInterface $loop)
|
||||
{
|
||||
// STDIN not defined ("php -a") or already closed (`fclose(STDIN)`)
|
||||
// also support starting program with closed STDIN ("example.php 0<&-")
|
||||
// the stream is a valid resource and is not EOF, but fstat fails
|
||||
if (!defined('STDIN') || !is_resource(STDIN) || fstat(STDIN) === false) {
|
||||
$stream = new ReadableResourceStream(fopen('php://memory', 'r'), $loop);
|
||||
$stream->close();
|
||||
return $stream;
|
||||
}
|
||||
|
||||
$stream = new ReadableResourceStream(STDIN, $loop);
|
||||
|
||||
if (function_exists('readline_callback_handler_install')) {
|
||||
// Prefer `ext-readline` to install dummy handler to turn on raw input mode.
|
||||
// We will never actually feed the readline handler and instead
|
||||
// handle all input in our `Readline` implementation.
|
||||
readline_callback_handler_install('', function () { });
|
||||
return $stream;
|
||||
}
|
||||
|
||||
if ($this->isTty()) {
|
||||
$this->originalTtyMode = rtrim(shell_exec('stty -g'), PHP_EOL);
|
||||
|
||||
// Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
|
||||
shell_exec('stty -icanon -echo');
|
||||
}
|
||||
|
||||
// register shutdown function to restore TTY mode in case of unclean shutdown (uncaught exception)
|
||||
// this will not trigger on SIGKILL etc., but the terminal should take care of this
|
||||
register_shutdown_function(array($this, 'close'));
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LoopInterface $loop
|
||||
* @return WritableStreamInterface
|
||||
* @codeCoverageIgnore this is covered by functional tests
|
||||
*/
|
||||
private function createStdout(LoopInterface $loop)
|
||||
{
|
||||
// STDOUT not defined ("php -a") or already closed (`fclose(STDOUT)`)
|
||||
// also support starting program with closed STDOUT ("example.php >&-")
|
||||
// the stream is a valid resource and is not EOF, but fstat fails
|
||||
if (!defined('STDOUT') || !is_resource(STDOUT) || fstat(STDOUT) === false) {
|
||||
$output = new WritableResourceStream(fopen('php://memory', 'r+'), $loop);
|
||||
$output->close();
|
||||
} else {
|
||||
$output = new WritableResourceStream(STDOUT, $loop);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
private function isTty()
|
||||
{
|
||||
if (PHP_VERSION_ID >= 70200) {
|
||||
// Prefer `stream_isatty()` (available as of PHP 7.2 only)
|
||||
return stream_isatty(STDIN);
|
||||
} elseif (function_exists('posix_isatty')) {
|
||||
// Otherwise use `posix_isatty` if available (requires `ext-posix`)
|
||||
return posix_isatty(STDIN);
|
||||
}
|
||||
|
||||
// otherwise try to guess based on stat file mode and device major number
|
||||
// Must be special character device: ($mode & S_IFMT) === S_IFCHR
|
||||
// And device major number must be allocated to TTYs (2-5 and 128-143)
|
||||
// For what it's worth, checking for device gid 5 (tty) is less reliable.
|
||||
// @link http://man7.org/linux/man-pages/man7/inode.7.html
|
||||
// @link https://www.kernel.org/doc/html/v4.11/admin-guide/devices.html#terminal-devices
|
||||
$stat = fstat(STDIN);
|
||||
$mode = isset($stat['mode']) ? ($stat['mode'] & 0170000) : 0;
|
||||
$major = isset($stat['dev']) ? (($stat['dev'] >> 8) & 0xff) : 0;
|
||||
|
||||
return ($mode === 0020000 && $major >= 2 && $major <= 143 && ($major <=5 || $major >= 128));
|
||||
}
|
||||
}
|
21
vendor/clue/term-react/LICENSE
vendored
Normal file
21
vendor/clue/term-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Christian Lück
|
||||
|
||||
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.
|
24
vendor/clue/term-react/composer.json
vendored
Normal file
24
vendor/clue/term-react/composer.json
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "clue/term-react",
|
||||
"description": "Streaming terminal emulator, built on top of ReactPHP",
|
||||
"keywords": ["terminal", "control codes", "xterm", "ANSI", "ASCII", "VT100", "csi", "osc", "apc", "dps", "pm", "C1", "C0", "streaming", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/reactphp-term",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Term\\": "src/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/stream": "^1.0 || ^0.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.0 || ^4.8",
|
||||
"react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3"
|
||||
}
|
||||
}
|
223
vendor/clue/term-react/src/ControlCodeParser.php
vendored
Normal file
223
vendor/clue/term-react/src/ControlCodeParser.php
vendored
Normal file
@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Term;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
|
||||
class ControlCodeParser extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $closed = false;
|
||||
private $buffer = '';
|
||||
|
||||
/**
|
||||
* we know about the following C1 types (7 bit only)
|
||||
*
|
||||
* followed by "[" means it's CSI (Control Sequence Introducer)
|
||||
* followed by "]" means it's OSC (Operating System Controls)
|
||||
* followed by "_" means it's APC (Application Program-Control)
|
||||
* followed by "P" means it's DPS (Device-Control string)
|
||||
* followed by "^" means it's PM (Privacy Message)
|
||||
*
|
||||
* Each of these will be parsed until the sequence ends and then emitted
|
||||
* under their respective name.
|
||||
*
|
||||
* All other C1 types will be emitted under the "c1" name without any
|
||||
* further processing.
|
||||
*
|
||||
* C1 types in 8 bit are currently not supported, as they require special
|
||||
* care with regards to whether UTF-8 mode is enabled. So far this has
|
||||
* turned out to be a non-issue because most terminal emulators *accept*
|
||||
* boths formats, but usually *send* in 7 bit mode exclusively.
|
||||
*/
|
||||
private $types = array(
|
||||
'[' => 'csi',
|
||||
']' => 'osc',
|
||||
'_' => 'apc',
|
||||
'P' => 'dps',
|
||||
'^' => 'pm',
|
||||
);
|
||||
|
||||
public function __construct(ReadableStreamInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
|
||||
if (!$this->input->isReadable()) {
|
||||
return $this->close();
|
||||
}
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
$this->buffer = '';
|
||||
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->buffer .= $data;
|
||||
|
||||
while ($this->buffer !== '') {
|
||||
// search for first control character (C0 and DEL)
|
||||
$c0 = false;
|
||||
for ($i = 0; isset($this->buffer[$i]); ++$i) {
|
||||
$code = ord($this->buffer[$i]);
|
||||
if ($code < 0x20 || $code === 0x7F) {
|
||||
$c0 = $i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// no C0 found, emit whole buffer as data
|
||||
if ($c0 === false) {
|
||||
$data = $this->buffer;
|
||||
$this->buffer = '';
|
||||
|
||||
$this->emit('data', array($data));
|
||||
return;
|
||||
}
|
||||
|
||||
// C0 found somewhere inbetween, emit everything before C0 as data
|
||||
if ($c0 !== 0) {
|
||||
$data = substr($this->buffer, 0, $c0);
|
||||
$this->buffer = substr($this->buffer, $c0);
|
||||
|
||||
$this->emit('data', array($data));
|
||||
continue;
|
||||
}
|
||||
|
||||
// C0 is now at start of buffer
|
||||
// check if this is a normal C0 code or an ESC (\x1B = \033)
|
||||
// normal C0 will be emitted, ESC will be parsed further
|
||||
if ($this->buffer[0] !== "\x1B") {
|
||||
$data = $this->buffer[0];
|
||||
$this->buffer = (string)substr($this->buffer, 1);
|
||||
|
||||
$this->emit('c0', array($data));
|
||||
continue;
|
||||
}
|
||||
|
||||
// check following byte to determine type
|
||||
if (!isset($this->buffer[1])) {
|
||||
// type currently unknown, wait for next data chunk
|
||||
break;
|
||||
}
|
||||
|
||||
// if this is an unknown type, just emit as "c1" without further parsing
|
||||
if (!isset($this->types[$this->buffer[1]])) {
|
||||
$data = substr($this->buffer, 0, 2);
|
||||
$this->buffer = (string)substr($this->buffer, 2);
|
||||
|
||||
$this->emit('c1', array($data));
|
||||
continue;
|
||||
}
|
||||
|
||||
// this is known type, check for the sequence end
|
||||
$type = $this->types[$this->buffer[1]];
|
||||
$found = false;
|
||||
|
||||
if ($type === 'csi') {
|
||||
// CSI is now at the start of the buffer, search final character
|
||||
for ($i = 2; isset($this->buffer[$i]); ++$i) {
|
||||
$code = ord($this->buffer[$i]);
|
||||
|
||||
// final character between \x40-\x7E
|
||||
if ($code >= 64 && $code <= 126) {
|
||||
$data = substr($this->buffer, 0, $i + 1);
|
||||
$this->buffer = (string)substr($this->buffer, $i + 1);
|
||||
|
||||
$this->emit($type, array($data));
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// all other types are terminated by ST
|
||||
// only OSC can also be terminted by BEL (whichever comes first)
|
||||
$st = strpos($this->buffer, "\x1B\\");
|
||||
$bel = ($type === 'osc') ? strpos($this->buffer, "\x07") : false;
|
||||
|
||||
if ($st !== false && ($bel === false || $bel > $st)) {
|
||||
// ST comes before BEL or no BEL found
|
||||
$data = substr($this->buffer, 0, $st + 2);
|
||||
$this->buffer = (string)substr($this->buffer, $st + 2);
|
||||
|
||||
$this->emit($type, array($data));
|
||||
$found = true;
|
||||
} elseif ($bel !== false) {
|
||||
// BEL comes before ST or no ST found
|
||||
$data = substr($this->buffer, 0, $bel + 1);
|
||||
$this->buffer = (string)substr($this->buffer, $bel + 1);
|
||||
|
||||
$this->emit($type, array($data));
|
||||
$found = true;
|
||||
}
|
||||
}
|
||||
|
||||
// no final character found => wait for next data chunk
|
||||
if (!$found) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if (!$this->closed) {
|
||||
if ($this->buffer === '') {
|
||||
$this->emit('end');
|
||||
} else {
|
||||
$this->emit('error', array(new \RuntimeException('Stream ended with incomplete control code sequence in buffer')));
|
||||
}
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $e)
|
||||
{
|
||||
$this->emit('error', array($e));
|
||||
$this->close();
|
||||
}
|
||||
}
|
21
vendor/clue/utf8-react/LICENSE
vendored
Normal file
21
vendor/clue/utf8-react/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Christian Lück
|
||||
|
||||
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.
|
24
vendor/clue/utf8-react/composer.json
vendored
Normal file
24
vendor/clue/utf8-react/composer.json
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "clue/utf8-react",
|
||||
"description": "Streaming UTF-8 parser, built on top of ReactPHP",
|
||||
"keywords": ["UTF-8", "utf8", "unicode", "streaming", "ReactPHP"],
|
||||
"homepage": "https://github.com/clue/php-utf8-react",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christian Lück",
|
||||
"email": "christian@lueck.tv"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "Clue\\React\\Utf8\\": "src/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3",
|
||||
"react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4 || ^0.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.0 || ^4.8",
|
||||
"react/stream": "^1.0 || ^0.7"
|
||||
}
|
||||
}
|
174
vendor/clue/utf8-react/src/Sequencer.php
vendored
Normal file
174
vendor/clue/utf8-react/src/Sequencer.php
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace Clue\React\Utf8;
|
||||
|
||||
use Evenement\EventEmitter;
|
||||
use React\Stream\ReadableStreamInterface;
|
||||
use React\Stream\WritableStreamInterface;
|
||||
use React\Stream\Util;
|
||||
|
||||
/**
|
||||
* forwards only complete UTF-8 sequences
|
||||
*/
|
||||
class Sequencer extends EventEmitter implements ReadableStreamInterface
|
||||
{
|
||||
private $input;
|
||||
private $invalid;
|
||||
|
||||
private $buffer = '';
|
||||
private $closed = false;
|
||||
|
||||
public function __construct(ReadableStreamInterface $input, $replacementCharacter = '?')
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->invalid = $replacementCharacter;
|
||||
|
||||
if (!$input->isReadable()) {
|
||||
return $this->close();
|
||||
}
|
||||
|
||||
$this->input->on('data', array($this, 'handleData'));
|
||||
$this->input->on('end', array($this, 'handleEnd'));
|
||||
$this->input->on('error', array($this, 'handleError'));
|
||||
$this->input->on('close', array($this, 'close'));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleData($data)
|
||||
{
|
||||
$this->buffer .= $data;
|
||||
$len = strlen($this->buffer);
|
||||
|
||||
$sequence = '';
|
||||
$expect = 0;
|
||||
$out = '';
|
||||
|
||||
for ($i = 0; $i < $len; ++$i) {
|
||||
$char = $this->buffer[$i];
|
||||
$code = ord($char);
|
||||
|
||||
if ($code & 128) {
|
||||
// multi-byte sequence
|
||||
if ($code & 64) {
|
||||
// this is the start of a sequence
|
||||
|
||||
// unexpected start of sequence because already within sequence
|
||||
if ($expect !== 0) {
|
||||
$out .= str_repeat($this->invalid, strlen($sequence));
|
||||
$sequence = '';
|
||||
}
|
||||
|
||||
$sequence = $char;
|
||||
$expect = 2;
|
||||
|
||||
if ($code & 32) {
|
||||
++$expect;
|
||||
if ($code & 16) {
|
||||
++$expect;
|
||||
|
||||
if ($code & 8) {
|
||||
// invalid sequence start length
|
||||
$out .= $this->invalid;
|
||||
$sequence = '';
|
||||
$expect = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// this is a follow-up byte in a sequence
|
||||
if ($expect === 0) {
|
||||
// we're not within a sequence in first place
|
||||
$out .= $this->invalid;
|
||||
} else {
|
||||
// valid following byte in sequence
|
||||
$sequence .= $char;
|
||||
|
||||
// sequence reached expected length => add to output
|
||||
if (strlen($sequence) === $expect) {
|
||||
$out .= $sequence;
|
||||
$sequence = '';
|
||||
$expect = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// simple ASCII character found
|
||||
|
||||
// unexpected because already within sequence
|
||||
if ($expect !== 0) {
|
||||
$out .= str_repeat($this->invalid, strlen($sequence));
|
||||
$sequence = '';
|
||||
$expect = 0;
|
||||
}
|
||||
|
||||
$out .= $char;
|
||||
}
|
||||
}
|
||||
|
||||
if ($out !== '') {
|
||||
$this->buffer = substr($this->buffer, strlen($out));
|
||||
|
||||
$this->emit('data', array($out));
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleEnd()
|
||||
{
|
||||
if ($this->buffer !== '' && $this->invalid !== '') {
|
||||
$data = str_repeat($this->invalid, strlen($this->buffer));
|
||||
$this->buffer = '';
|
||||
|
||||
$this->emit('data', array($data));
|
||||
}
|
||||
|
||||
if (!$this->closed) {
|
||||
$this->emit('end');
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public function handleError(\Exception $error)
|
||||
{
|
||||
$this->emit('error', array($error));
|
||||
$this->close();
|
||||
}
|
||||
|
||||
public function isReadable()
|
||||
{
|
||||
return !$this->closed && $this->input->isReadable();
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
if ($this->closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->closed = true;
|
||||
$this->buffer = '';
|
||||
|
||||
$this->input->close();
|
||||
|
||||
$this->emit('close');
|
||||
$this->removeAllListeners();
|
||||
}
|
||||
|
||||
public function pause()
|
||||
{
|
||||
$this->input->pause();
|
||||
}
|
||||
|
||||
public function resume()
|
||||
{
|
||||
$this->input->resume();
|
||||
}
|
||||
|
||||
public function pipe(WritableStreamInterface $dest, array $options = array())
|
||||
{
|
||||
Util::pipe($this, $dest, $options);
|
||||
|
||||
return $dest;
|
||||
}
|
||||
}
|
445
vendor/composer/ClassLoader.php
vendored
Normal file
445
vendor/composer/ClassLoader.php
vendored
Normal file
@ -0,0 +1,445 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Composer.
|
||||
*
|
||||
* (c) Nils Adermann <naderman@naderman.de>
|
||||
* Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
/**
|
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
|
||||
*
|
||||
* $loader = new \Composer\Autoload\ClassLoader();
|
||||
*
|
||||
* // register classes with namespaces
|
||||
* $loader->add('Symfony\Component', __DIR__.'/component');
|
||||
* $loader->add('Symfony', __DIR__.'/framework');
|
||||
*
|
||||
* // activate the autoloader
|
||||
* $loader->register();
|
||||
*
|
||||
* // to enable searching the include path (eg. for PEAR packages)
|
||||
* $loader->setUseIncludePath(true);
|
||||
*
|
||||
* In this example, if you try to use a class in the Symfony\Component
|
||||
* namespace or one of its children (Symfony\Component\Console for instance),
|
||||
* the autoloader will first look for the class under the component/
|
||||
* directory, and it will then fallback to the framework/ directory if not
|
||||
* found before giving up.
|
||||
*
|
||||
* This class is loosely based on the Symfony UniversalClassLoader.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @see http://www.php-fig.org/psr/psr-0/
|
||||
* @see http://www.php-fig.org/psr/psr-4/
|
||||
*/
|
||||
class ClassLoader
|
||||
{
|
||||
// PSR-4
|
||||
private $prefixLengthsPsr4 = array();
|
||||
private $prefixDirsPsr4 = array();
|
||||
private $fallbackDirsPsr4 = array();
|
||||
|
||||
// PSR-0
|
||||
private $prefixesPsr0 = array();
|
||||
private $fallbackDirsPsr0 = array();
|
||||
|
||||
private $useIncludePath = false;
|
||||
private $classMap = array();
|
||||
private $classMapAuthoritative = false;
|
||||
private $missingClasses = array();
|
||||
private $apcuPrefix;
|
||||
|
||||
public function getPrefixes()
|
||||
{
|
||||
if (!empty($this->prefixesPsr0)) {
|
||||
return call_user_func_array('array_merge', $this->prefixesPsr0);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getPrefixesPsr4()
|
||||
{
|
||||
return $this->prefixDirsPsr4;
|
||||
}
|
||||
|
||||
public function getFallbackDirs()
|
||||
{
|
||||
return $this->fallbackDirsPsr0;
|
||||
}
|
||||
|
||||
public function getFallbackDirsPsr4()
|
||||
{
|
||||
return $this->fallbackDirsPsr4;
|
||||
}
|
||||
|
||||
public function getClassMap()
|
||||
{
|
||||
return $this->classMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $classMap Class to filename map
|
||||
*/
|
||||
public function addClassMap(array $classMap)
|
||||
{
|
||||
if ($this->classMap) {
|
||||
$this->classMap = array_merge($this->classMap, $classMap);
|
||||
} else {
|
||||
$this->classMap = $classMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix, either
|
||||
* appending or prepending to the ones previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 root directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*/
|
||||
public function add($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr0
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr0 = array_merge(
|
||||
$this->fallbackDirsPsr0,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$first = $prefix[0];
|
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) {
|
||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
|
||||
|
||||
return;
|
||||
}
|
||||
if ($prepend) {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixesPsr0[$first][$prefix]
|
||||
);
|
||||
} else {
|
||||
$this->prefixesPsr0[$first][$prefix] = array_merge(
|
||||
$this->prefixesPsr0[$first][$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace, either
|
||||
* appending or prepending to the ones previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
* @param bool $prepend Whether to prepend the directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function addPsr4($prefix, $paths, $prepend = false)
|
||||
{
|
||||
if (!$prefix) {
|
||||
// Register directories for the root namespace.
|
||||
if ($prepend) {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
(array) $paths,
|
||||
$this->fallbackDirsPsr4
|
||||
);
|
||||
} else {
|
||||
$this->fallbackDirsPsr4 = array_merge(
|
||||
$this->fallbackDirsPsr4,
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
|
||||
// Register directories for a new namespace.
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
} elseif ($prepend) {
|
||||
// Prepend directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
(array) $paths,
|
||||
$this->prefixDirsPsr4[$prefix]
|
||||
);
|
||||
} else {
|
||||
// Append directories for an already registered namespace.
|
||||
$this->prefixDirsPsr4[$prefix] = array_merge(
|
||||
$this->prefixDirsPsr4[$prefix],
|
||||
(array) $paths
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-0 directories for a given prefix,
|
||||
* replacing any others previously set for this prefix.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
* @param array|string $paths The PSR-0 base directories
|
||||
*/
|
||||
public function set($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr0 = (array) $paths;
|
||||
} else {
|
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a set of PSR-4 directories for a given namespace,
|
||||
* replacing any others previously set for this namespace.
|
||||
*
|
||||
* @param string $prefix The prefix/namespace, with trailing '\\'
|
||||
* @param array|string $paths The PSR-4 base directories
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setPsr4($prefix, $paths)
|
||||
{
|
||||
if (!$prefix) {
|
||||
$this->fallbackDirsPsr4 = (array) $paths;
|
||||
} else {
|
||||
$length = strlen($prefix);
|
||||
if ('\\' !== $prefix[$length - 1]) {
|
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
|
||||
}
|
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
|
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on searching the include path for class files.
|
||||
*
|
||||
* @param bool $useIncludePath
|
||||
*/
|
||||
public function setUseIncludePath($useIncludePath)
|
||||
{
|
||||
$this->useIncludePath = $useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to check if the autoloader uses the include path to check
|
||||
* for classes.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getUseIncludePath()
|
||||
{
|
||||
return $this->useIncludePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off searching the prefix and fallback directories for classes
|
||||
* that have not been registered with the class map.
|
||||
*
|
||||
* @param bool $classMapAuthoritative
|
||||
*/
|
||||
public function setClassMapAuthoritative($classMapAuthoritative)
|
||||
{
|
||||
$this->classMapAuthoritative = $classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should class lookup fail if not found in the current class map?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isClassMapAuthoritative()
|
||||
{
|
||||
return $this->classMapAuthoritative;
|
||||
}
|
||||
|
||||
/**
|
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
|
||||
*
|
||||
* @param string|null $apcuPrefix
|
||||
*/
|
||||
public function setApcuPrefix($apcuPrefix)
|
||||
{
|
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The APCu prefix in use, or null if APCu caching is not enabled.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getApcuPrefix()
|
||||
{
|
||||
return $this->apcuPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this instance as an autoloader.
|
||||
*
|
||||
* @param bool $prepend Whether to prepend the autoloader or not
|
||||
*/
|
||||
public function register($prepend = false)
|
||||
{
|
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this instance as an autoloader.
|
||||
*/
|
||||
public function unregister()
|
||||
{
|
||||
spl_autoload_unregister(array($this, 'loadClass'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given class or interface.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
* @return bool|null True if loaded, null otherwise
|
||||
*/
|
||||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the path to the file where the class is defined.
|
||||
*
|
||||
* @param string $class The name of the class
|
||||
*
|
||||
* @return string|false The path if found, false otherwise
|
||||
*/
|
||||
public function findFile($class)
|
||||
{
|
||||
// class map lookup
|
||||
if (isset($this->classMap[$class])) {
|
||||
return $this->classMap[$class];
|
||||
}
|
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
|
||||
return false;
|
||||
}
|
||||
if (null !== $this->apcuPrefix) {
|
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
|
||||
if ($hit) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
$file = $this->findFileWithExtension($class, '.php');
|
||||
|
||||
// Search for Hack files if we are running on HHVM
|
||||
if (false === $file && defined('HHVM_VERSION')) {
|
||||
$file = $this->findFileWithExtension($class, '.hh');
|
||||
}
|
||||
|
||||
if (null !== $this->apcuPrefix) {
|
||||
apcu_add($this->apcuPrefix.$class, $file);
|
||||
}
|
||||
|
||||
if (false === $file) {
|
||||
// Remember that this class does not exist.
|
||||
$this->missingClasses[$class] = true;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
private function findFileWithExtension($class, $ext)
|
||||
{
|
||||
// PSR-4 lookup
|
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
|
||||
|
||||
$first = $class[0];
|
||||
if (isset($this->prefixLengthsPsr4[$first])) {
|
||||
$subPath = $class;
|
||||
while (false !== $lastPos = strrpos($subPath, '\\')) {
|
||||
$subPath = substr($subPath, 0, $lastPos);
|
||||
$search = $subPath . '\\';
|
||||
if (isset($this->prefixDirsPsr4[$search])) {
|
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
|
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) {
|
||||
if (file_exists($file = $dir . $pathEnd)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-4 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr4 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 lookup
|
||||
if (false !== $pos = strrpos($class, '\\')) {
|
||||
// namespaced class name
|
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
|
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
|
||||
} else {
|
||||
// PEAR-like class name
|
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
|
||||
}
|
||||
|
||||
if (isset($this->prefixesPsr0[$first])) {
|
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
|
||||
if (0 === strpos($class, $prefix)) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 fallback dirs
|
||||
foreach ($this->fallbackDirsPsr0 as $dir) {
|
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// PSR-0 include paths.
|
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope isolated include.
|
||||
*
|
||||
* Prevents access to $this/self from included files.
|
||||
*/
|
||||
function includeFile($file)
|
||||
{
|
||||
include $file;
|
||||
}
|
21
vendor/composer/LICENSE
vendored
Normal file
21
vendor/composer/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
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.
|
||||
|
9
vendor/composer/autoload_classmap.php
vendored
Normal file
9
vendor/composer/autoload_classmap.php
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
);
|
16
vendor/composer/autoload_files.php
vendored
Normal file
16
vendor/composer/autoload_files.php
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
// autoload_files.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php',
|
||||
'972fda704d680a3a53c68e34e193cb22' => $vendorDir . '/react/promise-timer/src/functions_include.php',
|
||||
'f67ee1ef46ff5893b59dcf4c4e98a0e8' => $vendorDir . '/clue/block-react/src/functions_include.php',
|
||||
'ebf8799635f67b5d7248946fe2154f4a' => $vendorDir . '/ringcentral/psr7/src/functions_include.php',
|
||||
'cea474b4340aa9fa53661e887a21a316' => $vendorDir . '/react/promise-stream/src/functions_include.php',
|
||||
'5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php',
|
||||
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
|
||||
);
|
11
vendor/composer/autoload_namespaces.php
vendored
Normal file
11
vendor/composer/autoload_namespaces.php
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Evenement' => array($vendorDir . '/evenement/evenement/src'),
|
||||
'Clue\\Redis\\Protocol' => array($vendorDir . '/clue/redis-protocol/src'),
|
||||
);
|
37
vendor/composer/autoload_psr4.php
vendored
Normal file
37
vendor/composer/autoload_psr4.php
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
|
||||
'Socket\\Raw\\' => array($vendorDir . '/clue/socket-raw/src'),
|
||||
'RingCentral\\Psr7\\' => array($vendorDir . '/ringcentral/psr7/src'),
|
||||
'React\\Stream\\' => array($vendorDir . '/react/stream/src'),
|
||||
'React\\Socket\\' => array($vendorDir . '/react/socket/src'),
|
||||
'React\\Promise\\Timer\\' => array($vendorDir . '/react/promise-timer/src'),
|
||||
'React\\Promise\\Stream\\' => array($vendorDir . '/react/promise-stream/src'),
|
||||
'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
|
||||
'React\\Http\\' => array($vendorDir . '/react/http/src'),
|
||||
'React\\HttpClient\\' => array($vendorDir . '/react/http-client/src'),
|
||||
'React\\EventLoop\\' => array($vendorDir . '/react/event-loop/src'),
|
||||
'React\\Dns\\' => array($vendorDir . '/react/dns/src'),
|
||||
'React\\Datagram\\' => array($vendorDir . '/react/datagram/src'),
|
||||
'React\\ChildProcess\\' => array($vendorDir . '/react/child-process/src'),
|
||||
'React\\Cache\\' => array($vendorDir . '/react/cache/src'),
|
||||
'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'),
|
||||
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
|
||||
'Predis\\' => array($vendorDir . '/predis/predis/src'),
|
||||
'ConnectionManager\\Extra\\' => array($vendorDir . '/clue/connection-manager-extra/src'),
|
||||
'Clue\\React\\Utf8\\' => array($vendorDir . '/clue/utf8-react/src'),
|
||||
'Clue\\React\\Term\\' => array($vendorDir . '/clue/term-react/src'),
|
||||
'Clue\\React\\Stdio\\' => array($vendorDir . '/clue/stdio-react/src'),
|
||||
'Clue\\React\\Socks\\' => array($vendorDir . '/clue/socks-react/src'),
|
||||
'Clue\\React\\Soap\\' => array($vendorDir . '/clue/soap-react/src'),
|
||||
'Clue\\React\\Redis\\' => array($vendorDir . '/clue/redis-react/src'),
|
||||
'Clue\\React\\Mq\\' => array($vendorDir . '/clue/mq-react/src'),
|
||||
'Clue\\React\\HttpProxy\\' => array($vendorDir . '/clue/http-proxy-react/src'),
|
||||
'Clue\\React\\Buzz\\' => array($vendorDir . '/clue/buzz-react/src'),
|
||||
);
|
70
vendor/composer/autoload_real.php
vendored
Normal file
70
vendor/composer/autoload_real.php
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit42ef24ab76510716787335cfbc6c878a
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
public static function loadClassLoader($class)
|
||||
{
|
||||
if ('Composer\Autoload\ClassLoader' === $class) {
|
||||
require __DIR__ . '/ClassLoader.php';
|
||||
}
|
||||
}
|
||||
|
||||
public static function getLoader()
|
||||
{
|
||||
if (null !== self::$loader) {
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit42ef24ab76510716787335cfbc6c878a', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit42ef24ab76510716787335cfbc6c878a', 'loadClassLoader'));
|
||||
|
||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
||||
if ($useStaticLoader) {
|
||||
require_once __DIR__ . '/autoload_static.php';
|
||||
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit42ef24ab76510716787335cfbc6c878a::getInitializer($loader));
|
||||
} else {
|
||||
$map = require __DIR__ . '/autoload_namespaces.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->set($namespace, $path);
|
||||
}
|
||||
|
||||
$map = require __DIR__ . '/autoload_psr4.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
$loader->setPsr4($namespace, $path);
|
||||
}
|
||||
|
||||
$classMap = require __DIR__ . '/autoload_classmap.php';
|
||||
if ($classMap) {
|
||||
$loader->addClassMap($classMap);
|
||||
}
|
||||
}
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
if ($useStaticLoader) {
|
||||
$includeFiles = Composer\Autoload\ComposerStaticInit42ef24ab76510716787335cfbc6c878a::$files;
|
||||
} else {
|
||||
$includeFiles = require __DIR__ . '/autoload_files.php';
|
||||
}
|
||||
foreach ($includeFiles as $fileIdentifier => $file) {
|
||||
composerRequire42ef24ab76510716787335cfbc6c878a($fileIdentifier, $file);
|
||||
}
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
|
||||
function composerRequire42ef24ab76510716787335cfbc6c878a($fileIdentifier, $file)
|
||||
{
|
||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
||||
require $file;
|
||||
|
||||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
|
||||
}
|
||||
}
|
203
vendor/composer/autoload_static.php
vendored
Normal file
203
vendor/composer/autoload_static.php
vendored
Normal file
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
// autoload_static.php @generated by Composer
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit42ef24ab76510716787335cfbc6c878a
|
||||
{
|
||||
public static $files = array (
|
||||
'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
|
||||
'972fda704d680a3a53c68e34e193cb22' => __DIR__ . '/..' . '/react/promise-timer/src/functions_include.php',
|
||||
'f67ee1ef46ff5893b59dcf4c4e98a0e8' => __DIR__ . '/..' . '/clue/block-react/src/functions_include.php',
|
||||
'ebf8799635f67b5d7248946fe2154f4a' => __DIR__ . '/..' . '/ringcentral/psr7/src/functions_include.php',
|
||||
'cea474b4340aa9fa53661e887a21a316' => __DIR__ . '/..' . '/react/promise-stream/src/functions_include.php',
|
||||
'5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php',
|
||||
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
|
||||
);
|
||||
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'S' =>
|
||||
array (
|
||||
'Symfony\\Polyfill\\Ctype\\' => 23,
|
||||
'Socket\\Raw\\' => 11,
|
||||
),
|
||||
'R' =>
|
||||
array (
|
||||
'RingCentral\\Psr7\\' => 17,
|
||||
'React\\Stream\\' => 13,
|
||||
'React\\Socket\\' => 13,
|
||||
'React\\Promise\\Timer\\' => 20,
|
||||
'React\\Promise\\Stream\\' => 21,
|
||||
'React\\Promise\\' => 14,
|
||||
'React\\Http\\' => 11,
|
||||
'React\\HttpClient\\' => 17,
|
||||
'React\\EventLoop\\' => 16,
|
||||
'React\\Dns\\' => 10,
|
||||
'React\\Datagram\\' => 15,
|
||||
'React\\ChildProcess\\' => 19,
|
||||
'React\\Cache\\' => 12,
|
||||
'Ramsey\\Uuid\\' => 12,
|
||||
),
|
||||
'P' =>
|
||||
array (
|
||||
'Psr\\Http\\Message\\' => 17,
|
||||
'Predis\\' => 7,
|
||||
),
|
||||
'C' =>
|
||||
array (
|
||||
'ConnectionManager\\Extra\\' => 24,
|
||||
'Clue\\React\\Utf8\\' => 16,
|
||||
'Clue\\React\\Term\\' => 16,
|
||||
'Clue\\React\\Stdio\\' => 17,
|
||||
'Clue\\React\\Socks\\' => 17,
|
||||
'Clue\\React\\Soap\\' => 16,
|
||||
'Clue\\React\\Redis\\' => 17,
|
||||
'Clue\\React\\Mq\\' => 14,
|
||||
'Clue\\React\\HttpProxy\\' => 21,
|
||||
'Clue\\React\\Buzz\\' => 16,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'Symfony\\Polyfill\\Ctype\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
|
||||
),
|
||||
'Socket\\Raw\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/clue/socket-raw/src',
|
||||
),
|
||||
'RingCentral\\Psr7\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/ringcentral/psr7/src',
|
||||
),
|
||||
'React\\Stream\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/react/stream/src',
|
||||
),
|
||||
'React\\Socket\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/react/socket/src',
|
||||
),
|
||||
'React\\Promise\\Timer\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/react/promise-timer/src',
|
||||
),
|
||||
'React\\Promise\\Stream\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/react/promise-stream/src',
|
||||
),
|
||||
'React\\Promise\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/react/promise/src',
|
||||
),
|
||||
'React\\Http\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/react/http/src',
|
||||
),
|
||||
'React\\HttpClient\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/react/http-client/src',
|
||||
),
|
||||
'React\\EventLoop\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/react/event-loop/src',
|
||||
),
|
||||
'React\\Dns\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/react/dns/src',
|
||||
),
|
||||
'React\\Datagram\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/react/datagram/src',
|
||||
),
|
||||
'React\\ChildProcess\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/react/child-process/src',
|
||||
),
|
||||
'React\\Cache\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/react/cache/src',
|
||||
),
|
||||
'Ramsey\\Uuid\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/ramsey/uuid/src',
|
||||
),
|
||||
'Psr\\Http\\Message\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/http-message/src',
|
||||
),
|
||||
'Predis\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/predis/predis/src',
|
||||
),
|
||||
'ConnectionManager\\Extra\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/clue/connection-manager-extra/src',
|
||||
),
|
||||
'Clue\\React\\Utf8\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/clue/utf8-react/src',
|
||||
),
|
||||
'Clue\\React\\Term\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/clue/term-react/src',
|
||||
),
|
||||
'Clue\\React\\Stdio\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/clue/stdio-react/src',
|
||||
),
|
||||
'Clue\\React\\Socks\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/clue/socks-react/src',
|
||||
),
|
||||
'Clue\\React\\Soap\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/clue/soap-react/src',
|
||||
),
|
||||
'Clue\\React\\Redis\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/clue/redis-react/src',
|
||||
),
|
||||
'Clue\\React\\Mq\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/clue/mq-react/src',
|
||||
),
|
||||
'Clue\\React\\HttpProxy\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/clue/http-proxy-react/src',
|
||||
),
|
||||
'Clue\\React\\Buzz\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/clue/buzz-react/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixesPsr0 = array (
|
||||
'E' =>
|
||||
array (
|
||||
'Evenement' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/evenement/evenement/src',
|
||||
),
|
||||
),
|
||||
'C' =>
|
||||
array (
|
||||
'Clue\\Redis\\Protocol' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/clue/redis-protocol/src',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit42ef24ab76510716787335cfbc6c878a::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit42ef24ab76510716787335cfbc6c878a::$prefixDirsPsr4;
|
||||
$loader->prefixesPsr0 = ComposerStaticInit42ef24ab76510716787335cfbc6c878a::$prefixesPsr0;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
}
|
1736
vendor/composer/installed.json
vendored
Normal file
1736
vendor/composer/installed.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
19
vendor/evenement/evenement/LICENSE
vendored
Normal file
19
vendor/evenement/evenement/LICENSE
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2011 Igor Wiedler
|
||||
|
||||
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.
|
34
vendor/evenement/evenement/composer.json
vendored
Normal file
34
vendor/evenement/evenement/composer.json
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "evenement/evenement",
|
||||
"description": "Événement is a very simple event dispatching library for PHP",
|
||||
"keywords": ["event-dispatcher", "event-emitter"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Igor Wiedler",
|
||||
"email": "igor@wiedler.ch"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6.0||^5.7||^4.8.35"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Evenement": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-0": {
|
||||
"Evenement": "tests"
|
||||
},
|
||||
"files": ["tests/Evenement/Tests/functions.php"]
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0-dev"
|
||||
}
|
||||
}
|
||||
}
|
17
vendor/evenement/evenement/src/Evenement/EventEmitter.php
vendored
Normal file
17
vendor/evenement/evenement/src/Evenement/EventEmitter.php
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Evenement.
|
||||
*
|
||||
* (c) Igor Wiedler <igor@wiedler.ch>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Evenement;
|
||||
|
||||
class EventEmitter implements EventEmitterInterface
|
||||
{
|
||||
use EventEmitterTrait;
|
||||
}
|
22
vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php
vendored
Normal file
22
vendor/evenement/evenement/src/Evenement/EventEmitterInterface.php
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Evenement.
|
||||
*
|
||||
* (c) Igor Wiedler <igor@wiedler.ch>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Evenement;
|
||||
|
||||
interface EventEmitterInterface
|
||||
{
|
||||
public function on($event, callable $listener);
|
||||
public function once($event, callable $listener);
|
||||
public function removeListener($event, callable $listener);
|
||||
public function removeAllListeners($event = null);
|
||||
public function listeners($event);
|
||||
public function emit($event, array $arguments = []);
|
||||
}
|
73
vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php
vendored
Normal file
73
vendor/evenement/evenement/src/Evenement/EventEmitterTrait.php
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Evenement.
|
||||
*
|
||||
* (c) Igor Wiedler <igor@wiedler.ch>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Evenement;
|
||||
|
||||
trait EventEmitterTrait
|
||||
{
|
||||
protected $listeners = [];
|
||||
|
||||
public function on($event, callable $listener)
|
||||
{
|
||||
if (!isset($this->listeners[$event])) {
|
||||
$this->listeners[$event] = [];
|
||||
}
|
||||
|
||||
$this->listeners[$event][] = $listener;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function once($event, callable $listener)
|
||||
{
|
||||
$onceListener = function () use (&$onceListener, $event, $listener) {
|
||||
$this->removeListener($event, $onceListener);
|
||||
|
||||
\call_user_func_array($listener, \func_get_args());
|
||||
};
|
||||
|
||||
$this->on($event, $onceListener);
|
||||
}
|
||||
|
||||
public function removeListener($event, callable $listener)
|
||||
{
|
||||
if (isset($this->listeners[$event])) {
|
||||
$index = \array_search($listener, $this->listeners[$event], true);
|
||||
if (false !== $index) {
|
||||
unset($this->listeners[$event][$index]);
|
||||
if (\count($this->listeners[$event]) === 0) {
|
||||
unset($this->listeners[$event]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function removeAllListeners($event = null)
|
||||
{
|
||||
if ($event !== null) {
|
||||
unset($this->listeners[$event]);
|
||||
} else {
|
||||
$this->listeners = [];
|
||||
}
|
||||
}
|
||||
|
||||
public function listeners($event)
|
||||
{
|
||||
return isset($this->listeners[$event]) ? $this->listeners[$event] : [];
|
||||
}
|
||||
|
||||
public function emit($event, array $arguments = [])
|
||||
{
|
||||
foreach ($this->listeners($event) as $listener) {
|
||||
\call_user_func_array($listener, $arguments);
|
||||
}
|
||||
}
|
||||
}
|
22
vendor/paragonie/random_compat/LICENSE
vendored
Normal file
22
vendor/paragonie/random_compat/LICENSE
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Paragon Initiative Enterprises
|
||||
|
||||
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.
|
||||
|
38
vendor/paragonie/random_compat/composer.json
vendored
Normal file
38
vendor/paragonie/random_compat/composer.json
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
||||
"keywords": [
|
||||
"csprng",
|
||||
"random",
|
||||
"polyfill",
|
||||
"pseudorandom"
|
||||
],
|
||||
"license": "MIT",
|
||||
"type": "library",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/paragonie/random_compat/issues",
|
||||
"email": "info@paragonie.com",
|
||||
"source": "https://github.com/paragonie/random_compat"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*|5.*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/random.php"
|
||||
]
|
||||
}
|
||||
}
|
195
vendor/paragonie/random_compat/lib/byte_safe_strings.php
vendored
Normal file
195
vendor/paragonie/random_compat/lib/byte_safe_strings.php
vendored
Normal file
@ -0,0 +1,195 @@
|
||||
<?php
|
||||
/**
|
||||
* Random_* Compatibility Library
|
||||
* for using the new PHP 7 random_* API in PHP 5 projects
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
if (!is_callable('RandomCompat_strlen')) {
|
||||
if (
|
||||
defined('MB_OVERLOAD_STRING')
|
||||
&&
|
||||
((int) ini_get('mbstring.func_overload')) & MB_OVERLOAD_STRING
|
||||
) {
|
||||
/**
|
||||
* strlen() implementation that isn't brittle to mbstring.func_overload
|
||||
*
|
||||
* This version uses mb_strlen() in '8bit' mode to treat strings as raw
|
||||
* binary rather than UTF-8, ISO-8859-1, etc
|
||||
*
|
||||
* @param string $binary_string
|
||||
*
|
||||
* @throws TypeError
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function RandomCompat_strlen($binary_string)
|
||||
{
|
||||
if (!is_string($binary_string)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_strlen() expects a string'
|
||||
);
|
||||
}
|
||||
|
||||
return (int) mb_strlen($binary_string, '8bit');
|
||||
}
|
||||
|
||||
} else {
|
||||
/**
|
||||
* strlen() implementation that isn't brittle to mbstring.func_overload
|
||||
*
|
||||
* This version just used the default strlen()
|
||||
*
|
||||
* @param string $binary_string
|
||||
*
|
||||
* @throws TypeError
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function RandomCompat_strlen($binary_string)
|
||||
{
|
||||
if (!is_string($binary_string)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_strlen() expects a string'
|
||||
);
|
||||
}
|
||||
return (int) strlen($binary_string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_callable('RandomCompat_substr')) {
|
||||
|
||||
if (
|
||||
defined('MB_OVERLOAD_STRING')
|
||||
&&
|
||||
((int) ini_get('mbstring.func_overload')) & MB_OVERLOAD_STRING
|
||||
) {
|
||||
/**
|
||||
* substr() implementation that isn't brittle to mbstring.func_overload
|
||||
*
|
||||
* This version uses mb_substr() in '8bit' mode to treat strings as raw
|
||||
* binary rather than UTF-8, ISO-8859-1, etc
|
||||
*
|
||||
* @param string $binary_string
|
||||
* @param int $start
|
||||
* @param int|null $length (optional)
|
||||
*
|
||||
* @throws TypeError
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function RandomCompat_substr($binary_string, $start, $length = null)
|
||||
{
|
||||
if (!is_string($binary_string)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_substr(): First argument should be a string'
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_int($start)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_substr(): Second argument should be an integer'
|
||||
);
|
||||
}
|
||||
|
||||
if ($length === null) {
|
||||
/**
|
||||
* mb_substr($str, 0, NULL, '8bit') returns an empty string on
|
||||
* PHP 5.3, so we have to find the length ourselves.
|
||||
*/
|
||||
/** @var int $length */
|
||||
$length = RandomCompat_strlen($binary_string) - $start;
|
||||
} elseif (!is_int($length)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_substr(): Third argument should be an integer, or omitted'
|
||||
);
|
||||
}
|
||||
|
||||
// Consistency with PHP's behavior
|
||||
if ($start === RandomCompat_strlen($binary_string) && $length === 0) {
|
||||
return '';
|
||||
}
|
||||
if ($start > RandomCompat_strlen($binary_string)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string) mb_substr(
|
||||
(string) $binary_string,
|
||||
(int) $start,
|
||||
(int) $length,
|
||||
'8bit'
|
||||
);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
/**
|
||||
* substr() implementation that isn't brittle to mbstring.func_overload
|
||||
*
|
||||
* This version just uses the default substr()
|
||||
*
|
||||
* @param string $binary_string
|
||||
* @param int $start
|
||||
* @param int|null $length (optional)
|
||||
*
|
||||
* @throws TypeError
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function RandomCompat_substr($binary_string, $start, $length = null)
|
||||
{
|
||||
if (!is_string($binary_string)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_substr(): First argument should be a string'
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_int($start)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_substr(): Second argument should be an integer'
|
||||
);
|
||||
}
|
||||
|
||||
if ($length !== null) {
|
||||
if (!is_int($length)) {
|
||||
throw new TypeError(
|
||||
'RandomCompat_substr(): Third argument should be an integer, or omitted'
|
||||
);
|
||||
}
|
||||
|
||||
return (string) substr(
|
||||
(string )$binary_string,
|
||||
(int) $start,
|
||||
(int) $length
|
||||
);
|
||||
}
|
||||
|
||||
return (string) substr(
|
||||
(string) $binary_string,
|
||||
(int) $start
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
77
vendor/paragonie/random_compat/lib/cast_to_int.php
vendored
Normal file
77
vendor/paragonie/random_compat/lib/cast_to_int.php
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* Random_* Compatibility Library
|
||||
* for using the new PHP 7 random_* API in PHP 5 projects
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
if (!is_callable('RandomCompat_intval')) {
|
||||
|
||||
/**
|
||||
* Cast to an integer if we can, safely.
|
||||
*
|
||||
* If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
|
||||
* (non-inclusive), it will sanely cast it to an int. If you it's equal to
|
||||
* ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
|
||||
* lose precision, so the <= and => operators might accidentally let a float
|
||||
* through.
|
||||
*
|
||||
* @param int|float $number The number we want to convert to an int
|
||||
* @param bool $fail_open Set to true to not throw an exception
|
||||
*
|
||||
* @return float|int
|
||||
* @psalm-suppress InvalidReturnType
|
||||
*
|
||||
* @throws TypeError
|
||||
*/
|
||||
function RandomCompat_intval($number, $fail_open = false)
|
||||
{
|
||||
if (is_int($number) || is_float($number)) {
|
||||
$number += 0;
|
||||
} elseif (is_numeric($number)) {
|
||||
/** @psalm-suppress InvalidOperand */
|
||||
$number += 0;
|
||||
}
|
||||
/** @var int|float $number */
|
||||
|
||||
if (
|
||||
is_float($number)
|
||||
&&
|
||||
$number > ~PHP_INT_MAX
|
||||
&&
|
||||
$number < PHP_INT_MAX
|
||||
) {
|
||||
$number = (int) $number;
|
||||
}
|
||||
|
||||
if (is_int($number)) {
|
||||
return (int) $number;
|
||||
} elseif (!$fail_open) {
|
||||
throw new TypeError(
|
||||
'Expected an integer.'
|
||||
);
|
||||
}
|
||||
return $number;
|
||||
}
|
||||
}
|
49
vendor/paragonie/random_compat/lib/error_polyfill.php
vendored
Normal file
49
vendor/paragonie/random_compat/lib/error_polyfill.php
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* Random_* Compatibility Library
|
||||
* for using the new PHP 7 random_* API in PHP 5 projects
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
if (!class_exists('Error', false)) {
|
||||
// We can't really avoid making this extend Exception in PHP 5.
|
||||
class Error extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists('TypeError', false)) {
|
||||
if (is_subclass_of('Error', 'Exception')) {
|
||||
class TypeError extends Error
|
||||
{
|
||||
|
||||
}
|
||||
} else {
|
||||
class TypeError extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
225
vendor/paragonie/random_compat/lib/random.php
vendored
Normal file
225
vendor/paragonie/random_compat/lib/random.php
vendored
Normal file
@ -0,0 +1,225 @@
|
||||
<?php
|
||||
/**
|
||||
* Random_* Compatibility Library
|
||||
* for using the new PHP 7 random_* API in PHP 5 projects
|
||||
*
|
||||
* @version 2.0.17
|
||||
* @released 2018-07-04
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
if (!defined('PHP_VERSION_ID')) {
|
||||
// This constant was introduced in PHP 5.2.7
|
||||
$RandomCompatversion = array_map('intval', explode('.', PHP_VERSION));
|
||||
define(
|
||||
'PHP_VERSION_ID',
|
||||
$RandomCompatversion[0] * 10000
|
||||
+ $RandomCompatversion[1] * 100
|
||||
+ $RandomCompatversion[2]
|
||||
);
|
||||
$RandomCompatversion = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* PHP 7.0.0 and newer have these functions natively.
|
||||
*/
|
||||
if (PHP_VERSION_ID >= 70000) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
|
||||
define('RANDOM_COMPAT_READ_BUFFER', 8);
|
||||
}
|
||||
|
||||
$RandomCompatDIR = dirname(__FILE__);
|
||||
|
||||
require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'byte_safe_strings.php';
|
||||
require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'cast_to_int.php';
|
||||
require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'error_polyfill.php';
|
||||
|
||||
if (!is_callable('random_bytes')) {
|
||||
/**
|
||||
* PHP 5.2.0 - 5.6.x way to implement random_bytes()
|
||||
*
|
||||
* We use conditional statements here to define the function in accordance
|
||||
* to the operating environment. It's a micro-optimization.
|
||||
*
|
||||
* In order of preference:
|
||||
* 1. Use libsodium if available.
|
||||
* 2. fread() /dev/urandom if available (never on Windows)
|
||||
* 3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM)
|
||||
* 4. COM('CAPICOM.Utilities.1')->GetRandom()
|
||||
*
|
||||
* See RATIONALE.md for our reasoning behind this particular order
|
||||
*/
|
||||
if (extension_loaded('libsodium')) {
|
||||
// See random_bytes_libsodium.php
|
||||
if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) {
|
||||
require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_libsodium.php';
|
||||
} elseif (method_exists('Sodium', 'randombytes_buf')) {
|
||||
require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_libsodium_legacy.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reading directly from /dev/urandom:
|
||||
*/
|
||||
if (DIRECTORY_SEPARATOR === '/') {
|
||||
// DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast
|
||||
// way to exclude Windows.
|
||||
$RandomCompatUrandom = true;
|
||||
$RandomCompat_basedir = ini_get('open_basedir');
|
||||
|
||||
if (!empty($RandomCompat_basedir)) {
|
||||
$RandomCompat_open_basedir = explode(
|
||||
PATH_SEPARATOR,
|
||||
strtolower($RandomCompat_basedir)
|
||||
);
|
||||
$RandomCompatUrandom = (array() !== array_intersect(
|
||||
array('/dev', '/dev/', '/dev/urandom'),
|
||||
$RandomCompat_open_basedir
|
||||
));
|
||||
$RandomCompat_open_basedir = null;
|
||||
}
|
||||
|
||||
if (
|
||||
!is_callable('random_bytes')
|
||||
&&
|
||||
$RandomCompatUrandom
|
||||
&&
|
||||
@is_readable('/dev/urandom')
|
||||
) {
|
||||
// Error suppression on is_readable() in case of an open_basedir
|
||||
// or safe_mode failure. All we care about is whether or not we
|
||||
// can read it at this point. If the PHP environment is going to
|
||||
// panic over trying to see if the file can be read in the first
|
||||
// place, that is not helpful to us here.
|
||||
|
||||
// See random_bytes_dev_urandom.php
|
||||
require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_dev_urandom.php';
|
||||
}
|
||||
// Unset variables after use
|
||||
$RandomCompat_basedir = null;
|
||||
} else {
|
||||
$RandomCompatUrandom = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* mcrypt_create_iv()
|
||||
*
|
||||
* We only want to use mcypt_create_iv() if:
|
||||
*
|
||||
* - random_bytes() hasn't already been defined
|
||||
* - the mcrypt extensions is loaded
|
||||
* - One of these two conditions is true:
|
||||
* - We're on Windows (DIRECTORY_SEPARATOR !== '/')
|
||||
* - We're not on Windows and /dev/urandom is readabale
|
||||
* (i.e. we're not in a chroot jail)
|
||||
* - Special case:
|
||||
* - If we're not on Windows, but the PHP version is between
|
||||
* 5.6.10 and 5.6.12, we don't want to use mcrypt. It will
|
||||
* hang indefinitely. This is bad.
|
||||
* - If we're on Windows, we want to use PHP >= 5.3.7 or else
|
||||
* we get insufficient entropy errors.
|
||||
*/
|
||||
if (
|
||||
!is_callable('random_bytes')
|
||||
&&
|
||||
// Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be.
|
||||
(DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307)
|
||||
&&
|
||||
// Prevent this code from hanging indefinitely on non-Windows;
|
||||
// see https://bugs.php.net/bug.php?id=69833
|
||||
(
|
||||
DIRECTORY_SEPARATOR !== '/' ||
|
||||
(PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613)
|
||||
)
|
||||
&&
|
||||
extension_loaded('mcrypt')
|
||||
) {
|
||||
// See random_bytes_mcrypt.php
|
||||
require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_mcrypt.php';
|
||||
}
|
||||
$RandomCompatUrandom = null;
|
||||
|
||||
/**
|
||||
* This is a Windows-specific fallback, for when the mcrypt extension
|
||||
* isn't loaded.
|
||||
*/
|
||||
if (
|
||||
!is_callable('random_bytes')
|
||||
&&
|
||||
extension_loaded('com_dotnet')
|
||||
&&
|
||||
class_exists('COM')
|
||||
) {
|
||||
$RandomCompat_disabled_classes = preg_split(
|
||||
'#\s*,\s*#',
|
||||
strtolower(ini_get('disable_classes'))
|
||||
);
|
||||
|
||||
if (!in_array('com', $RandomCompat_disabled_classes)) {
|
||||
try {
|
||||
$RandomCompatCOMtest = new COM('CAPICOM.Utilities.1');
|
||||
if (method_exists($RandomCompatCOMtest, 'GetRandom')) {
|
||||
// See random_bytes_com_dotnet.php
|
||||
require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_bytes_com_dotnet.php';
|
||||
}
|
||||
} catch (com_exception $e) {
|
||||
// Don't try to use it.
|
||||
}
|
||||
}
|
||||
$RandomCompat_disabled_classes = null;
|
||||
$RandomCompatCOMtest = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* throw new Exception
|
||||
*/
|
||||
if (!is_callable('random_bytes')) {
|
||||
/**
|
||||
* We don't have any more options, so let's throw an exception right now
|
||||
* and hope the developer won't let it fail silently.
|
||||
*
|
||||
* @param mixed $length
|
||||
* @psalm-suppress InvalidReturnType
|
||||
* @throws Exception
|
||||
* @return string
|
||||
*/
|
||||
function random_bytes($length)
|
||||
{
|
||||
unset($length); // Suppress "variable not used" warnings.
|
||||
throw new Exception(
|
||||
'There is no suitable CSPRNG installed on your system'
|
||||
);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_callable('random_int')) {
|
||||
require_once $RandomCompatDIR . DIRECTORY_SEPARATOR . 'random_int.php';
|
||||
}
|
||||
|
||||
$RandomCompatDIR = null;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user