summaryrefslogtreecommitdiffstats
path: root/vendor/clue
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/clue')
-rw-r--r--vendor/clue/block-react/LICENSE21
-rw-r--r--vendor/clue/block-react/composer.json29
-rw-r--r--vendor/clue/block-react/src/functions.php357
-rw-r--r--vendor/clue/block-react/src/functions_include.php8
-rw-r--r--vendor/clue/buzz-react/LICENSE21
-rw-r--r--vendor/clue/buzz-react/composer.json38
-rw-r--r--vendor/clue/buzz-react/src/Browser.php867
-rw-r--r--vendor/clue/buzz-react/src/Io/ChunkedEncoder.php93
-rw-r--r--vendor/clue/buzz-react/src/Io/Sender.php161
-rw-r--r--vendor/clue/buzz-react/src/Io/Transaction.php305
-rw-r--r--vendor/clue/buzz-react/src/Message/MessageFactory.php139
-rw-r--r--vendor/clue/buzz-react/src/Message/ReadableBodyStream.php153
-rw-r--r--vendor/clue/buzz-react/src/Message/ResponseException.php43
-rw-r--r--vendor/clue/connection-manager-extra/LICENSE21
-rw-r--r--vendor/clue/connection-manager-extra/composer.json29
-rw-r--r--vendor/clue/connection-manager-extra/src/ConnectionManagerDelay.php30
-rw-r--r--vendor/clue/connection-manager-extra/src/ConnectionManagerReject.php41
-rw-r--r--vendor/clue/connection-manager-extra/src/ConnectionManagerRepeat.php52
-rw-r--r--vendor/clue/connection-manager-extra/src/ConnectionManagerSwappable.php26
-rw-r--r--vendor/clue/connection-manager-extra/src/ConnectionManagerTimeout.php35
-rw-r--r--vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConcurrent.php36
-rw-r--r--vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConsecutive.php62
-rw-r--r--vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerRandom.php14
-rw-r--r--vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerSelective.php111
-rw-r--r--vendor/clue/http-proxy-react/LICENSE21
-rw-r--r--vendor/clue/http-proxy-react/composer.json31
-rw-r--r--vendor/clue/http-proxy-react/src/ProxyConnector.php274
-rw-r--r--vendor/clue/mq-react/LICENSE21
-rw-r--r--vendor/clue/mq-react/composer.json29
-rw-r--r--vendor/clue/mq-react/src/Queue.php448
-rw-r--r--vendor/clue/redis-protocol/composer.json19
-rw-r--r--vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Factory.php51
-rw-r--r--vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/BulkReply.php34
-rw-r--r--vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ErrorReply.php34
-rw-r--r--vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/IntegerReply.php31
-rw-r--r--vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ModelInterface.php23
-rw-r--r--vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/MultiBulkReply.php100
-rw-r--r--vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/Request.php53
-rw-r--r--vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/StatusReply.php34
-rw-r--r--vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/MessageBuffer.php40
-rw-r--r--vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserException.php10
-rw-r--r--vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserInterface.php28
-rw-r--r--vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/RequestParser.php125
-rw-r--r--vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ResponseParser.php151
-rw-r--r--vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/RecursiveSerializer.php111
-rw-r--r--vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/SerializerInterface.php83
-rw-r--r--vendor/clue/redis-react/LICENSE21
-rw-r--r--vendor/clue/redis-react/composer.json32
-rw-r--r--vendor/clue/redis-react/src/Client.php54
-rw-r--r--vendor/clue/redis-react/src/Factory.php191
-rw-r--r--vendor/clue/redis-react/src/LazyClient.php219
-rw-r--r--vendor/clue/redis-react/src/StreamingClient.php203
-rw-r--r--vendor/clue/soap-react/LICENSE21
-rw-r--r--vendor/clue/soap-react/composer.json27
-rw-r--r--vendor/clue/soap-react/src/Client.php323
-rw-r--r--vendor/clue/soap-react/src/Protocol/ClientDecoder.php53
-rw-r--r--vendor/clue/soap-react/src/Protocol/ClientEncoder.php69
-rw-r--r--vendor/clue/soap-react/src/Proxy.php50
-rw-r--r--vendor/clue/socket-raw/LICENSE21
-rw-r--r--vendor/clue/socket-raw/composer.json23
-rw-r--r--vendor/clue/socket-raw/src/Exception.php91
-rw-r--r--vendor/clue/socket-raw/src/Factory.php282
-rw-r--r--vendor/clue/socket-raw/src/Socket.php562
-rw-r--r--vendor/clue/socks-react/LICENSE21
-rw-r--r--vendor/clue/socks-react/composer.json31
-rw-r--r--vendor/clue/socks-react/src/Client.php452
-rw-r--r--vendor/clue/socks-react/src/Server.php399
-rw-r--r--vendor/clue/socks-react/src/StreamReader.php149
-rw-r--r--vendor/clue/stdio-react/LICENSE21
-rw-r--r--vendor/clue/stdio-react/composer.json37
-rw-r--r--vendor/clue/stdio-react/src/Readline.php1017
-rw-r--r--vendor/clue/stdio-react/src/Stdio.php630
-rw-r--r--vendor/clue/term-react/LICENSE21
-rw-r--r--vendor/clue/term-react/composer.json27
-rw-r--r--vendor/clue/term-react/src/ControlCodeParser.php223
-rw-r--r--vendor/clue/utf8-react/LICENSE21
-rw-r--r--vendor/clue/utf8-react/composer.json27
-rw-r--r--vendor/clue/utf8-react/src/Sequencer.php174
78 files changed, 9935 insertions, 0 deletions
diff --git a/vendor/clue/block-react/LICENSE b/vendor/clue/block-react/LICENSE
new file mode 100644
index 0000000..dc09d1e
--- /dev/null
+++ b/vendor/clue/block-react/LICENSE
@@ -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.
diff --git a/vendor/clue/block-react/composer.json b/vendor/clue/block-react/composer.json
new file mode 100644
index 0000000..ddfc6c8
--- /dev/null
+++ b/vendor/clue/block-react/composer.json
@@ -0,0 +1,29 @@
+{
+ "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@clue.engineering"
+ }
+ ],
+ "autoload": {
+ "files": [ "src/functions_include.php" ]
+ },
+ "autoload-dev": {
+ "psr-4": { "Clue\\Tests\\React\\Block\\": "tests/" }
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/event-loop": "^1.2",
+ "react/promise": "^3.0 || ^2.7 || ^1.2.1",
+ "react/promise-timer": "^1.5"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35",
+ "react/http": "^1.4"
+ }
+}
diff --git a/vendor/clue/block-react/src/functions.php b/vendor/clue/block-react/src/functions.php
new file mode 100644
index 0000000..6afe2e0
--- /dev/null
+++ b/vendor/clue/block-react/src/functions.php
@@ -0,0 +1,357 @@
+<?php
+
+namespace Clue\React\Block;
+
+use React\EventLoop\Loop;
+use React\EventLoop\LoopInterface;
+use React\Promise;
+use React\Promise\CancellablePromiseInterface;
+use React\Promise\PromiseInterface;
+use React\Promise\Timer;
+use React\Promise\Timer\TimeoutException;
+use Exception;
+use UnderflowException;
+
+/**
+ * Wait/sleep for `$time` seconds.
+ *
+ * ```php
+ * Clue\React\Block\sleep(1.5, $loop);
+ * ```
+ *
+ * This function will only return after the given `$time` has elapsed. In the
+ * meantime, the event loop will run any other events attached to the same loop
+ * until the timer fires. If there are no other events attached to this loop,
+ * it will behave similar to the built-in [`sleep()`](https://www.php.net/manual/en/function.sleep.php).
+ *
+ * Internally, the `$time` argument will be used as a timer for the loop so that
+ * it keeps running until this timer 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.
+ *
+ * This function takes an optional `LoopInterface|null $loop` parameter that can be used to
+ * pass the event loop instance to use. You can use a `null` value here in order to
+ * use the [default loop](https://github.com/reactphp/event-loop#loop). This value
+ * SHOULD NOT be given unless you're sure you want to explicitly use a given event
+ * loop instance.
+ *
+ * Note that this function will assume control over the event loop. Internally, it
+ * will actually `run()` the loop until the timer fires and then calls `stop()` to
+ * terminate execution of the loop. This means this function is more suited for
+ * short-lived program executions when using async APIs is not feasible. For
+ * long-running applications, using event-driven APIs by leveraging timers
+ * is usually preferable.
+ *
+ * @param float $time
+ * @param ?LoopInterface $loop
+ * @return void
+ */
+function sleep($time, LoopInterface $loop = null)
+{
+ await(Timer\resolve($time, $loop), $loop);
+}
+
+/**
+ * Block waiting for the given `$promise` to be fulfilled.
+ *
+ * ```php
+ * $result = Clue\React\Block\await($promise, $loop);
+ * ```
+ *
+ * This function will only return after the given `$promise` has settled, i.e.
+ * either fulfilled or rejected. In the meantime, the event loop will run any
+ * events attached to the same loop until the promise settles.
+ *
+ * Once the promise is fulfilled, this function will return whatever the promise
+ * resolved 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.
+ *
+ * ```php
+ * try {
+ * $result = Clue\React\Block\await($promise, $loop);
+ * // promise successfully fulfilled with $result
+ * echo 'Result: ' . $result;
+ * } catch (Exception $exception) {
+ * // promise rejected with $exception
+ * echo 'ERROR: ' . $exception->getMessage();
+ * }
+ * ```
+ *
+ * See also the [examples](../examples/).
+ *
+ * This function takes an optional `LoopInterface|null $loop` parameter that can be used to
+ * pass the event loop instance to use. You can use a `null` value here in order to
+ * use the [default loop](https://github.com/reactphp/event-loop#loop). This value
+ * SHOULD NOT be given unless you're sure you want to explicitly use a given event
+ * loop instance.
+ *
+ * If no `$timeout` argument is given and the promise stays pending, then this
+ * will potentially wait/block forever until the promise is settled. To avoid
+ * this, API authors creating promises are expected to provide means to
+ * configure a timeout for the promise instead. For more details, see also the
+ * [`timeout()` function](https://github.com/reactphp/promise-timer#timeout).
+ *
+ * If the deprecated `$timeout` argument 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.
+ *
+ * Note that this function will assume control over the event loop. Internally, it
+ * will actually `run()` the loop until the promise settles and then calls `stop()` to
+ * terminate execution of the loop. This means this function is more suited for
+ * short-lived promise executions when using promise-based APIs is not feasible.
+ * For long-running applications, using promise-based APIs by leveraging chained
+ * `then()` calls is usually preferable.
+ *
+ * @param PromiseInterface $promise
+ * @param ?LoopInterface $loop
+ * @param ?float $timeout [deprecated] (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 = null, $timeout = null)
+{
+ $wait = true;
+ $resolved = null;
+ $exception = null;
+ $rejected = false;
+ $loop = $loop ?: Loop::get();
+
+ 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 instanceof \Throwable) {
+ $exception = new \UnexpectedValueException(
+ 'Promise rejected with unexpected value of type ' . (is_object($exception) ? get_class($exception) : gettype($exception))
+ );
+ } elseif (!$exception instanceof \Exception) {
+ $exception = new \UnexpectedValueException(
+ 'Promise rejected with unexpected ' . get_class($exception) . ': ' . $exception->getMessage(),
+ $exception->getCode(),
+ $exception
+ );
+ }
+
+ throw $exception;
+ }
+
+ return $resolved;
+}
+
+/**
+ * Wait for ANY of the given promises to be fulfilled.
+ *
+ * ```php
+ * $promises = array(
+ * $promise1,
+ * $promise2
+ * );
+ *
+ * $firstResult = Clue\React\Block\awaitAny($promises, $loop);
+ *
+ * echo 'First result: ' . $firstResult;
+ * ```
+ *
+ * See also the [examples](../examples/).
+ *
+ * This function will only return after ANY of the given `$promises` has been
+ * fulfilled or will throw when ALL of them have been rejected. In the meantime,
+ * the event loop will run any events attached to the same loop.
+ *
+ * Once ANY promise is fulfilled, this function will return whatever this
+ * promise resolved to and will try to `cancel()` all remaining promises.
+ *
+ * Once ALL promises reject, this function will fail and throw an `UnderflowException`.
+ * Likewise, this will throw if an empty array of `$promises` is passed.
+ *
+ * This function takes an optional `LoopInterface|null $loop` parameter that can be used to
+ * pass the event loop instance to use. You can use a `null` value here in order to
+ * use the [default loop](https://github.com/reactphp/event-loop#loop). This value
+ * SHOULD NOT be given unless you're sure you want to explicitly use a given event
+ * loop instance.
+ *
+ * If no `$timeout` argument is given and ALL promises stay pending, then this
+ * will potentially wait/block forever until the promise is fulfilled. To avoid
+ * this, API authors creating promises are expected to provide means to
+ * configure a timeout for the promise instead. For more details, see also the
+ * [`timeout()` function](https://github.com/reactphp/promise-timer#timeout).
+ *
+ * If the deprecated `$timeout` argument is given and ANY promises are 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.
+ *
+ * Note that this function will assume control over the event loop. Internally, it
+ * will actually `run()` the loop until the promise settles and then calls `stop()` to
+ * terminate execution of the loop. This means this function is more suited for
+ * short-lived promise executions when using promise-based APIs is not feasible.
+ * For long-running applications, using promise-based APIs by leveraging chained
+ * `then()` calls is usually preferable.
+ *
+ * @param PromiseInterface[] $promises
+ * @param ?LoopInterface $loop
+ * @param ?float $timeout [deprecated] (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 = null, $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 be fulfilled.
+ *
+ * ```php
+ * $promises = array(
+ * $promise1,
+ * $promise2
+ * );
+ *
+ * $allResults = Clue\React\Block\awaitAll($promises, $loop);
+ *
+ * echo 'First promise resolved with: ' . $allResults[0];
+ * ```
+ *
+ * See also the [examples](../examples/).
+ *
+ * This function will only return after ALL of the given `$promises` have been
+ * fulfilled or will throw when ANY of them have been rejected. In the meantime,
+ * the event loop will run any events attached to the same loop.
+ *
+ * Once ALL promises are fulfilled, 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.
+ *
+ * Once ANY promise rejects, 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.
+ *
+ * This function takes an optional `LoopInterface|null $loop` parameter that can be used to
+ * pass the event loop instance to use. You can use a `null` value here in order to
+ * use the [default loop](https://github.com/reactphp/event-loop#loop). This value
+ * SHOULD NOT be given unless you're sure you want to explicitly use a given event
+ * loop instance.
+ *
+ * If no `$timeout` argument is given and ANY promises stay pending, then this
+ * will potentially wait/block forever until the promise is fulfilled. To avoid
+ * this, API authors creating promises are expected to provide means to
+ * configure a timeout for the promise instead. For more details, see also the
+ * [`timeout()` function](https://github.com/reactphp/promise-timer#timeout).
+ *
+ * If the deprecated `$timeout` argument is given and ANY promises are 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.
+ *
+ * Note that this function will assume control over the event loop. Internally, it
+ * will actually `run()` the loop until the promise settles and then calls `stop()` to
+ * terminate execution of the loop. This means this function is more suited for
+ * short-lived promise executions when using promise-based APIs is not feasible.
+ * For long-running applications, using promise-based APIs by leveraging chained
+ * `then()` calls is usually preferable.
+ *
+ * @param PromiseInterface[] $promises
+ * @param ?LoopInterface $loop
+ * @param ?float $timeout [deprecated] (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 = null, $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
+ * @return void
+ */
+function _cancelAllPromises(array $promises)
+{
+ foreach ($promises as $promise) {
+ if ($promise instanceof PromiseInterface && ($promise instanceof CancellablePromiseInterface || !\interface_exists('React\Promise\CancellablePromiseInterface'))) {
+ $promise->cancel();
+ }
+ }
+}
diff --git a/vendor/clue/block-react/src/functions_include.php b/vendor/clue/block-react/src/functions_include.php
new file mode 100644
index 0000000..b3ad74c
--- /dev/null
+++ b/vendor/clue/block-react/src/functions_include.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Clue\React\Block;
+
+if (!function_exists('Clue\\React\\Block\\sleep')) {
+ require __DIR__ . '/functions.php';
+}
+
diff --git a/vendor/clue/buzz-react/LICENSE b/vendor/clue/buzz-react/LICENSE
new file mode 100644
index 0000000..da15612
--- /dev/null
+++ b/vendor/clue/buzz-react/LICENSE
@@ -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.
diff --git a/vendor/clue/buzz-react/composer.json b/vendor/clue/buzz-react/composer.json
new file mode 100644
index 0000000..99e6d69
--- /dev/null
+++ b/vendor/clue/buzz-react/composer.json
@@ -0,0 +1,38 @@
+{
+ "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@clue.engineering"
+ }
+ ],
+ "autoload": {
+ "psr-4": { "Clue\\React\\Buzz\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "Clue\\Tests\\React\\Buzz\\": "tests/" }
+ },
+ "require": {
+ "php": ">=5.3",
+ "psr/http-message": "^1.0",
+ "react/event-loop": "^1.0 || ^0.5",
+ "react/http-client": "^0.5.10",
+ "react/promise": "^2.2.1 || ^1.2.1",
+ "react/promise-stream": "^1.0 || ^0.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": "^9.0 || ^5.7 || ^4.8.35",
+ "react/http": "^0.8"
+ }
+}
diff --git a/vendor/clue/buzz-react/src/Browser.php b/vendor/clue/buzz-react/src/Browser.php
new file mode 100644
index 0000000..8f1a751
--- /dev/null
+++ b/vendor/clue/buzz-react/src/Browser.php
@@ -0,0 +1,867 @@
+<?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 $baseUrl;
+ private $protocolVersion = '1.1';
+
+ /**
+ * 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 Clue\React\Buzz\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 Clue\React\Buzz\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
+ );
+ }
+
+ /**
+ * Sends an HTTP GET request
+ *
+ * ```php
+ * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * var_dump((string)$response->getBody());
+ * });
+ * ```
+ *
+ * See also [example 01](../examples/01-google.php).
+ *
+ * > For BC reasons, this method accepts the `$url` as either a `string`
+ * value or as an `UriInterface`. It's recommended to explicitly cast any
+ * objects implementing `UriInterface` to `string`.
+ *
+ * @param string|UriInterface $url URL for the request.
+ * @param array $headers
+ * @return PromiseInterface<ResponseInterface>
+ */
+ public function get($url, array $headers = array())
+ {
+ return $this->requestMayBeStreaming('GET', $url, $headers);
+ }
+
+ /**
+ * Sends an HTTP POST request
+ *
+ * ```php
+ * $browser->post(
+ * $url,
+ * [
+ * 'Content-Type' => 'application/json'
+ * ],
+ * json_encode($data)
+ * )->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * var_dump(json_decode((string)$response->getBody()));
+ * });
+ * ```
+ *
+ * See also [example 04](../examples/04-post-json.php).
+ *
+ * This method is also commonly used to submit HTML form data:
+ *
+ * ```php
+ * $data = [
+ * 'user' => 'Alice',
+ * 'password' => 'secret'
+ * ];
+ *
+ * $browser->post(
+ * $url,
+ * [
+ * 'Content-Type' => 'application/x-www-form-urlencoded'
+ * ],
+ * http_build_query($data)
+ * );
+ * ```
+ *
+ * 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 React\Stream\ThroughStream();
+ * $loop->addTimer(1.0, function () use ($body) {
+ * $body->end("hello world");
+ * });
+ *
+ * $browser->post($url, array('Content-Length' => '11'), $body);
+ * ```
+ *
+ * > For BC reasons, this method accepts the `$url` as either a `string`
+ * value or as an `UriInterface`. It's recommended to explicitly cast any
+ * objects implementing `UriInterface` to `string`.
+ *
+ * @param string|UriInterface $url URL for the request.
+ * @param array $headers
+ * @param string|ReadableStreamInterface $contents
+ * @return PromiseInterface<ResponseInterface>
+ */
+ public function post($url, array $headers = array(), $contents = '')
+ {
+ return $this->requestMayBeStreaming('POST', $url, $headers, $contents);
+ }
+
+ /**
+ * Sends an HTTP HEAD request
+ *
+ * ```php
+ * $browser->head($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * var_dump($response->getHeaders());
+ * });
+ * ```
+ *
+ * > For BC reasons, this method accepts the `$url` as either a `string`
+ * value or as an `UriInterface`. It's recommended to explicitly cast any
+ * objects implementing `UriInterface` to `string`.
+ *
+ * @param string|UriInterface $url URL for the request.
+ * @param array $headers
+ * @return PromiseInterface<ResponseInterface>
+ */
+ public function head($url, array $headers = array())
+ {
+ return $this->requestMayBeStreaming('HEAD', $url, $headers);
+ }
+
+ /**
+ * Sends an HTTP PATCH request
+ *
+ * ```php
+ * $browser->patch(
+ * $url,
+ * [
+ * 'Content-Type' => 'application/json'
+ * ],
+ * json_encode($data)
+ * )->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * var_dump(json_decode((string)$response->getBody()));
+ * });
+ * ```
+ *
+ * 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 React\Stream\ThroughStream();
+ * $loop->addTimer(1.0, function () use ($body) {
+ * $body->end("hello world");
+ * });
+ *
+ * $browser->patch($url, array('Content-Length' => '11'), $body);
+ * ```
+ *
+ * > For BC reasons, this method accepts the `$url` as either a `string`
+ * value or as an `UriInterface`. It's recommended to explicitly cast any
+ * objects implementing `UriInterface` to `string`.
+ *
+ * @param string|UriInterface $url URL for the request.
+ * @param array $headers
+ * @param string|ReadableStreamInterface $contents
+ * @return PromiseInterface<ResponseInterface>
+ */
+ public function patch($url, array $headers = array(), $contents = '')
+ {
+ return $this->requestMayBeStreaming('PATCH', $url , $headers, $contents);
+ }
+
+ /**
+ * Sends an HTTP PUT request
+ *
+ * ```php
+ * $browser->put(
+ * $url,
+ * [
+ * 'Content-Type' => 'text/xml'
+ * ],
+ * $xml->asXML()
+ * )->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * var_dump((string)$response->getBody());
+ * });
+ * ```
+ *
+ * See also [example 05](../examples/05-put-xml.php).
+ *
+ * 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 React\Stream\ThroughStream();
+ * $loop->addTimer(1.0, function () use ($body) {
+ * $body->end("hello world");
+ * });
+ *
+ * $browser->put($url, array('Content-Length' => '11'), $body);
+ * ```
+ *
+ * > For BC reasons, this method accepts the `$url` as either a `string`
+ * value or as an `UriInterface`. It's recommended to explicitly cast any
+ * objects implementing `UriInterface` to `string`.
+ *
+ * @param string|UriInterface $url URL for the request.
+ * @param array $headers
+ * @param string|ReadableStreamInterface $contents
+ * @return PromiseInterface<ResponseInterface>
+ */
+ public function put($url, array $headers = array(), $contents = '')
+ {
+ return $this->requestMayBeStreaming('PUT', $url, $headers, $contents);
+ }
+
+ /**
+ * Sends an HTTP DELETE request
+ *
+ * ```php
+ * $browser->delete($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * var_dump((string)$response->getBody());
+ * });
+ * ```
+ *
+ * > For BC reasons, this method accepts the `$url` as either a `string`
+ * value or as an `UriInterface`. It's recommended to explicitly cast any
+ * objects implementing `UriInterface` to `string`.
+ *
+ * @param string|UriInterface $url URL for the request.
+ * @param array $headers
+ * @param string|ReadableStreamInterface $contents
+ * @return PromiseInterface<ResponseInterface>
+ */
+ public function delete($url, array $headers = array(), $contents = '')
+ {
+ return $this->requestMayBeStreaming('DELETE', $url, $headers, $contents);
+ }
+
+ /**
+ * Sends an arbitrary HTTP request.
+ *
+ * The preferred way to send an HTTP request is by using the above
+ * [request methods](#request-methods), for example the [`get()`](#get)
+ * method to send an HTTP `GET` request.
+ *
+ * As an alternative, if you want to use a custom HTTP request method, you
+ * can use this method:
+ *
+ * ```php
+ * $browser->request('OPTIONS', $url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * var_dump((string)$response->getBody());
+ * });
+ * ```
+ *
+ * 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`).
+ *
+ * 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 React\Stream\ThroughStream();
+ * $loop->addTimer(1.0, function () use ($body) {
+ * $body->end("hello world");
+ * });
+ *
+ * $browser->request('POST', $url, array('Content-Length' => '11'), $body);
+ * ```
+ *
+ * > Note that this method is available as of v2.9.0 and always buffers the
+ * response body before resolving.
+ * It does not respect the deprecated [`streaming` option](#withoptions).
+ * If you want to stream the response body, you can use the
+ * [`requestStreaming()`](#requeststreaming) method instead.
+ *
+ * @param string $method HTTP request method, e.g. GET/HEAD/POST etc.
+ * @param string $url URL for the request
+ * @param array $headers Additional request headers
+ * @param string|ReadableStreamInterface $body HTTP request body contents
+ * @return PromiseInterface<ResponseInterface,Exception>
+ * @since 2.9.0
+ */
+ public function request($method, $url, array $headers = array(), $body = '')
+ {
+ return $this->withOptions(array('streaming' => false))->requestMayBeStreaming($method, $url, $headers, $body);
+ }
+
+ /**
+ * Sends an arbitrary HTTP request and receives a streaming response without buffering the response body.
+ *
+ * The preferred way to send an HTTP request is by using the above
+ * [request methods](#request-methods), for example the [`get()`](#get)
+ * method to send an HTTP `GET` request. Each of these methods will buffer
+ * the whole response body in memory by default. This is easy to get started
+ * and works reasonably well for smaller responses.
+ *
+ * In some situations, it's a better idea to use a streaming approach, where
+ * only small chunks have to be kept in memory. You can use this method to
+ * send an arbitrary HTTP request and receive a streaming response. It uses
+ * the same HTTP message API, but does not buffer the response body in
+ * memory. It only processes the response body in small chunks as data is
+ * received and forwards this data through [ReactPHP's Stream API](https://github.com/reactphp/stream).
+ * This works for (any number of) responses of arbitrary sizes.
+ *
+ * ```php
+ * $browser->requestStreaming('GET', $url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * $body = $response->getBody();
+ * assert($body instanceof Psr\Http\Message\StreamInterface);
+ * assert($body instanceof React\Stream\ReadableStreamInterface);
+ *
+ * $body->on('data', function ($chunk) {
+ * echo $chunk;
+ * });
+ *
+ * $body->on('error', function (Exception $error) {
+ * echo 'Error: ' . $error->getMessage() . PHP_EOL;
+ * });
+ *
+ * $body->on('close', function () {
+ * echo '[DONE]' . PHP_EOL;
+ * });
+ * });
+ * ```
+ *
+ * See also [`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface)
+ * and the [streaming response](#streaming-response) for more details,
+ * examples and possible use-cases.
+ *
+ * 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`).
+ *
+ * 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 React\Stream\ThroughStream();
+ * $loop->addTimer(1.0, function () use ($body) {
+ * $body->end("hello world");
+ * });
+ *
+ * $browser->requestStreaming('POST', $url, array('Content-Length' => '11'), $body);
+ * ```
+ *
+ * > Note that this method is available as of v2.9.0 and always resolves the
+ * response without buffering the response body.
+ * It does not respect the deprecated [`streaming` option](#withoptions).
+ * If you want to buffer the response body, use can use the
+ * [`request()`](#request) method instead.
+ *
+ * @param string $method HTTP request method, e.g. GET/HEAD/POST etc.
+ * @param string $url URL for the request
+ * @param array $headers Additional request headers
+ * @param string|ReadableStreamInterface $body HTTP request body contents
+ * @return PromiseInterface<ResponseInterface,Exception>
+ * @since 2.9.0
+ */
+ public function requestStreaming($method, $url, $headers = array(), $contents = '')
+ {
+ return $this->withOptions(array('streaming' => true))->requestMayBeStreaming($method, $url, $headers, $contents);
+ }
+
+ /**
+ * [Deprecated] Submits an array of field values similar to submitting a form (`application/x-www-form-urlencoded`).
+ *
+ * ```php
+ * // deprecated: see post() instead
+ * $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`.
+ *
+ * > For BC reasons, this method accepts the `$url` as either a `string`
+ * value or as an `UriInterface`. It's recommended to explicitly cast any
+ * objects implementing `UriInterface` to `string`.
+ *
+ * @param string|UriInterface $url URL for the request.
+ * @param array $fields
+ * @param array $headers
+ * @param string $method
+ * @return PromiseInterface<ResponseInterface>
+ * @deprecated 2.9.0 See self::post() instead.
+ * @see self::post()
+ */
+ 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->requestMayBeStreaming($method, $url, $headers, $contents);
+ }
+
+ /**
+ * [Deprecated] Sends an arbitrary instance implementing the [`RequestInterface`](#requestinterface) (PSR-7).
+ *
+ * The preferred way to send an HTTP request is by using the above
+ * [request methods](#request-methods), for example the [`get()`](#get)
+ * method to send an HTTP `GET` request.
+ *
+ * As an alternative, if you want to use a custom HTTP request method, you
+ * can use this method:
+ *
+ * ```php
+ * $request = new Request('OPTIONS', $url);
+ *
+ * // deprecated: see request() instead
+ * $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<ResponseInterface>
+ * @deprecated 2.9.0 See self::request() instead.
+ * @see self::request()
+ */
+ public function send(RequestInterface $request)
+ {
+ if ($this->baseUrl !== null) {
+ // ensure we're actually below the base URL
+ $request = $request->withUri($this->messageFactory->expandBase($request->getUri(), $this->baseUrl));
+ }
+
+ return $this->transaction->send($request);
+ }
+
+ /**
+ * Changes the maximum timeout used for waiting for pending requests.
+ *
+ * You can pass in the number of seconds to use as a new timeout value:
+ *
+ * ```php
+ * $browser = $browser->withTimeout(10.0);
+ * ```
+ *
+ * You can pass in a bool `false` to disable any timeouts. In this case,
+ * requests can stay pending forever:
+ *
+ * ```php
+ * $browser = $browser->withTimeout(false);
+ * ```
+ *
+ * You can pass in a bool `true` to re-enable default timeout handling. This
+ * will respects PHP's `default_socket_timeout` setting (default 60s):
+ *
+ * ```php
+ * $browser = $browser->withTimeout(true);
+ * ```
+ *
+ * See also [timeouts](#timeouts) for more details about timeout handling.
+ *
+ * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
+ * method actually returns a *new* [`Browser`](#browser) instance with the
+ * given timeout value applied.
+ *
+ * @param bool|number $timeout
+ * @return self
+ */
+ public function withTimeout($timeout)
+ {
+ if ($timeout === true) {
+ $timeout = null;
+ } elseif ($timeout === false) {
+ $timeout = -1;
+ } elseif ($timeout < 0) {
+ $timeout = 0;
+ }
+
+ return $this->withOptions(array(
+ 'timeout' => $timeout,
+ ));
+ }
+
+ /**
+ * Changes how HTTP redirects will be followed.
+ *
+ * You can pass in the maximum number of redirects to follow:
+ *
+ * ```php
+ * $new = $browser->withFollowRedirects(5);
+ * ```
+ *
+ * The request will automatically be rejected when the number of redirects
+ * is exceeded. You can pass in a `0` to reject the request for any
+ * redirects encountered:
+ *
+ * ```php
+ * $browser = $browser->withFollowRedirects(0);
+ *
+ * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * // only non-redirected responses will now end up here
+ * var_dump($response->getHeaders());
+ * });
+ * ```
+ *
+ * You can pass in a bool `false` to disable following any redirects. In
+ * this case, requests will resolve with the redirection response instead
+ * of following the `Location` response header:
+ *
+ * ```php
+ * $browser = $browser->withFollowRedirects(false);
+ *
+ * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * // any redirects will now end up here
+ * var_dump($response->getHeaderLine('Location'));
+ * });
+ * ```
+ *
+ * You can pass in a bool `true` to re-enable default redirect handling.
+ * This defaults to following a maximum of 10 redirects:
+ *
+ * ```php
+ * $browser = $browser->withFollowRedirects(true);
+ * ```
+ *
+ * See also [redirects](#redirects) for more details about redirect handling.
+ *
+ * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
+ * method actually returns a *new* [`Browser`](#browser) instance with the
+ * given redirect setting applied.
+ *
+ * @param bool|int $followRedirects
+ * @return self
+ */
+ public function withFollowRedirects($followRedirects)
+ {
+ return $this->withOptions(array(
+ 'followRedirects' => $followRedirects !== false,
+ 'maxRedirects' => \is_bool($followRedirects) ? null : $followRedirects
+ ));
+ }
+
+ /**
+ * Changes whether non-successful HTTP response status codes (4xx and 5xx) will be rejected.
+ *
+ * You can pass in a bool `false` to disable rejecting incoming responses
+ * that use a 4xx or 5xx response status code. In this case, requests will
+ * resolve with the response message indicating an error condition:
+ *
+ * ```php
+ * $browser = $browser->withRejectErrorResponse(false);
+ *
+ * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * // any HTTP response will now end up here
+ * var_dump($response->getStatusCode(), $response->getReasonPhrase());
+ * });
+ * ```
+ *
+ * You can pass in a bool `true` to re-enable default status code handling.
+ * This defaults to rejecting any response status codes in the 4xx or 5xx
+ * range:
+ *
+ * ```php
+ * $browser = $browser->withRejectErrorResponse(true);
+ *
+ * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * // any successful HTTP response will now end up here
+ * var_dump($response->getStatusCode(), $response->getReasonPhrase());
+ * }, function (Exception $e) {
+ * if ($e instanceof Clue\React\Buzz\Message\ResponseException) {
+ * // any HTTP response error message will now end up here
+ * $response = $e->getResponse();
+ * var_dump($response->getStatusCode(), $response->getReasonPhrase());
+ * } else {
+ * var_dump($e->getMessage());
+ * }
+ * });
+ * ```
+ *
+ * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
+ * method actually returns a *new* [`Browser`](#browser) instance with the
+ * given setting applied.
+ *
+ * @param bool $obeySuccessCode
+ * @return self
+ */
+ public function withRejectErrorResponse($obeySuccessCode)
+ {
+ return $this->withOptions(array(
+ 'obeySuccessCode' => $obeySuccessCode,
+ ));
+ }
+
+ /**
+ * Changes the base URL used to resolve relative URLs to.
+ *
+ * If you configure a base URL, any requests to relative URLs will be
+ * processed by first prepending this absolute base URL. Note that this
+ * merely prepends the base URL and does *not* resolve any relative path
+ * references (like `../` etc.). This is mostly useful for (RESTful) API
+ * calls where all endpoints (URLs) are located under a common base URL.
+ *
+ * ```php
+ * $browser = $browser->withBase('http://api.example.com/v3');
+ *
+ * // will request http://api.example.com/v3/example
+ * $browser->get('/example')->then(…);
+ * ```
+ *
+ * You can pass in a `null` base URL to return a new instance that does not
+ * use a base URL:
+ *
+ * ```php
+ * $browser = $browser->withBase(null);
+ * ```
+ *
+ * Accordingly, any requests using relative URLs to a browser that does not
+ * use a base URL can not be completed and will be rejected without sending
+ * a request.
+ *
+ * This method will throw an `InvalidArgumentException` if the given
+ * `$baseUrl` argument is not a valid URL.
+ *
+ * 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 URL applied.
+ *
+ * > For BC reasons, this method accepts the `$baseUrl` as either a `string`
+ * value or as an `UriInterface`. It's recommended to explicitly cast any
+ * objects implementing `UriInterface` to `string`.
+ *
+ * > Changelog: As of v2.9.0 this method accepts a `null` value to reset the
+ * base URL. Earlier versions had to use the deprecated `withoutBase()`
+ * method to reset the base URL.
+ *
+ * @param string|null|UriInterface $baseUrl absolute base URL
+ * @return self
+ * @throws InvalidArgumentException if the given $baseUrl is not a valid absolute URL
+ * @see self::withoutBase()
+ */
+ public function withBase($baseUrl)
+ {
+ $browser = clone $this;
+ if ($baseUrl === null) {
+ $browser->baseUrl = null;
+ return $browser;
+ }
+
+ $browser->baseUrl = $this->messageFactory->uri($baseUrl);
+ if (!\in_array($browser->baseUrl->getScheme(), array('http', 'https')) || $browser->baseUrl->getHost() === '') {
+ throw new \InvalidArgumentException('Base URL must be absolute');
+ }
+
+ return $browser;
+ }
+
+ /**
+ * Changes the HTTP protocol version that will be used for all subsequent requests.
+ *
+ * All the above [request methods](#request-methods) default to sending
+ * requests as HTTP/1.1. This is the preferred HTTP protocol version which
+ * also provides decent backwards-compatibility with legacy HTTP/1.0
+ * servers. As such, there should rarely be a need to explicitly change this
+ * protocol version.
+ *
+ * If you want to explicitly use the legacy HTTP/1.0 protocol version, you
+ * can use this method:
+ *
+ * ```php
+ * $newBrowser = $browser->withProtocolVersion('1.0');
+ *
+ * $newBrowser->get($url)->then(…);
+ * ```
+ *
+ * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
+ * method actually returns a *new* [`Browser`](#browser) instance with the
+ * new protocol version applied.
+ *
+ * @param string $protocolVersion HTTP protocol version to use, must be one of "1.1" or "1.0"
+ * @return self
+ * @throws InvalidArgumentException
+ * @since 2.8.0
+ */
+ public function withProtocolVersion($protocolVersion)
+ {
+ if (!\in_array($protocolVersion, array('1.0', '1.1'), true)) {
+ throw new InvalidArgumentException('Invalid HTTP protocol version, must be one of "1.1" or "1.0"');
+ }
+
+ $browser = clone $this;
+ $browser->protocolVersion = (string) $protocolVersion;
+
+ return $browser;
+ }
+
+ /**
+ * Changes the maximum size for buffering a response body.
+ *
+ * The preferred way to send an HTTP request is by using the above
+ * [request methods](#request-methods), for example the [`get()`](#get)
+ * method to send an HTTP `GET` request. Each of these methods will buffer
+ * the whole response body in memory by default. This is easy to get started
+ * and works reasonably well for smaller responses.
+ *
+ * By default, the response body buffer will be limited to 16 MiB. If the
+ * response body exceeds this maximum size, the request will be rejected.
+ *
+ * You can pass in the maximum number of bytes to buffer:
+ *
+ * ```php
+ * $browser = $browser->withResponseBuffer(1024 * 1024);
+ *
+ * $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
+ * // response body will not exceed 1 MiB
+ * var_dump($response->getHeaders(), (string) $response->getBody());
+ * });
+ * ```
+ *
+ * Note that the response body buffer has to be kept in memory for each
+ * pending request until its transfer is completed and it will only be freed
+ * after a pending request is fulfilled. As such, increasing this maximum
+ * buffer size to allow larger response bodies is usually not recommended.
+ * Instead, you can use the [`requestStreaming()` method](#requeststreaming)
+ * to receive responses with arbitrary sizes without buffering. Accordingly,
+ * this maximum buffer size setting has no effect on streaming responses.
+ *
+ * Notice that the [`Browser`](#browser) is an immutable object, i.e. this
+ * method actually returns a *new* [`Browser`](#browser) instance with the
+ * given setting applied.
+ *
+ * @param int $maximumSize
+ * @return self
+ * @see self::requestStreaming()
+ */
+ public function withResponseBuffer($maximumSize)
+ {
+ return $this->withOptions(array(
+ 'maximumSize' => $maximumSize
+ ));
+ }
+
+ /**
+ * [Deprecated] 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
+ * // deprecated
+ * $newBrowser = $browser->withOptions(array(
+ * 'timeout' => null, // see withTimeout() instead
+ * 'followRedirects' => true, // see withFollowRedirects() instead
+ * 'maxRedirects' => 10, // see withFollowRedirects() instead
+ * 'obeySuccessCode' => true, // see withRejectErrorResponse() instead
+ * 'streaming' => false, // deprecated, see requestStreaming() instead
+ * ));
+ * ```
+ *
+ * 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
+ * @deprecated 2.9.0 See self::withTimeout(), self::withFollowRedirects() and self::withRejectErrorResponse() instead.
+ * @see self::withTimeout()
+ * @see self::withFollowRedirects()
+ * @see self::withRejectErrorResponse()
+ */
+ public function withOptions(array $options)
+ {
+ $browser = clone $this;
+ $browser->transaction = $this->transaction->withOptions($options);
+
+ return $browser;
+ }
+
+ /**
+ * [Deprecated] Removes the base URL.
+ *
+ * ```php
+ * // deprecated: see withBase() instead
+ * $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 URL applied.
+ *
+ * See also [`withBase()`](#withbase).
+ *
+ * @return self
+ * @deprecated 2.9.0 See self::withBase() instead.
+ * @see self::withBase()
+ */
+ public function withoutBase()
+ {
+ return $this->withBase(null);
+ }
+
+ /**
+ * @param string $method
+ * @param string|UriInterface $url
+ * @param array $headers
+ * @param string|ReadableStreamInterface $contents
+ * @return PromiseInterface<ResponseInterface,Exception>
+ */
+ private function requestMayBeStreaming($method, $url, array $headers = array(), $contents = '')
+ {
+ return $this->send($this->messageFactory->request($method, $url, $headers, $contents, $this->protocolVersion));
+ }
+}
diff --git a/vendor/clue/buzz-react/src/Io/ChunkedEncoder.php b/vendor/clue/buzz-react/src/Io/ChunkedEncoder.php
new file mode 100644
index 0000000..3b74e0c
--- /dev/null
+++ b/vendor/clue/buzz-react/src/Io/ChunkedEncoder.php
@@ -0,0 +1,93 @@
+<?php
+
+namespace Clue\React\Buzz\Io;
+
+use Evenement\EventEmitter;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\Util;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * [Internal] Encodes given payload stream with "Transfer-Encoding: chunked" and emits encoded data
+ *
+ * This is used internally to encode outgoing requests with this encoding.
+ *
+ * @internal
+ * @link https://github.com/reactphp/http/blob/master/src/Io/ChunkedEncoder.php Originally from react/http
+ */
+class ChunkedEncoder extends EventEmitter implements ReadableStreamInterface
+{
+ private $input;
+ private $closed = false;
+
+ public function __construct(ReadableStreamInterface $input)
+ {
+ $this->input = $input;
+
+ $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())
+ {
+ return Util::pipe($this, $dest, $options);
+ }
+
+ public function close()
+ {
+ if ($this->closed) {
+ return;
+ }
+
+ $this->closed = true;
+ $this->input->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+
+ /** @internal */
+ public function handleData($data)
+ {
+ if ($data !== '') {
+ $this->emit('data', array(
+ dechex(strlen($data)) . "\r\n" . $data . "\r\n"
+ ));
+ }
+ }
+
+ /** @internal */
+ public function handleError(\Exception $e)
+ {
+ $this->emit('error', array($e));
+ $this->close();
+ }
+
+ /** @internal */
+ public function handleEnd()
+ {
+ $this->emit('data', array("0\r\n\r\n"));
+
+ if (!$this->closed) {
+ $this->emit('end');
+ $this->close();
+ }
+ }
+}
diff --git a/vendor/clue/buzz-react/src/Io/Sender.php b/vendor/clue/buzz-react/src/Io/Sender.php
new file mode 100644
index 0000000..06c1212
--- /dev/null
+++ b/vendor/clue/buzz-react/src/Io/Sender.php
@@ -0,0 +1,161 @@
+<?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, $request) {
+ // apply response header values from response stream
+ $deferred->resolve($messageFactory->response(
+ $responseStream->getVersion(),
+ $responseStream->getCode(),
+ $responseStream->getReasonPhrase(),
+ $responseStream->getHeaders(),
+ $responseStream,
+ $request->getMethod()
+ ));
+ });
+
+ if ($body instanceof ReadableStreamInterface) {
+ if ($body->isReadable()) {
+ // length unknown => apply chunked transfer-encoding
+ if ($size === null) {
+ $body = new ChunkedEncoder($body);
+ }
+
+ // pipe body into request stream
+ // add dummy write to immediately start request even if body does not emit any data yet
+ $body->pipe($requestStream);
+ $requestStream->write('');
+
+ $body->on('close', $close = function () use ($deferred, $requestStream) {
+ $deferred->reject(new \RuntimeException('Request failed because request body closed unexpectedly'));
+ $requestStream->close();
+ });
+ $body->on('error', function ($e) use ($deferred, $requestStream, $close, $body) {
+ $body->removeListener('close', $close);
+ $deferred->reject(new \RuntimeException('Request failed because request body reported an error', 0, $e));
+ $requestStream->close();
+ });
+ $body->on('end', function () use ($close, $body) {
+ $body->removeListener('close', $close);
+ });
+ } 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();
+ }
+}
diff --git a/vendor/clue/buzz-react/src/Io/Transaction.php b/vendor/clue/buzz-react/src/Io/Transaction.php
new file mode 100644
index 0000000..adb5796
--- /dev/null
+++ b/vendor/clue/buzz-react/src/Io/Transaction.php
@@ -0,0 +1,305 @@
+<?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\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;
+
+ private $maximumSize = 16777216; // 16 MiB = 2^24 bytes
+
+ 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;
+
+ // 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"));
+
+ $loop = $this->loop;
+ $this->next($request, $deferred)->then(
+ function (ResponseInterface $response) use ($deferred, $loop, &$timeout) {
+ if (isset($deferred->timeout)) {
+ $loop->cancelTimer($deferred->timeout);
+ unset($deferred->timeout);
+ }
+ $timeout = -1;
+ $deferred->resolve($response);
+ },
+ function ($e) use ($deferred, $loop, &$timeout) {
+ if (isset($deferred->timeout)) {
+ $loop->cancelTimer($deferred->timeout);
+ unset($deferred->timeout);
+ }
+ $timeout = -1;
+ $deferred->reject($e);
+ }
+ );
+
+ if ($timeout < 0) {
+ return $deferred->promise();
+ }
+
+ $body = $request->getBody();
+ if ($body instanceof ReadableStreamInterface && $body->isReadable()) {
+ $that = $this;
+ $body->on('close', function () use ($that, $deferred, &$timeout) {
+ if ($timeout >= 0) {
+ $that->applyTimeout($deferred, $timeout);
+ }
+ });
+ } else {
+ $this->applyTimeout($deferred, $timeout);
+ }
+
+ return $deferred->promise();
+ }
+
+ /**
+ * @internal
+ * @param Deferred $deferred
+ * @param number $timeout
+ * @return void
+ */
+ public function applyTimeout(Deferred $deferred, $timeout)
+ {
+ $deferred->timeout = $this->loop->addTimer($timeout, function () use ($timeout, $deferred) {
+ $deferred->reject(new \RuntimeException(
+ 'Request timed out after ' . $timeout . ' seconds'
+ ));
+ if (isset($deferred->pending)) {
+ $deferred->pending->cancel();
+ unset($deferred->pending);
+ }
+ });
+ }
+
+ 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();
+
+ $size = $stream->getSize();
+ if ($size !== null && $size > $this->maximumSize) {
+ $stream->close();
+ return \React\Promise\reject(new \OverflowException(
+ 'Response body size of ' . $size . ' bytes exceeds maximum of ' . $this->maximumSize . ' bytes',
+ \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 0
+ ));
+ }
+
+ // 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;
+ $maximumSize = $this->maximumSize;
+ $promise = \React\Promise\Stream\buffer($stream, $maximumSize)->then(
+ function ($body) use ($response, $messageFactory) {
+ return $response->withBody($messageFactory->body($body));
+ },
+ function ($e) use ($stream, $maximumSize) {
+ // try to close stream if buffering fails (or is cancelled)
+ $stream->close();
+
+ if ($e instanceof \OverflowException) {
+ $e = new \OverflowException(
+ 'Response body size exceeds maximum of ' . $maximumSize . ' bytes',
+ \defined('SOCKET_EMSGSIZE') ? \SOCKET_EMSGSIZE : 0
+ );
+ }
+
+ 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;
+ }
+}
diff --git a/vendor/clue/buzz-react/src/Message/MessageFactory.php b/vendor/clue/buzz-react/src/Message/MessageFactory.php
new file mode 100644
index 0000000..8a3dd6d
--- /dev/null
+++ b/vendor/clue/buzz-react/src/Message/MessageFactory.php
@@ -0,0 +1,139 @@
+<?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
+ * @param string $protocolVersion
+ * @return Request
+ */
+ public function request($method, $uri, $headers = array(), $content = '', $protocolVersion = '1.1')
+ {
+ return new Request($method, $uri, $headers, $this->body($content), $protocolVersion);
+ }
+
+ /**
+ * Creates a new instance of ResponseInterface for the given response parameters
+ *
+ * @param string $protocolVersion
+ * @param int $status
+ * @param string $reason
+ * @param array $headers
+ * @param ReadableStreamInterface|string $body
+ * @param ?string $requestMethod
+ * @return Response
+ * @uses self::body()
+ */
+ public function response($protocolVersion, $status, $reason, $headers = array(), $body = '', $requestMethod = null)
+ {
+ $response = new Response($status, $headers, $body instanceof ReadableStreamInterface ? null : $body, $protocolVersion, $reason);
+
+ if ($body instanceof ReadableStreamInterface) {
+ $length = null;
+ $code = $response->getStatusCode();
+ if ($requestMethod === 'HEAD' || ($code >= 100 && $code < 200) || $code == 204 || $code == 304) {
+ $length = 0;
+ } elseif (\strtolower($response->getHeaderLine('Transfer-Encoding')) === 'chunked') {
+ $length = null;
+ } elseif ($response->hasHeader('Content-Length')) {
+ $length = (int)$response->getHeaderLine('Content-Length');
+ }
+
+ $response = $response->withBody(new ReadableBodyStream($body, $length));
+ }
+
+ return $response;
+ }
+
+ /**
+ * 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 returned as-is.
+ *
+ * @param UriInterface $uri
+ * @param UriInterface $base
+ * @return UriInterface
+ */
+ public function expandBase(UriInterface $uri, UriInterface $base)
+ {
+ if ($uri->getScheme() !== '') {
+ 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);
+ }
+}
diff --git a/vendor/clue/buzz-react/src/Message/ReadableBodyStream.php b/vendor/clue/buzz-react/src/Message/ReadableBodyStream.php
new file mode 100644
index 0000000..eac27f7
--- /dev/null
+++ b/vendor/clue/buzz-react/src/Message/ReadableBodyStream.php
@@ -0,0 +1,153 @@
+<?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 $position = 0;
+ private $size;
+ private $closed = false;
+
+ public function __construct(ReadableStreamInterface $input, $size = null)
+ {
+ $this->input = $input;
+ $this->size = $size;
+
+ $that = $this;
+ $pos =& $this->position;
+ $input->on('data', function ($data) use ($that, &$pos, $size) {
+ $that->emit('data', array($data));
+
+ $pos += \strlen($data);
+ if ($size !== null && $pos >= $size) {
+ $that->handleEnd();
+ }
+ });
+ $input->on('error', function ($error) use ($that) {
+ $that->emit('error', array($error));
+ $that->close();
+ });
+ $input->on('end', array($that, 'handleEnd'));
+ $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 $this->size;
+ }
+
+ 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;
+ }
+
+ /** @internal */
+ public function handleEnd()
+ {
+ if ($this->position !== $this->size && $this->size !== null) {
+ $this->emit('error', array(new \UnderflowException('Unexpected end of response body after ' . $this->position . '/' . $this->size . ' bytes')));
+ } else {
+ $this->emit('end');
+ }
+
+ $this->close();
+ }
+}
diff --git a/vendor/clue/buzz-react/src/Message/ResponseException.php b/vendor/clue/buzz-react/src/Message/ResponseException.php
new file mode 100644
index 0000000..081103a
--- /dev/null
+++ b/vendor/clue/buzz-react/src/Message/ResponseException.php
@@ -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 [`withRejectErrorResponse()` method](#withrejecterrorresponse).
+ *
+ * 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;
+ }
+}
diff --git a/vendor/clue/connection-manager-extra/LICENSE b/vendor/clue/connection-manager-extra/LICENSE
new file mode 100644
index 0000000..da15612
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/LICENSE
@@ -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.
diff --git a/vendor/clue/connection-manager-extra/composer.json b/vendor/clue/connection-manager-extra/composer.json
new file mode 100644
index 0000000..7534f80
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/composer.json
@@ -0,0 +1,29 @@
+{
+ "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/reactphp-connection-manager-extra",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering"
+ }
+ ],
+ "autoload": {
+ "psr-4": { "ConnectionManager\\Extra\\": "src" }
+ },
+ "autoload-dev": {
+ "psr-4": { "ConnectionManager\\Tests\\Extra\\": "tests/" }
+ },
+ "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": "^9.3 || ^5.7 || ^4.8"
+ }
+}
diff --git a/vendor/clue/connection-manager-extra/src/ConnectionManagerDelay.php b/vendor/clue/connection-manager-extra/src/ConnectionManagerDelay.php
new file mode 100644
index 0000000..b5112c6
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/src/ConnectionManagerDelay.php
@@ -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);
+ });
+ }
+}
diff --git a/vendor/clue/connection-manager-extra/src/ConnectionManagerReject.php b/vendor/clue/connection-manager-extra/src/ConnectionManagerReject.php
new file mode 100644
index 0000000..1222c83
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/src/ConnectionManagerReject.php
@@ -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);
+ }
+}
diff --git a/vendor/clue/connection-manager-extra/src/ConnectionManagerRepeat.php b/vendor/clue/connection-manager-extra/src/ConnectionManagerRepeat.php
new file mode 100644
index 0000000..10f3f5a
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/src/ConnectionManagerRepeat.php
@@ -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();
+ }
+ });
+ }
+}
diff --git a/vendor/clue/connection-manager-extra/src/ConnectionManagerSwappable.php b/vendor/clue/connection-manager-extra/src/ConnectionManagerSwappable.php
new file mode 100644
index 0000000..d133225
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/src/ConnectionManagerSwappable.php
@@ -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;
+ }
+}
diff --git a/vendor/clue/connection-manager-extra/src/ConnectionManagerTimeout.php b/vendor/clue/connection-manager-extra/src/ConnectionManagerTimeout.php
new file mode 100644
index 0000000..5ec0872
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/src/ConnectionManagerTimeout.php
@@ -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;
+ });
+ }
+}
diff --git a/vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConcurrent.php b/vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConcurrent.php
new file mode 100644
index 0000000..c1eb9cf
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConcurrent.php
@@ -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;
+ });
+ }
+}
diff --git a/vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConsecutive.php b/vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConsecutive.php
new file mode 100644
index 0000000..1474b85
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerConsecutive.php
@@ -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();
+ }
+ });
+ }
+}
diff --git a/vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerRandom.php b/vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerRandom.php
new file mode 100644
index 0000000..88d1fd6
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerRandom.php
@@ -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);
+ }
+}
diff --git a/vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerSelective.php b/vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerSelective.php
new file mode 100644
index 0000000..859ea90
--- /dev/null
+++ b/vendor/clue/connection-manager-extra/src/Multiple/ConnectionManagerSelective.php
@@ -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;
+ }
+}
diff --git a/vendor/clue/http-proxy-react/LICENSE b/vendor/clue/http-proxy-react/LICENSE
new file mode 100644
index 0000000..7baae8e
--- /dev/null
+++ b/vendor/clue/http-proxy-react/LICENSE
@@ -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.
diff --git a/vendor/clue/http-proxy-react/composer.json b/vendor/clue/http-proxy-react/composer.json
new file mode 100644
index 0000000..9840cf0
--- /dev/null
+++ b/vendor/clue/http-proxy-react/composer.json
@@ -0,0 +1,31 @@
+{
+ "name": "clue/http-proxy-react",
+ "description": "Async HTTP proxy connector, tunnel 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@clue.engineering"
+ }
+ ],
+ "require": {
+ "php": ">=5.3",
+ "react/promise": " ^2.1 || ^1.2.1",
+ "react/socket": "^1.9",
+ "ringcentral/psr7": "^1.2"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.1",
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8",
+ "react/event-loop": "^1.2",
+ "react/http": "^1.5"
+ },
+ "autoload": {
+ "psr-4": { "Clue\\React\\HttpProxy\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "Clue\\Tests\\React\\HttpProxy\\": "tests/" }
+ }
+}
diff --git a/vendor/clue/http-proxy-react/src/ProxyConnector.php b/vendor/clue/http-proxy-react/src/ProxyConnector.php
new file mode 100644
index 0000000..0bca17e
--- /dev/null
+++ b/vendor/clue/http-proxy-react/src/ProxyConnector.php
@@ -0,0 +1,274 @@
+<?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\Connector;
+use React\Socket\ConnectorInterface;
+use React\Socket\FixedUriConnector;
+use React\Socket\UnixConnector;
+
+/**
+ * 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 (Optional) Connector to use.
+ * @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 = null, 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 ?: new UnixConnector()
+ );
+ }
+
+ 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 ?: new 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);
+
+ // Exception trace arguments are not available on some PHP 7.4 installs
+ // @codeCoverageIgnoreStart
+ foreach ($trace as &$one) {
+ if (isset($one['args'])) {
+ foreach ($one['args'] as &$arg) {
+ if ($arg instanceof \Closure) {
+ $arg = 'Object(' . get_class($arg) . ')';
+ }
+ }
+ }
+ }
+ // @codeCoverageIgnoreEnd
+ $r->setValue($e, $trace);
+ });
+
+ return $deferred->promise();
+ }
+}
diff --git a/vendor/clue/mq-react/LICENSE b/vendor/clue/mq-react/LICENSE
new file mode 100644
index 0000000..984ff9d
--- /dev/null
+++ b/vendor/clue/mq-react/LICENSE
@@ -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.
diff --git a/vendor/clue/mq-react/composer.json b/vendor/clue/mq-react/composer.json
new file mode 100644
index 0000000..d6e7793
--- /dev/null
+++ b/vendor/clue/mq-react/composer.json
@@ -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/reactphp-mq",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering"
+ }
+ ],
+ "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",
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35",
+ "react/event-loop": "^1.2",
+ "react/http": "^1.5"
+ }
+}
diff --git a/vendor/clue/mq-react/src/Queue.php b/vendor/clue/mq-react/src/Queue.php
new file mode 100644
index 0000000..7d399ac
--- /dev/null
+++ b/vendor/clue/mq-react/src/Queue.php
@@ -0,0 +1,448 @@
+<?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 queue 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
+ * $browser = new React\Http\Browser();
+ *
+ * $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 `InvalidArgumentException`
+ * 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();
+ }
+ }
+ });
+ }
+
+ /**
+ * Concurrently process the given jobs through the given `$handler` and
+ * resolve with first resolution value.
+ *
+ * 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 result of the first job on success and will then try
+ * to `cancel()` all outstanding jobs.
+ *
+ * ```php
+ * $browser = new React\Http\Browser();
+ *
+ * $promise = Queue::any(3, $urls, function ($url) use ($browser) {
+ * return $browser->get($url);
+ * });
+ *
+ * $promise->then(function (ResponseInterface $response) {
+ * echo 'First response: ' . $response->getBody() . PHP_EOL;
+ * });
+ * ```
+ *
+ * If all of the jobs fail, it will reject the resulting promise. 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::any(10, $jobs, $handler);
+ * ```
+ *
+ * ```php
+ * // handle each job after another without concurrency (waterfall)
+ * $promise = Queue::any(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 have no effect, the promise will simply resolve with the
+ * job results of the first successful job as returned by the `$handler`.
+ * If this array is empty, this method will reject 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::any(10, $jobs, function ($url) use ($browser) {
+ * return $browser->get($url);
+ * });
+ * ```
+ *
+ * ```php
+ * // accepts any callable, so PHP's array notation is also supported
+ * $promise = Queue::any(10, $jobs, array($browser, 'get'));
+ * ```
+ *
+ * @param int $concurrency concurrency soft limit
+ * @param array $jobs
+ * @param callable $handler
+ * @return PromiseInterface Returns a Promise<mixed> which resolves with a single resolution value
+ * or rejects when all of the operations reject.
+ */
+ public static function any($concurrency, array $jobs, $handler)
+ {
+ // explicitly reject with empty jobs (https://github.com/reactphp/promise/pull/34)
+ if (!$jobs) {
+ return Promise\reject(new \UnderflowException('No jobs given'));
+ }
+
+ 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\any($promises)->then(function ($result) use ($promises, $resolve) {
+ // cancel all pending promises if a single result is ready
+ foreach (array_reverse($promises) as $promise) {
+ if ($promise instanceof CancellablePromiseInterface) {
+ $promise->cancel();
+ }
+ }
+
+ // resolve with original resolution value
+ $resolve($result);
+ }, $reject);
+ }, 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 concurrency 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();
+ }
+
+ #[\ReturnTypeWillChange]
+ 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);
+ }
+ );
+ }
+}
diff --git a/vendor/clue/redis-protocol/composer.json b/vendor/clue/redis-protocol/composer.json
new file mode 100644
index 0000000..d99e2ee
--- /dev/null
+++ b/vendor/clue/redis-protocol/composer.json
@@ -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" }
+ }
+}
diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Factory.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Factory.php
new file mode 100644
index 0000000..3997f04
--- /dev/null
+++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Factory.php
@@ -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();
+ }
+}
diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/BulkReply.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/BulkReply.php
new file mode 100644
index 0000000..e069fda
--- /dev/null
+++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/BulkReply.php
@@ -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);
+ }
+}
diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ErrorReply.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ErrorReply.php
new file mode 100644
index 0000000..556e93b
--- /dev/null
+++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ErrorReply.php
@@ -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());
+ }
+}
diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/IntegerReply.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/IntegerReply.php
new file mode 100644
index 0000000..ba1ff05
--- /dev/null
+++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/IntegerReply.php
@@ -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);
+ }
+}
diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ModelInterface.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ModelInterface.php
new file mode 100644
index 0000000..b97939e
--- /dev/null
+++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/ModelInterface.php
@@ -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);
+}
diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/MultiBulkReply.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/MultiBulkReply.php
new file mode 100644
index 0000000..7198dc6
--- /dev/null
+++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/MultiBulkReply.php
@@ -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);
+ }
+}
diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/Request.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/Request.php
new file mode 100644
index 0000000..f5881e9
--- /dev/null
+++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/Request.php
@@ -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);
+ }
+}
diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/StatusReply.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/StatusReply.php
new file mode 100644
index 0000000..4ea2fcd
--- /dev/null
+++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Model/StatusReply.php
@@ -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);
+ }
+}
diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/MessageBuffer.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/MessageBuffer.php
new file mode 100644
index 0000000..c1e3001
--- /dev/null
+++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/MessageBuffer.php
@@ -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;
+ }
+}
diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserException.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserException.php
new file mode 100644
index 0000000..e57c5bc
--- /dev/null
+++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserException.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Clue\Redis\Protocol\Parser;
+
+use UnexpectedValueException;
+
+class ParserException extends UnexpectedValueException
+{
+
+}
diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserInterface.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserInterface.php
new file mode 100644
index 0000000..a322719
--- /dev/null
+++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ParserInterface.php
@@ -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);
+}
diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/RequestParser.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/RequestParser.php
new file mode 100644
index 0000000..a47d137
--- /dev/null
+++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/RequestParser.php
@@ -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;
+ }
+}
diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ResponseParser.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ResponseParser.php
new file mode 100644
index 0000000..19ac90c
--- /dev/null
+++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Parser/ResponseParser.php
@@ -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;
+ }
+}
diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/RecursiveSerializer.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/RecursiveSerializer.php
new file mode 100644
index 0000000..6e25125
--- /dev/null
+++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/RecursiveSerializer.php
@@ -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;
+ }
+}
diff --git a/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/SerializerInterface.php b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/SerializerInterface.php
new file mode 100644
index 0000000..bb7cb3e
--- /dev/null
+++ b/vendor/clue/redis-protocol/src/Clue/Redis/Protocol/Serializer/SerializerInterface.php
@@ -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);
+}
diff --git a/vendor/clue/redis-react/LICENSE b/vendor/clue/redis-react/LICENSE
new file mode 100644
index 0000000..da15612
--- /dev/null
+++ b/vendor/clue/redis-react/LICENSE
@@ -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.
diff --git a/vendor/clue/redis-react/composer.json b/vendor/clue/redis-react/composer.json
new file mode 100644
index 0000000..c1752cc
--- /dev/null
+++ b/vendor/clue/redis-react/composer.json
@@ -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@clue.engineering"
+ }
+ ],
+ "require": {
+ "php": ">=5.3",
+ "clue/redis-protocol": "0.3.*",
+ "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
+ "react/event-loop": "^1.2",
+ "react/promise": "^2.0 || ^1.1",
+ "react/promise-timer": "^1.8",
+ "react/socket": "^1.9"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.1",
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35"
+ },
+ "autoload": {
+ "psr-4": { "Clue\\React\\Redis\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "Clue\\Tests\\React\\Redis\\": "tests/" }
+ }
+}
diff --git a/vendor/clue/redis-react/src/Client.php b/vendor/clue/redis-react/src/Client.php
new file mode 100644
index 0000000..ec54229
--- /dev/null
+++ b/vendor/clue/redis-react/src/Client.php
@@ -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 fulfilled 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();
+}
diff --git a/vendor/clue/redis-react/src/Factory.php b/vendor/clue/redis-react/src/Factory.php
new file mode 100644
index 0000000..4e94905
--- /dev/null
+++ b/vendor/clue/redis-react/src/Factory.php
@@ -0,0 +1,191 @@
+<?php
+
+namespace Clue\React\Redis;
+
+use Clue\Redis\Protocol\Factory as ProtocolFactory;
+use React\EventLoop\Loop;
+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;
+
+class Factory
+{
+ /** @var LoopInterface */
+ private $loop;
+
+ /** @var ConnectorInterface */
+ private $connector;
+
+ /** @var ProtocolFactory */
+ private $protocol;
+
+ /**
+ * @param ?LoopInterface $loop
+ * @param ?ConnectorInterface $connector
+ * @param ?ProtocolFactory $protocol
+ */
+ public function __construct(LoopInterface $loop = null, ConnectorInterface $connector = null, ProtocolFactory $protocol = null)
+ {
+ $this->loop = $loop ?: Loop::get();
+ $this->connector = $connector ?: new Connector(array(), $this->loop);
+ $this->protocol = $protocol ?: new ProtocolFactory();
+ }
+
+ /**
+ * Create Redis client connected to address of given redis instance
+ *
+ * @param string $uri Redis server URI to connect to
+ * @return \React\Promise\PromiseInterface<Client,\Exception> Promise that will
+ * be fulfilled with `Client` on success or rejects with `\Exception` on error.
+ */
+ public function createClient($uri)
+ {
+ // support `redis+unix://` scheme for Unix domain socket (UDS) paths
+ if (preg_match('/^(redis\+unix:\/\/(?:[^:]*:[^@]*@)?)(.+?)?$/', $uri, $match)) {
+ $parts = parse_url($match[1] . 'localhost/' . $match[2]);
+ } else {
+ if (strpos($uri, '://') === false) {
+ $uri = 'redis://' . $uri;
+ }
+
+ $parts = parse_url($uri);
+ }
+
+ $uri = preg_replace(array('/(:)[^:\/]*(@)/', '/([?&]password=).*?($|&)/'), '$1***$2', $uri);
+ if ($parts === false || !isset($parts['scheme'], $parts['host']) || !in_array($parts['scheme'], array('redis', 'rediss', 'redis+unix'))) {
+ return \React\Promise\reject(new \InvalidArgumentException(
+ 'Invalid Redis URI given (EINVAL)',
+ defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
+ ));
+ }
+
+ $args = array();
+ parse_str(isset($parts['query']) ? $parts['query'] : '', $args);
+
+ $authority = $parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 6379);
+ if ($parts['scheme'] === 'rediss') {
+ $authority = 'tls://' . $authority;
+ } elseif ($parts['scheme'] === 'redis+unix') {
+ $authority = 'unix://' . substr($parts['path'], 1);
+ unset($parts['path']);
+ }
+ $connecting = $this->connector->connect($authority);
+
+ $deferred = new Deferred(function ($_, $reject) use ($connecting, $uri) {
+ // connection cancelled, start with rejecting attempt, then clean up
+ $reject(new \RuntimeException(
+ 'Connection to ' . $uri . ' cancelled (ECONNABORTED)',
+ defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103
+ ));
+
+ // 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) use ($uri) {
+ throw new \RuntimeException(
+ 'Connection to ' . $uri . ' failed: ' . $e->getMessage(),
+ $e->getCode(),
+ $e
+ );
+ });
+
+ // use `?password=secret` query or `user:secret@host` password form URL
+ $pass = isset($args['password']) ? $args['password'] : (isset($parts['pass']) ? rawurldecode($parts['pass']) : null);
+ if (isset($args['password']) || isset($parts['pass'])) {
+ $pass = isset($args['password']) ? $args['password'] : rawurldecode($parts['pass']);
+ $promise = $promise->then(function (StreamingClient $redis) use ($pass, $uri) {
+ return $redis->auth($pass)->then(
+ function () use ($redis) {
+ return $redis;
+ },
+ function (\Exception $e) use ($redis, $uri) {
+ $redis->close();
+
+ $const = '';
+ $errno = $e->getCode();
+ if ($errno === 0) {
+ $const = ' (EACCES)';
+ $errno = $e->getCode() ?: (defined('SOCKET_EACCES') ? SOCKET_EACCES : 13);
+ }
+
+ throw new \RuntimeException(
+ 'Connection to ' . $uri . ' failed during AUTH command: ' . $e->getMessage() . $const,
+ $errno,
+ $e
+ );
+ }
+ );
+ });
+ }
+
+ // use `?db=1` query or `/1` path (skip first slash)
+ if (isset($args['db']) || (isset($parts['path']) && $parts['path'] !== '/')) {
+ $db = isset($args['db']) ? $args['db'] : substr($parts['path'], 1);
+ $promise = $promise->then(function (StreamingClient $redis) use ($db, $uri) {
+ return $redis->select($db)->then(
+ function () use ($redis) {
+ return $redis;
+ },
+ function (\Exception $e) use ($redis, $uri) {
+ $redis->close();
+
+ $const = '';
+ $errno = $e->getCode();
+ if ($errno === 0 && strpos($e->getMessage(), 'NOAUTH ') === 0) {
+ $const = ' (EACCES)';
+ $errno = defined('SOCKET_EACCES') ? SOCKET_EACCES : 13;
+ } elseif ($errno === 0) {
+ $const = ' (ENOENT)';
+ $errno = defined('SOCKET_ENOENT') ? SOCKET_ENOENT : 2;
+ }
+
+ throw new \RuntimeException(
+ 'Connection to ' . $uri . ' failed during SELECT command: ' . $e->getMessage() . $const,
+ $errno,
+ $e
+ );
+ }
+ );
+ });
+ }
+
+ $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 = isset($args['timeout']) ? (float) $args['timeout'] : (int) 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) use ($uri) {
+ if ($e instanceof TimeoutException) {
+ throw new \RuntimeException(
+ 'Connection to ' . $uri . ' timed out after ' . $e->getTimeout() . ' seconds (ETIMEDOUT)',
+ defined('SOCKET_ETIMEDOUT') ? SOCKET_ETIMEDOUT : 110
+ );
+ }
+ 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);
+ }
+}
diff --git a/vendor/clue/redis-react/src/LazyClient.php b/vendor/clue/redis-react/src/LazyClient.php
new file mode 100644
index 0000000..d82b257
--- /dev/null
+++ b/vendor/clue/redis-react/src/LazyClient.php
@@ -0,0 +1,219 @@
+<?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((string) \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 $redis) use ($self, &$pending, &$idleTimer, &$subscribed, &$psubscribed, $loop) {
+ // connection completed => remember only until closed
+ $redis->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
+ $redis->on('subscribe', function ($channel) use (&$subscribed) {
+ $subscribed[$channel] = true;
+ });
+ $redis->on('psubscribe', function ($pattern) use (&$psubscribed) {
+ $psubscribed[$pattern] = true;
+ });
+ $redis->on('unsubscribe', function ($channel) use (&$subscribed) {
+ unset($subscribed[$channel]);
+ });
+ $redis->on('punsubscribe', function ($pattern) use (&$psubscribed) {
+ unset($psubscribed[$pattern]);
+ });
+
+ Util::forwardEvents(
+ $redis,
+ $self,
+ array(
+ 'message',
+ 'subscribe',
+ 'unsubscribe',
+ 'pmessage',
+ 'psubscribe',
+ 'punsubscribe',
+ )
+ );
+
+ return $redis;
+ }, 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 (ENOTCONN)',
+ defined('SOCKET_ENOTCONN') ? SOCKET_ENOTCONN : 107
+ ));
+ }
+
+ $that = $this;
+ return $this->client()->then(function (Client $redis) use ($name, $args, $that) {
+ $that->awake();
+ return \call_user_func_array(array($redis, $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 $redis) use ($that) {
+ $redis->on('close', function () use ($that) {
+ $that->close();
+ });
+ $redis->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 $redis) {
+ $redis->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 && $this->promise !== null) {
+ $idleTimer =& $this->idleTimer;
+ $promise =& $this->promise;
+ $idleTimer = $this->loop->addTimer($this->idlePeriod, function () use (&$idleTimer, &$promise) {
+ $promise->then(function (Client $redis) {
+ $redis->close();
+ });
+ $promise = null;
+ $idleTimer = null;
+ });
+ }
+ }
+}
diff --git a/vendor/clue/redis-react/src/StreamingClient.php b/vendor/clue/redis-react/src/StreamingClient.php
new file mode 100644
index 0000000..8afd84d
--- /dev/null
+++ b/vendor/clue/redis-react/src/StreamingClient.php
@@ -0,0 +1,203 @@
+<?php
+
+namespace Clue\React\Redis;
+
+use Clue\Redis\Protocol\Factory as ProtocolFactory;
+use Clue\Redis\Protocol\Model\ErrorReply;
+use Clue\Redis\Protocol\Model\ModelInterface;
+use Clue\Redis\Protocol\Model\MultiBulkReply;
+use Clue\Redis\Protocol\Parser\ParserException;
+use Clue\Redis\Protocol\Parser\ParserInterface;
+use Clue\Redis\Protocol\Serializer\SerializerInterface;
+use Evenement\EventEmitter;
+use React\Promise\Deferred;
+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(new \UnexpectedValueException(
+ 'Invalid data received: ' . $error->getMessage() . ' (EBADMSG)',
+ defined('SOCKET_EBADMSG') ? SOCKET_EBADMSG : 77,
+ $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 ' . ($this->closed ? 'closed' : 'closing'). ' (ENOTCONN)',
+ defined('SOCKET_ENOTCONN') ? SOCKET_ENOTCONN : 107
+ ));
+ } elseif (count($args) !== 1 && in_array($name, $pubsubs)) {
+ $request->reject(new \InvalidArgumentException(
+ 'PubSub commands limited to single argument (EINVAL)',
+ defined('SOCKET_EINVAL') ? SOCKET_EINVAL : 22
+ ));
+ } elseif ($name === 'monitor') {
+ $request->reject(new \BadMethodCallException(
+ 'MONITOR command explicitly not supported (ENOTSUP)',
+ defined('SOCKET_ENOTSUP') ? SOCKET_ENOTSUP : (defined('SOCKET_EOPNOTSUPP') ? SOCKET_EOPNOTSUPP : 95)
+ ));
+ } 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 (ENOMSG)',
+ defined('SOCKET_ENOMSG') ? SOCKET_ENOMSG : 42
+ );
+ }
+
+ $request = array_shift($this->requests);
+ assert($request instanceof 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;
+
+ $remoteClosed = $this->stream->isReadable() === false && $this->stream->isWritable() === false;
+ $this->stream->close();
+
+ $this->emit('close');
+
+ // reject all remaining requests in the queue
+ while ($this->requests) {
+ $request = array_shift($this->requests);
+ assert($request instanceof Deferred);
+
+ if ($remoteClosed) {
+ $request->reject(new \RuntimeException(
+ 'Connection closed by peer (ECONNRESET)',
+ defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104
+ ));
+ } else {
+ $request->reject(new \RuntimeException(
+ 'Connection closing (ECONNABORTED)',
+ defined('SOCKET_ECONNABORTED') ? SOCKET_ECONNABORTED : 103
+ ));
+ }
+ }
+ }
+}
diff --git a/vendor/clue/soap-react/LICENSE b/vendor/clue/soap-react/LICENSE
new file mode 100644
index 0000000..9426ad3
--- /dev/null
+++ b/vendor/clue/soap-react/LICENSE
@@ -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.
diff --git a/vendor/clue/soap-react/composer.json b/vendor/clue/soap-react/composer.json
new file mode 100644
index 0000000..3b7fc4a
--- /dev/null
+++ b/vendor/clue/soap-react/composer.json
@@ -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"
+ }
+}
diff --git a/vendor/clue/soap-react/src/Client.php b/vendor/clue/soap-react/src/Client.php
new file mode 100644
index 0000000..85ee7af
--- /dev/null
+++ b/vendor/clue/soap-react/src/Client.php
@@ -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;
+ }
+}
diff --git a/vendor/clue/soap-react/src/Protocol/ClientDecoder.php b/vendor/clue/soap-react/src/Protocol/ClientDecoder.php
new file mode 100644
index 0000000..39cb745
--- /dev/null
+++ b/vendor/clue/soap-react/src/Protocol/ClientDecoder.php
@@ -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;
+ }
+}
diff --git a/vendor/clue/soap-react/src/Protocol/ClientEncoder.php b/vendor/clue/soap-react/src/Protocol/ClientEncoder.php
new file mode 100644
index 0000000..e9d0018
--- /dev/null
+++ b/vendor/clue/soap-react/src/Protocol/ClientEncoder.php
@@ -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 '';
+ }
+}
diff --git a/vendor/clue/soap-react/src/Proxy.php b/vendor/clue/soap-react/src/Proxy.php
new file mode 100644
index 0000000..06d70a0
--- /dev/null
+++ b/vendor/clue/soap-react/src/Proxy.php
@@ -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);
+ }
+}
diff --git a/vendor/clue/socket-raw/LICENSE b/vendor/clue/socket-raw/LICENSE
new file mode 100644
index 0000000..da15612
--- /dev/null
+++ b/vendor/clue/socket-raw/LICENSE
@@ -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.
diff --git a/vendor/clue/socket-raw/composer.json b/vendor/clue/socket-raw/composer.json
new file mode 100644
index 0000000..2add27e
--- /dev/null
+++ b/vendor/clue/socket-raw/composer.json
@@ -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/socket-raw",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering"
+ }
+ ],
+ "autoload": {
+ "psr-4": {"Socket\\Raw\\": "src"}
+ },
+ "require": {
+ "ext-sockets": "*",
+ "php": ">=5.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35"
+ }
+}
diff --git a/vendor/clue/socket-raw/src/Exception.php b/vendor/clue/socket-raw/src/Exception.php
new file mode 100644
index 0000000..bdabf78
--- /dev/null
+++ b/vendor/clue/socket-raw/src/Exception.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Socket\Raw;
+
+use RuntimeException;
+
+class Exception extends RuntimeException
+{
+ /**
+ * Create an Exception after a socket operation on the given $resource failed
+ *
+ * @param \Socket|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')
+ {
+ if (PHP_VERSION_ID >= 80000) {
+ try {
+ $code = socket_last_error($resource);
+ } catch (\Error $e) {
+ $code = SOCKET_ENOTSOCK;
+ }
+ } elseif (is_resource($resource)) {
+ $code = socket_last_error($resource);
+ socket_clear_error($resource);
+ } else {
+ // socket already closed, return fixed error code instead of operating on invalid handle
+ $code = SOCKET_ENOTSOCK;
+ }
+
+ 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;
+ }
+}
diff --git a/vendor/clue/socket-raw/src/Factory.php b/vendor/clue/socket-raw/src/Factory.php
new file mode 100644
index 0000000..a5068f9
--- /dev/null
+++ b/vendor/clue/socket-raw/src/Factory.php
@@ -0,0 +1,282 @@
+<?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
+ * @throws \Error PHP 8 only: throws \Error when arguments are invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when arguments are invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when arguments are invalid
+ * @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|null $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;
+ }
+}
diff --git a/vendor/clue/socket-raw/src/Socket.php b/vendor/clue/socket-raw/src/Socket.php
new file mode 100644
index 0000000..67407f2
--- /dev/null
+++ b/vendor/clue/socket-raw/src/Socket.php
@@ -0,0 +1,562 @@
+<?php
+
+namespace Socket\Raw;
+
+/**
+ * Simple and lightweight OOP wrapper for the low-level sockets extension (ext-sockets)
+ *
+ * @author clue
+ * @link https://github.com/clue/php-socket-raw
+ */
+class Socket
+{
+ /**
+ * reference to actual socket resource
+ *
+ * @var \Socket|resource
+ */
+ private $resource;
+
+ /**
+ * instanciate socket wrapper for given socket resource
+ *
+ * should usually not be called manually, see Factory
+ *
+ * @param \Socket|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 \Socket|resource returns the socket resource (a `Socket` object as of PHP 8)
+ */
+ 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
+ * @throws \Error PHP 8 only: throws \Error when socket is invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @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)
+ * @throws \Error PHP 8 only: throws \Error when socket is invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @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: socket becomes writable on success or error
+ // Windows requires special care because it uses exceptfds for socket errors: https://github.com/reactphp/event-loop/issues/206
+ $r = null;
+ $w = array($this->resource);
+ $e = DIRECTORY_SEPARATOR === '\\' ? $w : null;
+ $ret = @socket_select($r, $w, $e, $timeout === null ? null : (int) $timeout, (int) (($timeout - floor($timeout)) * 1000000));
+
+ if ($ret === false) {
+ throw Exception::createFromGlobalSocketOperation('Failed to select socket for writing');
+ } elseif ($ret === 0) {
+ 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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket is invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket is invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @uses socket_select()
+ */
+ public function selectRead($sec = 0)
+ {
+ $usec = $sec === null ? 0 : (int) (($sec - floor($sec)) * 1000000);
+ $r = array($this->resource);
+ $n = null;
+ $ret = @socket_select($r, $n, $n, $sec === null ? null : (int) $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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @uses socket_select()
+ */
+ public function selectWrite($sec = 0)
+ {
+ $usec = $sec === null ? 0 : (int) (($sec - floor($sec)) * 1000000);
+ $w = array($this->resource);
+ $n = null;
+ $ret = @socket_select($n, $w, $n, $sec === null ? null : (int) $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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket or arguments are invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket is invalid
+ * @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
+ * @throws \Error PHP 8 only: throws \Error when socket is invalid
+ * @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;
+ }
+}
diff --git a/vendor/clue/socks-react/LICENSE b/vendor/clue/socks-react/LICENSE
new file mode 100644
index 0000000..8efa9aa
--- /dev/null
+++ b/vendor/clue/socks-react/LICENSE
@@ -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.
diff --git a/vendor/clue/socks-react/composer.json b/vendor/clue/socks-react/composer.json
new file mode 100644
index 0000000..c57742b
--- /dev/null
+++ b/vendor/clue/socks-react/composer.json
@@ -0,0 +1,31 @@
+{
+ "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@clue.engineering"
+ }
+ ],
+ "require": {
+ "php": ">=5.3",
+ "react/promise": "^2.1 || ^1.2",
+ "react/socket": "^1.9"
+ },
+ "require-dev": {
+ "clue/block-react": "^1.1",
+ "clue/connection-manager-extra": "^1.0 || ^0.7",
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35",
+ "react/event-loop": "^1.2",
+ "react/http": "^1.5"
+ },
+ "autoload": {
+ "psr-4": { "Clue\\React\\Socks\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "Clue\\Tests\\React\\Socks\\": "tests/" }
+ }
+}
diff --git a/vendor/clue/socks-react/src/Client.php b/vendor/clue/socks-react/src/Client.php
new file mode 100644
index 0000000..5350b54
--- /dev/null
+++ b/vendor/clue/socks-react/src/Client.php
@@ -0,0 +1,452 @@
+<?php
+
+namespace Clue\React\Socks;
+
+use React\Promise;
+use React\Promise\PromiseInterface;
+use React\Promise\Deferred;
+use React\Socket\ConnectionInterface;
+use React\Socket\Connector;
+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;
+
+ /**
+ * @param string $socksUri
+ * @param ?ConnectorInterface $connector
+ * @throws InvalidArgumentException
+ */
+ public function __construct($socksUri, ConnectorInterface $connector = null)
+ {
+ // 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 ?: new 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 ?: new 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);
+
+ // Exception trace arguments are not available on some PHP 7.4 installs
+ // @codeCoverageIgnoreStart
+ foreach ($trace as &$one) {
+ if (isset($one['args'])) {
+ foreach ($one['args'] as &$arg) {
+ if ($arg instanceof \Closure) {
+ $arg = 'Object(' . \get_class($arg) . ')';
+ }
+ }
+ }
+ }
+ // @codeCoverageIgnoreEnd
+ $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');
+ }
+ });
+ }
+}
diff --git a/vendor/clue/socks-react/src/Server.php b/vendor/clue/socks-react/src/Server.php
new file mode 100644
index 0000000..2405f3e
--- /dev/null
+++ b/vendor/clue/socks-react/src/Server.php
@@ -0,0 +1,399 @@
+<?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\Loop;
+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;
+
+ /** @var LoopInterface */
+ private $loop;
+
+ /** @var ConnectorInterface */
+ private $connector;
+
+ /**
+ * @var null|callable
+ */
+ private $auth;
+
+ /**
+ *
+ * This class takes an optional `LoopInterface|null $loop` parameter that can be used to
+ * pass the event loop instance to use for this object. You can use a `null` value
+ * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
+ * This value SHOULD NOT be given unless you're sure you want to explicitly use a
+ * given event loop instance.
+ *
+ * @param ?LoopInterface $loop
+ * @param ?ConnectorInterface $connector
+ * @param null|array|callable $auth
+ */
+ public function __construct(LoopInterface $loop = null, ConnectorInterface $connector = null, $auth = null)
+ {
+ 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 ?: Loop::get();
+ $this->connector = $connector ?: new Connector(array(), $this->loop);
+ }
+
+ /**
+ * @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);
+ });
+ }
+}
diff --git a/vendor/clue/socks-react/src/StreamReader.php b/vendor/clue/socks-react/src/StreamReader.php
new file mode 100644
index 0000000..f01d252
--- /dev/null
+++ b/vendor/clue/socks-react/src/StreamReader.php
@@ -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;
+ }
+}
diff --git a/vendor/clue/stdio-react/LICENSE b/vendor/clue/stdio-react/LICENSE
new file mode 100644
index 0000000..da15612
--- /dev/null
+++ b/vendor/clue/stdio-react/LICENSE
@@ -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.
diff --git a/vendor/clue/stdio-react/composer.json b/vendor/clue/stdio-react/composer.json
new file mode 100644
index 0000000..0e86dcb
--- /dev/null
+++ b/vendor/clue/stdio-react/composer.json
@@ -0,0 +1,37 @@
+{
+ "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@clue.engineering"
+ }
+ ],
+ "require": {
+ "php": ">=5.3",
+ "clue/term-react": "^1.0 || ^0.1.1",
+ "clue/utf8-react": "^1.0 || ^0.1",
+ "react/event-loop": "^1.2",
+ "react/stream": "^1.2"
+ },
+ "require-dev": {
+ "clue/arguments": "^2.0",
+ "clue/commander": "^1.2",
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35"
+ },
+ "suggest": {
+ "ext-mbstring": "Using ext-mbstring should provide slightly better performance for handling I/O"
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": { "Clue\\React\\Stdio\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "Clue\\Tests\\React\\Stdio\\": "tests/" }
+ }
+}
diff --git a/vendor/clue/stdio-react/src/Readline.php b/vendor/clue/stdio-react/src/Readline.php
new file mode 100644
index 0000000..b75650e
--- /dev/null
+++ b/vendor/clue/stdio-react/src/Readline.php
@@ -0,0 +1,1017 @@
+<?php
+
+namespace Clue\React\Stdio;
+
+use Clue\React\Term\ControlCodeParser;
+use Clue\React\Utf8\Sequencer as Utf8Sequencer;
+use Evenement\EventEmitter;
+use Evenement\EventEmitterInterface;
+use React\Stream\ReadableStreamInterface;
+use React\Stream\Util;
+use React\Stream\WritableStreamInterface;
+
+/**
+ * @deprecated 2.3.0 Use `Stdio` instead
+ * @see Stdio
+ */
+class Readline extends EventEmitter implements ReadableStreamInterface
+{
+ private $prompt = '';
+ private $linebuffer = '';
+ private $linepos = 0;
+ private $echo = true;
+ private $move = true;
+ private $bell = true;
+ private $encoding = 'utf-8';
+
+ private $input;
+ private $output;
+ private $sequencer;
+ private $closed = false;
+
+ private $historyLines = array();
+ private $historyPosition = null;
+ private $historyUnsaved = null;
+ private $historyLimit = 500;
+
+ private $autocomplete = null;
+ private $autocompleteSuggestions = 8;
+
+ public function __construct(ReadableStreamInterface $input, WritableStreamInterface $output, EventEmitterInterface $base = null)
+ {
+ $this->input = $input;
+ $this->output = $output;
+
+ if (!$this->input->isReadable()) {
+ $this->close();
+ return;
+ }
+ // push input through control code parser
+ $parser = new ControlCodeParser($input);
+
+ $that = $this;
+ $codes = array(
+ "\n" => 'onKeyEnter', // ^J
+ "\x7f" => 'onKeyBackspace', // ^?
+ "\t" => 'onKeyTab', // ^I
+ "\x04" => 'handleEnd', // ^D
+
+ "\033[A" => 'onKeyUp',
+ "\033[B" => 'onKeyDown',
+ "\033[C" => 'onKeyRight',
+ "\033[D" => 'onKeyLeft',
+
+ "\033[1~" => 'onKeyHome',
+// "\033[2~" => 'onKeyInsert',
+ "\033[3~" => 'onKeyDelete',
+ "\033[4~" => 'onKeyEnd',
+
+// "\033[20~" => 'onKeyF10',
+ );
+ $decode = function ($code) use ($codes, $that, $base) {
+ // The user confirms input with enter key which should usually
+ // generate a NL (`\n`) character. Common terminals also seem to
+ // accept a CR (`\r`) character in place and handle this just like a
+ // NL. Similarly `ext-readline` uses different `icrnl` and `igncr`
+ // TTY settings on some platforms, so we also accept CR as an alias
+ // for NL here. This implies key binding for NL will also trigger.
+ if ($code === "\r") {
+ $code = "\n";
+ }
+
+ // forward compatibility: check if any key binding exists on base Stdio instance
+ if ($base !== null && $base->listeners($code)) {
+ $base->emit($code, array($code));
+ return;
+ }
+
+ // deprecated: check if any key binding exists on this Readline instance
+ if ($that->listeners($code)) {
+ $that->emit($code, array($code));
+ return;
+ }
+
+ if (isset($codes[$code])) {
+ $method = $codes[$code];
+ $that->$method($code);
+ return;
+ }
+ };
+
+ $parser->on('csi', $decode);
+ $parser->on('c0', $decode);
+
+ // push resulting data through utf8 sequencer
+ $utf8 = new Utf8Sequencer($parser);
+ $utf8->on('data', function ($data) use ($that, $base) {
+ $that->onFallback($data, $base);
+ });
+
+ // process all stream events (forwarded from input stream)
+ $utf8->on('end', array($this, 'handleEnd'));
+ $utf8->on('error', array($this, 'handleError'));
+ $utf8->on('close', array($this, 'close'));
+ }
+
+ /**
+ * prompt to prepend to input line
+ *
+ * Will redraw the current input prompt with the current input buffer.
+ *
+ * @param string $prompt
+ * @return self
+ * @uses self::redraw()
+ * @deprecated use Stdio::setPrompt() instead
+ */
+ public function setPrompt($prompt)
+ {
+ if ($prompt === $this->prompt) {
+ return $this;
+ }
+
+ $this->prompt = $prompt;
+
+ return $this->redraw();
+ }
+
+ /**
+ * returns the prompt to prepend to input line
+ *
+ * @return string
+ * @see self::setPrompt()
+ * @deprecated use Stdio::getPrompt() instead
+ */
+ public function getPrompt()
+ {
+ return $this->prompt;
+ }
+
+ /**
+ * 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 self
+ * @uses self::redraw()
+ * @deprecated use Stdio::setEcho() instead
+ */
+ public function setEcho($echo)
+ {
+ if ($echo === $this->echo) {
+ return $this;
+ }
+
+ $this->echo = $echo;
+
+ // only redraw if there is any input
+ if ($this->linebuffer !== '') {
+ $this->redraw();
+ }
+
+ return $this;
+ }
+
+ /**
+ * 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 self
+ * @uses self::redraw()
+ * @deprecated use Stdio::setMove() instead
+ */
+ public function setMove($move)
+ {
+ $this->move = !!$move;
+
+ return $this->moveCursorTo($this->strlen($this->linebuffer));
+ }
+
+ /**
+ * 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
+ * @deprecated use Stdio::getCursorPosition() instead
+ */
+ public function getCursorPosition()
+ {
+ return $this->linepos;
+ }
+
+ /**
+ * 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()
+ * @deprecated use Stdio::getCursorCell() instead
+ */
+ public function getCursorCell()
+ {
+ if ($this->echo === false) {
+ return 0;
+ }
+ if ($this->echo !== true) {
+ return $this->strwidth($this->echo) * $this->linepos;
+ }
+ return $this->strwidth($this->substr($this->linebuffer, 0, $this->linepos));
+ }
+
+ /**
+ * 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 self
+ * @uses self::moveCursorTo()
+ * @uses self::redraw()
+ * @deprecated use Stdio::moveCursorBy() instead
+ */
+ public function moveCursorBy($n)
+ {
+ return $this->moveCursorTo($this->linepos + $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 self
+ * @uses self::redraw()
+ * @deprecated use Stdio::moveCursorTo() instead
+ */
+ public function moveCursorTo($n)
+ {
+ if ($n < 0 || $n === $this->linepos || $n > $this->strlen($this->linebuffer)) {
+ return $this;
+ }
+
+ $old = $this->getCursorCell();
+ $this->linepos = $n;
+
+ // only redraw if visible cell position change (implies cursor is actually visible)
+ if ($this->getCursorCell() !== $old) {
+ $this->redraw();
+ }
+
+ return $this;
+ }
+
+ /**
+ * 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 self
+ * @uses self::redraw()
+ * @deprecated use Stdio::addInput() instead
+ */
+ public function addInput($input)
+ {
+ if ($input === '') {
+ return $this;
+ }
+
+ // read everything up until before current position
+ $pre = $this->substr($this->linebuffer, 0, $this->linepos);
+ $post = $this->substr($this->linebuffer, $this->linepos);
+
+ $this->linebuffer = $pre . $input . $post;
+ $this->linepos += $this->strlen($input);
+
+ // only redraw if input should be echo'ed (i.e. is not hidden anyway)
+ if ($this->echo !== false) {
+ $this->redraw();
+ }
+
+ return $this;
+ }
+
+ /**
+ * set current text input buffer
+ *
+ * this moves the cursor to the end of the current
+ * input buffer (if any).
+ *
+ * @param string $input
+ * @return self
+ * @uses self::redraw()
+ * @deprecated use Stdio::setInput() instead
+ */
+ public function setInput($input)
+ {
+ if ($this->linebuffer === $input) {
+ return $this;
+ }
+
+ // remember old input length if echo replacement is used
+ $oldlen = (is_string($this->echo)) ? $this->strlen($this->linebuffer) : null;
+
+ $this->linebuffer = $input;
+ $this->linepos = $this->strlen($this->linebuffer);
+
+ // only redraw if input should be echo'ed (i.e. is not hidden anyway)
+ // and echo replacement is used, make sure the input length changes
+ if ($this->echo !== false && $this->linepos !== $oldlen) {
+ $this->redraw();
+ }
+
+ return $this;
+ }
+
+ /**
+ * get current text input buffer
+ *
+ * @return string
+ * @deprecated use Stdio::getInput() instead
+ */
+ public function getInput()
+ {
+ return $this->linebuffer;
+ }
+
+ /**
+ * Adds a new line to the (bottom position of the) history list
+ *
+ * @param string $line
+ * @return self
+ * @uses self::limitHistory() to make sure list does not exceed limits
+ * @deprecated use Stdio::addHistory() instead
+ */
+ public function addHistory($line)
+ {
+ $this->historyLines []= $line;
+
+ return $this->limitHistory($this->historyLimit);
+ }
+
+ /**
+ * Clears the complete history list
+ *
+ * @return self
+ * @deprecated use Stdio::clearHistory() instead
+ */
+ public function clearHistory()
+ {
+ $this->historyLines = array();
+ $this->historyPosition = null;
+
+ if ($this->historyUnsaved !== null) {
+ $this->setInput($this->historyUnsaved);
+ $this->historyUnsaved = null;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns an array with all lines in the history
+ *
+ * @return string[]
+ * @deprecated use Stdio::listHistory() instead
+ */
+ public function listHistory()
+ {
+ return $this->historyLines;
+ }
+
+ /**
+ * Limits the history to a maximum of N entries and truncates the current history list accordingly
+ *
+ * @param int|null $limit
+ * @return self
+ * @deprecated use Stdio::limitHistory() instead
+ */
+ public function limitHistory($limit)
+ {
+ $this->historyLimit = $limit === null ? null : $limit;
+
+ // limit send and currently exceeded
+ if ($this->historyLimit !== null && isset($this->historyLines[$this->historyLimit])) {
+ // adjust position in history according to new position after applying limit
+ if ($this->historyPosition !== null) {
+ $this->historyPosition -= count($this->historyLines) - $this->historyLimit;
+
+ // current position will drop off from list => restore original
+ if ($this->historyPosition < 0) {
+ $this->setInput($this->historyUnsaved);
+ $this->historyPosition = null;
+ $this->historyUnsaved = null;
+ }
+ }
+
+ $this->historyLines = array_slice($this->historyLines, -$this->historyLimit, $this->historyLimit);
+ }
+
+ return $this;
+ }
+
+ /**
+ * set autocompletion handler to use
+ *
+ * The autocomplete handler will be called whenever the user hits the TAB
+ * key.
+ *
+ * @param callable|null $autocomplete
+ * @return self
+ * @throws \InvalidArgumentException if the given callable is invalid
+ * @deprecated use Stdio::setAutocomplete() instead
+ */
+ public function setAutocomplete($autocomplete)
+ {
+ if ($autocomplete !== null && !is_callable($autocomplete)) {
+ throw new \InvalidArgumentException('Invalid autocomplete function given');
+ }
+
+ $this->autocomplete = $autocomplete;
+
+ return $this;
+ }
+
+ /**
+ * 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
+ * @internal use Stdio::setBell() instead
+ */
+ public function setBell($bell)
+ {
+ $this->bell = (bool)$bell;
+ }
+
+ /**
+ * redraw the current input prompt
+ *
+ * Usually, there should be no need to call this method manually. It will
+ * be invoked automatically whenever we detect the readline input needs to
+ * be (re)written to the output.
+ *
+ * Clear the current line and draw the input prompt. If input echo is
+ * enabled, will also draw the current input buffer and move to the current
+ * input buffer position.
+ *
+ * @return self
+ * @internal
+ */
+ public function redraw()
+ {
+ // Erase characters from cursor to end of line and then redraw actual input
+ $this->output->write("\r\033[K" . $this->getDrawString());
+
+ return $this;
+ }
+
+ /**
+ * Returns the string that is used to draw the input prompt
+ *
+ * @return string
+ * @internal
+ */
+ public function getDrawString()
+ {
+ $output = $this->prompt;
+ if ($this->echo !== false) {
+ if ($this->echo === true) {
+ $buffer = $this->linebuffer;
+ } else {
+ $buffer = str_repeat($this->echo, $this->strlen($this->linebuffer));
+ }
+
+ // write output, then move back $reverse chars (by sending backspace)
+ $output .= $buffer . str_repeat("\x08", $this->strwidth($buffer) - $this->getCursorCell());
+ }
+
+ return $output;
+ }
+
+ /** @internal */
+ public function onKeyBackspace()
+ {
+ // left delete only if not at the beginning
+ if ($this->linepos === 0) {
+ $this->bell();
+ } else {
+ $this->deleteChar($this->linepos - 1);
+ }
+ }
+
+ /** @internal */
+ public function onKeyDelete()
+ {
+ // right delete only if not at the end
+ if ($this->isEol()) {
+ $this->bell();
+ } else {
+ $this->deleteChar($this->linepos);
+ }
+ }
+
+ /** @internal */
+ public function onKeyHome()
+ {
+ if ($this->move && $this->linepos !== 0) {
+ $this->moveCursorTo(0);
+ } else {
+ $this->bell();
+ }
+ }
+
+ /** @internal */
+ public function onKeyEnd()
+ {
+ if ($this->move && !$this->isEol()) {
+ $this->moveCursorTo($this->strlen($this->linebuffer));
+ } else {
+ $this->bell();
+ }
+ }
+
+ /** @internal */
+ public function onKeyTab()
+ {
+ if ($this->autocomplete === null) {
+ $this->bell();
+ return;
+ }
+
+ // current word prefix and offset for start of word in input buffer
+ // "echo foo|bar world" will return just "foo" with word offset 5
+ $word = $this->substr($this->linebuffer, 0, $this->linepos);
+ $start = 0;
+ $end = $this->linepos;
+
+ // buffer prefix and postfix for everything that will *not* be matched
+ // above example will return "echo " and "bar world"
+ $prefix = '';
+ $postfix = $this->substr($this->linebuffer, $this->linepos);
+
+ // skip everything before last space
+ $pos = strrpos($word, ' ');
+ if ($pos !== false) {
+ $prefix = (string)substr($word, 0, $pos + 1);
+ $word = (string)substr($word, $pos + 1);
+ $start = $this->strlen($prefix);
+ }
+
+ // skip double quote (") or single quote (') from argument
+ $quote = null;
+ if (isset($word[0]) && ($word[0] === '"' || $word[0] === '\'')) {
+ $quote = $word[0];
+ ++$start;
+ $prefix .= $word[0];
+ $word = (string)substr($word, 1);
+ }
+
+ // invoke autocomplete callback
+ $words = call_user_func($this->autocomplete, $word, $start, $end);
+
+ // return early if autocomplete does not return anything
+ if ($words === null) {
+ return;
+ }
+
+ // remove from list of possible words that do not start with $word or are duplicates
+ $words = array_unique($words);
+ if ($word !== '' && $words) {
+ $words = array_filter($words, function ($w) use ($word) {
+ return strpos($w, $word) === 0;
+ });
+ }
+
+ // return if neither of the possible words match
+ if (!$words) {
+ $this->bell();
+ return;
+ }
+
+ // search longest common prefix among all possible matches
+ $found = reset($words);
+ $all = count($words);
+ if ($all > 1) {
+ while ($found !== '') {
+ // count all words that start with $found
+ $matches = count(array_filter($words, function ($w) use ($found) {
+ return strpos($w, $found) === 0;
+ }));
+
+ // ALL words match $found => common substring found
+ if ($all === $matches) {
+ break;
+ }
+
+ // remove last letter from $found and try again
+ $found = $this->substr($found, 0, -1);
+ }
+
+ // found more than one possible match with this prefix => print options
+ if ($found === $word || $found === '') {
+ // limit number of possible matches
+ if (count($words) > $this->autocompleteSuggestions) {
+ $more = count($words) - ($this->autocompleteSuggestions - 1);
+ $words = array_slice($words, 0, $this->autocompleteSuggestions - 1);
+ $words []= '(+' . $more . ' others)';
+ }
+
+ $this->output->write("\n" . implode(' ', $words) . "\n");
+ $this->redraw();
+
+ return;
+ }
+ }
+
+ if ($quote !== null && $all === 1 && (strpos($postfix, $quote) === false || strpos($postfix, $quote) > strpos($postfix, ' '))) {
+ // add closing quote if word started in quotes and postfix does not already contain closing quote before next space
+ $found .= $quote;
+ } elseif ($found === '') {
+ // add single quotes around empty match
+ $found = '\'\'';
+ }
+
+ if ($postfix === '' && $all === 1) {
+ // append single space after match unless there's a postfix or there are multiple completions
+ $found .= ' ';
+ }
+
+ // replace word in input with best match and adjust cursor
+ $this->linebuffer = $prefix . $found . $postfix;
+ $this->moveCursorBy($this->strlen($found) - $this->strlen($word));
+ }
+
+ /** @internal */
+ public function onKeyEnter()
+ {
+ if ($this->echo !== false) {
+ $this->output->write("\n");
+ }
+ $this->processLine("\n");
+ }
+
+ /** @internal */
+ public function onKeyLeft()
+ {
+ if ($this->move && $this->linepos !== 0) {
+ $this->moveCursorBy(-1);
+ } else {
+ $this->bell();
+ }
+ }
+
+ /** @internal */
+ public function onKeyRight()
+ {
+ if ($this->move && !$this->isEol()) {
+ $this->moveCursorBy(1);
+ } else {
+ $this->bell();
+ }
+ }
+
+ /** @internal */
+ public function onKeyUp()
+ {
+ // ignore if already at top or history is empty
+ if ($this->historyPosition === 0 || !$this->historyLines) {
+ $this->bell();
+ return;
+ }
+
+ if ($this->historyPosition === null) {
+ // first time up => move to last entry
+ $this->historyPosition = count($this->historyLines) - 1;
+ $this->historyUnsaved = $this->getInput();
+ } else {
+ // somewhere in the list => move by one
+ $this->historyPosition--;
+ }
+
+ $this->setInput($this->historyLines[$this->historyPosition]);
+ }
+
+ /** @internal */
+ public function onKeyDown()
+ {
+ // ignore if not currently cycling through history
+ if ($this->historyPosition === null) {
+ $this->bell();
+ return;
+ }
+
+ if (isset($this->historyLines[$this->historyPosition + 1])) {
+ // this is still a valid position => advance by one and apply
+ $this->historyPosition++;
+ $this->setInput($this->historyLines[$this->historyPosition]);
+ } else {
+ // moved beyond bottom => restore original unsaved input
+ $this->setInput($this->historyUnsaved);
+ $this->historyPosition = null;
+ $this->historyUnsaved = null;
+ }
+ }
+
+ /**
+ * Will be invoked for character(s) that could not otherwise be processed by the sequencer
+ *
+ * @internal
+ */
+ public function onFallback($chars, EventEmitterInterface $base = null)
+ {
+ // check if there's any special key binding for any of the chars
+ $buffer = '';
+ foreach ($this->strsplit($chars) as $char) {
+ // forward compatibility: check if any key binding exists on base Stdio instance
+ // deprecated: check if any key binding exists on this Readline instance
+ $emit = null;
+ if ($base !== null && $base->listeners($char)) {
+ $emit = $base;
+ } else if ($this->listeners($char)) {
+ $emit = $this;
+ }
+
+ if ($emit !== null) {
+ // special key binding for this character found
+ // process all characters before this one before invoking function
+ if ($buffer !== '') {
+ $this->addInput($buffer);
+ $buffer = '';
+ }
+ $emit->emit($char, array($char));
+ } else {
+ $buffer .= $char;
+ }
+ }
+
+ // process remaining input characters after last special key binding
+ if ($buffer !== '') {
+ $this->addInput($buffer);
+ }
+ }
+
+ /**
+ * delete a character at the given position
+ *
+ * Removing a character left to the current cursor will also move the cursor
+ * to the left.
+ *
+ * @param int $n
+ */
+ private function deleteChar($n)
+ {
+ // read everything up until before current position
+ $pre = $this->substr($this->linebuffer, 0, $n);
+ $post = $this->substr($this->linebuffer, $n + 1);
+
+ $this->linebuffer = $pre . $post;
+
+ // move cursor one cell to the left if we're deleting in front of the cursor
+ if ($n < $this->linepos) {
+ --$this->linepos;
+ }
+
+ $this->redraw();
+ }
+
+ /**
+ * process the current line buffer, emit event and redraw empty line
+ *
+ * @uses self::setInput()
+ */
+ protected function processLine($eol)
+ {
+ // reset history cycle position
+ $this->historyPosition = null;
+ $this->historyUnsaved = null;
+
+ // store and reset/clear/redraw current input
+ $line = $this->linebuffer;
+ if ($line !== '') {
+ // the line is not empty, reset it (and implicitly redraw prompt)
+ $this->setInput('');
+ } elseif ($this->echo !== false) {
+ // explicitly redraw prompt after empty line
+ $this->redraw();
+ }
+
+ // process stored input buffer
+ $this->emit('data', array($line . $eol));
+ }
+
+ /**
+ * @param string $str
+ * @return int
+ * @codeCoverageIgnore
+ */
+ private function strlen($str)
+ {
+ // prefer mb_strlen() if available
+ if (function_exists('mb_strlen')) {
+ return mb_strlen($str, $this->encoding);
+ }
+
+ // otherwise replace all unicode chars with dots and count dots
+ return strlen(preg_replace('/./us', '.', $str));
+ }
+
+ /**
+ * @param string $str
+ * @param int $start
+ * @param ?int $len
+ * @return string
+ * @codeCoverageIgnore
+ */
+ private function substr($str, $start = 0, $len = null)
+ {
+ if ($len === null) {
+ $len = $this->strlen($str) - $start;
+ }
+
+ // prefer mb_substr() if available
+ if (function_exists('mb_substr')) {
+ return (string)mb_substr($str, $start, $len, $this->encoding);
+ }
+
+ // otherwise build array with all unicode chars and slice array
+ preg_match_all('/./us', $str, $matches);
+
+ return implode('', array_slice($matches[0], $start, $len));
+ }
+
+ /**
+ * @internal
+ * @param string $str
+ * @return int
+ * @codeCoverageIgnore
+ */
+ public function strwidth($str)
+ {
+ // prefer mb_strwidth() if available
+ if (function_exists('mb_strwidth')) {
+ return mb_strwidth($str, $this->encoding);
+ }
+
+ // otherwise replace each double-width unicode graphemes with two dots, all others with single dot and count number of dots
+ // mbstring's list of double-width graphemes is *very* long: https://3v4l.org/GEg3u
+ // let's use symfony's list from https://github.com/symfony/polyfill-mbstring/blob/e79d363049d1c2128f133a2667e4f4190904f7f4/Mbstring.php#L523
+ // which looks like they originally came from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
+ return strlen(preg_replace(
+ array(
+ '/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u',
+ '/./us',
+ ),
+ array(
+ '..',
+ '.',
+ ),
+ $str
+ ));
+ }
+
+ /**
+ * @param string $str
+ * @return string[]
+ */
+ private function strsplit($str)
+ {
+ return preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY);
+ }
+
+ /**
+ * @return bool
+ */
+ private function isEol()
+ {
+ return $this->linepos === $this->strlen($this->linebuffer);
+ }
+
+ /**
+ * @return void
+ */
+ private function bell()
+ {
+ if ($this->bell) {
+ $this->output->write("\x07"); // BEL a.k.a. \a
+ }
+ }
+
+ /** @internal */
+ public function handleEnd()
+ {
+ if ($this->linebuffer !== '') {
+ $this->processLine('');
+ }
+
+ 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 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->input->close();
+
+ $this->emit('close');
+ $this->removeAllListeners();
+ }
+}
diff --git a/vendor/clue/stdio-react/src/Stdio.php b/vendor/clue/stdio-react/src/Stdio.php
new file mode 100644
index 0000000..aff2959
--- /dev/null
+++ b/vendor/clue/stdio-react/src/Stdio.php
@@ -0,0 +1,630 @@
+<?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;
+
+ /**
+ *
+ * This class takes an optional `LoopInterface|null $loop` parameter that can be used to
+ * pass the event loop instance to use for this object. You can use a `null` value
+ * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
+ * This value SHOULD NOT be given unless you're sure you want to explicitly use a
+ * given event loop instance.
+ *
+ * @param ?LoopInterface $loop
+ * @param ?ReadableStreamInterface $input
+ * @param ?WritableStreamInterface $output
+ * @param ?Readline $readline
+ */
+ public function __construct(LoopInterface $loop = null, 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 = null)
+ {
+ // 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 = null)
+ {
+ // 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));
+ }
+}
diff --git a/vendor/clue/term-react/LICENSE b/vendor/clue/term-react/LICENSE
new file mode 100644
index 0000000..7baae8e
--- /dev/null
+++ b/vendor/clue/term-react/LICENSE
@@ -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.
diff --git a/vendor/clue/term-react/composer.json b/vendor/clue/term-react/composer.json
new file mode 100644
index 0000000..d1b3ce5
--- /dev/null
+++ b/vendor/clue/term-react/composer.json
@@ -0,0 +1,27 @@
+{
+ "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@clue.engineering"
+ }
+ ],
+ "autoload": {
+ "psr-4": { "Clue\\React\\Term\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "Clue\\Tests\\React\\Term\\": "tests/" }
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/stream": "^1.0 || ^0.7"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8",
+ "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3"
+ }
+}
diff --git a/vendor/clue/term-react/src/ControlCodeParser.php b/vendor/clue/term-react/src/ControlCodeParser.php
new file mode 100644
index 0000000..abbe400
--- /dev/null
+++ b/vendor/clue/term-react/src/ControlCodeParser.php
@@ -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();
+ }
+}
diff --git a/vendor/clue/utf8-react/LICENSE b/vendor/clue/utf8-react/LICENSE
new file mode 100644
index 0000000..7baae8e
--- /dev/null
+++ b/vendor/clue/utf8-react/LICENSE
@@ -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.
diff --git a/vendor/clue/utf8-react/composer.json b/vendor/clue/utf8-react/composer.json
new file mode 100644
index 0000000..931708f
--- /dev/null
+++ b/vendor/clue/utf8-react/composer.json
@@ -0,0 +1,27 @@
+{
+ "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/reactphp-utf8",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering"
+ }
+ ],
+ "autoload": {
+ "psr-4": { "Clue\\React\\Utf8\\": "src/" }
+ },
+ "autoload-dev": {
+ "psr-4": { "Clue\\Tests\\React\\Utf8\\": "tests/" }
+ },
+ "require": {
+ "php": ">=5.3",
+ "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4 || ^0.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3 ||^5.7 || ^4.8",
+ "react/stream": "^1.0 || ^0.7"
+ }
+}
diff --git a/vendor/clue/utf8-react/src/Sequencer.php b/vendor/clue/utf8-react/src/Sequencer.php
new file mode 100644
index 0000000..e9bf433
--- /dev/null
+++ b/vendor/clue/utf8-react/src/Sequencer.php
@@ -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;
+ }
+}